Weaveworks 2022.03 release featuring Magalix PaC | Learn more
Balance innovation and agility with security and compliance
risks using a 3-step process across all cloud infrastructure.
Step up business agility without compromising
security or compliance
Everything you need to become a Kubernetes expert.
Always for free!
Everything you need to know about Magalix
culture and much more
The initialization logic is common among programming languages in general. In Object-Oriented Programming languages, we have the concept of the constructor. The constructor is a function (or method) that is called whenever an object gets instantiated. The purpose of the constructor is to “prepare” the object for the work it’s due to do. For example, it sets the default values for variables, creates the database connection objects, ensures the existence of the necessary prerequisites for the object to function correctly. For example, if a user object gets created, it needs at least the username, the first name, and the last name of the user so that it can function correctly. Constructor implementation is different among different languages. Yet, all of them are invoked only once and only at object instantiation.
The purpose of the initialization pattern is to decouple an object from its initialization logic. So, if an object needs some seed data to be fed into a database, this falls under the constructor logic rather than the application logic. This allows you to make changes to how the object “starts” without affecting how it “works”.
Kubernetes uses the same pattern. While the object is the atomic unit of Object-Oriented languages, Kubernetes has Pods. So, if you have an application running on a container that needs some initialization logic, it’s a good practice to hand this work to another container. Kubernetes has a type of container for that specific job: init containers.
In Kubernetes, an init container is the one that starts and executes before other containers in the same Pod. It’s meant to perform initialization logic for the main application hosted on the Pod. For example, create the necessary user accounts, perform database migrations, create database schemas and so on.
There are some considerations that you should take into account when you create init containers:
As we’ve just discussed, init containers always start before other application containers on the same Pod. As a result, the scheduler gives higher precedence to the resources and limits of the init containers. Such behavior must be thoroughly considered as it may result in undesired results. For example, if you have one init container and one application container and you set the resources and limits of the init container to be higher than those of the application container, then the entire Pod is scheduled only if there’s an available node that satisfies the init container requirements. In other words, even if there’s an unused node where the application container can run, the Pod will not get deployed to this node if the init container has higher resource prerequisites that this node can handle. Hence, you should be as strict as possible when defining the requests and limits of an init container. As a best practice, do not set those parameters to higher values than the application containers’ unless absolutely required.
In this scenario, we are serving a MySQL database. This database is used for testing an application. It doesn’t have to contain real data, but it must be seeded with enough data so that we can test the application's query speed. We use an init container to handle downloading the SQL dump file and restore it to the database, which is hosted in another container. This scenario can be illustrated as below:
The definition file may look like this:
apiVersion: v1
kind: Pod
metadata:
name: mydb
labels:
app: db
spec:
initContainers:
- name: fetch
image: mwendler/wget
command: ["wget","--no-check-certificate","https://sample-videos.com/sql/Sample-SQL-File-1000rows.sql","-O","/docker-entrypoint-initdb.d/dump.sql"]
volumeMounts:
- mountPath: /docker-entrypoint-initdb.d
name: dump
containers:
- name: mysql
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "example"
volumeMounts:
- mountPath: /docker-entrypoint-initdb.d
name: dump
volumes:
- emptyDir: {}
name: dump
The above definition creates a Pod that hosts two containers: the init container and the application one. Let’s have a look at the interesting aspects of this definition:
In this example, we use the initialization pattern to establish the separation of concerns best practice. If we’d implement the same logic without using an init pattern, we’d had to create a new image based on the mysql base image, install wget, and use it to download the SQL file. The drawbacks of this approach are:
Another common use case for init containers is when you need your application to wait until another service is full running (responding to requests). The following definition demonstrates this scenario:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
So, assuming that our application, running on myapp-container does not function correctly except when myservice application is running. We need to delay myapp lanch until myservice is ready. We do this by using a simple nslookup command (line 11) that constantly checks for the successful name resolution of “myservice”. If nslookup was able to resolve “myservice”, then the service is started. With a success exit code, the init container terminates giving way for the application container to start. Otherwise, the container sleeps for two seconds before trying again, delaying the application container start.
For completeness, this is the definition file for myservice:
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
*The outline of this article outline is inspired by the book of Roland Huss and Bilgin Ibryam: Kubernetes Patterns.
Self-service developer platform is all about creating a frictionless development process, boosting developer velocity, and increasing developer autonomy. Learn more about self-service platforms and why it’s important.
Explore how you can get started with GitOps using Weave GitOps products: Weave GitOps Core and Weave GitOps Enterprise. Read more.
More and more businesses are adopting GitOps. Learn about the 5 reasons why GitOps is important for businesses.
Implement the proper governance and operational excellence in your Kubernetes clusters.
Comments and Responses