Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How best to run one-off migration tasks in a kubernetes cluster

I have database migrations which I'd like to run before deploying a new version of my app into a Kubernetes cluster. I want these migrations to be run automatically as part of a Continuous Delivery pipeline. The migration will be encapsulated as a container image. What's the best mechanism to achieve this?

Requirements for a solution:

  • be able to determine if a migration failed so that we don't subsequently try to deploy a new version of the app into the cluster.
  • give up if a migration fails - don't keep retrying it.
  • be able to access logs to diagnose failed migrations.

I had assumed that the Jobs functionality in Kubernetes would make this easy, but there appear to be a few challenges:

  • Kubernetes will repeatedly re-run containers whose processes terminate with a non-zero exit code, even if the Job has a restartPolicy of never.
  • blocking while waiting on the result of a queued-up job seems to require hand-rolled scripts

Would using "bare pods" be a better approach? If so, how might that work?

like image 351
Pete Hodgson Avatar asked May 05 '16 19:05

Pete Hodgson


People also ask

When should you run migrations?

Run the database migrations first, before you deploy the new code. This means the before code must work with both database schemas, but the after code can assume that the tables have already been added.

Can 2 pods communicate in Kubernetes?

Kubernetes assumes that pods can communicate with other pods, regardless of which host they land on. Kubernetes gives every pod its own cluster-private IP address, so you do not need to explicitly create links between pods or map container ports to host ports.

What is the smallest deployable resource in Kubernetes?

Pods are the smallest deployable units of computing that you can create and manage in Kubernetes. A Pod (as in a pod of whales or pea pod) is a group of one or more containers, with shared storage and network resources, and a specification for how to run the containers.


2 Answers

blocking while waiting on the result of a queued-up job seems to require hand-rolled scripts

This isn't necessary anymore thanks to the kubectl wait command.

Here's how I'm running db migrations in CI:

kubectl apply -f migration-job.yml kubectl wait --for=condition=complete --timeout=60s job/migration kubectl delete job/migration 

In case of failure or timeout, one of the two first CLI commands returns with an erroneous exit code which then forces the rest of the CI pipeline to terminate.

migration-job.yml describes a kubernetes Job resource configured with restartPolicy: Never and a reasonably low activeDeadlineSeconds.

You could also use the spec.ttlSecondsAfterFinished attribute instead of manually running kubectl delete but that's still in alpha at the time of writing and not supported by Google Kubernetes Engine at least.

like image 69
Aleksi Avatar answered Sep 21 '22 09:09

Aleksi


You could try to make both the migration jobs and app independent of each other by doing the following:

  • Have the migration job return successfully even when the migration failed. Keep a machine-consumable record somewhere of what the outcome of the migration was. This could be done either explicitly (by, say, writing the latest schema version into some database table field) or implicitly (by, say, assuming that a specific field must have been created along a successful migration job). The migration job would only return an error code if it failed for technical reasons (auch as unavailability of the database that the migration should be applied to). This way, you can do the migrations via Kubernetes Jobs and rely on its ability to run to completion eventually.
  • Built the new app version such that it can work with the database in both pre and post migration phases. What this means depends on your business requirements: The app could either turn idle until the migration has completed successfully, or it could return different results to its clients depending on the current phase. The key point here is that the app processes the migration outcome that the migration jobs produced previously and acts accordingly without terminating erroneously.

Combining these two design approaches, you should be able to develop and execute the migration jobs and app independently of each other and not have to introduce any temporal coupling.

Whether this idea is actually reasonable to implement depends on more specific details of your case, such as the complexity of your database migration efforts. The alternative, as you mentioned, is to simply deploy unmanaged pods into the cluster that do the migration. This requires a bit more wiring as you will need to regularly check the result and distinguish between successful and failed outcomes.

like image 32
Timo Reimann Avatar answered Sep 18 '22 09:09

Timo Reimann