Sidecar Pattern
사이드카 패턴
메인 애플리케이션 컨테이너 옆에 보조 컨테이너(사이드카)를 배치하여 로깅, 모니터링, 프록시, 보안 등의 횡단 관심사(Cross-cutting Concerns)를 분리하는 마이크로서비스 아키텍처 패턴입니다. Kubernetes의 Pod에서 하나의 Pod 안에 여러 컨테이너를 배치하는 방식으로 구현됩니다.
사이드카 패턴
메인 애플리케이션 컨테이너 옆에 보조 컨테이너(사이드카)를 배치하여 로깅, 모니터링, 프록시, 보안 등의 횡단 관심사(Cross-cutting Concerns)를 분리하는 마이크로서비스 아키텍처 패턴입니다. Kubernetes의 Pod에서 하나의 Pod 안에 여러 컨테이너를 배치하는 방식으로 구현됩니다.
패턴 원리: 사이드카 패턴은 오토바이 옆에 부착된 사이드카에서 이름을 따왔습니다. 메인 애플리케이션(오토바이)은 비즈니스 로직에만 집중하고, 부가 기능(승객)은 사이드카 컨테이너가 담당합니다. 두 컨테이너는 같은 네트워크 네임스페이스와 스토리지를 공유하므로 localhost로 통신하고 파일 시스템을 공유할 수 있습니다.
사용 시나리오: 가장 대표적인 사용 사례는 Service Mesh(Istio, Linkerd)에서 Envoy 프록시를 사이드카로 배치하는 것입니다. 모든 네트워크 트래픽이 사이드카를 통과하면서 mTLS 암호화, 트래픽 제어, 관측성(Observability)이 자동으로 적용됩니다. 로그 수집(Fluentd), 설정 동기화(Config Sync), 시크릿 주입(Vault Agent) 등도 사이드카로 구현합니다.
장점: 애플리케이션 코드 수정 없이 기능을 추가할 수 있습니다. 언어/프레임워크에 독립적이므로 폴리글랏 환경에서 일관된 인프라 기능을 제공합니다. 사이드카만 업그레이드하면 되므로 유지보수가 쉽고, 관심사 분리로 테스트와 디버깅이 용이합니다.
단점: 컨테이너 수가 늘어나 리소스 오버헤드가 발생합니다. Pod 내 컨테이너 간 시작 순서 관리가 복잡하고, 사이드카 장애가 메인 애플리케이션에 영향을 줄 수 있습니다. 네트워크 홉이 추가되어 지연 시간이 미세하게 증가합니다.
Kubernetes 1.28부터는 네이티브 사이드카 컨테이너가 지원되어 initContainer와 결합한 사이드카의 시작/종료 순서 문제가 해결되었습니다. AWS App Mesh, Google Cloud Traffic Director 등 클라우드 벤더도 사이드카 기반 서비스 메시를 제공합니다.
# Kubernetes Sidecar Pattern 기본 예제
apiVersion: v1
kind: Pod
metadata:
name: web-app-with-sidecar
labels:
app: web-app
spec:
containers:
# 메인 애플리케이션 컨테이너
- name: main-app
image: my-web-app:v1.0
ports:
- containerPort: 8080
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: shared-data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# 사이드카 컨테이너 1: 로그 수집
- name: log-collector
image: fluentd:v1.16
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
readOnly: true
- name: fluentd-config
mountPath: /fluentd/etc
env:
- name: ELASTICSEARCH_HOST
value: "elasticsearch.logging.svc.cluster.local"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
# 사이드카 컨테이너 2: 프록시
- name: envoy-proxy
image: envoyproxy/envoy:v1.28.0
ports:
- containerPort: 9901 # Envoy admin
- containerPort: 10000 # Listener
volumeMounts:
- name: envoy-config
mountPath: /etc/envoy
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
# Kubernetes 1.28+ 네이티브 사이드카 (restartPolicy: Always)
initContainers:
- name: vault-agent
image: hashicorp/vault:1.15
restartPolicy: Always # 사이드카로 동작
volumeMounts:
- name: shared-data
mountPath: /data
env:
- name: VAULT_ADDR
value: "https://vault.default.svc.cluster.local:8200"
volumes:
- name: shared-logs
emptyDir: {}
- name: shared-data
emptyDir: {}
- name: fluentd-config
configMap:
name: fluentd-config
- name: envoy-config
configMap:
name: envoy-config
---
# Service - 메인 앱 포트만 노출
apiVersion: v1
kind: Service
metadata:
name: web-app-service
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
# Envoy Sidecar 설정 - Service Mesh 패턴
# envoy-config.yaml
static_resources:
listeners:
# 인바운드 트래픽 (외부 → 앱)
- name: inbound_listener
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: inbound_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: local_app
http_filters:
# 분산 추적 (Tracing)
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
# 아웃바운드 트래픽 (앱 → 외부)
- name: outbound_listener
address:
socket_address:
address: 127.0.0.1
port_value: 15001
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: outbound_http
route_config:
name: outbound_route
virtual_hosts:
- name: external_services
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: external_service
# 재시도 정책
retry_policy:
retry_on: "5xx,connect-failure"
num_retries: 3
per_try_timeout: 2s
# 타임아웃
timeout: 30s
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
# 로컬 애플리케이션
- name: local_app
type: STATIC
connect_timeout: 1s
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: local_app
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
# 헬스체크
health_checks:
- timeout: 1s
interval: 10s
healthy_threshold: 2
unhealthy_threshold: 3
http_health_check:
path: /health
# 외부 서비스 (Circuit Breaker 적용)
- name: external_service
type: STRICT_DNS
connect_timeout: 5s
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: external_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: api.external.com
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
# Circuit Breaker 설정
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 100
max_pending_requests: 100
max_requests: 100
max_retries: 3
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901
# 로깅 사이드카 패턴 - Fluentd 예제
# 메인 앱이 파일로 로그를 쓰면, 사이드카가 수집하여 중앙 저장소로 전송
---
# Fluentd ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-sidecar-config
data:
fluent.conf: |
# 애플리케이션 로그 수집
@type tail
path /var/log/app/*.log
pos_file /var/log/fluentd/app.log.pos
tag app.logs
@type json
time_key timestamp
time_format %Y-%m-%dT%H:%M:%S.%NZ
# 액세스 로그 수집
@type tail
path /var/log/app/access.log
pos_file /var/log/fluentd/access.log.pos
tag app.access
@type regexp
expression /^(?[^ ]*) - (?[^ ]*) \[(?
# 메타데이터 추가
@type record_transformer
hostname "#{Socket.gethostname}"
pod_name "#{ENV['POD_NAME']}"
namespace "#{ENV['POD_NAMESPACE']}"
container_name "#{ENV['CONTAINER_NAME']}"
# Elasticsearch로 전송
@type elasticsearch
host "#{ENV['ELASTICSEARCH_HOST']}"
port 9200
index_name fluentd-${tag}-%Y.%m.%d
@type file
path /var/log/fluentd/buffer
flush_interval 5s
chunk_limit_size 5MB
total_limit_size 500MB
retry_max_interval 30s
retry_forever true
---
# 실제 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-logging-sidecar
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
# 메인 애플리케이션
- name: app
image: my-app:v1.0
ports:
- containerPort: 8080
volumeMounts:
- name: log-volume
mountPath: /var/log/app
env:
- name: LOG_PATH
value: /var/log/app/app.log
- name: LOG_FORMAT
value: json
# Fluentd 사이드카
- name: fluentd-sidecar
image: fluent/fluentd-kubernetes-daemonset:v1.16-debian-elasticsearch8
volumeMounts:
- name: log-volume
mountPath: /var/log/app
readOnly: true
- name: fluentd-config
mountPath: /fluentd/etc
- name: fluentd-buffer
mountPath: /var/log/fluentd
env:
- name: ELASTICSEARCH_HOST
value: elasticsearch.logging.svc.cluster.local
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "200Mi"
cpu: "200m"
volumes:
- name: log-volume
emptyDir: {}
- name: fluentd-config
configMap:
name: fluentd-sidecar-config
- name: fluentd-buffer
emptyDir: {}
"Istio를 도입하면 모든 Pod에 Envoy 사이드카가 자동 주입됩니다. 애플리케이션 코드 수정 없이 mTLS, 트래픽 제어, 분산 추적이 가능해져요. 다만 사이드카당 메모리 50~100MB 정도 오버헤드가 있으니 리소스 계획에 반영하세요."
"각 Pod에 Fluentd 사이드카를 붙여서 로그를 수집하는 방식과 DaemonSet으로 노드당 하나씩 배포하는 방식이 있습니다. 사이드카 방식이 격리성은 좋지만 리소스 사용량이 많아요. 로그 볼륨이 크지 않다면 DaemonSet 권장합니다."
"Vault Agent를 사이드카로 배포하면 시크릿을 자동으로 갱신해서 공유 볼륨에 써줍니다. 애플리케이션은 파일만 읽으면 되니까 Vault SDK 없이도 시크릿을 사용할 수 있어요. 시크릿 갱신 시 앱 재시작도 필요 없습니다."
사이드카가 준비되기 전에 메인 앱이 시작하면 문제가 됩니다. Kubernetes 1.28+ 네이티브 사이드카나 initContainer 활용, 앱 레벨 재시도 로직을 구현하세요.
사이드카마다 CPU/메모리를 할당하면 전체 클러스터 리소스가 부족해집니다. 프로파일링으로 실제 사용량을 측정하고 적절한 requests/limits를 설정하세요.
프록시 사이드카가 죽으면 메인 앱도 통신 불가합니다. 사이드카의 liveness/readiness probe를 설정하고, 사이드카 없이도 동작하는 fallback 모드를 고려하세요.
경량 사이드카 이미지 사용, 공유 볼륨으로 효율적 통신, 사이드카 버전 일괄 관리(Helm/Kustomize), 모니터링으로 사이드카 리소스 추적.