Sidecar 컨테이너는 기존 컨테이너의 기능을 변경하지 않고 확장하고 향상시키는 패턴입니다.
Sidecar 패턴은 하나의 Pod 내에서 메인 컨테이너와 헬퍼 컨테이너(Sidecar)가 협력하여 동작하는 기본적인 컨테이너 패턴입니다.
마치 오토바이에 부착된 사이드카처럼, Sidecar 컨테이너는 메인 컨테이너를 보조하면서도 독립적으로 동작합니다.
이를 통해
현대의 컨테이너 기반 애플리케이션은 다음과 같은 과제에 직면합니다
Sidecar 패턴은 다음과 같은 방식으로 문제를 해결합니다.
Kubernetes의 Pod는 Sidecar 패턴을 구현하는 핵심 단위입니다.
Pod (pause 컨테이너)
├── 네트워크 네임스페이스 (공유)
├── IPC 네임스페이스 (공유)
└── 볼륨 마운트 (공유 가능)
├── 메인 컨테이너
│ └── /var/www/html → emptyDir
└── Sidecar 컨테이너
└── /data → emptyDir (동일한 볼륨)
• Pause 컨테이너(Pod Sandbox)
→ Pod의 네임스페이스(Network / IPC / UTS / PID) 생성 및 유지
→ 나머지 컨테이너들은 생성된 네임스페이스에 Join
• Main & Sidecar 컨테이너
→ 동일한 IP, 포트 공간, localhost 공유
→ 동일 볼륨 사용 시 파일 시스템 공유 가능
| 개념 | 컨테이너 확장 방식 | OOP 비유 | 특징 |
|---|---|---|---|
| 상속 | Dockerfile FROM | class Child extends Parent | - 빌드 타임 결합 - "is-a" 관계 - 강한 결합 |
| 조합 | Pod 내 다중 컨테이너 | class has SidecarService | - 런타임 결합 - "has-a" 관계 - 느슨한 결합 |
# 상속 방식 (Dockerfile)
FROM nginx:1.27
COPY git-sync.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/git-sync.sh
# 문제: nginx 업데이트 시 재빌드 필요, Git 동기화 로직이 컨테이너에 고정됨
# 조합 방식 (Pod)
spec:
containers:
- name: nginx
image: nginx:1.27 # nginx만 업데이트 가능
- name: git-sync
image: git-sync:v4 # git-sync만 업데이트 가능
# 장점: 각 컨테이너 독립적으로 관리
| 특성 | Init Container | Sidecar Container |
|---|---|---|
| 실행 시점 | Pod 시작 전 | Pod 실행 중 |
| 실행 방식 | 순차적 실행 후 종료 | 메인 컨테이너와 병렬 실행 |
| 생명주기 | 작업 완료 후 종료 | Pod 종료까지 계속 실행 |
| 재시작 | 실패 시 재시작 | 실패 시 재시작 (정책에 따라) |
| 사용 사례 | - DB 초기화 - 설정 파일 다운로드 - 의존성 대기 | - 로깅 - 모니터링 - 프록시 - 데이터 동기화 |
시나리오: 웹 서버가 Git 저장소의 정적 파일을 서빙
emptyDir 볼륨시나리오: 애플리케이션 로그를 중앙 로그 시스템으로 전송
시나리오: 마이크로서비스 간 통신 관리
시나리오: 원격 설정 저장소의 변경사항 감지 및 반영
정의: 메인 애플리케이션이 Sidecar의 존재를 인지하지 못하는 방식
대표적인 예: Envoy Proxy
# Envoy가 모든 트래픽을 가로채는 방식
spec:
containers:
- name: app
image: my-app:1.0
# 애플리케이션은 Envoy를 모름
- name: envoy
image: envoyproxy/envoy:v1.31
# iptables 규칙으로 트래픽 자동 리다이렉션
외부 요청 → Envoy Sidecar → 메인 앱
↑ (iptables redirect)
정의: 메인 애플리케이션이 명시적으로 Sidecar API를 호출하는 방식
spec:
containers:
- name: app
image: my-app:1.0
env:
- name: DAPR_HTTP_PORT
value: "3500" # Dapr API 포트
- name: daprd
image: daprio/daprd:latest
args: ["--app-port", "8080"]
import requests
# Dapr Sidecar에 명시적으로 요청
response = requests.post(
"<http://localhost:3500/v1.0/state/statestore>",
json={"key": "user123", "value": "John Doe"}
)
| 특성 | Transparent Sidecar | Explicit Sidecar |
|---|---|---|
| 애플리케이션 인지 | 불필요 | 필요 |
| 트래픽 처리 | 자동 가로채기 | 명시적 호출 |
| 대표 예시 | Envoy, Linkerd | Dapr, AWS App Mesh |
| 사용 사례 | - 서비스 메시 - 네트워크 정책 - 보안 | - 상태 관리 - 이벤트 기반 - 외부 통합 |
| 학습 곡선 | 낮음 (앱 변경 없음) | 중간 (API 학습 필요) |
다음은 책에서 소개된 기본 예제를 개선한 버전입니다
# 01-basic-sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
name: web-app-basic
labels:
app: web-app
pattern: sidecar
spec:
containers:
# 메인 컨테이너: HTTP 서버
- name: app
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html # Nginx 정적 파일 경로
name: git-data
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
# Sidecar 컨테이너: Git 동기화
- name: git-sync
image: k8s.gcr.io/git-sync/git-sync:v4.2.4
env:
- name: GITSYNC_REPO
value: "<https://github.com/kubernetes/website>"
- name: GITSYNC_ROOT
value: "/data"
- name: GITSYNC_DEST
value: "website"
- name: GITSYNC_PERIOD
value: "60s" # 60초마다 동기화
- name: GITSYNC_ONE_TIME
value: "false"
volumeMounts:
- mountPath: /data
name: git-data
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "64Mi"
cpu: "100m"
volumes:
- name: git-data
emptyDir: {} # Pod 재시작 시 초기화되는 임시 볼륨
git-sync가 Git 저장소를 /data에 clonenginx가 동일한 볼륨(/usr/share/nginx/html)에서 파일 읽기git-sync가 git pull 실행# Pod 생성
kubectl apply -f 01-basic-sidecar.yaml
# Pod 상태 확인
kubectl get pod web-app-basic
# Nginx 로그 확인
kubectl logs web-app-basic -c app
# Git-sync 로그 확인
kubectl logs web-app-basic -c git-sync
# HTTP 응답 테스트
kubectl port-forward web-app-basic 8080:80
curl <http://localhost:8080>
기존에는 Sidecar를 구현하기 위해 두 가지 방법만 존재했습니다
이 두 방법 모두 "메인 앱과 함께 실행되면서 시작 순서도 제어"하는 요구사항을 만족하지 못했습니다.
restartPolicy: AlwaysKubernetes 1.28부터 Init Container에 restartPolicy 필드를 추가하여 Native Sidecar를 지원합니다.
apiVersion: v1
kind: Pod
metadata:
name: native-sidecar-example
spec:
initContainers:
# 일반 Init Container
- name: init-setup
image: busybox:1.36
command: ['sh', '-c', 'echo "Setup complete"']
# Native Sidecar Container
- name: envoy-proxy
image: envoyproxy/envoy:v1.31-latest
restartPolicy: Always # ← 이 필드가 핵심!
ports:
- containerPort: 8080
name: proxy
containers:
- name: main-app
image: my-app:1.0
| 특성 | 일반 Init Container | Native Sidecar | 일반 Container |
|---|---|---|---|
restartPolicy | - | Always | - |
| 시작 순서 | 순차적 | 순차적 시작 | 순서 보장 안 됨 |
| 종료 시점 | 작업 완료 후 | Pod 종료 시 | Pod 종료 시 |
| 재시작 | 실패 시만 | 항상 | 정책에 따라 |
| Startup Probe | 지원 | 지원 | 지원 |
1. init-setup 실행 → 완료 후 종료
2. envoy-proxy 시작 → Startup Probe 성공 대기
3. envoy-proxy Startup Probe 성공
4. main-app 시작
5. envoy-proxy와 main-app이 함께 실행
6. envoy-proxy 종료 시 자동 재시작
# kube-apiserver, kubelet에 플래그 추가
--feature-gates=SidecarContainers=true
기존 Kubernetes는 Pod 레벨에서만 재시작 정책을 설정할 수 있었습니다
spec:
restartPolicy: Always # Pod의 모든 컨테이너에 적용
해결책: restartPolicyRules
Kubernetes 1.34는 컨테이너별 재시작 정책과 Exit Code 기반 규칙을 도입했습니다.
apiVersion: v1
kind: Pod
metadata:
name: restart-policy-example
spec:
restartPolicy: Never # Pod 레벨 기본 정책
containers:
# Exit code 42(임시 오류)에만 재시작하는 앱
- name: retriable-app
image: my-app:1.0
restartPolicy: Never
restartPolicyRules:
# Exit code 42일 때만 재시작
- action: Restart
onExitCodes:
operator: In
values: [42] # OOM 또는 일시적 오류
# Exit code 1일 때는 재시작하지 않음
- action: DoNotRestart
onExitCodes:
operator: In
values: [1] # 설정 오류 (영구적)
# 항상 재시작하는 모니터링 Sidecar
- name: monitoring-sidecar
image: monitoring:1.0
restartPolicy: Always # 어떤 exit code든 항상 재시작
# 실패 시에만 재시작하는 로깅 Sidecar
- name: log-forwarder
image: fluentd:latest
restartPolicy: OnFailure
restartPolicyAlways: 항상 재시작OnFailure: exit code가 0이 아닐 때만 재시작Never: 재시작하지 않음restartPolicyRules: Exit code 기반 세밀한 제어action: Restart 또는 DoNotRestartonExitCodes.operator: In 또는 NotInonExitCodes.values: Exit code 리스트# GPU를 사용하는 ML 학습 작업
spec:
restartPolicy: Never
containers:
- name: ml-trainer
image: tensorflow:latest
restartPolicy: Never
restartPolicyRules:
# Exit 42: GPU OOM → 재시작 (학습 재개 가능)
- action: Restart
onExitCodes:
operator: In
values: [42]
# Exit 1: 데이터셋 오류 → 재시작하지 않음 (수정 필요)
- action: DoNotRestart
onExitCodes:
operator: In
values: [1]
resources:
limits:
nvidia.com/gpu: 1
| 버전 | 기능 | 상태 | 핵심 키워드 |
|---|---|---|---|
| 1.28 | Native Sidecar | Beta | restartPolicy: Always on initContainers |
| 1.29 | Native Sidecar GA 준비 | Beta → Stable 진행 중 | - |
| 1.34 | Per-Container Restart | Alpha | restartPolicyRules, onExitCodes |
코드 작성 (01-basic-sidecar.yaml)
# 기본 Sidecar 패턴 예제
# HTTP 서버와 Git 동기화 사이드카를 사용하는 Pod
apiVersion: v1
kind: Pod
metadata:
name: web-app-basic
labels:
app: web-server
pattern: sidecar
spec:
containers:
# 메인 애플리케이션 컨테이너
- name: app
image: nginx:1.27
ports:
- containerPort: 80
name: http
volumeMounts:
- mountPath: /usr/share/nginx/html
name: git-content
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
# Sidecar 컨테이너: Git 동기화
- name: git-sync
image: registry.k8s.io/git-sync/git-sync:v4.3.0
volumeMounts:
- mountPath: /tmp/git
name: git-content
env:
- name: GITSYNC_REPO
value: "https://github.com/kubernetes/website"
- name: GITSYNC_ROOT
value: "/tmp/git"
- name: GITSYNC_LINK
value: "current"
- name: GITSYNC_PERIOD
value: "60s" # 60초마다 동기화
- name: GITSYNC_ONE_TIME
value: "false"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
volumes:
- name: git-content
emptyDir: {}
---
# Service를 통해 웹 서버 노출
apiVersion: v1
kind: Service
metadata:
name: web-app-service
spec:
selector:
app: web-server
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
파드 생성
kubectl apply -f ./01-basic-sidecar.yaml
파드 상태 확인
kubectl get pod web-app-basic -w

컨테이너 별 로그 확인
kubectl logs web-app-basic -c app # Nginx 로그
kubectl logs web-app-basic -c git-sync # Git 동기화 로그


HTTP 응답 테스트
kubectl port-forward web-app-basic 8080:80
curl http://localhost:8080

공유 볼륨 확인
kubectl exec web-app-basic -c app -- ls -la /usr/share/nginx/html
kubectl exec web-app-basic -c git-sync -- ls -la /data


코드 정리
kubectl delete pod web-app-basic
# 버전확인
kubectl version # 1.28 이상 확인

코드 작성
# Kubernetes v1.33+ Native Sidecar 패턴
# restartPolicy: Always를 사용한 Init Container 기반 Sidecar
apiVersion: v1
kind: Pod
metadata:
name: web-app-native-sidecar
labels:
app: web-server-native
pattern: native-sidecar
spec:
initContainers:
# Native Sidecar: restartPolicy Always를 사용
- name: log-collector
image: busybox:1.36
restartPolicy: Always # v1.29+에서 지원, v1.33에서 stable
command:
- sh
- -c
- |
echo "Log collector sidecar started"
while true; do
if [ -f /var/log/app/app.log ]; then
tail -f /var/log/app/app.log | while read line; do
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $line"
done
fi
sleep 1
done
volumeMounts:
- name: log-volume
mountPath: /var/log/app
resources:
requests:
memory: "32Mi"
cpu: "50m"
limits:
memory: "64Mi"
cpu: "100m"
# Startup probe를 사용하는 Sidecar
- name: metrics-exporter
image: prom/node-exporter:v1.8.2
restartPolicy: Always
ports:
- containerPort: 9100
name: metrics
startupProbe:
httpGet:
path: /metrics
port: 9100
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /metrics
port: 9100
periodSeconds: 10
readinessProbe:
httpGet:
path: /metrics
port: 9100
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
containers:
# 메인 애플리케이션
- name: app
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: log-volume
mountPath: /var/log/app
lifecycle:
postStart:
exec:
command:
- sh
- -c
- |
# 로그 디렉토리 설정
mkdir -p /var/log/app
# Access 로그를 파일로 리다이렉트
ln -sf /dev/stdout /var/log/nginx/access.log
ln -sf /dev/stderr /var/log/nginx/error.log
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
volumes:
- name: log-volume
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: web-app-native-service
spec:
selector:
app: web-server-native
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: metrics
protocol: TCP
port: 9100
targetPort: 9100
type: ClusterIP
배포 진행
kubectl apply -f ./02-native-sidecar-v1.33.yaml
Init Container(Native Sidecar) 구성 확인
kubectl describe pod web-app-native-sidecar


컨테이너 시작 순서 및 상태 확인 (Native Sidecar -> main Container)
kubectl get pod web-app-native-sidecar -w

메인 앱 테스트 확인
kubectl port-forward svc/web-app-native-service 8080:80
curl http://localhost:8080

Metrics Sidecar(Native Sidecar) 동작 테스트
kubectl port-forward svc/web-app-native-service 9100:9100
curl http://localhost:9100/metrics | head

Native Sidecar 재시작 동작 확인
kubectl exec web-app-native-sidecar -c log-collector -- kill 1
파드 상태 모니터링
kubectl get pod web-app-native-sidecar \
-o jsonpath='{range .status.initContainerStatuses[*]}{.name} {.restartCount} {.state}{"\n"}{end}'
log-collector 1 {"running":{"startedAt":"2025-11-22T08:51:14Z"}}
metrics-exporter 0 {"running":{"startedAt":"2025-11-22T08:51:15Z"}}
실습 코드 정리
kubectl delete -f ./02-native-sidecar-v1.33.yaml
횡단 관심사(Cross-Cutting Concerns)
관심사 분리
재사용성
단일 책임 원칙
# 나쁜 예: 너무 많은 책임
- name: swiss-army-knife
image: all-in-one:latest
# 로깅 + 모니터링 + 프록시 + 백업
# 좋은 예: 하나의 책임
- name: log-forwarder
image: fluentd:latest
- name: metrics-collector
image: telegraf:latest
리소스 제한 설정
# 항상 requests와 limits 설정
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
헬스 체크 구현
# Sidecar에도 적절한 Probe 설정
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
우아한 종료 처리
# PreStop hook으로 우아한 종료
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
최소 권한 원칙
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
네트워크 정책
# Sidecar만 외부 통신 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: sidecar-network-policy
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
role: log-server
ports:
- protocol: TCP
port: 24224
시크릿 관리
# 환경 변수 대신 볼륨 마운트 사용
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: app-secrets
볼륨 타입 선택
| 볼륨 타입 | 사용 사례 | 특징 |
|---|---|---|
emptyDir | 임시 데이터 공유 | - Pod 재시작 시 초기화 - 노드의 디스크 사용 |
emptyDir.medium: Memory | 빠른 I/O 필요 | - 메모리 사용 (빠름) - 크기 제한 주의 |
configMap | 설정 파일 | - 읽기 전용 - 크기 제한 (1MB) |
persistentVolumeClaim | 영구 데이터 | - Pod 재시작 후에도 유지 |
# 고성능 I/O가 필요한 경우
volumes:
- name: cache
emptyDir:
medium: Memory
sizeLimit: 1Gi
Sidecar 시작 순서 제어
# Native Sidecar 사용 (1.28+)
initContainers:
- name: envoy
restartPolicy: Always
startupProbe:
httpGet:
path: /ready
port: 15021
failureThreshold: 30
periodSeconds: 1
CPU/메모리 공유 최적화
# QoS Class: Guaranteed (예측 가능한 성능)
containers:
- name: app
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "256Mi" # requests와 동일
cpu: "500m" # requests와 동일
레이블링 전략
metadata:
labels:
app: my-app
component: sidecar
sidecar-type: logging
version: v1.2.3
로그 구조화
// Sidecar 로그는 JSON 형식으로
{
"timestamp": "2025-01-15T10:30:00Z",
"level": "INFO",
"component": "git-sync",
"message": "Synced successfully",
"repo": "<https://github.com/example/repo>"
}
메트릭 수집
# Prometheus annotations
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
기능을 추가하고 싶다면?
│
├─ 기능이 여러 앱에서 재사용되는가?
│ ├─ YES → Sidecar (조합)
│ └─ NO → 다음 질문으로
│
├─ 기능이 메인 앱과 다른 릴리스 주기를 가지는가?
│ ├─ YES → Sidecar (조합)
│ └─ NO → 다음 질문으로
│
├─ 기능이 다른 팀/언어로 개발되는가?
│ ├─ YES → Sidecar (조합)
│ └─ NO → 다음 질문으로
│
└─ 런타임에 기능을 교체할 가능성이 있는가?
├─ YES → Sidecar (조합)
└─ NO → Dockerfile (상속)
공식 문서
디자인 패턴
실무 사례
책
GitHub 예제