TT Lab
Get started
Learn Learning paths Courses

Sidecars and Multi-Container Patterns

A Sidecar Is Not Free

Continue in TT Lab

In one line

Adding one sidecar means the Pod's requests add up, startup time grows, and there is one more point of failure. Add one only when the benefit outweighs that cost.

Why this was needed

If you keep adding sidecars with the reasoning "let's bolt on one more feature," the Pod eventually ends up with five containers. Here is what happens then.

Resources add up. A Pod's requests are the sum over its containers. If you give a sidecar requests.cpu: 100m, 200 Pods reserve 20 cores. The scheduler looks for room based on that sum, so node density drops. Even if the sidecar does not actually use it, the reservation is still held.

Startup gets slower. An official sidecar (an init container with restartPolicy: Always) comes up before the main container. If the proxy takes 3 seconds to start, every Pod's startup grows by 3 seconds. In a rolling update, that is multiplied by the number of Pods.

Failure points increase. If a sidecar dies from OOM, the Pod loses its ready state while that container restarts, and if it repeats, the whole Pod becomes unusable with CrashLoopBackOff. If the log collector is configured to pile its buffer up in memory, exactly that happens when traffic surges.

How it works

The criterion for setting resources is simple.

Container requests limits
Main container p50 of actual usage p99 plus headroom
Sidecar Very small (10–50m) Generous

If you squeeze a sidecar's limits, the main container dies along with it. The right answer is small requests and generous limits. This is the opposite direction from the main container, so it is easy to mix up.

The shutdown order matters too. If the proxy dies first while the main container is still handling requests, those requests fail. An official sidecar terminates after the main container finishes, so this problem is solved, but before that it was common to work around it by holding on for a few seconds with a preStop hook.

Common misconceptions

"The main container survives even if the sidecar dies" — A Pod is a single restart unit. restartPolicy is at the Pod level, and if one container keeps dying, the whole Pod goes into CrashLoopBackOff.

"If you don't give it resources, it doesn't use any" — If you don't give requests, the QoS class becomes BestEffort, and under memory pressure it dies first. If the sidecar dies first, the main container goes with it.

The startup and shutdown order problem

The long-standing headache with sidecars was order. If the main container comes up first and the sidecar (the proxy) is not there yet, the first requests fail, and at the end the sidecar dies first, so the last requests and logs are lost.

❌ 옛 동작
   시작: 모든 컨테이너 동시 → 프록시가 늦으면 초기 요청 실패
   종료: 모든 컨테이너 동시 → 프록시가 먼저 죽으면 하류 호출 실패

✅ 네이티브 사이드카 (k8s 1.29+)
   initContainers 에 restartPolicy: Always 를 주면
   시작: 사이드카가 Ready 가 된 뒤에 메인이 시작
   종료: 메인이 끝난 뒤에 사이드카가 종료
spec:
  initContainers:
    - name: proxy
      image: envoyproxy/envoy:v1.31
      restartPolicy: Always        # ← 이 한 줄이 네이티브 사이드카로 만든다
  containers:
    - name: app
      image: myapp:1.0

Previously you had to put code in the application that waited for the proxy, or work around it by placing a sleep in preStop. Now that is unnecessary. Checking the cluster version first and then using it is the only condition for this feature.

The problem of Jobs that never finish

If a batch Pod has a sidecar, the sidecar keeps running even after the main container finishes, so the Job stays in Running forever. This is where the incident of CronJobs piling up every day comes from.

Native sidecars solve this too — when the main container finishes, Kubernetes sends SIGTERM to the sidecar. Before 1.29, you have to put in a script yourself that signals the sidecar when the main container finishes.

Not counting resources twice

If you set generous requests and limits for every sidecar, one Pod reserves twice what it actually needs. Only half as many fit on a node.

Sidecar Common request Actual need
Log collector 100m / 128Mi Usually 20m / 64Mi
Proxy (Envoy) 500m / 512Mi 50–200m depending on traffic
Metrics exporter 100m / 128Mi 10m / 32Mi

Measure and cut back. If you over-reserve 100m on hundreds of Pods, dozens of cores sit idle. Use kubectl top pod --containers to see actual usage per container.

What really matters in practice

The one question to ask before adding a sidecar is this — can't a DaemonSet do it?

If you attach a container to every Pod for a job that needs only one per node (log collection, node metrics), the number of processes grows by the number of Pods. A sidecar is justified in only three cases.

  1. You need the Pod's network namespace (proxy, mTLS)
  2. You need the Pod's volume (files at a specific path)
  3. You must isolate per tenant (the configuration differs for each Pod)

If none of the three applies, a DaemonSet is cheaper.