Sidecars and Multi-Container Patterns
A Sidecar Is Not Free
한국어 원문으로 표시합니다.
한 줄 요약
사이드카를 하나 붙이면 파드의 요청량이 더해지고, 기동 시간이 늘고, 실패 지점이 하나 생긴다. 그 대가를 넘는 이득이 있을 때만 붙인다.
왜 이게 필요했나
"기능을 하나 더 붙이자" 는 이유로 사이드카를 늘리면 어느 순간 파드가 컨테이너 다섯 개짜리가 된다. 그때 벌어지는 일은 이렇다.
자원이 더해진다. 파드의 요청량은 컨테이너들의 합이다. 사이드카에 requests.cpu: 100m 를 주면 파드 200개에 20코어가 예약된다. 스케줄러는 그 합을 보고 자리를 찾으므로 노드 밀도가 떨어진다. 실제로 쓰지 않아도 예약은 잡힌다.
기동이 느려진다. 정식 사이드카(restartPolicy: Always 인 init 컨테이너)는 본체보다 먼저 뜬다. 프록시가 뜨는 데 3초가 걸리면 모든 파드의 기동이 3초 늘어난다. 롤링 업데이트에서 그게 파드 수만큼 곱해진다.
실패 지점이 는다. 사이드카가 OOM 으로 죽으면 그 컨테이너가 재시작되는 동안 파드는 준비 상태를 잃고, 반복되면 CrashLoopBackOff 로 파드 전체를 못 쓰게 된다. 로그 수집기가 버퍼를 메모리에 쌓는 설정이면 트래픽이 몰릴 때 정확히 그 일이 난다.
어떻게 동작하나
자원을 정할 때의 기준은 단순하다.
| 컨테이너 | requests | limits |
|---|---|---|
| 본체 | 실제 사용량의 p50 | p99 + 여유 |
| 사이드카 | 아주 작게(10~50m) | 넉넉하게 |
사이드카의 limits 를 조이면 본체까지 같이 죽는다. requests 는 작게, limits 는 넉넉하게가 정답이다. 이건 본체와 반대 방향이라 헷갈리기 쉽다.
종료 순서도 중요하다. 본체가 아직 요청을 처리하는 중에 프록시가 먼저 죽으면 그 요청들이 실패한다. 정식 사이드카는 본체가 끝난 뒤 종료되므로 이 문제가 해결됐지만, 그 전에는 preStop 훅으로 몇 초 버티게 하는 우회가 흔했다.
흔한 착각
"사이드카가 죽어도 본체는 산다" — 파드는 하나의 재시작 단위다. restartPolicy 는 파드 수준이고, 컨테이너 하나가 계속 죽으면 파드 전체가 CrashLoopBackOff 로 간다.
"자원을 안 주면 안 쓴다" — requests 를 안 주면 QoS 가 BestEffort 가 되어 메모리 압박 시 가장 먼저 죽는다. 사이드카가 먼저 죽으면 본체도 함께 간다.
시작과 종료 순서 문제
사이드카의 오래된 골칫거리는 순서 였습니다. 메인 컨테이너가 먼저 떠 사이드카 (프록시)가 아직 없으면 첫 요청이 실패하고, 끝날 때는 사이드카가 먼저 죽어 마지막 요청과 로그가 사라집니다.
❌ 옛 동작
시작: 모든 컨테이너 동시 → 프록시가 늦으면 초기 요청 실패
종료: 모든 컨테이너 동시 → 프록시가 먼저 죽으면 하류 호출 실패
✅ 네이티브 사이드카 (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
이전에는 애플리케이션이 프록시를 기다리는 코드를 넣거나 preStop 에 sleep 을
두는 우회로를 썼습니다. 지금은 필요 없습니다. 클러스터 버전을 먼저 확인 하고
쓰는 것이 이 기능의 유일한 조건입니다.
Job 이 끝나지 않는 문제
배치 작업 파드에 사이드카가 있으면 메인이 끝나도 사이드카가 계속 돌아 Job 이 영원히 Running 입니다. CronJob 이 매일 쌓이는 사고가 여기서 납니다.
네이티브 사이드카는 이것도 해결합니다 — 메인이 끝나면 쿠버네티스가 사이드카에 SIGTERM 을 보냅니다. 1.29 이전이라면 메인이 끝날 때 사이드카에 신호를 보내는 스크립트를 직접 넣어야 합니다.
자원을 두 배로 세지 않기
사이드카마다 요청·한도를 넉넉히 잡으면 파드 하나가 실제 필요의 두 배 를 예약합니다. 노드에 절반만 들어갑니다.
| 사이드카 | 흔한 요청 | 실제 필요 |
|---|---|---|
| 로그 수집기 | 100m / 128Mi | 대개 20m / 64Mi |
| 프록시(Envoy) | 500m / 512Mi | 트래픽에 따라 50~200m |
| 지표 익스포터 | 100m / 128Mi | 10m / 32Mi |
실측하고 줄입니다. 파드 수백 개에 100m 씩 과하게 잡으면 코어 수십 개가 놀고
있습니다. kubectl top pod --containers 로 컨테이너별 실사용을 봅니다.
실무에서 진짜 중요한 것
사이드카를 넣기 전에 물어야 할 것은 하나다 — DaemonSet 으로 안 되나?
노드마다 하나면 되는 일(로그 수집, 노드 지표)에 파드마다 컨테이너를 붙이면 프로세스 수가 파드 수만큼 늘어난다. 사이드카가 정당한 경우는 세 가지뿐이다.
- 파드의 네트워크 네임스페이스가 필요하다(프록시, mTLS)
- 파드의 볼륨이 필요하다(특정 경로의 파일)
- 테넌트별로 격리해야 한다(설정이 파드마다 다르다)
셋 중 어디에도 안 걸리면 DaemonSet 이 싸다.