As more companies migrate their applications to the cloud, they are faced with the decision to “lift and shift”, or to re-architect their solutions to be “cloud-native”, which varies in definition. This post will focus on the latter, and specifically, a useful technique that may aid in moving applications to containers (i.e., Docker) and orchestrating them using Kubernetes.
Bootstrapping challenge
One promise of DevOps, and cloud migrations, is to reduce toil in traditional operations by automating manual processes. The declarative nature of Kubernetes to automatically guarantee the desired state of your applications is very powerful. Sometimes, however, applications may need “bootstrapping” steps before they can successfully run, or must be started in a particular order to handle dependencies on other systems like queues or databases.
In order for the app to run successfully, you could re-code it with custom retry logic until optimal conditions exist, but this might delay your migration. An alternative to avoid code rewrites would be to create tiny apps, scripts, or commands, and add them as InitContainers.
Potential use cases for Init Containers
- A blockchain app that needs to register itself amongst its peers
- An app that must fetch an access token from an identity provider
- Dynamic data fetched from a database and cached for the app to run after startup
- Fetch encrypted secrets from a vault and write to file system
- Block app startup until another system is available (i.e., a queue or database server)
Simple Example Using Two Busybox Images
In this example let’s assume an app might need to be dynamically populated with some data before it starts up (like scenarios above). This example could be achieved using a ConfigMap, but for illustrative purposes on the power of this technique, keep an open mind on what else you could do.
- Create a PersistentVolumeClaim
First, we need some storage volume that we can access
$ kubectl apply -f test-pvc.yaml
persistentvolumeclaim/test-pvc created
2. Bootstrap a Deployment config YAML file
$ kubectl create deployment test-app --image=busybox:1.28.0 -o yaml --dry-run > test-app.yaml
3. Edit Deployment and add volume and Init Container as follows
Notice in this file we added the volume, and then added another Busybox image in the initContainer. This could literally be any image, but for this example, I just wanted to write some content to a file in the mounted file system, and then when the app starts with the other Busybox image, it accesses the file and reads it’s content.
4. Run Deployment and monitor logs
# deploy app $ kubectl apply -f test-app.yaml
deployment.apps/test-app configured
# monitor logs $ kubectl logs -f deployment/test-app
File content: Hello, World!
If you see “File content: Hello, World!”, then it worked. From this basic example, you could then customize your app to replace the main container image, and you can add more init containers as desired to make sure your app starts with everything it needs. The promise of this technique is you can automate deploying your apps without the need to write complex retry logic or error handling to address former manual dependencies.
For additional information about Init Containers, check out the official Kubernetes documentation:
https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
Enjoy!