Imagine the following:
Once upon a time a team of platform engineers created a Cloud stack for an application development team to deploy their WordPress application. The stack consisted of a Kubernetes cluster deployed to AWS using EKS and other necessary supporting infrastructure such as Virtual Private Cloud (VPC), a MySQL database using Amazon Aurora, etc.
The platform team followed all good practices like describing the infrastructure using Infrastructure as Code (IaC) and separation of concerns by providing the application team the database cluster endpoint and required WordPress user and password to connect to the database to run the WordPress application and a CI/CD pipeline to create and destroy the infrastructure as needed.
In a static environment, meaning an environment that rarely changes once deployed, the platform team could manually create the WordPress database and give the WordPress user the right privileges to the database and call it a day. When the environment is not static, meaning the infrastructure could be continuously deleted and recreated, when testing or developing features for example, or when multiple copies of the environment need to be created, it becomes a challenge for the platform team to continuously create the database and user rights.
There are many ways to solve this, but today I will explore solving it by using Kubernetes Init containers.
What are Init containers?
Init containers are a special type of container that runs before the main application container in a pod. They are used to perform tasks that need to be completed before the main container starts, such as:
- Downloading and installing dependencies
- Creating directories and files
- Running scripts
- Bootstrapping resource the application needs
Why use Init containers?
Some reasons to use Init containers are:
- To improve the security and management of your applications using separation of concerns
- To reduce the size of your application container images
- To improve the reliability of your applications
- To perform tasks that need to be completed before the main container starts, such as downloading and installing dependencies.
- To delay the start of the main container until a set of preconditions are met.
Init containers can be used to isolate tasks that could make the main container image less secure. For our example, we will use an Init container to connect to a brand new database and create the needed database and user privileges in a separate container from the main application container. This would prevent a sensitive process from being included in the application container image, which would make it smaller, more secure and have a clear separation of concerns. The Init container image is owned and managed by the platform team, and the application team will just need to include the Init container in their Pod manifest.
Init containers could also be used to delay the start of the main container until a set of preconditions are met. For example, you could use an Init container to wait for a database to be ready before your application container starts.
How do Init containers work?
Init containers are defined in the same way as application containers, but they are placed in the initContainer
section of the pod spec. When a pod is created, the Init containers are started in order, and they must all complete successfully before the main application container is started.
Example of using Init containers:
As mentioned, we will use an Init container to bootstrap our database and have it ready for the WordPress application. In order to use our Init containers there are a few Kubernetes resources we need to create:
- Secrets to hold the Database endpoint, and admin user and password
- ConfigMap that contains the initialization script our Init container will use
- Deployment that contains the Volumes, Volume Mounts, main container and Init container specifications
Using Kustomize you can create a Kubernetes Secrets using secretGenerator
as such:
secrets.yaml
secretGenerator:
- name: mysql-pass
namespace: wordpress
options:
disableNameSuffixHash: true
literals:
- dbpassword=
- dbhost=
- dbuser=
- dbase=
resources:
- namespace.yaml
configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: mysql-init
namespace: wordpress
data:
mysqlInit.sh: |
#!/bin/bash
mysql --host=$WORDPRESS_DB_HOST --user=$WORDPRESS_DB_USER --password=$WORDPRESS_DB_PASSWORD -e "CREATE DATABASE IF NOT EXISTS wordpress;USE wordpress;GRANT ALL PRIVILEGES ON wordpress TO 'admin'@'%';"
The ConfigMap holds a bash script that will call the MySQL client passing the database host, user and password stored in the Secret and passed to the Init container as environment variables, then it will create the database and provide the admin user privileges to the database.
wordpress.yaml
apiVersion: v1
kind: Service
metadata:
name: wordpress
namespace: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: wordpress:6.2.1-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbhost
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbuser
- name: WORDPRESS_DB_NAME
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbase
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbpassword
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
initContainers:
- name: mysql-initializer
image: mysql:5.7
volumeMounts:
- name: mysql-init
mountPath: /opt
env:
- name: WORDPRESS_DB_HOST
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbhost
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbuser
- name: WORDPRESS_DB_NAME
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbase
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: dbpassword
command: ["sh", "-c", "./opt/mysqlInit.sh"]
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wordpress-storage-claim
- name: mysql-init
configMap:
name: mysql-init
defaultMode: 0770
In the wordpress.yaml
, we create a pair of Kubernetes resources. A Service to expose the WordPress the application behind an AWS Load Balancer and a deployment containing the the main container, the Init container and volumes required for each. You can see in the initContainers
section where I set the environment variables, the volume mounts and call the init script.
Conclusion
Init containers are a powerful tool that can be used to improve the security and reliability of your Kubernetes applications. By understanding how Init containers work and when to use them, you can make your applications more efficient by automating processes the application needs without adding unnecessary business logic, thus creating a clean separation of concern .
Here are some additional tips for using Init containers:
- Use Init containers to perform tasks that are specific to your application.
- Don't use Init containers to perform tasks that could be done in the main container application logic.
- When deciding when to use Init containers, think about separation of concerns.
I hope this blog post has helped you to learn more about Init containers. If you have any questions, please leave a comment or a below.