We’ve shown you how to create a Pod using YAML files last time, so in this section we’ll talk about the Pod lifecycle and how to do it.

Pod is the smallest execution unit in a Kubernetes cluster, and Pod is composed of container groups, so before discussing the Pod life cycle, let’s first understand the container life cycle.

Pod hook function

In fact, Kubernetes provides lifetime hooks for our containers, such as Pod Hooks. Pod hooks are initiated by Kubelet. When a process in a container is started or terminated, the Hook function can be run. Is contained within the lifecycle of the container.

Kubernetes generally has two hook functions available:

  • PostStart: This hook is executed after the container is created. It is used for environment preparation and resource deployment. However, it is important to note that if the hook is executed for too long, the container will not function properly and will be suspended.

  • PreStop: This hook is called before the container terminates. It is blocked, meaning it is synchronous, so it must be done before the call to delete the container is issued. It is used to gracefully close applications, notify other systems, etc.

If the PostStart or PreStop hook fails to execute, it kills the corresponding container. So we should make the hook function as light as possible.

There are also two ways to implement the above hook function:

  • Exec mode – By executing a specific command, the resources consumed by the command are credited to the container.
  • HTTP – An HTTP request is made to a specific endpoint on the container.

Example 1: PostStart

In the following example, we define an Nginx Pod with the PostStart hook function that writes a sentence to the /usr/share/message file after the container is successfully created.

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo1
spec:
  containers:
  - name: hook-demo1
    image: nginx
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh"."-c"."echo hello postStart handler > /usr/share/message"]
Copy the code

Example 2: Gracefully delete resources

When there is a request to remove a resource object containing a POD (such as Deployment, etc.), K8S provides two information notifications to handle in order for the application to be gracefully shut down (that is, to allow the application being processed to complete and then shut down) :

  • The default: K8S tells Node to executedocker stopCommand, and docker will first send system signals to the processes in the container whose PID is 1SIGTERMWait for the application in the container to terminate execution. If the waiting time reaches the preset timeout period or the default timeout period (30s), the system sends the SIGKILL signal to kill the process.
  • Use the PreStop hook function: This hook function is executed to close the resource before sending a termination signal.

For example, we define an Nginx Pod with the PreStop hook function, which gracefully closes Nginx before exiting the container:

apiVersion: v1
kind: Pod
metadata:
  name: hook-demo2
spec:
  containers:
  - name: hook-demo2
    image: nginx
    lifecycle:
      preStop:
        exec:
          command: ["/usr/sbin/nginx"."-s"."quit"]

---
apiVersion: v1
kind: Pod
metadata:
  name: hook-demo2
  labels:
    app: hook
spec:
  containers:
  - name: hook-demo2
    image: nginx
    ports:
    - name: webport
      containerPort: 80
    volumeMounts:
    - name: message
      mountPath: /usr/share/
    lifecycle:
      preStop:
        exec:
          command: ['/bin/sh'.'-c'.'echo hello preStop Handler > /usr/share/message']
Copy the code

Pod Health Check

PostStart is executed immediately after the container is created, and PreStop is executed before the container terminates.

In addition to the above two hook functions, there is another configuration that affects the lifecycle of the container, and that is the health check probe.

In the Kubernetes cluster, we were able to influence the lifecycle of the container by configuring LIVENESS probes and Readiness probes.

Exec mode checks container survival

The following is the use of the survival probe. First, we use exec to detect the survival of the container, as follows:

apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
  labels:
    test: liveness
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 500
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 6
      periodSeconds: 3
Copy the code

PeriodSeconds attribute indicates that kubelet periodSeconds execute the probe every 3 seconds, i.e., execute the above cat/TMP /healthy command once every 3 seconds. If zero is returned on success, kubelet considers the current container alive and monitorable. If non-zero is returned, Kubelet kills the container and restarts it.

InitialDelaySeconds means that we wait 6 seconds for the first probe execution, which ensures that our container will have enough time to start up. If you wait too long for the first probe execution, it is possible that the survival probe will be checked before the container is properly started, which will always fail and will result in repeated restarts. So it is important to set a reasonable initialDelaySeconds.

Configure the survival probe in HTTP mode

In addition, we can configure our survival probe using AN HTTP GET request, which uses a LIVENESS mirror to verify:

apiVersion: v1
kind: Pod
metadata:
labels:
  test: liveness
name: liveness-http
spec:
containers:
- name: liveness
  image: routeman/liveness
  args:
  - /server
  livenessProbe:
    httpGet:
      path: /healthz
      port: 8080
      httpHeaders:
      - name: X-Custom-Header
        value: Awesome
    initialDelaySeconds: 3
    periodSeconds: 3
Copy the code

Also, periodSeconds shows that Kubelet needs to execute a LiVENESS probe every three seconds, which sends an HTTP GET request to port 8080 of the server in the container. If the server’s/healthZ path handler returns a successful return code, Kubelet determines that the container is healthy and alive.

If a failed return code is returned, Kubelet will kill the container and restart. InitialDelaySeconds Specifies that Kubelet should wait 3 seconds before performing the first probe.

From the YAML file above, we can see that the Configuration of the Readiness Probe is similar to that of the LiVENESS Probe. The difference is using readinessProbe instead of livenessProbe.

Both, if used together, ensure that traffic does not reach the container that is not ready, and restart the container if the application encounters an error.

This is how liVENESS probe and Readiness probe are used.

Let’s continue with initialization containers in the Pod lifecycle.

Init Container

Previously we learned two probes for container health checks: Liveness probe and Readiness Probe are used to influence the lifecycle of the container, including the PostStart and PreStop hook functions we mentioned earlier.

The next thing we’re going to talk about today is Init Container.

An Init Container is a Container used for initialization. It can be one or more containers. If there are more than one Container, they will be executed in a defined order. We know that all containers in a Pod share data volumes and network namespaces, so the data generated in Init Container can be used by the main Container.

Init Container looks a little bit like the hook function, just doing some work before the Container executes, right? From an intuitive point of view, initializing a Container looks a bit like PreStart, but the hook function is in a different stage from our Init Container.

From the diagram above, we can see that PostStart and PreStop, including LiVENESS and Readiness, are within the lifecycle of the primary Container, while Init Container is independent of the primary Container. Of course, they are all part of the Pod lifecycle, so we should now understand the difference between Init Container and hook functions.

In addition, we can see that there is an infra container on the right side of our Pod. What kind of container is this? In the cluster environment, we can check the Docker container corresponding to any Pod. We can find that each Pod contains an image of pause-AMD64, which is our infra image. We know that all containers under Pod share the same network namespace, and this image is used to do this, so each Pod will contain one of these images.

We say Init Container is mainly used to initialize the Container. What are the application scenarios?

Wait for the dependency module to be Ready

This can be used to resolve dependencies between services, such as if we have a Web service that depends on another database service, but when we start the Web service we can’t guarantee that the database service that we depend on will be started. Therefore, the Web service may fail to connect to the database for a period of time.

To solve this problem, we can use an InitContainer in the Pod of the Web service. In the InitContainer, we check if the database is ready. When the initialization container is ready, we exit and our main container Web service is started. This time to connect to the database will not have a problem.

Perform initial configuration

For example, the cluster detects all existing member nodes, and the master container prepares the cluster configuration information, so that the master container can use this configuration information to join the cluster.

The other scenario

Such as registering a POD with a central database, configuration center, and so on.

Example of service dependency scenarios

Here is how to initialize container usage in the scenario of service dependency, Pod definition method:

apiVersion: v1
kind: Pod
metadata:
name: init-pod-demo
labels:
  app: init
spec:
containers:
- name: init-container
  image: busybox
  command: ['sh'.'-c'.'echo app is running! && sleep 600']
initContainers:
- name: init-myservice
  image: busybox
  command: ['sh'.'-c'.'until nslookup myservice; do echo waiting for myservice; sleep 3; done; ']
- name: init-mydb
  image: busybox
  command: ['sh'.'-c'.'until nslookup mydb; do echo waiting for mydb; sleep 3; done; ']
Copy the code

The yamL file corresponding to Service is as follows:

kind: Service
apiVersion: v1
metadata:
name: myservice
spec:
ports:
- protocol: TCP
  port: 80
  targetPort: 6376
---
kind: Service
apiVersion: v1
metadata:
name: mydb
spec:
ports:
- protocol: TCP
  port: 80
  targetPort: 6377
Copy the code

You can create the Pod above, check the Pod status, and then create the Service to see if the status is different.

During Pod startup, initialization containers are started sequentially after network and data volume initialization. Each container must exit successfully before the next container starts.

If the container fails to start due to a run-time or failed exit, it retries according to the Pod’s restartPolicy. If Pod restartPolicy is set to Always, the Init container will be continuously restarted when it fails.

conclusion

That’s the understanding of the POD and how to initialize the container, and we’ve covered the major parts of the pod lifecycle.

  • The first is the container’s two hook functions: PostStart and PreStop
  • There are two VENESS probes for container health checks: LiVENESS Probe and Readiness Probe
  • And then finally Init Container.