 So I'll just get started. Like Colin said, they started the architecture group. And some of my friends who were starting that group reached out and said, hey, would you like to do a talk? And I said, no. And then I thought, well, I always say that. So maybe I'll do it this time. And here I am. So this talk is about, like Colin said, taking the solid principles. It is not about object-oriented design. That's what solid is about. That's what there are many, many great lectures from really smart people online. Dr. Bob Martin is the founder of that ideology. This is about cloud architectures. And as I was going through the slides last night, I thought this really isn't even an architecture talk. This is a DevOps talk. But the good news is one way to have solid architecture is to have a solid understanding of how all the pieces are coming together. That's in large part, at least in my opinion, what DevOps is about. So you're wondering about the pony. When I googled solid clouds to see if anybody else was talking about this, this was the only hit in Google. So that's what it's really about. Just a quick disclaimer, my opinions are mine. Not my employers. My employer has lots of great opinions. I work at Petuam. We're hiring. On most of the slides, you will see a QR code. If I'm getting boring, get out your phone, shoot the QR code. There's something interesting there, Easter eggs. This one is the link to get a job at Petuam. Then you can ask McGresh. It's a great place to work. So I'm going to go through a couple of background slides just to cover the basics. This slide is from a talk by Sam Newman about microservice architecture. Again, a great read if you click through to that. These slides will be posted later. I'll link it in the architecture group on the Slack channel. So the general idea, there are a lot of different principles that Sam talks about going into the design of a good microservice architecture. But one of the things that I noticed in here that is very similar to what I'm going to be talking about is the idea of isolation, the idea of independence, the idea of keeping things simple. Another thing to keep in mind, at least what I'm talking about cloud architectures is Kubernetes, because that's the box that I live in most days. Kubernetes is a fabric for deploying microservices, really for deploying things in containers on a cloud, sorry, on a cloud architecture, on a cloud. So Heroku put together these 12 factors as best practices for building applications on their platform. And since the talk is really about building solid cloud applications, why isn't it a 12 factor talk? Because there's a lot of 12 factor talks. Because I'm lazy and remembering five things is a lot easier than remembering 12. And most of all, because talking about solid clouds gives me an excuse to use the ponygif. And the last background slide is what we're here to talk about, except not object-oriented design. Just quickly buzz through them, and then we're going to go through a lot more detail in the next 30 or 40 slides. So the first one, single responsibility principle is, I think of it as the rule of one. It's about keeping it simple, keeping it isolated. The open-closed principle is, the tagline for that is change is bad. Lisk-off substitution is really just a reasonable expectation of types. Interface segregation principle is the rule of small. And dependency inversion is about decoupling, which some concepts to keep in mind as we're going through this. So the first responsibility is, like I said, it's the rule of one, the idea being that it should only change when the requirements change. And if you have, if your thing only has one reason for being, one concept, if it's isolated, then the reasons for which you may have to change it, the reasons for which it will break, the reasons for which were bugs will be introduced, becomes a lot smaller. And with a smaller bucket, you have fewer bugs. You have a single point of truth. When you package your thing together, it's in a single package, whether it's a tarball or a helm package or a dead file. But there is a way to package it that gets all the miscellaneous parts, where they belong, the way that they belong. You don't want to check out a Git repo and try and build it on your production servers. That will work a bunch of times, and then it won't in a really bad way. So taking a look at the single responsibility principle as it applies to DevOps, just some ideas to take away. When we talk about it in object-oriented design, we're thinking about one class. That function has one purpose. There's a single path of execution. But when we expand these concepts out and we start talking about how they apply to our cloud design or to our DevOps principles, some of the things you can think about is, I'm going to have one Git repo for a thing, whether it's a DLL or a jar file or it's the content side of your website. But you've got these things isolated in a single code base that builds into a single releaseable thing, one package. And each one of those things has an explicit version or tag per release so that you always know which one of that thing isn't. One action per build job. So you've got your build script, and it's very easy to understand. Where did this fail? You can see which balloon turned red, which line has the big fatal on it. It's much easier to trace in and figure out where you made your mistake and turn it around. And that's a big part of keeping that idea of single responsibility is, if there's only a single responsibility, if there's only a single behavior for the thing, then there's only ideally one way that it's going to break. And it becomes much simpler to diagnose, fix, and move on so you don't get locked up. And then just keep going, one source for trusted artifacts, a single configuration template. But maybe that configuration template breaks out into multiple value sets, one for dev, one for production, one for my desktop, one for yours. Another way that you can look at single responsibility applying to beyond just the code base, single testability. So each thing should be testable. Each of those tests should have a single path of execution for a single assertion to the test. And just the simple quality of is it testable? Is there something that I can identify about this thing I built that I can describe in terms of a test? So moving on to the O of solid, open for extension, closed for modification, there's, when I stumble across this notion of it, this is the definition right there. It's a LEGO brick. They are infinitely composable, but you're not going to take it apart. This is only going to have eight bumps on the top. It's only going to have eight sockets on the bottom with those three little holes if you're feeling kind of crazy. But it doesn't change. But you can do myriad things with it. The take away from the tagline for OCP principle of extension closed for modification is that you want to resist change. So this LEGO brick is not going to change. And by the way, that's not a LEGO brick. It's somebody's CAD drawing that was in free images online. Because all the LEGO stuff is copyrighted. But it's not going to change. You're not going to mutate it. You're not going to modify its behavior. But how you use it, it's a brick. It's a car. It's a leg. It's a foot. It's a hat. It's all about context. So when we take that open, closed principle out to the cloud, one of the ways that really seems pretty clear to me when I think about it is how my code behaves depending on the configuration set that I give it. So when I build my code into containers, 12th Factor talks about storing the config in the environment as one of the 12 factors. And so when I think about that in Kubernetes, I'm isolating my secrets. So my secrets are deployed as a thing in my Kubernetes environment in a particular namespace or generally across all the namespaces. And they are separate from the code itself, which means, so that's the way that OCP begins to affect the overall security of my platform. I don't know if any of you have ever looked in a Git repo or in other source control and seen passwords or certificates. So it's not where you want to be. So in the environment, it's not in the source code? Not in the source code. Well, so 12th Factor means specifically, they stay specifically in the environment. But I would generalize it a lot more to just say, it's in its own place. It's separate from. So there are secrets, and you have a way to get secrets in. And it's only about getting back to single responsibility. Because all of these principles, no one of them is terribly useful if you try to religiously follow a particular one. It might make your code better, but it might also, you end up kind of lost. They all go together. They all feed into one another to build a more solid application architecture. So in Kubernetes, I'll take those secrets out of the code, but I'll also inherit a lot of environment from my namespace, from the cloud room deploying. If I'm in AWS, there are certain variables that are going to be injected into my runtime. There are, and then within my Kubernetes deployment, there are additional environment variables that will be set, and all of those will filter in. And if I build my containers so that they're reading in a lot of that information just from the environment, then there's those nested contexts begin to change the behavior of what I'm deploying. How many replicas do I get? What subdomain are they looking up other services on? Permissions, storage, backends, et cetera. So then an additional, so then you can, so one of the things I get with Kubernetes is, so I've packaged my code in one thing and I've got configuration and I've got my container and now I need a way to pull all that together. Helm is a tool that I use to pull all those things together. It gives me a way to, I can still very explicitly declare this is the value set for this particular installation. I can also have another value set that's built into the Helm package and then yet another value set that's built into the spec for that pod, for that Kubernetes app. And just each of those abstractions, each of those layers is defining just that single behavior, but then as they all fold together, they begin to affect the behavior of the overall system. So a little rant that I've been playing on my head against lately is, is anybody familiar with Docker latest? You do a get pull latest and then you run it and then you come back a week later, you push some changes, you got a new container out there, it's working great, get pull latest, get pull comes back immediately, you run it, it still was broken as it was the day before. Yeah, that's because it's a tag. And this thing is it. So a lot of what I'm trying to talk about is the right way to do it, but this is a case where Docker kind of blew it. They've got another thing called labels. Labels on the other hand are immutable. When I create a new container, when I do Docker build, I can specify label, get hash, I can specify a label for the architecture, I can specify a label for my birthday, just in case you guys wanna keep track of it, I can specify a label for anything. And those are fixed in the container image. Unfortunately, the only way I can see them is by running Docker Inspect and parsing that JSON out and extracting those values. So they've got the feature that you need to be able to get the thing that you need, except it's hidden behind tags, which everybody changes tags and the Docker pull mechanism doesn't really quite work. In Kubernetes, they addressed it somewhat with pull policies. So I can have a pull policy on my pod, which is my collection of one or more Docker containers that says always pull, sometimes pull, pull weekly, but that's a case of a leaky abstraction. So we've got the responsibility for which instance of this thing, is this container, is really on the container, it's on the Docker framework. The fact that Kubernetes had to build something into the pod spec to kind of force that override that broken behavior. It's just a demonstration of how it kept broken. And now that that's a normal thing in Kubernetes, it makes it even more confusing when I try to run my containers in a CI test, because it works great in the Kubernetes cluster, but now it doesn't work great when I run it in a Docker compose or a swarm. Barbara Liskov, she's the one that gave us Liskov substitution, all of the other letters in this are actual descriptive names, acronyms are hard, naming stuff is hard, finding something that started with L that fit in this that made sense, it was apparently also hard, but Barbara Liskov wrote a very complicated explanation of is a versus has a relationships. And Liskov substitution is talking about is a relationship, meaning that if there is a thing and it exhibits an interface, that all things that exhibit that interface will behave the same way. So in a cloud or Microsoft architecture, it points to I've got containers that are no JS servers. So I can reasonably expect that they're all going to have a service port that's going to serve up some form of HTTP interface. But it also extends to how a team or an organization or an industry segment will converge on certain expectations, like logging to standard out. So we assume that all of our things now in the cloud are logging to standard out, so that all we need to do is scrape standard out most of our frameworks do that and pipe it off to a place where we can aggregate our logs and have some clue about what went wrong, when things go wrong. Pull metrics versus trying to push them out, abstracting security and permissions away from the actual implementations of the things. So another one way to look at how Liskov substitution applies in some of the 12 factors, these are the two 12 factor definitions port binding that like I said with the no JS server that I can always expect that there's going to be a port and that even though outside the container it's bound to port 8080 inside the container or vice versa inside the container it's bound to 8080 outside the container it's bound to anything. That means it makes it very easy for me to scale out 30 of those and all of those ports whether they're on one host or many hosts will map to a reasonable port within the container. They have prod parity meaning that as a developer I have a reasonable, when I push things into my development environment I have a reasonable expectation that it behaved well there it's going to behave well in production. Obviously I'm not going to be running to the scale or the load that I do in production but I should have similar deployment mechanisms. I should have, if I'm deploying to Kubernetes in production I shouldn't be running it in mesos on my desktop. But really, that was all kind of boring. I recently ran across a tweet which is this code about garage doors in HTTP. Has anybody still come across that story? This guy, actually I'm going to click on it. So this guy. So HTTP get, right? You get this page, you keep getting this page. I've gotten this page several times today. And it always comes up the same way and nothing else happens. Yeah, so this guy, it's a relay. It's hooked up to a push button on the garage door because it doesn't work. So he hooked up this thing so that he could open and close a garage door from his laptop except it's a get request. So every time he hit that URL, this garage door opens, this garage door closes. So that's an example of Liskoff not done right. And that's what happens. You get a garage door that's going to be here. So the I in a solid, obviously nervous as I'm racing through these. So if you guys have questions, comments, catch me making something up, feel free to give a shout. The, so the I in interface segregation is, like I said, it's the rule of the small. The idea being that each one of these things can stand alone. But even more so to abstract it out a little bit away from just that object already, that code mentality. So thinking about it in terms of what is the minimum useful bit of functionality that can be defined in that particular context. And this is where the interface comes in. Where does that context overlap with something else? So my interface isn't what my thing does. My interface is where it touches something else. It's how am I implementing HTTP handlers? How, how do my routes map in my app? How, how am I accessing back end storage? How am I getting my code to the different environments? Those are all the interfaces, the places where the things intersect. And when you start thinking about how are those isolated? And again, so interface segregation couples very, very well, at least in my mind, with that single responsibility principle in that I don't want my interfaces to overlap. If there is a way to do something from here to get from here to there, I want there to be one path. I want it to be a clear path. Now you can stick in, then you apply something on this golf and that one path, there are many ways to actually get there but they're all taking the same path through that same interface. They all exhibit that same behavior. Again, making it easier to understand your system, easier to compose your system. And even more, I skipped past it on the open close slide but one of the big things that I've seen that makes a lot of products, architecture solutions wildly successful, is when people figure out how to do things that you didn't anticipate and that's composability. If you give them the ability to make something that you never imagined with what you built, they're going to do that eventually. And that's often an ingredient in wild success referencing back to that Lego brick. So thinking about interface segregation in terms of 12 factor, a couple of the things they talk about, administrative processes that every one of your administrative tasks is an isolated one off thing. It's separate from your application code. It's outside of your deployment logic. Similarly, your processes. They're also isolated, excuse the app, is one of more stateless processes. That also is an important thing to think about when you're building your Docker containers. A popular Docker anti-pattern is the container as a virtual machine where you've got startup scripts that are triggered from an ITD and they're starting some background processes and you've got some other things going on. You want one PID per container, one process. A container is literally a wrapper around a Linux process. So this guy, why is he here? In Kubernetes, a lot of teams deploy what I've read called a bastion node, which is it's outside the cluster. And this is a way that you can separate some of those administrative processes. So one of the things that I've seen those bastion nodes used for is an SSH proxy, is as an SSH proxy. So I've got my Kubernetes cluster out there in AWS and I don't want to make any public routes available to that control plane because if you get that vulnerability and you start mining Bitcoin on there, my AWS fees are gonna go through the roof. So one of the things to do is don't open anything. You can't SSH to my master, you can't SSH to my nodes. There's a box out there that if you know the credentials for the next hop that you can SSH proxy through, but then you've gotta get two different vulnerabilities. So that's a purposeful isolation for security. Another way that those bastion nodes are used is setting up some kind of a gateway server and that's doing your authentication. That's pushing users to come in, they hit that really quickly. That box is tuned very well to do the credential auth or whatever auth you're using and to authenticate the users and then it passes them on in your application, gets into, okay, I know that this person is who they say they are. Now you can start to do the authorization to see what they're allowed to do and you use the tools that are built into your platform and the Kubernetes to authorize that role. Another huge way that interface segregation has personally saved me a great deal of time and I think it's a really important thing to think about when you're designing your applications is observability. So if each of this, my request comes in, it hits one container that parses it out, determines what the intended action is, then it gets routed to another container that decorates something or triggers a side job, jumps it into a queue, another thing starts a worker. It bounces through all of these things in a matter of a couple seconds. I got a couple thousand of those hitting my cluster every minute. It becomes very difficult to understand where did it go wrong if when I'm logging I'm not expressing an identity for each one of those interfaces that's being hit. I'm not the correlation ID. So there's an ID for the request, there's an ID for each of the nodes, there's an ID for the version, there's an ID for the host where it's running, all of that information gets logged and then suddenly your logs become a really valuable tool and a really powerful tool for diagnosing what happened. And it gets into, so on one hand it's about separating those interfaces, those isolating them, on another hand it's about making sure that each thing has a distinct responsibility so that I know when I see container food got hit. I know that somebody was trying to get some food. Moving on. So the D, made all the way through. Fendancy inversion principle. And I just copied the, I was getting tired when I put this one together and I got to the last slide. So I just copied the definition. High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions need not depend on details. Details should depend on abstractions. And that's what I hope that I've touched on this a little bit as we've gotten to get this far. But it's about separating those things but it's what happens when we separate those things. So when I take that configuration data out of my container. So let's say I've got an init container and that, or I've got a container. And it knows how to get things from Amazon S3 and copy them over to storage. When I configure that, when I test that out on my desktop, I run that Docker. I mount the volume to a local directory. I set some credentials in the environment. It goes out, gets stuff, ooh, that worked. I push it up to CI, it does the same thing except it pipes that off to another filter that makes sure that the file content is correctly. It runs through a hash, it checks the SHA awesome. Okay, great, I actually got the file that I expected to get, moves on. I deploy that in my cluster. Now it gets configured with a map to a persistent volume from Kubernetes that's either mapped to that node host or maybe it's mapped to some kind of a shared file system that all of these nodes are using and I configure it to be an init container. So it comes up and dies before the actual application starts. So now this thing comes up, but I deployed seven of them. They come up and I also told them to communicate, to broadcast over a local channel what they were getting to make sure that if somebody else is getting that, they don't duplicate it. Now all five of my clusters come up, they'll go out, they grab different channels on all that data, they pull it down really fast, they start up, it always comes up. If only three of them start up immediately and two of them are lagging because they couldn't get resources, that's okay. Those two will come up and they'll say, oh, the files are there, I don't do anything. Exit, everybody comes up, the data's still there. So you change the behavior by getting those details out of the implementation. The implementation still stays really simple. Copy stuff. But then as you apply those layers, those configuration details, and you extract them out into, through the different, I talked about how the Helm modules give me some control over it. And the Kubernetes spec gives me some control over it. And my cloud provider gives me some control over it. All of those things. Just check my notes and see if I actually talk about what I said. And one last rant. I hate your database. I hate it with a passion. These people databases do not belong in the cloud. They are the antithesis of stateless. Their whole reason for purpose is to store state. Except sometimes you need them and sometimes they're really easy. So you build abstractions around them to make them work. Use a stateful set to make sure that when that thing goes down, it always comes back up on the same host. So that where your database still has quick access to a local drive, it's not trying to read and write from over NFS and struggling. Yeah, so think simple. Right, so one of the other challenges you have with databases is it's not as simple as bumping your replication number from two to three because all of your clients are not necessarily gonna map to the right thing. But you can build abstractions around that to make it work. So then suddenly you've got six copies of Postgres running in your cloud. You're all serving up the same indices. You're staying reasonably in sync, but you didn't have to do a monolithic replication back end behind it. Simply by building those abstractions around it. In my opinion, the better answer is, don't use a SQL database. Use something else, abstract out your storage. So the frequency with which you actually need to have all of your data in a single table is not very high. Occasionally you'll need that. Maybe you're doing some kind of analytics. But even then, most frameworks that you're gonna be using don't want it as a cursor. They want it as a CSV, as a text file, as a bunch of objects in a queue. So you're gonna end up abstracting it anyway to get it into the analytics framework that you're gonna be using. Right, so some of the mitigations that you'll end up applying if you do wanna use that relational database in your cloud application. So another way to think about it, getting back to the DevOps cycle, is when I build my thing, I'm gonna have dependencies. And I want to explicitly declare that I know this works for that version and that version and that version of my dependencies. Getting back to the latest tag. I don't want to say that I'm using whatever the newest thing is. In your gem file, you generally wanna say that you're gonna bound it at version one, but not greater than one. Or you might say 109 if you know that this is the only one that works and I'm not trusting anything else. So dependency version across configuration management and quality, if the API is able to express and abstract the relationship with its dependencies specifically, then deploying that thing with confidence becomes more reasonable because you know exactly what you're gonna be deploying with. And then you're gonna want the abstractions around that. That's what Docker gives you. That's what your cloud is gonna give you by segmenting your cloud and having those dependencies where you expect them to be in the underlying layers in the base containers as you build the new versions and build on top of those building blocks, you inherit the behavior that you expect. So that's the end. I copied a lot of images from the internet. This is where I got them from. That's the end. So be the pony. So any questions, comments, suggestions? I would love to hear them. Rdms, I guess, where do you prefer to store it? NoSQL is some variation of NoSQL depending on what the application is and what the usage is. Sometimes don't store the state. Sometimes the state is, it comes in, it goes through transitions, it comes back out. There might be static content. There might be dynamic content that's delivered. And maybe behind that content is an Rdms. Maybe there's just a traditional web app that's serving up that content. But when I think about applications where I've got a lot of microservices that are working together, I want that storage. I want that unit to be well abstracted away from the rest of my cloud. So wrapping the database of service, is that more in your... That's a mitigation that you... But it's pushing it up to kind of a blockier level of interaction, chunkier, chunking-sized data. Rather than, you know, do this right to the street, do this right to the street. You're like, I'm gonna work with this thing that data sets. Right, so it's, yeah. Here's my updates should be met. And the more abstractions you can put around that, the better off you are. And then one of the things that you'll find after you start building out those abstractions is, oh wow, I don't need the database. I can just drop these messages in a durable queue. Because I don't need them to be around forever. Or maybe I do need them to be around forever. In which case, I'm gonna have something pulling on that durable queue and serializing them off to a no SQL storage or something. Replicates. It's designed. Left or a lot better. It's designed for cloud applications. Databases grew up in an era of big client-server computing where it was reasonable for me to order another $40,000 computer when I needed to expand. Yeah, but it's just that generally for a microservice architecture, it's you can get that. Right, and that's what I was saying is, as you build the abstractions around, you're already gonna ask to make it work well in a cloud solution, in a microservice solution. You're gonna find, oh wow, instead of an already been asked behind this, I could just use something that's a lot simpler and cheaper to deploy. What about like the hosted options, the one we got like with the RDS or something, basically they manage it for you and they handle all of that. They've built an API around it and they deal with all the headaches for you. There are ceiling ones. That works, yeah. And I've seen it work well and I've seen it hurt. And that's true for any of these things. I mean, there's no perfect answers. There's no right and wrong. These are just concepts to think about. And that's really what I wanted to get across with this, is as you're designing, as you're building, ask these questions. And a lot of times you're gonna ask the question and you're gonna say, you know what? I need to get this out the door this week. It's going out this way. I'm breaking the rules and sending it because you need to get it done and it's good enough. So yeah, it's basically it's about fostering your different patterns a less fine grade approach to the data and persistence. In this particular, yeah, on that slide, yeah. Oh, yeah, I'm doing better on this slide. Yeah. I've heard of Helm, but I don't know anything really about it. Could you maybe just talk it maybe at a high level what Helm is and for what it does for you? Sure. Helm uses Go template. So do you know how you deploy? So the way you deploy a Kubernetes application or pod is you write out this YAML file. And Helm uses Go templates. It's a templating library built into the Go language to mock those out. So you can rough out your YAML file for your deployment as a template and stick into little variables. You've got looping up operations and you've got different mutations that you can apply to the strings that are, and you feed it a dictionary of values. I don't know, it's not my code. I can't make sure. And so what Helm gives you the ability to do is I can then describe the container, the actual Docker instances that I want to use, and I can describe the configuration for my deployment, how big the replication set is. And then within that dictionary, I can have fields for, I could say replicas, colon, and then underneath of that instead of just four, I can have dev staging production developer and four, two, eight, 36, depending on what environment I'm deploying to. And then when I say Helm install, I say dash ENV equals whatever, and Helm uses my Kubernetes credentials to push that out. It reads some information from the Kube config, it reads some information when it starts talking to its counterpart, Tiller, in the Kubernetes cluster, and actually fills out the rest of those templates and then applies those specs to the cluster to create whatever you're trying to create. It's a great tool. It's like a Mica later in about two minutes. It's an installer package, really. It's just a way to describe how to install something and then bundle it together neatly into a tar ball. And it has reasonable install semantics, install, remove, scale, anybody else? Well, thank you all very much for coming out. I hope it was useful. If it wasn't, like I said, there's some great Easter eggs in there. You can hear more intelligent people talk about these topics.