[EKS Best Practices] Reliability - Applications

원문: Reliability - Applications

EKS Best Practices - 애플리케이션 신뢰성 (Reliability)

Kubernetes는 선언적(declarative) 관리를 통해 애플리케이션을 고가용성(HA)으로 운영할 수 있게 해줍니다. 이 문서는 AWS EKS 환경에서 애플리케이션의 신뢰성을 높이기 위한 핵심 권장사항을 정리합니다.

전체 구조 개요

mindmap
  root((애플리케이션 신뢰성))
    고가용성 구성
      Pod Disruption Budget
      다중 Replica 운영
      노드/AZ 분산 배치
      Metrics Server
    오토스케일링
      HPA - 수평 확장
      VPA - 수직 확장
    업데이트 전략
      Rolling Update
      Blue/Green
      Canary
      Rollback 메커니즘
    Health Check
      Liveness Probe
      Startup Probe
      Readiness Probe
    Disruption 대응
      PDB
      Graceful Shutdown
      Chaos Engineering
      Service Mesh
    Observability
      Monitoring
      Logging
      Distributed Tracing

1. 고가용성 애플리케이션 구성

1.1 핵심 권장사항 요약

권장사항 설명 중요도
PDB 설정 동시 중단 Pod 수를 제한하여 가용성 보장 필수
Singleton Pod 금지 단일 Pod 대신 반드시 Deployment 사용 필수
다중 Replica 운영 최소 2개 이상 Replica로 장애 대비 필수
노드/AZ 분산 배치 Anti-Affinity 또는 Topology Spread 활용 권장
Metrics Server 설치 HPA/VPA 오토스케일링의 기반 필수

1.2 Pod Disruption Budget (PDB)

PDB는 자발적 중단(voluntary disruption) 시 동시에 중단되는 Pod 수를 제한합니다. EKS Auto Mode, Karpenter, Cluster Autoscaler 모두 PDB를 준수합니다.

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: my-svc-pdb
spec:
  minAvailable: 3        # 최소 3개 Pod는 항상 유지
  selector:
    matchLabels:
      app: my-svc

참고: EKS Managed Node Group 업그레이드 시 15분 timeout이 적용됩니다. 15분 내에 drain이 완료되지 않으면 강제 업데이트가 아닌 경우 업데이트가 실패합니다.

1.3 Replica 분산 배치 전략

Pod를 여러 노드와 AZ에 분산 배치해야 단일 노드 장애 시에도 서비스가 유지됩니다.

graph TB
    subgraph AZ-a
        Node1[Node 1]
        Pod1[Pod replica 1]
        Pod2[Pod replica 2]
        Node1 --> Pod1
        Node1 --> Pod2
    end
    subgraph AZ-b
        Node2[Node 2]
        Pod3[Pod replica 3]
        Pod4[Pod replica 4]
        Node2 --> Pod3
        Node2 --> Pod4
    end
    subgraph AZ-c
        Node3[Node 3]
        Pod5[Pod replica 5]
        Pod6[Pod replica 6]
        Node3 --> Pod5
        Node3 --> Pod6
    end

    style AZ-a fill:#e1f5fe
    style AZ-b fill:#e8f5e9
    style AZ-c fill:#fff3e0

방법 1: Pod Anti-Affinity

노드와 AZ 기준으로 Pod를 분산시킵니다. preferredDuringSchedulingIgnoredDuringExecution을 사용하면 분산을 선호하되 불가능한 경우에도 스케줄링을 허용합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spread-host-az
  labels:
    app: web-server
spec:
  replicas: 4
  selector:
    matchLabels:
      app: web-server
  template:
    metadata:
      labels:
        app: web-server
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - web-server
              topologyKey: topology.kubernetes.io/zone  # AZ 분산
            weight: 100
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - web-server
              topologyKey: kubernetes.io/hostname  # 노드 분산
            weight: 99
      containers:
      - name: web-app
        image: nginx:1.16-alpine

Tip: Replica 수가 AZ 수와 같다면(예: 3 Replica, 3 AZ) requiredDuringSchedulingIgnoredDuringExecution을 사용하여 각 AZ에 반드시 1개씩 배치할 수 있습니다.

방법 2: Pod Topology Spread Constraints

Anti-Affinity보다 세밀한 분산 제어가 가능합니다. 핵심 속성은 다음과 같습니다.

속성 설명 예시
maxSkew 토폴로지 도메인 간 최대 불균형 허용치 1 → 10개 Pod가 3 AZ에 4,3,3으로 분산
topologyKey 분산 기준이 되는 노드 레이블 키 topology.kubernetes.io/zone
whenUnsatisfiable 제약 조건 미충족 시 동작 ScheduleAnyway 또는 DoNotSchedule
labelSelector 분산 대상 Pod 선택 기준 matchLabels: {app: web-server}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spread-host-az
spec:
  replicas: 10
  selector:
    matchLabels:
      app: web-server
  template:
    metadata:
      labels:
        app: web-server
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: "topology.kubernetes.io/zone"
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: web-server
      containers:
      - name: web-app
        image: nginx:1.16-alpine

Anti-Affinity vs Topology Spread Constraints 비교

항목 Pod Anti-Affinity Topology Spread Constraints
분산 방식 Pod 간 반발력(repelling) 기반 균등 분배(even distribution) 기반
불균형 제어 제한적 (1개씩만 배치되는 경향) maxSkew로 세밀한 제어 가능
Fault Tolerance 도메인당 1개 Replica만 존재할 수 있음 도메인당 여러 Replica 유지 가능
리소스 효율 낮음 (단일 Replica 전용 노드 발생) 높음 (균등 분배로 효율적)
적합한 상황 단순한 분산 요구 복잡한 분산 요구, 대규모 Replica

2. 오토스케일링

2.1 Horizontal Pod Autoscaler (HPA)

HPA는 트래픽 증가에 대응하여 Pod 수를 수평으로 자동 확장합니다. 주기적으로 메트릭을 조회하는 Control Loop로 구현됩니다.

flowchart LR
    A[Metrics Source] --> B{HPA Controller}
    B -->|Scale Up| C[Deployment]
    B -->|Scale Down| C
    C --> D[Pod Replicas]

    subgraph "메트릭 API"
        E["metrics.k8s.io\n(CPU/Memory)"]
        F["custom.metrics.k8s.io\n(Prometheus 등)"]
        G["external.metrics.k8s.io\n(SQS, ELB 등)"]
    end

    E --> A
    F --> A
    G --> A

HPA 메트릭 소스

API 설명 사용 예시
metrics.k8s.io CPU/Memory 리소스 메트릭 Pod CPU 사용률 70% 초과 시 확장
custom.metrics.k8s.io 클러스터 내부 커스텀 메트릭 Prometheus 메트릭 기반 확장
external.metrics.k8s.io 클러스터 외부 메트릭 SQS Queue 깊이, ELB 지연 시간 기반 확장

커스텀/외부 메트릭 활용

# Custom Metrics 확인
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/

2.2 Vertical Pod Autoscaler (VPA)

VPA는 Pod의 CPU/Memory 리소스 요청(request)을 자동으로 조정하여 Right-sizing을 수행합니다.

항목 HPA VPA
확장 방향 수평 (Pod 수 증감) 수직 (Pod 리소스 증감)
In-place 업데이트 해당 없음 현재 미지원 (Pod 재생성 필요)
가용성 영향 낮음 일시적 서비스 중단 가능
적합한 워크로드 Stateless 웹 서비스 리소스 요구량이 가변적인 앱

주의: VPA는 현재 in-place 조정을 지원하지 않으므로, 스케일링 시 Pod가 재생성됩니다. 일시적인 서비스 중단이 발생할 수 있습니다.

Fairwinds Goldilocks를 사용하면 VPA 권장 값을 대시보드로 시각화하고, 자동 스케일링을 설정할 수 있습니다.


3. 애플리케이션 업데이트 전략

3.1 Rolling Update (기본 전략)

Kubernetes Deployment의 기본 업데이트 전략입니다. 전체 Pod를 한 번에 교체하지 않고, 일부씩 순차적으로 교체합니다.

속성 설명 기본값 권장 설정
maxUnavailable 업데이트 중 동시에 중단 가능한 Pod 수/비율 25% 워크로드에 맞게 조정
maxSurge 원하는 Pod 수 이상으로 추가 생성 가능한 Pod 수/비율 25% 워크로드에 맞게 조정

예시: 100개 Pod, maxUnavailable=25%인 경우 업데이트 중 최소 75개 Pod만 가동됩니다. 최소 80개가 필요하다면 maxUnavailable=20%로 설정해야 합니다.

Rollback 메커니즘

# 이미지 업데이트 (--record 플래그로 변경 이력 기록)
kubectl --record deployment.apps/nginx-deployment set image \
  nginx-deployment nginx=nginx:1.16.1

# 변경 이력 확인
kubectl rollout history deployment nginx-deployment

# Rollback 수행
kubectl rollout undo deployment nginx-deployment

3.2 세 가지 배포 전략 비교

graph LR
    subgraph Rolling Update
        R1[v1 Pod] -->|순차 교체| R2[v2 Pod]
    end

    subgraph Blue/Green
        B1[v1 전체] -->|트래픽 전환| B2[v2 전체]
    end

    subgraph Canary
        C1[v1 90%] -->|점진적 전환| C2[v2 10%]
        C2 -->|확인 후 확대| C3[v2 100%]
    end
전략 설명 장점 단점 도구
Rolling Update Pod를 순차적으로 교체 간단, 기본 제공 이전/이후 버전 혼재 기간 존재 Kubernetes 내장
Blue/Green 새 환경을 완전히 구성 후 트래픽 전환 즉시 Rollback 가능 2배의 리소스 필요 AWS LB Controller, Flux, Jenkins, Spinnaker
Canary 소량의 트래픽만 새 버전으로 전환, 점진적 확대 리스크 최소화 구성 복잡 Flagger + Istio, KEDA

3.3 Blue/Green 배포 구현

Kubernetes에서 Blue/Green 배포를 구현하는 방법:

  1. 기존 Deployment와 동일한 새 Deployment를 생성
  2. 새 Pod가 정상 동작하는지 검증
  3. Service의 selector를 새 Deployment의 Pod로 변경하여 트래픽 전환
  4. 문제 발생 시 selector를 원래 Deployment로 되돌려 Rollback

4. Health Check 및 Self-Healing

Kubernetes는 3가지 유형의 Health Check를 제공하며, kubelet이 모든 체크를 수행합니다.

flowchart TD
    A[kubelet] --> B{Probe 유형}
    B -->|Startup Probe| C[앱 시작 완료 확인]
    B -->|Liveness Probe| D[앱 정상 동작 확인]
    B -->|Readiness Probe| E[트래픽 수신 가능 확인]

    C -->|성공| F[Liveness/Readiness 활성화]
    C -->|실패| G[Pod 재생성]

    D -->|실패| H[Pod 재생성]
    D -->|성공| I[정상 유지]

    E -->|실패| J[Service에서 제외]
    E -->|성공| K[트래픽 수신]

    style G fill:#ffcdd2
    style H fill:#ffcdd2
    style J fill:#fff9c4
    style K fill:#c8e6c9

4.1 Probe 유형별 비교

Probe 목적 실패 시 동작 사용 시점
Liveness Deadlock 등 앱 비정상 상태 감지 Pod 재생성 앱이 실행 중이지만 응답 불가 상태 감지
Startup 앱 시작 완료 여부 확인 Pod 재생성 시작 시간이 긴 앱 (예: 캐시 로딩 Java 앱)
Readiness 일시적 서비스 불가 감지 Service에서 제외 (트래픽 차단) I/O 집중 작업 등 일시적 부하

4.2 Probe 체크 방식

kubelet은 3가지 방식으로 Pod 상태를 확인할 수 있습니다:

방식 설명 적합한 상황
exec 컨테이너 내부에서 Shell 명령 실행 커스텀 스크립트로 복잡한 상태 확인
httpGet HTTP GET 요청 (200-399 = 정상) 웹 서비스의 Health Endpoint
tcpSocket TCP 연결 가능 여부 확인 데이터베이스 등 TCP 기반 서비스

4.3 Probe 설정 시 주의사항

주의사항 설명
외부 의존성 제외 Liveness/Readiness Probe에 외부 DB 등 의존성을 포함하지 말 것. DB 장애 시 모든 Pod가 동시에 실패하여 전체 서비스 중단 발생
동시 실패 방지 모든 Pod가 동시에 Liveness 실패하면 Kubernetes가 전부 재생성 시도 -- 서비스 완전 중단 + Control Plane 부하
exec Probe timeout exec 방식 사용 시 timeoutSeconds 내에 명령이 완료되어야 함. 초과 시 <defunct> 프로세스 발생 -- 노드 장애
Startup Probe 사용 기준 시작 시간이 예측 불가한 경우 Startup Probe 사용. 고정된 시작 시간이라면 initialDelaySeconds로 충분
Readiness와 업데이트 Readiness Probe 사용 시 Deployment 업데이트가 느려질 수 있음 (새 Pod가 Ready 되어야 이전 Pod 제거)

5. Disruption 대응

5.1 Graceful Shutdown 프로세스

Pod 종료 시 다음 순서로 진행됩니다:

sequenceDiagram
    participant K as Kubernetes
    participant C as Container (PID 1)
    participant P as PreStop Hook

    K->>P: 1. PreStop Hook 실행
    P-->>K: Hook 완료
    K->>C: 2. SIGTERM 전송
    Note over C: Grace Period 대기
(기본 30초) K->>C: 3. SIGKILL 전송
(Grace Period 초과 시)

중요: terminationGracePeriodSeconds는 PreStop Hook 실행 시점부터 카운트됩니다. SIGTERM 전송 시점이 아닙니다.

5.2 PID 1 문제와 해결

Shell script가 ENTRYPOINT인 경우, 실제 애플리케이션 프로세스가 PID 1이 아닐 수 있습니다. 이 경우 SIGTERM이 앱에 전달되지 않습니다.

문제 상황 해결 방법
Shell script가 PID 1 ENTRYPOINT를 앱 프로세스로 직접 변경
복잡한 초기화 스크립트 필요 dumb-init 등 init 시스템 사용
종료 전 정리 작업 필요 PreStop Hook 활용
# 문제 확인: PID 1이 shell script인 경우
$ kubectl exec python-app -it -- ps
PID USER TIME COMMAND
1   root 0:00 {script.sh} /bin/sh ./script.sh   # <-- SIGTERM 수신
5   root 0:00 python app.py                      # <-- SIGTERM 미수신!

5.3 Chaos Engineering

Chaos Engineering은 운영 환경에서 예기치 않은 장애에 대한 시스템의 복원력을 검증하는 방법론입니다.

도구 설명
Gremlin 상용 Chaos Engineering 플랫폼
AWS FIS AWS 관리형 장애 주입 서비스
Litmus 오픈소스 Kubernetes Chaos Engineering

5.4 Service Mesh 활용

Service Mesh는 마이크로서비스 간 통신에 Sidecar Proxy를 추가하여 다음 기능을 제공합니다:

기능 설명
자동 재시도 (Retry) 실패한 요청을 자동으로 재시도
Timeout 응답 지연 시 요청 중단
Circuit Breaking 장애 전파 방지
Rate Limiting 과도한 트래픽 제한
Observability 네트워크 통계, Access Log, 분산 추적 헤더 자동 생성

주요 Service Mesh 옵션: Istio, LinkerD, Consul


6. Observability (관측 가능성)

6.1 세 가지 관측 축

graph TB
    O[Observability] --> M[Monitoring
메트릭 수집/알림] O --> L[Logging
로그 수집/보관] O --> T[Tracing
분산 추적] M --> M1[Prometheus] M --> M2[CloudWatch Container Insights] L --> L1[Fluent Bit / Fluentd] L --> L2[CloudWatch Logs] L --> L3[Elasticsearch] T --> T1[AWS X-Ray] T --> T2[Jaeger] T --> T3[Service Mesh 기반]

6.2 Monitoring 권장사항

주요 메트릭 방법론

방법론 의미 적용 대상
RED Requests, Errors, Duration 서비스 중심 모니터링
USE Utilization, Saturation, Errors 인프라/리소스 중심 모니터링

권장 도구 구성

영역 도구 용도
메트릭 수집 Prometheus, Prometheus Client Library 클러스터 및 앱 메트릭 수집, 커스텀 메트릭 노출
대시보드/알림 Grafana, CloudWatch Container Insights 시각화 및 경보 설정
로그 수집 Fluent Bit, Fluentd DaemonSet으로 컨테이너 로그 수집
로그 저장 CloudWatch Logs, Elasticsearch, InfluxDB 중앙 집중식 로그 저장
분산 추적 AWS X-Ray, Jaeger 서비스 간 요청 흐름 추적

6.3 EKS Control Plane 로그

EKS는 5가지 Control Plane 로그를 CloudWatch Logs로 전송할 수 있습니다 (기본값: 비활성화).

로그 유형 용도
API Server API 요청/응답 로그
Audit API 호출 감사 로그
Authenticator 인증 관련 로그
Controller Manager Control Plane 진단 (병목, 오류 식별)
Scheduler 스케줄링 관련 진단

6.4 Distributed Tracing 구현 방식

방식 장점 단점
공유 라이브러리 (코드 레벨) 세밀한 제어 가능 코드 수정 필요, 다중 언어 환경에서 유지보수 부담
Service Mesh 코드 수정 최소화, 표준화된 메트릭/로그/추적 추가 인프라 오버헤드

종합 체크리스트

카테고리 체크 항목
고가용성 Singleton Pod 없이 Deployment 사용
최소 2개 이상 Replica 운영
PDB 설정 완료
Pod를 여러 노드/AZ에 분산 배치
스케일링 Metrics Server 설치
HPA 설정 (CPU/Memory 또는 커스텀 메트릭)
VPA 또는 Goldilocks로 Right-sizing
업데이트 Rolling Update 파라미터 튜닝
Rollback 절차 문서화/테스트
Blue/Green 또는 Canary 전략 검토
Health Check Liveness Probe 설정 (외부 의존성 제외)
Readiness Probe 설정 (외부 의존성 제외)
시작이 느린 앱에 Startup Probe 적용
Disruption Graceful Shutdown 구현 (PID 1 확인)
PreStop Hook 필요 시 설정
terminationGracePeriodSeconds 적정값 설정
Observability 중앙 집중식 로그 수집 설정
EKS Control Plane 로그 활성화
Prometheus 등 모니터링 도구 설치
분산 추적 시스템 도입 검토
RED/USE 메트릭 기반 알림 설정