 We have here Marcus. Are you there, Marcus? I am. Good morning. Good morning. Indeed. So, let's learn about Kubernetes, right, Marcus? Yes. It's a wonderful piece of thing that I'm going to talk about today. All right. I'll leave it to you. Thanks. Hello, everybody. And in an in-person event, I would have started off this talk with, like, looking at you in the audience and then asking a couple of questions, like who have you heard of Kubernetes and who have had you, has used it in its past or is about to use it and is therefore in this talk now. But unfortunately, EuroPerson 2021 needs to happen online again. So, well, here we are. This is an online talk. I hope you enjoy it. This talk should be a brief introduction into what Kubernetes is and how it works. Not on this low level what happens underneath the surface. That's not the intention of this talk. But what I want to provide in this talk is more the higher level usage of Kubernetes that we as developers, DevOps engineers might be using, are using in our world these days. So, how do we deploy our code to Kubernetes? Some of you may know me from my engagement in the Django project. And historically, I've been primarily contributing to its migration system. Over time, my focus shifted to organizing some Django cons in Europe and Australia, as well as being a member of its operations and security teams. In my day job, I'm a staff engineer and team lead at Microbiolytics and responsible for our cloud infrastructure and software. At Microbiolytics, we build hardware and software to analyze chemical liquids and then try to revolutionize them, modernize that industry and bring it into the modern world and into the context of industry 4.0. We're also hiring for Python, Django, C-sharp and Angular and numerous other positions. But I want to come back to the first question of this talk, what is Kubernetes? And first and foremost, Kubernetes is a distributed system to run containers. Not only Docker containers, because there's also others out there, but mostly it's Docker that is still out there. Another term for Kubernetes would be it's an orchestrator. However, you can't just run a container in Kubernetes. There's a bit more around there that you need to take care of and that will allow Kubernetes to do the things it does. The smallest deployable object in Kubernetes is a something called a pod. A pod runs at least one container. They like the pods ensure that their containers are restarted if needed or desired. So if a container dies for whatever reason, the pod or Kubernetes underneath will ensure that the container is restarted. Or if you want the pods vanish when all containers are terminated. So if you have one of scripts, Kubernetes could start a pod, run the container in there and when the container terminates, the pod also goes away. And I'll be going a bit more into detail on pods in a moment. But for now, let's look at how you'd actually communicate with Kubernetes. So you generally talk to a REST API that runs in manager nodes in Kubernetes. The counterparts to those nodes are worker nodes. Those are the nodes that actually run your code, run the pods, run the containers. So you interact with Kubernetes typically using a command-like tool called kubectl as I prefer to call it. And with that, you essentially can do pretty much everything within Kubernetes. Kubernetes has this property of being eventually consistent, which means just because you're told Kubernetes through kubectl to the API to do something and the API responded with, okay, I'm done. It doesn't actually mean it's done. Well, not necessarily anyway. It's eventually consistent. So some operations may take a while to be fulfilled. So imagine you deploy 100 pods with containers in there. All the servers underneath will need to pull the Docker images or pull the images and then start them. So this may take a while. This is eventually consistent because it eventually will reach the point where it has deployed and run all the containers. And it's also eventually consistent because, well, if a container dies, as mentioned, Kubernetes can restart it. Or if a worker node dies, Kubernetes will be able to automatically move all the workload on that node to other nodes in the cluster. Or depending on how you deploy Kubernetes itself, like not the stuff in Kubernetes, but Kubernetes itself, maybe it will even restart or spin up a new worker node, potentially even automatically when there's too much work on the cluster. So what does Kubernetes look like on an architectural level? Let's say this is the overall cluster that you can see here. The cluster is typically standalone, but you can technically run multiple clusters and then have them interact with each other. Within the clusters, you have the nodes. I left out the master nodes or the manager nodes here for now because they are not really relevant in this design here. So these are the worker nodes that we are talking about. So within the cluster, you have nodes, worker nodes. And in the chart here, I'm using three of them. When you run Kubernetes locally on your laptop or computer with tools like Minikube or K3S, you probably only have one node because that's usually sufficient there. But this moment when you want to do something somewhat serious with Kubernetes, you want at least two nodes, but even better three because that accounts for better reliability. So this is solely for reliability and redundancy to have more than one node. Nodes can either be physical bare metal servers or VMs in your cloud provider such as EC2 instances on AWS. And in fact, when you use your cloud providers hosted or Kubernetes as a service like AKS, EKS and GKS, they effectively use the VMs of those cloud systems underneath. So if you want a cluster to survive a data centerfire and such, you would make sure to place the nodes across different data centers and set them up networking. But this is something that your typical cloud provider does for you and you don't really need to worry about that. As mentioned earlier, on the nodes, we have these pods and they are spun up on the nodes that have the required resources available. So when you have a couple of fairly beefy nodes then they potentially run more pods than others. And this one node that's very small and doesn't really have any memory or CPUs. And yeah, there exists just a single instance of a pod here in this example with the yellow one. This is just a single pod that's being started in Kubernetes. Other pods can exist multiple times. And this is, for example, the case when you have something called a deployment, which essentially starts the same pod with the same containers underneath multiple times. So when you have your replication that you want to scale out, you can increase the number of pods of the same thing and it will, quote, just work. So for example, the blue and green pods here exist two times. The orange and red ones exist three times. For the orange one, something called a pod anti-affinity is defined. In order to prevent the same pod to be run more than once per node, essentially. So this is something usually a good idea when you want to scale out. Otherwise, when a node goes down, it might take too many pods of the same kind down simultaneously, which would be the case, for example, for the red pods when node three goes down. Similarly, there's something called a pod affinity, which could be set for the green and blue pods to ensure that they run on the same nodes, like always a blue and a green pods next to each other. And this is something you could do when you want to make sure to reduce, for example, a network communication by then communicating directly between the pods on the same node without going through the, let's call it, wiring between two nodes. And there's also a concept called node affinity, which lets you spin up a pod on a specific type of node. So if you have these nodes that run that have graphic cards in there with GPUs and you have some computational expensive operations that you can run on a GPU, then you could run and pin those pods with that to containers onto those nodes that have the GPUs and avoid other pods being run there, for example. Well, and lastly, within the pods, we have the containers. And as mentioned, there's at least one. And it's important to remember that all containers within a pod run on the same node. Now, let's look a bit at the concepts behind Kubernetes. As a developer engineer or DevOps person, what are we going to be exposed to? Well, essentially, everything within Kubernetes is a resource. And we've seen one of them already, which are the pods. And resources have a couple of attributes. For example, the kind, which essentially defines what kind of resource it is, a version, some metadata, a specification, for example, for technical requirements or so. And part of the metadata is also a name, a unique identifier, and a dozen other things. And resources can either exist within the whole cluster, which means they are cluster-scoped, or they could exist within namespaces. And there is an interesting part is that a namespace itself is a resource, which is cluster-scoped. So everything that you put inside a namespace is then namespace-scoped. And yeah, all the other resources that are out there is just a sheer endless list. So I'm not going to go through all of them, but some of the typical ones that we need in the example towards the end of the presentation. But yeah, in this list, you find all the official building blocks for Kubernetes, and then there's hundreds more out there that can be installed separately. I've already mentioned namespaces. They are a key resource because other resources exist on or can exist within the namespace itself. So when you want to create a pod, you need to create the namespace first for that pod to live in, for example. Now, on what you can see here on the left side is a bit of YAML, which is like the D thing in the Kubernetes world you write when you define something. And these four lines essentially define how a namespace should look like or that you want to create a namespace. Other resources look fairly similar, and we'll see that in a second. Here's an API version, there's a name, and there's some metadata. When you're interacting with Kubernetes, you typically, as mentioned, use a tool called kubectl. And on the right side, by default, when you do kubectl, get namespaces, you see a list of already present namespaces such as the kubesystem or the default namespace. Then there's this kubectl apply command, which you can pass a filename, which essentially then let's kubectl take the content of that file, put it to the Kubernetes API, and do something with that. And when you then go and list the namespaces again, well, in this case, we see that the namespace now already exists. As mentioned, there are pods as the lowest deployable entity, and this is what a pod could look like. Again, we have the API version, we have the kind. But what we have here more importantly is that in the spec, we have a kind of list of containers, and we can define that the image for this container should be the traffic, who am I, docker container. The name, essentially, let's, when it is uniquely, I identify the container within the pod. So the name needs to be unique within the container. And then we are exposing some pod to the outside. Well, not to the internet in that sense, but outside of the pod. And similarly, with the kubectl command, you can do get pods and apply to create those. For now, additionally, we have services that we can use for network routing. So you don't necessarily need to fill around with IP addresses, but mostly with names and labels in Kubernetes. Here, for example, we have a service which has a name, who am I, which looks for pods which have this label in the selector pod and which have a pod that is also specified there. So pod 8080 or pods target name HTTP and exposes this as pod 8080 with the name HTTP, which we then, in the next step, can express to the internet using HTTP. And when we deploy Django, for example, or any of other, and most other web applications, these days, we have a reverse proxy somewhere, usually, or typically, or fairly often, NGINX, but also Apache and whatnot. So the ingresses in Kubernetes are essentially our reverse proxies. And NGINX is kind of the de facto standard in the Kubernetes world, but there are others such as traffic. And in recent versions of Kubernetes, the ingress resource has become a bit more complex, but you'll get to understand them eventually once you start using them a bit more frequently. Essentially, when the HTTP requests hostisexample.com, we'll redirect or NGINX will redirect everything that starts with a single forward slash to our Who am I service on pod 8080, which in turn will forward the request to any of the pods that were selected by that service. So essentially, what we did here is we can do here with ingresses and services route any arbitrary HTTP traffic that can come into our Kubernetes cluster to any arbitrary pod. And with the setup we have here, if you have a two-staged service with a dedicated front-end and back-end application, you could, for example, route everything which starts with slash API to your back-end and everything else to your front-end. And then there are config maps and secrets, which are essentially the thing that has been also known as 12-factor as a thing, part of the 12-factor approach or app. So everything should be configured using environment variables, if possible. And yeah, we can do that in Kubernetes as well. We can either hard-code the variables in pod definitions or using contract maps and secrets. There are a few differences between the latter two, but I'm not going into the differences here because that's outside the scope. This depends on how the data is effectively saved inside the Kubernetes cluster inside. Config maps are usually for random configuration information. Secrets are for, well, secrets such as your secret key database credentials, email credentials, all this. And while config maps usually contain the value in clear text, the value in secrets is typically base 64 encoded. I suspect because secrets typically are fairly often have special characters, such as quotes and whatnot in them, and then base 64 encoding them in YAML is a good idea to avoid confusion there. And the last Kubernetes results we're going to look at are deployments. It's a way to tell Kubernetes to run the pod using a given template a set number of times. This is a fairly big amount of YAML here on the left side. But the key part is that at the top we can set replicas, and we want us to have the pods running underneath. We want three pods, identical pods. And the selector labels here are essentially for Kubernetes to find the pods that it should manage. And similar with the template, or in such a template we can put everything that we can put in a pod definition to then go and use this template to create new pods and a bunch of them. On the right side, again, we can see how we can use kubectl. And what we see in the last part section there is this kubectl where we list all pods. You can see this one UMI pod that we deployed earlier. But also these three new Spanner pods with these random arbitrary names or parts of the names in there. So yeah, this is what the pods would then look like. Well, now that the basics are hopefully clear, what we can do with Kubernetes and how Kubernetes works on a high level, let's look at how this would look for a Django project. It turns out it's not that much. It's fairly small things we need to do. So we need a config map where we need to set a loudhouse. And the first one, this myapp.com is probably clear. This is the domain we want to run our app underneath. But then the another one, the myapp.mynamespace, as we see cluster local, is essentially a Kubernetes internal DNS name. And with that, we can talk to this service within our cluster without needing to know the external domain. The secret essentially contains the secret key in the database URL, which points to some database either inside Kubernetes or running outside. And yeah, a secret key. And then within Django, we can use the wonderful djdatabase URL package to load the database field and have it automatically set up and pass if it's per square source equivalent or not. We can split the allowed hosts on a comma to ensure we can support multiple. We make sure that debug needs to be explicitly turned on, which we didn't do in Kubernetes as the correct choice there. And yeah, we load the secret key from environment variable because Django will refuse loading or starting without a secret key. We can also just arrow out when there's a key arrow when the environment variable isn't even defined. And then in our deployment, we refer to the config maps and secrets in their entirety and not only key by key. What you can see in the end from, which essentially maps all the keys in this maps as the environment name and the values as the corresponding values. And then the last two things I briefly want to mention are the Dockerfile, which has a custom entry point and the default command, which is then essentially the Unicorn set up to run the app. The entry point is, does a couple of things. First of all, it's make sure that the database is up. So while for Postgres, for example, there's a pgready command using the Django admin shell here with this way with the command allows us to avoid double configuration of environment variables. And essentially by repeatedly checking that the app is up while we started, we can just, well, start the port and it will wait until the database connection is actually established and available. And secondly, it applies all migrations. While this can be tricky given proper testing and careful conservation, the implications of migrations between two relays in this, I've used this pattern of just running migrations on every deploy numerous times now. And you can check more actually my talk from DjangoCon Europe this year to get more insight on this particular topic there. And then lastly, if you do have UI components in your backend, you can run collect static for the static files to collect them. Well, and then we execute the command that you started off with. And that is essentially Kubernetes. And yeah, I hope there's a couple of information in here that's helped you and that you can now go and like, okay, at least have a bit of understanding of what Kubernetes is. And yeah, thank you. Thank you so much, Marcus. There's definitely a lot of YAML. Oh, yes, there's a lot of YAML. We have a couple of questions. First one, is there any resource you recommend for beginners to learn Kubernetes in practice? Doing it, playing around with it, and then reading up on things that don't work. Like literally sit down and... I'm not sure if I seem to remember there's a bit of a tutorial on the Kubernetes website itself, but I'm not entirely sure. Cool. So that would be... Yeah. For the very basics. I mean, there's so many things that I haven't covered here with regards to SSL, with regards to permissions, with regards to all kinds of other things. Yeah. Cool. I heard about the Learn Kubernetes in the hard way. If you know about that. I don't know if it's good. I haven't tried it yet. I've heard about it. That's about it. I have not looked at it at all. My experience with Kubernetes is pretty much being thrown into the cold water and playing around with it and figuring it out while having people at hand that have more experience with it. Fair enough. Second question is similar. Is there any good example of a Kubernetes config or like a project that uses Kubernetes that one can read to learn about it? Not that I know from the top of my head. I can't name anything right now. Cool. Norris, I guess people can use your slides as boilerplates for Django app, right? I suspect so. Yes. Awesome. That's it. Thanks, Markus. Thanks again.