Easiest way to Deploy a multi container pod to a K8S cluster

  • by

Easiest way to Deploy a multi container pod to a K8S cluster

Introduction

Containers are a form of operating system virtualization. A single container might be used to run anything from a small micro-service or software process to a larger application. Inside a container are all the necessary executable, binary code, libraries, and configuration files. Compared to server or machine virtualization approaches, however, containers do not contain operating system images. This makes them more lightweight and portable, with significantly less overhead. In larger application deployments, multiple containers may be deployed as one or more container clusters. Such clusters might be managed by a container orchestration tool such as Kubernetes. In previous articles, we have explored few areas on Cloud Computing. Today in this article, we will review the process of deploy a multi container pod. We will explore their inter-communication mechanism. Let’s start..

What is a Kubernetes Pod?

A pod is the smallest unit that can be deployed and managed by Kubernetes. It is a group of containers that are scheduled onto the same host. Pods serve as units of scheduling, deployment, and horizontal scaling/replication. Pods share fate, and share some resources, such as storage volumes and IP addresses.

Why does Kubernetes use a Pod as the smallest deploy-able unit, and not a single container?

It would definitely seem simpler to just deploy a single container directly, however, there are good reasons to add a layer of abstraction represented by the Pod. A container is an existing entity, which refers to a specific component – it can be a Docker container, but it might also be a rocket container, or a VM. Each of these has different requirements.

What’s more, to manage a container, Kubernetes needs additional information, such as a restart policy, which defines what to do with a container when it terminates, or a probe that defines an action to detect if a process in a container is still alive from the application’s perspective, such as a web server responding to HTTP requests or not.

Instead of overloading the existing “component” with additional properties, Kubernetes architects have decided to use a new entity, what is called Pod, that logically contains (wraps) one or more containers that should be managed as a single entity.

Why does Kubernetes allow more than one container in a Pod?

Containers in a Pod run on a ‘logical host’, they use the same network namespace (in other words, the same IP address and port space), and the same IPC namespace. They can also use shared volumes. These properties make it possible for these containers to efficiently communicate, ensuring data locality. Also, Pods enable you to manage several tightly coupled application containers as a single unit.

So if an application needs several containers running on the same host, why not just make a single container with everything you need? Well, first, you’re likely to violate the “one process per container” principle. This is important because with multiple processes in the same container, it is harder to troubleshoot the container because logs from different processes will be mixed together, and it is harder manage the processes life-cycle, for example to take care of zombie processes when their parent process dies. Second, using several containers for an application is simpler, more transparent, and enables decoupling software dependencies. Also, more granular containers can be reused between teams.

Use Cases for deploy a Multi Container Pods

The primary purpose of a multi container pod is to support co-located, co-managed helper processes for a primary application. There are some general patterns for using helper processes in pods:

Sidecar containers “help” the main container. Some examples include log or data change watchers, monitoring adapters, and so on. A log watcher, for example, can be built once by a different team and reused across different applications. Another example of a sidecar container is a file or data loader that generates data for the main container.
Proxies, bridges, and adapters connect the main container with the external world. For example, Apache HTTP server or nginx can serve static files. It can also act as a reverse proxy to a web application in the main container to log and limit HTTP requests. Another example is a helper container that re-routes requests from the main container to the external world. This makes it possible for the main container to connect to localhost to access, for example, an external database, but without any service discovery.
While you can host a multi-tier application (such as WordPress) in a single Pod, the recommended way is to use separate Pods for each tier, for the simple reason that you can scale tiers up independently and distribute them across cluster nodes.

How to define a multi container pod?

As with everything in the realm of Kubernetes, we define our multi container pod in a YAML file. So create the new file with the command:

vi multi-pod.yml

In that file, paste the following contents:

apiVersion: v1
kind: Pod
metadata:
  name: multi-pod
spec:

  restartPolicy: Never

  volumes:
  - name: shared-data
    emptyDir: {}

  containers:

  - name: nginx-container
    image: nginx
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html

  - name: ubuntu-container
    image: ubuntu
    volumeMounts:
    - name: shared-data
      mountPath: /pod-data
    command: ["/bin/sh"]
    args: ["-c", "echo Hello, StartLearningOnline > /pod-data/index.html"]

Take a look through the YAML file. You’ll see that we’ve deployed one container based on the NGINX image, as our web server. The second container, named ubuntu-container, deploys a container, based on the Ubuntu image, and writes the text “Hello, StartLearningOnline” to the index.html file served up by the first container.

Save and close that file (type: wq!).

How to deploy a multi container pod


To deploy a multi container pod, issue the command:

kubectl apply -f multi-pod.yml

Once the pod is deployed, give the containers a bit to actually change to the running state (although only the first container will continue running) and then access the nginx-container shell with the command:

kubectl exec -it multi-pod -c nginx-container -- /bin/bash

You should now find yourself at the bash prompt of the nginx container. To make sure our second container did its job, issue the command:

curl localhost

Communication between containers in a Pod

Having multiple containers in a single Kubernetes pod makes it relatively straightforward for them to communicate with each other. They can do this using several different methods.

Shared volumes in a Kubernetes Pod

In Kubernetes, you can use a shared Kubernetes Volume as a simple and efficient way to share data between containers in a Pod. For most cases, it is sufficient to use a directory on the host that is shared with all containers within a Pod.

Kubernetes Volumes enables data to survive container restarts, but these volumes have the same lifetime as the Pod. That means that the volume (and the data it holds) exists exactly as long as that Pod exists. If that Pod is deleted for any reason, even if an identical replacement is created, the shared Volume is also destroyed and recreated.

A standard use case for a multi container Pod with a shared Volume is when one container writes logs or other files to the shared directory, and the other container reads from the shared directory.

In the above deployment, we define a volume named ‘shared-data’. Its type is emptyDir, which means that the volume is first created when a Pod is assigned to a node, and exists as long as that Pod is running on that node. As the name says, it is initially empty. The 1st container runs nginx server and has the shared volume mounted to the directory /usr/share/nginx/html. The 2nd container uses the Debian image and has the shared volume mounted to the directory /shared-data. Every second, the 2nd container adds the current date and time into the index.html file, which is located in the shared volume. When the user makes an HTTP request to the Pod, the Nginx server reads this file and transfers it back to the user in response to the request.

Inter-process communications (IPC)

Containers in a Pod share the same IPC namespace, which means they can also communicate with each other using standard inter-process communications such as SystemV semaphores or POSIX shared memory.

In the following example, we define a Pod with two containers. We use the same Docker image for both. The first container, producer, creates a standard Linux message queue, writes a number of random messages, and then writes a special exit message. The second container, consumer, opens that same message queue for reading and reads messages until it receives the exit message. We also set the restart policy to ‘Never’, so the Pod stops after termination of both containers.

apiVersion: v1
kind: Pod
metadata:
  name: slo2
spec:
  containers:
  - name: producer
    image: allingeek/ch6_ipc
    command: ["./ipc", "-producer"]
  - name: consumer
    image: allingeek/ch6_ipc
    command: ["./ipc", "-consumer"]
  restartPolicy: Never

To check this out, create the pod using kubectl create and watch the Pod status:

$ kubectl get pods --show-all -w
NAME      READY     STATUS              RESTARTS  AGE
slo2       0/2       Pending             0         0s
slo2       0/2       ContainerCreating   0         0s
slo2       0/2       Completed           0         29s

Now you can check logs for each container and verify that the 2nd container received all messages from the 1st container, including the exit message:

$ kubectl logs slo2 -c producer
...
Produced: 6c
Produced: 1d
Produced: 9e
Produced: 27
$ kubectl logs slo2 -c consumer
...
Consumed: 6c
Consumed: 1d
Consumed: 9e
Consumed: 27
Consumed: done

There is one major problem with this Pod, however, and it has to do with how containers start up.

Container dependencies and startup order

Currently, all containers in a Pod are being started in parallel and there is no way to define that one container must be started after other container. In the IPC example, there is a chance that the second container might finish starting before the first one has started and created the message queue. In this case, the second container will fail, because it expects that the message queue already exists.

Some efforts to provide some measure of control over how containers start, such as Kubernetes Init Containers, which start first (and sequentially), are under development, but in a cloud native environment, it’s always better to plan for failures outside of your immediate control. For example, one way to to fix this issue would be to change the application to wait for the message queue to be created.

Inter-container network communication

Containers in a Pod are accessible via localhost; they use the same network namespace. Also, for containers, the observable host name is a Pod’s name. Because containers share the same IP address and port space, you should use different ports in containers for incoming connections. In other words, applications in a Pod must coordinate their usage of ports.

Conclusion:

Kubernetes provides a great deal of flexibility for orchestrating how containers behave, and how they communicate with each other. They can share file volumes, they can communicate over the network, and they can even communicate using IPC.