Video Thumbnail for Lesson
10.1: Developer Iteration (Tilt)

Developer Iteration (Tilt)

Transcript

At this point, we have our application running in Kubernetes. We have a number of auxiliary tools that are running to make our experience better. However, there's still a handful of developer experience challenges that I want to call out and address here in this module. Specifically, I want to look at iteration speed, so how long it takes you from making a change to your application to being able to test that change in a representative environment. And two, secrets management. Every time we've shown a secret, I have a big warning that says, don't store this in version control, etc. And so we're going to take a look at how you can manage secrets effectively without needing to put them at risk. In terms of the iteration loop or the speed with which you can make a change and see that change represented in your application, there's a number of different approaches that people take. Some people do their development outside of containers and just run the processes local on their system. The big challenge with that is now your dev environment can be significantly different than your staging or production environments, and eventually you're going to hit edge cases where the application behaves differently based on those differences. The next approach that I see a number of teams taking is using Docker Compose. And Docker Compose allows you to declaratively specify a group of services defined as containers along with networking and configuration. It does allow you to test your applications in their containerized form. However, because you're configuring it with a different tool, the configurations and networking and things like ingress, anything that lives at that cluster layer are going to be different in Docker Compose versus Kubernetes. So you're going to have some... While it's more similar than testing without containers, you're still going to have some differences between your Docker Compose environment and your Kubernetes environment that you're deploying into. That being said, Docker Compose is very easy to get started with, and therefore it can be a good option early on. The next four tools all use Kubernetes directly and have some variation on detecting changes and automatically rebuilding and pushing images to enable those changes to show up in that cluster environment as quickly as possible. Some of them you work with local clusters, others you set up a proxy to a remote cluster, and they do some technical magic to intercept network requests and route them to the right places. As you're figuring out how your team is going to work with Kubernetes and develop applications for Kubernetes, I would explore these options. As I said, we're going to showcase how to use Tilt to build out an environment like this locally. Let's jump over to Code Editor and do just that. Let's go ahead and navigate into our module 10 directory, into the Tilt subdirectory, and take a look at our Tilt file. This top-level Tilt file just references four additional Tilt files, one for each of our services that we're developing. We've got one for our Go API, one for Node, one for Client React, and one for our load generator. Now, you could set this up to also deploy things like the Helm chart for Postgres. Tilt does support Helm out of the box. But for this demo, I just wanted to showcase a basic setup where we're going to deploy everything into the cluster and then run Tilt to iterate on our services. Let's go take a look at what the Node API Tilt file looks like. That lives in module 6 alongside the code. And I put it alongside the code just because it makes it easier to detect changes and rebuild, and that's where the Docker file lives. So if we go here and open our Tilt file, this is about as simple as it gets. We specify how to build the image. In this case, I'm giving it a container registry name, giving it a build context, and telling it to use Docker. It's going to use the Docker file that lives alongside this Tilt file. I'm then telling it where the YAML for the Kubernetes resource using that container image lives. This is the module 7 API node deployment.yaml. That's the consumer of the container image. And then I'm telling it to port forward port 3000 such that we can access this API. The Golang one is going to look just a little different, and that's because we're not using Docker build. We're using Co to build it. So if we open up this, there's an extension for Tilt that uses Co, runs the Co build command. Again, we pass it some arguments, tell it where the YAML for the consumer of the container image lives, which port we want to forward network traffic to. The Tilt files for the other two resources are going to look quite similar, so we don't need to go through them. We're going to make sure we're pointing to the kind cluster. Then we're going to make sure that we've deployed our base resources. So as I mentioned, currently the Tilt file only deploys those four services. We want to make sure we have Postgres already running. We want to make sure we have traffic already running, et cetera. So let's go ahead and do that by navigating to the module 7. Then I can do a T, apply all. It's going to install Postgres, install traffic, configure all of my services, et cetera. We can see here all the services are in a running state, except for the load generator, which is in an image pullback off state. It's because I haven't created that Docker config JSON secret in order to pull the image from the private Docker registry. I'm going to ignore that error for now because Tilt is actually going to build a new version of the image locally and push that to my container registry, so it's not going to be a problem. In order to access this application, we can look at the services in our cluster. Remember, in order to get an external IP for a load balancer type service, we had to run this cloud provider kind software on the host. So I'm going to run that in the background. That's going to allow traffic sent to this IP address to be routed to my ingress controller. I've added this to my Etsy host so that when I access kubernetes-course-devops-director.com, it'll route that traffic to this local IP address. And so now, if I access this in the browser, I'm getting back my API responses. At this point, we're ready to run our tilt-up command. So just running tilt-up in the context where that top-level tilt file lives is going to bring up this dialog. In this case, I'm going to hit space. It's going to open up a browser, and we can see the services defined within our system. If we click into the details page, we can see logs associated with the various services, as well as any logs coming from the Tilt system around changes to those systems. One thing I noticed here is I'd forgotten to change the text that's displayed in the browser bar here. This still says DevOps Directive Docker Course. Let's go ahead and make that change and see how Tilt rebuilds and updates our image. So I'll go here into my browser. Search for DevOps Directive Docker Course. Here it is. Change it to Kubernetes. Save it. And now let's go back to our Tilt window. You can see Client React detected that change, rebuilt our image in five seconds, and now if we go back to this tab and click refresh, we get the updated text here. And so any change we make to any of our services, Tilt is going to detect that and make that modification. You can see the load generator is querying our node API, and so we're getting the logs from both of those. This is collating all those logs together. We could filter it down to a specific service, or we can get all of the logs in one window. We can also filter by regular expressions or whether the logs are coming from the Tilt system or our applications. If we go into our Go application, let's just change the health check to instead of return pong, return blah, blah, blah. And then we click save. You'll see immediately Tilt detects the Golang change. It's rebuilding our image. You can see the logs from that build process. And with that, in 12 seconds, it rebuilt our image. Also, now that my Docker cache is hot, let's change that back and see how long it takes to build. If I go back over to Tilt, updating, and it looks like the time was cut in about half. Also, some of these other services are building much quicker. And so this is where you really want to optimize your Docker file so that your Docker image cache is working as efficiently as possible. And Tilt also has a concept of live updates, which allow you to specify certain files within a project that will get synced directly into a container without needing to rebuild it. For an interpreted language, this can make a huge difference. You may be able to skip the Docker build process entirely and sync changes directly into your running container. For example, if you were working with Python, you could specify that anything under the API directory should perform a sync, whereas any change to the dependencies via the requirements.txt file, that will run a full build. For a compiled language, it's a little less intuitive how this works, but as you can see in this Java example, it's syncing in some of the compiled class files from the build slash jar directory and then restarting the process. Following these steps, they were able to optimize their build time for this Java application from their initial naive approach of nearly 90 seconds, which is clearly unacceptable for a local development loop, all the way down to under five seconds. There's docs for using and optimizing Tilt across a number of different languages, such as Go, Python, Node, etc. As you're setting up Tilt, make sure to go here and follow their best practices to make sure that your iteration speed is as quick as possible. We can also take a look at our deployments to see how Tilt is working behind the scenes. So if we scroll up here to the image, now instead of using the tag that I had specified in my YAML file, Tilt has gone and built this specific image and updated my deployment such that it uses the new image rather than the one I had specified. If we are done with our development, we can just kill this process. And so by using something like Tilt, you can now develop directly against a Kubernetes configuration that will match as close as possible to what you're going to run in production. This will allow you to identify and debug issues that live not only at your application layer, but also at that Kubernetes layer or maybe in your Ingress controller. So definitely take a look at Tilt, Scaffold, MirrorD, or maybe Telepresence and see if one of those meets the needs of your development team.