Deploying Application Updates to Kubernetes
Now that the meal planning application has been deployed to the Google Cloud we will need to be able to deploy our new changes to it. In this tutorial, we will go through just what it takes to roll out updates to the applications in our Kubernetes cluster.
- Deploy application updates for our Rails application in Google Cloud.
- Determine how to go about deploying database migrations.
Now that we have an application running in our Kubernetes cluster we will want to keep on deploying our development efforts to it. Even though we’re currently working with a Ruby on Rails application there aren’t really any unique concerns that we have to deal with. The flow for deploying most web applications will be the same:
- Create updated image (new tag) with the most up to date code.
- Upload image to a repository.
- Update Deployment to use the new image tag.
- Apply changes to our Kubernetes cluster.
Enhancing the Rails Application
We need to make some changes before we can actually deploy anything useful. For our case, we’re going to be adding a new model to our application. This particular change will be good for us because it will require us to make a change to the database schema. To get started, we will use the Rails model generator to create most of the files that we need (making some tweaks as we go). To run the generator make sure you either have rails 5 installed locally or execute it from within the
app Docker container. First, let’s modify the generator configuration so it doesn’t create more than we want:
Now we can start the Docker containers and create the model:
$ docker-compose up -d $ docker-compose run --rm app rails g model Ingredient name:string
Before we run our migration we’re going to tweak this a little so that we can ensure a few ingredients exist.
class CreateIngredients < ActiveRecord::Migration[5.0] def up create_table :ingredients do |t| t.string :name t.timestamps end Ingredient.create(name: "Thyme") Ingredient.create(name: "Olives") Ingredient.create(name: "Lettuce") end def down drop_table :ingredients end end
Next, we’ll migrate the database:
$ rails db:migrate
Lastly, we will add a temporary piece of data to the homepage stating the number of ingredients so that we can ensure that this is deployed later.
<h1>Welcome</h1> <p>Existing recipes: <%= Recipe.count %></p> <p>Unique ingredients: <%= Ingredient.count %></p>
Now we’ve made a significant enough change to deploy.
Database Migrations in a Containerized Application
How should we go about actually deploying this new database migration? We can’t deploy the code first and then manually migrate the database because it could lead to users attempting to access the application getting a missing migration error. There are a few ways to handle this:
- Don’t ship database migrations with code changes. Migrate the database additively before ever publishing the code that requires the change.
- Add migration as part of the container start process in production.
We’re going to go with the second option in our case, but it’s worth mentioning that this is the more “dangerous” option. We won’t have the means to “rollback” a database migration if it’s automatically done as part of the deployment. We need to make sure that the migrations that we write are always safe to run on deployment. As your application grows it is probably a better idea to transition to using the first strategy.
#!/bin/bash -e if [[ -a /tmp/puma.pid ]]; then rm /tmp/puma.pid fi if [[ $RAILS_ENV == "production" ]]; then rake assets:precompile rake db:migrate mkdir -p /usr/share/nginx/html cp -R public/* /usr/share/nginx/html mkdir -p /etc/nginx/conf.d/ cp site.conf /etc/nginx/conf.d/default.conf fi rails server -b 0.0.0.0 -P /tmp/puma.pid
Packaging Up a New Image
Now that we have all of the code that we need in place it’s time to update our image. The image that I’m currently using is
us.gcr.io/aesthetic-genre-165010/mealplan:1.0.0, but you will have a different one (watch the previous tutorial get up to speed with our Kubernetes cluster set up). Let’s build the image now:
docker build -f Dockerfile.prod -t coderjourney/mealplan:1.1.0 . docker tag coderjourney/mealplan:1.1.0 us.gcr.io/aesthetic-genre-165010/mealplan:1.1.0
With the image built and tagged properly, we can now publish this to the Google Container Registry.
$ gcloud docker -- push us.gcr.io/aesthetic-genre-165010/mealplan:1.1.0
Deploying the Image Change to Kubernetes
We now have an image to work with and all we have left to do is to update our Deployment to use it. We’ll make a change to the file now.
# Only showing updated `image` line image: us.gcr.io/aesthetic-genre-165010/mealplan:1.1.0
Using what we’ve done up to this point we would use
kubectl create in order to build the proper object, but since the object already exists we would see this error if we tried that.
$ kubectl create -f deployments/mealplan.yml Error from server (AlreadyExists): error when creating "deployments/mealplan.yml": deployments.extensions "mealplan" already exists
create is not the only tool that
kubectl gives us. What we’re really doing here is “applying” a change to the deployment, so for that, we can use the
$ kubectl apply -f deployments/mealplan.yml Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply deployment "mealplan" configured
We receive a warning here because we should have actually created the objects originally using the
apply command (and that’s what you should use moving forward). When first starting out the
create command makes more sense than “applying” to nothing.
Looking at the public IP for our frontend service, we should see that our 3 ingredients are being mentioned on the homepage:
In this tutorial, you went through making a routine change with migration to a Rails application and looked at how to deploy those changes. The big takeaways from this tutorial should be the pros & cons of how we deploy migrations and the addition of
apply to your Kubernetes tool belt.