 My name is Flores. I've been writing Python for quite a while and I've been starting to use Kubernetes for about one and a half years. I think I've worked with it since just before it became 1.0. And this is sort of a number of things that can make your life easier towards building your application more suitable for Kubernetes. Kubernetes is part of the Cloud Native Computing Foundation. That's kind of where the title Cloud Native Python comes from. And one of the things is like it's not really necessary to do all of those things. You can start without actually doing any of these things as soon as you go, you can dockerize your application. You can run it in Kubernetes and start benefiting. It's just a number of things you can start adding to your application gradually as you need them, as you want them kind of thing. This is sort of the roadmap. So I'll go through very quickly, give a bit of background about how Kubernetes actually works. If you know of Kubernetes already, that may be a repetition, but I'll keep it very small and simple. Then I'll sort of say on which parts of the execution environment that you get out of when you're running inside Kubernetes that you can sort of rely on what shortcuts you can use. I'll talk about handling logging. Then I'll go a bit about endpoints and monitoring. This is kind of my summary of Kubernetes. So Kubernetes is inside of in one slide, if you want. So Kubernetes is actually, it's an orchestrator for running your application on our cluster. So the idea is that you just have your process, which you wrap into a container. It's just your single Python process that runs in your container. Currently, that basically means Docker. Kubernetes will just make sure that your application runs somewhere on one of the machines in the cluster. Kubernetes refers to machines as nodes in the cluster. So you've got your application, which is your process wrapped in a container, and Kubernetes abstracts away into a pod. A pod basically has got some extra bells and whistles. For this talk, you don't really have to worry about what else you can do with a pod. Pods don't really have much guarantees when they run on this cluster. If the node goes away for any reason, your pod goes away. Pods may get killed for other reasons because they start using too much resources of some sort or whatever. So they don't provide any consistency. So the next abstraction on top that Kubernetes sort of gives is the replica set. And the replica set kind of monitors your pod. And the idea is that you just say, can I want to have this many copies? I want to have one or whatever number of copies of my application, of this application running. And that will just watch your pod. And if your pod goes away for any reason, you create a new one. And that also allows you to kind of scale up as you get more load, you add more. You just say, I'll give me more replicas and more pods will run. And they'll run distributor over the cluster. There's some shadow re-involved that figures out where the best place is for your pods to run. There's other abstractions other than replica set, but I'm not going to go into those. There's various other ways of looking after your pods, basically. But as concepts for now, that's kind of enough to sort of, everything is built around this concept and a lot of them use replica sets. The other thing is like, because your pods are so ephemeral, they just move around, they come in, they go, you don't know which machine they're on. You need to be able to address them. So there are IP addresses in pods if you have a service that listens for incoming connections need to be addressed. So that's where the service abstraction, the service object comes in with Kubernetes. So the idea there is that it's basically kind of a level for proxy or load balancer. And it just, it's fixed IP address for your pod that anyone in the cluster, there's normally the name associated with that as well. So you can easily find it. And whenever you need to talk to your service, you connect to that IP address and that will route the traffic to wherever the actual pods is. If you have multiple pods, it will round robin between them or something like that. Again, there's mobiles and missiles, but that's kind of, if you got those three things you know enough about, you know, in the basics of both Kubernetes, kind of how Kubernetes manages things around. So it brings us to kind of what you can do in your execution environment. So you're running in your application, your Python program is basically running inside the spot that's being looked after by replica sets, et cetera. And basically it allows you to kind of skip a bunch of boilerplate. You know, your audience is an operations team that knows how to handle Kubernetes. And also it means you can rely on the architecture for scalability, et cetera. So you can make your internal application actually a lot simpler. You don't have to worry about it as much. One of the, so the first kind of thing I think is kind of useful is don't try and recover errors. If you get an unexpected exception, just make it, just make it crash your whole application. You're probably fine because your application is being watched after. So inside Kubernetes you got a lot, all the operations is kind of done with a lot of YAML files. So if you've played the Kubernetes before you might recognize this, this is just a partial kind of view of that. So this would be some sort of way of defining the application to run. You have an image and some arguments. It's actually a bit, but the bit that matters here is the restart policy at the very last line. That basically ensures that whenever your application dies, it will get restarted. And that allows you to, you know, when you have any unexpected conditions, your request data doesn't validate or something like that. You can just crash and you can rely on the infrastructure to take care of it. Again, it will be logged that the crash happened and you have all the information to look at what went wrong. The one thing to think about is if you have a service that accepts incoming connections and processes requests that way, you do have to worry about when you crash what you're going to lose. So if you have a number of requests queued up inside your application, if you crash and you lose all those requests, they will start timing out. And that might not be as ideal. So you may want to think a little bit about your architecture there and make sure that maybe you can use a messaging bus to process your requests so that when you crash you don't lose all of those. So you have to look out a little bit for those. But yeah, otherwise, don't try and make your application too robust, really. And the other thing kind of linking into that is you can also make your application a lot easier. You don't have to handle a lot of scale. The idea is to scale by the process model, which means you just run more processes in parallel. And if you have a messaging system already to talk between your services, it's very easy. So as I showed earlier in the Kubernetes slide, the idea of scaling is just to create more of your pods with your application running into. And that means you're not terminating kind of the end user request. You should have a load balancer or reverse proxy if you're handling an HTTP traffic. You'll have a reverse proxy in front of that. So you're not handling directly the end user requests. So you can just, your internal architecture can be very simple. You just have a main loop. You handle one request at a time. You don't, and making this architecture that way means you don't have to worry about the scale too much because the scaling is done on a different level, really. Yeah, so next on, I think it's useful to say a little bit about logging. So again, kind of on a very simple level, if you have ever played with Docker or Kubernetes, by default things go to standard out. If you're the main process in your container, and I would recommend you just run one single main process in your container anyways, then anything you write to standard out or standard error will be captured in the logs. And these logs will be sent through from Docker to Kubernetes, and Kubernetes will then keep these logs. It's very simple. It works nice while developing. But it does have its downsides because records are basically line-based. So if you have one line, that's your log message. If you got a trace back that you got in your logs, that spans multiple lines. That's kind of no longer one logical. Once it's gone through the Docker and Kubernetes logging, that's no longer one logical line. So oftentimes, the sort of log aggregation that might be running, your operations will probably start running only once they deal with lots of logs. We'll try and join back together these records into single records. But the thing here is that even though it's just standard out, you want to make sure that it is configurable. It's up to you, depending on how your operations manages things. So if they want to send all the logs to something like the Elkstack Elastic Search, Logstash, and Kibana, or something like that, so if they want to index it, search it and make it search, all that sort of stuff, that kind of gets built on top. But you still want to give the option to the operations to configure their logging. And so you don't want to beg that completely into your application. So it's often very, very useful. I prefer it as combined line arguments. You can stick things into the environment variables as well. But yeah, make sure to sort of make it configurable so your operations can choose what they do there. The other thing is don't skip using logging libraries because it's just standard out. The logging libraries, just to get things going on standard out, are very easy to set up anyway. It means you straight away get your different log levels that you can use. All logging libraries are basically kind of wrappers around the horrible amount of global state that it's quite terrifying. Pick the one you like most, I would say. Some handle the global state in different ways. The thing I'm showing in the middle block with the logbook example, it sort of kind of leads on to the next step that you might want to take with logging is once you're using a logging library and your operations have started moving to a log aggregator, you might actually want to, instead of going via the docker and Kubernetes route of letting your logs be captured, you might actually want to start bypassing and start things directly into your log aggregation via something like a gray log or system D. You could set that up to send. So you can add your additional log handlers to your log library. In that case, you probably want to put the exception logging right wrapped around your main function basically because that will actually allow your whole exception to be sent as one single log record rather than what you, if you just let it up to Python to bubble out, it will end up on the normal Python handler, will print it on standard out again and it goes back as a bunch of unrelated records again. The last one, struct log, I think is very nice, especially if you aggregate just lots of things because it allows you to put a lot more machine passable data in. So it's kind of a nice wrapper around things as well. Yeah, so that's pretty much everything around logging, I think. So basically, yeah, standard out is not amazing, but using a log library allows you to evolve towards better log record handling. Next on, I would like to talk about the health endpoints that Kubernetes kind of provides. The thing here that we're trying to address here is when your application starts up, it might be initializing some data and it might be contacting various other things to collect data or whatever, but between process start and being ready to accept connections, this is obviously in the case your application is one that receives connections, there is a non-finite time. Unfortunately, as soon as your application starts up, your pod gets created in Kubernetes. It will be if you have a service pointing to it, which you need to accept your connections. So as soon as that happens, traffic will start flowing towards you. If you haven't opened your socket yet, then those connections are just going to get connection refused and you're losing your requests basically. So the idea here is that you tell Kubernetes to wait to add your pod to the service as one of the handles of that service until you're actually ready to accept connections. And they do this with a readiness probe and there's various probes, various types here. Basically, the three types that they support is a TCP socket and HTTP request and a command to execute inside your container itself. The last one is not always terribly useful, I find, but the first two are very useful. So if you're for the TCP socket example, which is the top one here, if your service basically opens a socket for whatever, maybe 0 and Q, G, R, P, C, whatever, something like that, if it directly opens a socket, once the socket is open, basically you can use your main listening socket for this probe in this case because once the socket is open, connections will start to be able to be made. They'll queue up in the kernel until you call accept on your socket. So it's a very useful probe. In this case, when you start the pod, it will try to port. If it's not ready yet, it will just wait. You can again configure this more detailed, how often to try and all the sort of things, how long to wait. If you're having a service which handles HTTP that's even simpler, you can just add basically another root to your API that you got. It's kind of okay to just this health set root basically. So this configuration will do a simple plain HTTP request. You probably find with plain HTTP because as I said, you have your reverse proxy already handling HTTPS, et cetera, in front of that. And the other thing is because you have your reverse proxy in front, you don't have to worry about putting that health endpoint on a different port really because your reverse proxy is not going to root any request to that endpoint anyway. So if you're accepting direct user request, users are still not going to see that endpoint because of your reverse proxy. So you can just do that in line with your main request and that gives you a very good sense of when your application is actually really handling the requests because it's not in special anymore. That kind of brings us to the next step on that, this kind of liveness checking on this. The idea here is that if your application hangs or something like that, you don't want it to be included in the set of endpoints for that service. This kind of depends on your application. Oftentimes, you shouldn't really just randomly hang because you don't know anything. If you can, you should just crash and raise an exception and crash basically. But if you do want, it's most useful probably if you have an HTTP service or API or something because then you can just handle it in line and that kind of gives you that guarantee. If yes, I'm handling requests in line. The exact example here, I'm not sure. Kat is certainly not a very practical example, but it just shows how that third kind of live probe. They're basically the same three types of probes that you have for readiness that you can use here as well. The one thing to look out with doing your HTTP requests in line is that you do have to balance it off like if you request a cued and taking a bit too long so you have to check that your timeouts on your liveness probes are okay because otherwise you might still be handling requests, but Kubernetes then decides that you're taking too long. There's lots of trade-offs and tuning to be done there. So it's kind of, it can be useful. It's a more advanced kind of probe or end point to add to it. But it does kind of lead on to save pod termination in the way of container application termination because it is something to kind of think about because basically what Kubernetes will do when you're asked to terminate, so say a node you're running on is taken into maintenance, so Kubernetes will kind of shut down all the pods on that node. That's not a problem for your application because your application should be making sure that new pods appear somewhere else. But your pod is still healthy and servicing requests at that point. So for a pod that's, or your application that's servicing user requests, what happens is basically it will send you a term. And in Python normally you get a keyboard interrupt for that. So you will want to register a signal handler for it. So you can do that gracefully instead of just straight away crashing. Once you sort of, in your signal handler you can wrap that up more nicely. You can say I'm going to shut down. As soon as you've got saved term you've basically already been removed from the service. So you're not going to get new requests, but you might still have requests currently queued up. So you want to finish your queue that you're running on. Make sure all your requests are handled so you don't drop any of the requests and only then carry on shutting down. Then the last kind of step into this is kind of monitoring or maybe I think more of it as application instrumentation. So monitoring wise there's already a bunch of kind of monitoring that operators can do out of the box with Kubernetes by running stuff like C-Advisor or something. They'll have an idea of your container and resources to consume, et cetera. Prometheus is a different project that is also part of the Cloud Native Computing Foundation. It's quite popular at the moment, but it allows you to basically instrument your application in various ways. This is a very, very short example basically. It's architecture, it's monitoring architecture of Prometheus. It's kind of very, I find it very similar to SNMP actually, but just with current technologies, it runs that we currently understand that. So it uses TCP which is more reliable or has more guarantees than UDP. But it's still very much a poll based kind of the server goes around to all instances that it knows and it goes and does a request and gets all the metrics down. Despite it being HTTP, the actual wire format is quite special. It's either some text format or it's protocol buffers, I think. So it's easiest to just use a client. They have an official Python client which this is a very primitive example of. So it basically comes with various kind of instrumentation things. So you can have counts, you can have gauges. Again, if you've ever used SNMP, they're kind of various similar concepts. So, yeah, it's just a bunch of metrics and they can come around and scrape. In this example here, just using some counters, you define the counters on the fly which is very convenient. And then you have things like context managers or decorators, et cetera, to help you with various things. That's kind of everything about, yeah, there's a lot more, obviously, that you can do with that instrumentation. But that's kind of the sort of the next step, I guess. And that's just about time, I think. So thank you very much. I hope that was useful. And if you have any questions.