Kubernetes abbreviated as k8s or Kube is an effective container orchestration tool initially developed by Google, but currently a community project. This tool is used to automate and scale container deployment. In recent days, the popularity of Kubernetes and its ecosystem has grown immensely with its ability to design patterns, workload types, and behavior.
One of the jewels in the crown is the init containers feature. This feature provides many advantages in the initialization of workloads.
What is an init Container?
An init container can be defined as a container with modified operational rules and behavior. They normally contain utilities and setup scripts that are not present in the app image. The most preeminent feature for init containers is that they start and terminate before the application containers.
In a single pod, there might be multiple containers running with one or more init containers as well. Init containers have similar features and fields to app containers. These features include volumes, security settings, and resource limits though resource limits in init containers are handled differently.
The main difference between init containers and regular containers are:
- Init containers always run to completion.
- Each init container must complete successfully before the next one starts.
In addition to the above, init containers do not support livenessProbe, readinessProbe, startupProbe or lifecycle since they must run to completion for the pod to be ready. Normally, when multiple init containers are specified in a pod, the kubelet runs them sequentially. Each container has to succeed before the next runs.
When an init pod fails, the Kubelet will restart it repeatedly until it succeeds. However, if the pod has a restartPolicy of Never, and the init Pod fails to run successfully, then the overall pod will be treated as a failed pod.
Init containers are used in the following areas:
- Fetch encrypted secrets from a vault and write them to a file system
- A blockchain app that needs to register itself amongst its peers
- Database migrations/applying seeds – Dynamic data fetched from a database and cached for the app to run after startup
- An app that must fetch an access token from an identity provider
- Fetching assets from a git repository or cloud storage that requires credentials.
- Generating configuration files using runtime properties, for example, pod IP, assigned external IP, hostname, external ingress IP, etc.
This guide provides an overview of how to create and use init containers in Kubernetes.
Before you Begin
I assume that you already have a Kubernetes cluster set up. If not, you can use the below guides to set up a Kubernetes cluster.
- Install Kubernetes Cluster on Rocky Linux 8 with Kubeadm & CRI-O
- Deploy Kubernetes Cluster on Linux With k0s
- Install Kubernetes Cluster on Ubuntu using K3s
- Run Kubernetes on Debian with Minikube
Once the cluster is configured, you can use the kubectl
utility to manage it. Install it as below:
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin
Export the admin conf to be able to manage the cluster.
# For k0s
export KUBECONFIG=/var/lib/k0s/pki/admin.conf
Creating and Using Init Containers in Kubernetes
There are several ways in which Init containers can be used. Some of them can be:
- Waiting for a service to start using a shell command:
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
- Waiting for some time before the app container is started.
sleep 120
- Register this Pod with a remote server from the downward API with a command:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
This guide provides a more practical example by creating a simple pod with two Init containers. Normally, init containers are defined in the pod.spec.initContainers array while the app containers are defined in the pod. spec.containers array.
Here, we will have the first init container that waits for service and the other waiting for database.
Create the configuration file.
vim application.yaml
The file will contain the below lines:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-service
image: busybox:1.28
command: ['sh', '-c', "until nslookup service.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for service; sleep 2; done"]
- name: init-db
image: busybox:1.28
command: ['sh', '-c', "until nslookup database.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for database; sleep 2; done"]
Apply the manifest.
$ kubectl apply -f application.yaml
pod/myapp-pod created
Check the status of the pod.
$ kubectl get -f application.yaml
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 23s
Alternatively use:
$ kubectl get pod myapp-pod
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 65s
From the output, we have two init containers. You can view more details by describing the pod:
$ kubectl describe -f application.yaml
Name: myapp-pod
Namespace: default
Priority: 0
Node: debian/192.168.205.4
Start Time: Sun, 24 Apr 2022 10:29:34 +0000
Labels: app=myapp
Annotations: kubernetes.io/psp: 00-k0s-privileged
Status: Pending
IP: 10.244.1.3
IPs:
IP: 10.244.1.3
Init Containers:
init-service:
Container ID: containerd://7ca9c41dbf829d3be660a68eb5dce4ebbe8902b6ae0562bbbd723453898ef9ce
Image: busybox:1.28
Image ID: docker.io/library/busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
Port: <none>
Host Port: <none>
Command:
sh
-c
until nslookup service.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for service; sleep 2; done
State: Running
Started: Sun, 24 Apr 2022 10:29:38 +0000
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-l29rv (ro)
init-db:
Container ID:
Image: busybox:1.28
Image ID:
Port: <none>
Host Port: <none>
Command:
sh
-c
until nslookup database.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for database; sleep 2; done
State: Waiting
Reason: PodInitializing
Ready: False
Restart Count: 0
Environment: <none>
.......
Inspect logs for the init containers in the pod(myapp) with the command:
# Inspect the first init container
kubectl logs myapp-pod -c init-service
# Inspect the second init container
kubectl logs myapp-pod -c init-db
The output suggests that the Init containers are waiting for the services database
and service
for them to run.
So we will create the two services as below:
vim service.yaml
Add the lines below to it.
---
apiVersion: v1
kind: Service
metadata:
name: service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
name: database
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
Create the services with the command:
$ kubectl apply -f service.yaml
service/service created
service/database created
Now check the status of the pod.
$ kubectl get -f application.yaml
myapp-pod 1/1 Running 0 4m52s
The two Init containers have started and terminated successfully, which has allowed the app container( myapp-pod) to start as well. That brings this guide on how to create and use init containers in Kubernetes to the finale. You can now use the gathered knowledge to create your own Init containers in Kubernetes.
Books For Learning Kubernetes Administration:
Interested in more?