07-02: 고급 파드 관리 및 스케줄링 전략
쿠버네티스에서 파드를 안정적으로 운영하고, 원하는 위치에 효율적으로 배포하기 위한 고급 개념들을 정리합니다.
1. Probe (프로브): 파드의 상태를 확인하는 건강검진
Kubelet(노드 관리자)이 파드 내 컨테이너의 상태를 주기적으로 확인하는 방법입니다. 프로브가 실패하면 쿠버네티스는 사전에 정의된 조치를 취합니다.
Liveness Probe (활동성 프로브)
- “살아있는가?” 를 확인합니다.
- 컨테이너가 응답 불능 상태(데드락 등)에 빠졌을 때, 이를 감지하고 컨테이너를 재시작시켜 애플리케이션의 가용성을 회복시킵니다.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5 # 파드 시작 후 5초 뒤부터 검사 시작
periodSeconds: 10 # 10초마다 검사Readiness Probe (준비성 프로브)
- “요청을 받을 준비가 되었는가?” 를 확인합니다.
- 컨테이너가 시작되었더라도, 무거운 데이터를 로딩하는 등의 이유로 즉시 요청을 처리할 수 없을 때 사용됩니다.
- 프로브가 실패하면, 쿠버네티스 서비스(Service)는 해당 파드를 서비스 엔드포인트에서 일시적으로 제외하여 트래픽을 보내지 않습니다.
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5Startup Probe (시작 프로브)
- “시작이 완료되었는가?” 를 확인합니다.
- 시작하는 데 시간이 매우 오래 걸리는 레거시 애플리케이션에 유용합니다.
- 이 프로브가 성공할 때까지 Liveness/Readiness 프로브는 시작되지 않아, 느린 시작 때문에 파드가 재시작되는 것을 방지합니다.
startupProbe:
tcpSocket:
port: 8080
failureThreshold: 30 # 30번 실패 시 실패로 간주 (총 5분)
periodSeconds: 102. Init Container (초기화 컨테이너)
메인 애플리케이션 컨테이너가 시작되기 전에 실행되어야 하는 사전 작업을 정의하는 컨테이너입니다.
- 용도: 데이터베이스 스키마 마이그레이션, 필수 파일 다운로드, 설정 스크립트 실행 등
- 특징:
- 여러 개를 정의할 수 있으며, 정의된 순서대로 실행됩니다.
- 모든 Init 컨테이너가 성공적으로 완료되어야만 메인 컨테이너가 시작됩니다.
spec:
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'echo "Initializing..." && sleep 5']
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', 'echo "Checking DB connection..." && sleep 5']
containers:
- name: main-app
image: nginx3. 멀티 컨테이너 디자인 패턴
하나의 파드 안에 여러 컨테이너를 함께 배치하여 기능을 확장하거나 문제를 해결하는 패턴입니다.
Sidecar Pattern (사이드카 패턴)
메인 컨테이너의 핵심 기능에 영향을 주지 않으면서, 부가적인 기능을 추가하는 패턴입니다.
- 예시: 로그 수집기, 서비스 메쉬 프록시(Istio-proxy), 데이터 동기화 등
- 장점: 메인 애플리케이션의 코드 변경 없이 기능을 확장하고, 관심사를 분리할 수 있습니다.
spec:
containers:
- name: main-app
image: nginx
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: sidecar-log-collector # 사이드카 컨테이너
image: busybox
command: ["sh", "-c", "while true; do cat /var/log/nginx/access.log; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
volumes:
- name: shared-logs
emptyDir: {}Ambassador Pattern (앰배서더 패턴)
메인 컨테이너가 외부 서비스와 통신하는 방식을 단순화시켜주는 프록시 역할을 합니다.
- 예시: 서비스 디스커버리, 인증/인가, 요청 재시도, 샤딩(Sharding) 로직 처리
- 장점: 메인 애플리케이션은
localhost로만 통신하면 되므로, 복잡한 외부 네트워크 환경으로부터 격리됩니다.
spec:
containers:
- name: main-app
# 이 앱은 localhost:9000 으로만 요청을 보냄
image: my-app
- name: ambassador-proxy # 앰배서더 컨테이너
image: envoyproxy/envoy
# Envoy 설정은 복잡한 외부 DB 주소나 샤딩 로직을 처리Adapter Pattern (어댑터 패턴)
메인 컨테이너의 출력(로그, 메트릭 등)을 외부 시스템이 요구하는 표준화된 형식으로 변환합니다.
- 예시: 다양한 형식의 애플리케이션 로그를 JSON 형식으로 통일하여 모니터링 시스템(Prometheus, Datadog)으로 전송
- 장점: 애플리케이션의 로그/메트릭 출력 방식을 변경하지 않고도, 표준 모니터링 시스템과 통합할 수 있습니다.
spec:
containers:
- name: main-app
image: my-legacy-app
# 이 앱은 /var/logs/app.log 에 일반 텍스트 로그를 남김
- name: adapter-log-formatter # 어댑터 컨테이너
image: fluentd
# Fluentd는 app.log를 읽어 JSON으로 변환 후 중앙 로그 서버로 전송4. Pod Affinity / Anti-Affinity (파드 어피니티 / 안티-어피니티)
파드를 다른 파드와의 관계에 따라 특정 노드에 함께 배치하거나, 혹은 떨어뜨려 배치하는 스케줄링 규칙입니다.
Pod Affinity (파드 선호도)
- “친한 파드와 같이 있자”
- 특정 라벨을 가진 파드가 실행 중인 노드에 새로운 파드를 함께 배치합니다.
- 용도: 네트워크 지연을 줄이기 위해 웹 서버와 캐시 서버를 같은 노드에 배치할 때 사용합니다.
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 반드시 함께
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: kubernetes.io/hostnamePod Anti-Affinity (파드 반선호도)
- “싫어하는 파드와는 떨어져 있자”
- 특정 라벨을 가진 파드가 실행 중인 노드를 피해 다른 노드에 배치합니다.
- 용도: 고가용성을 위해 동일한 애플리케이션의 복제본들을 서로 다른 노드/존/리전에 분산시킬 때 사용합니다.
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 가급적이면 떨어져서
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: kubernetes.io/hostname5. Topology Spread Constraints (토폴로지 분배 제약조건)
고가용성을 위해 파드를 여러 토폴로지 도메인(노드, 존, 리전 등)에 걸쳐 최대한 균등하게 분배하는 가장 강력하고 유연한 방법입니다.
- 용도: 특정 노드나 존에 장애가 발생해도 서비스 전체에 미치는 영향을 최소화합니다.
- 주요 필드:
maxSkew: 도메인 간에 허용되는 파드 수의 최대 불균형 값topologyKey: 파드를 분배할 기준 (예:kubernetes.io/hostname,topology.kubernetes.io/zone)whenUnsatisfiable: 제약조건을 만족할 수 없을 때의 동작 (DoNotSchedule또는ScheduleAnyway)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone # '존'을 기준으로 분배
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-distributed-app
- maxSkew: 1
topologyKey: kubernetes.io/hostname # '노드'를 기준으로 분배
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-distributed-app