Understanding Kubernetes Multi-Container Pod Patterns
A guide to Sidecar, Ambassador, and Adapter patterns with hands-on examples
A Kubernetes Pod is the basic building block of Kubernetes. Comprising of one or more containers, it is the smallest entity you can break Kubernetes architecture into.
When I was new to Kubernetes, I often wondered why they designed it so. I mean why containers did not become the basic build block instead. Well, a bit of doing things in the real environment and it makes more sense now.
So, Pods can contain multiple containers, for some excellent reasons — primarily, the fact that containers in a pod get scheduled in the same node in a multi-node cluster. This makes communication between them faster and more secure, and they can share volume mounts and filesystems with each other.
That helps us build on patterns we can implement to solve some particular problems. There are three widely recognised, multi-container pod patterns:
Let’s look at each of these with some hands-on examples.
For the purpose of the hands-on exercise, you need to have a running Kubernetes cluster, and you need to clone this repo, and cd into it.
Sidecars derive their name from motorcycle sidecars. While your motorcycle can work fine without the sidecar, having one enhances or extends the functionality of your bike, by giving it an extra seat. Similarly, in Kubernetes, a sidecar pattern is used to enhance or extend the existing functionality of the container.
Your container works perfectly well without the sidecar, but with it, it can perform some extra functions. Some great examples are using a sidecar for monitoring and logging and adding an agent for these purposes.
For the demo, let’s look at a sidecar pattern with an application generating logs at a particular file path, where the sidecar pushes the records to the
nginx HTML directory for users to view.
So, if you look at the manifest you’ll see we have two containers:
app-container continuously streams logs to
/var/log/app.log, and the sidecar container mounts the logs to the
nginx HTML directory. This allows anyone to visualise the logs using a web browser.
When you apply the manifest and run a port-forward on port 80, you should be able to access the logs using a browser. For this demo, we will use
curl for that.
As you can see, logs can be viewed through
curl, and though it’s a very rudimentary way of doing things, it explains the concept.
The ambassador pattern derives its name from an Ambassador, who is an envoy and a person a country chooses to represent their country and connect with the rest of the world. Similarly, in the Kubernetes perspective, an Ambassador pattern implements a proxy to the external world. Let me give you an example — If you build an application that needs to connect with a database server, the server configuration, etc, changes with the environment.
Now, the official recommendation to handle these is to use Config Maps, but what if you have legacy code that is already using another way of connecting to the database. Maybe, a properties file, or even worse, a hardcoded set of values. What if you want to communicate with localhost, and you can leave the rest to the admin? You can use the Ambassador pattern for these kinds of scenarios.
So, what we can do is create another container that can act as a TCP Proxy to the database, and you can connect to the proxy via localhost. The sysadmin can then use config maps and secrets with the proxy container to inject the correct connection and auth information.
For the demo, we will use a simple NGINX config that acts as a TCP proxy to example.com. That should also work for databases and other back ends.
If you look carefully in the manifest YAML, you will find there are three containers. The app-container-poller continuously calls
http://localhost:81 and sends the content to
The app-container-server runs
nginx and listens on port 80 to handle external requests. Both these containers share a common
mountPath. That is similar to the sidecar approach.
There is an
ambassador-container running within the pod that listens on
localhost:81 and proxies the connection to
example.com, so when we curl the
app-container-server endpoint on port 80, we get a response from
Let’s have a look:
The Adapter is another pattern that you can implement with multiple containers. The adapter pattern helps you standardise something heterogeneous in nature. For example, you’re running multiple applications within separate containers, but every application has a different way of outputting log files.
Now, you have a centralised logging system that accepts logs in a particular format only. What can you do in such a situation? Well, you can either change the source code of each application to output a standard log format or use an adapter to standardise the logs before sending it to your central server. That’s where the adapter pattern comes in.
For our hands-on exercise, let’s consider that an application that outputs logs in a particular format, that we want to change to something standard.
In the manifest, we have an
app-container that outputs a stream of dates to a log file. The
log-adapter container adds a
Date prefix in front. Yes, it’s a very rudimentary example, but enough to get how the adapter works.
Let’s apply the manifest and see for ourselves.
So, when we cat the log file
/var/log/out.log for the
log-adapter, we see a
Date prefixed to the output that was not in the original log
/var/log/app.log. You can then use a sidecar to export these logs to your monitoring and alerting engine.
Multi-container pods exist for a good reason in Kubernetes. The patterns above are just one of the most widely used — there are other scenarios where you may want to use them. Containers within a Pod use the same IPC Namespace and Networking, so you may need to tightly couple two containers based on their utility.
Thanks for reading! I hope you enjoyed the article.