TT Lab
Get started
Learn Learning paths Courses

Sidecars and Multi-Container Patterns

What Do You Put Beside It

Continue in TT Lab

In one line

Containers in a Pod share a network namespace and volumes. Add a container only when you want to share those two things. Otherwise, don't add one.

Why this was needed

If you keep adding sidecars with the reasoning "let's bolt on one more feature," the Pod gets heavier, the deployment unit gets bigger, and when one container dies the whole Pod restarts. A sidecar is not free.

In the end, there are only three good reasons to add one.

Pattern What it does Example
Sidecar Adds a feature without touching the main container Log collection, metric conversion, certificate renewal
Ambassador Talks to the outside world on behalf of the main container Service mesh proxy, DB connection pool
Adapter Converts the main container's output into a standard format App-specific metrics → Prometheus format

The criterion that separates the three is the direction of the traffic. A sidecar helps from the side, an ambassador stands at the exit for outgoing traffic, and an adapter changes the format used to answer incoming requests.

How it works

Design starts with knowing exactly what is shared and what is not.

Shared Not shared
Network (the same localhost, the same IP) Filesystem root (each container has its own image)
IPC, volumes (only what is mounted) Process list (the default — you can turn sharing on with shareProcessNamespace: true)

So for a sidecar to read the main container's logs, both containers must mount the same volume. For example, you mount one emptyDir at /var/log/app in the main container and at /logs in the collector.

The old problem 1.29 fixed

In the past, a sidecar was just "one more container." That caused two mismatches.

  1. The startup order could not be guaranteed. If the main container came up before the proxy, the first requests failed.
  2. Jobs never finished. Even after the main container finished, the sidecar kept running, so the Job stayed in Running forever.

From 1.29, giving restartPolicy: Always to an entry in initContainers makes it an official sidecar. Like an init container, it starts before the main container, and it terminates together with the main container when that one finishes.

spec:
  initContainers:
    - name: proxy
      image: envoyproxy/envoy:v1.31-latest
      restartPolicy: Always      # ← 이 한 줄이 사이드카로 만든다
  containers:
    - name: app
      image: myapp:1.0

Common misconceptions

Thinking resource requests add up. A Pod's requests are the sum over its containers. If you give a sidecar requests.cpu: 100m, 100 Pods reserve 10 cores. The scheduler looks for room based on that sum, so a single sidecar can significantly lower cluster density.

Thinking a log sidecar is always right. A collector running as a DaemonSet, one per node, is usually cheaper. Attaching a collector to every Pod adds that many processes. Use a sidecar collector only when you need to isolate it from other Pods (multi-tenancy) or when the file path is unusual.

Think first about not adding one

Sidecars look convenient, so they tend to multiply. But if you first check whether the same job can be done somewhere else, you will conclude that for a good share of cases, not adding one is better.

What you want to do Instead of a sidecar When a sidecar is still the answer
Log collection A collector running once per node When the file path is unusual or tenants must be isolated
Exposing metrics The application exposes them directly When it is commercial software whose code you cannot change
Certificate renewal A controller renews the Secret and the app re-reads it When the app only reads the file and cannot re-read it
Configuration reload Put the configuration hash in the Pod template and do a rolling update When the change must take effect without a restart

If none of the right-hand column applies, don't add one. One sidecar is multiplied by the number of Pods, so with 200 Pods you add 200 processes and 200 shares of requests. On top of that, image updates, vulnerability fixes, and configuration changes for that container all ride on the main container's deployment.

If you do decide to add one, settle a few things together. Does it start before the main container and die after it (the restartPolicy: Always from earlier), how do you set its limits (especially generous for a collector that piles up a buffer), and what should happen to the main container when the sidecar fails. The last one is easy to forget. When the proxy dies, it is better in most cases for the main container to drop out of traffic through readiness than to keep accepting requests and failing all of them. For that, the main container's readiness must also watch the sidecar's state.

What really matters in practice

A sidecar shares a lifecycle with the main container. If a sidecar dies from OOM, the feature the main container relied on stops while that container comes back up, and if the restarts repeat, the Pod falls into CrashLoopBackOff and becomes unusable as a whole. So set the sidecar's memory limit generously, and so that it does not die before the main container. This matters especially when the log collector is configured to pile its buffer up in memory.