🎯 쿠버네티스 고급 파드 관리 및 스케줄링 전략

문서 개요

쿠버네티스에서 파드를 안정적으로 운영하고, 원하는 위치에 효율적으로 배포하기 위한 고급 개념들을 비전공자 관점에서 상세히 정리한 문서입니다.

📑 목차


1. Probe (프로브): 파드의 건강검진 시스템

핵심 개념

쿠버네티스가 컨테이너의 상태를 자동으로 확인하는 건강검진 시스템입니다. 마치 병원에서 정기검진을 받는 것처럼, 컨테이너도 주기적으로 검사를 받습니다.

💓 Liveness Probe (생존 확인)

🤔 질문: “이 컨테이너가 살아있나요?”

🏠 실생활 비유

graph LR
    A[직장 동료가 응답 없음] --> B[깨우기 시도]
    B --> C[대신 일 처리]
    
    D[컨테이너 응답 없음] --> E[Liveness Probe 감지]
    E --> F[컨테이너 재시작]

📋 구체적인 시나리오

실제 장애 상황

  1. 문제: 웹서버가 무한루프에 빠져 응답 불가 상태
  2. 감지: Liveness Probe가 /health 엔드포인트 호출 실패 감지
  3. 조치: 쿠버네티스가 자동으로 컨테이너 재시작
  4. 결과: 서비스 정상화

💻 실제 설정 예시

# 📊 Liveness Probe 상세 설정
livenessProbe:
  httpGet:
    path: /healthz              # 건강 상태 확인 URL
    port: 8080                  # 포트 번호
    httpHeaders:                # 추가 헤더 (선택사항)
    - name: Custom-Header
      value: health-check
  initialDelaySeconds: 5        # 시작 후 5초 대기
  periodSeconds: 10             # 10초마다 검사
  timeoutSeconds: 3             # 3초 안에 응답 없으면 실패
  failureThreshold: 3           # 3번 연속 실패하면 재시작
  successThreshold: 1           # 1번 성공하면 정상 판정

📊 프로브 타입별 비교

타입용도예시
httpGetHTTP 엔드포인트 확인GET /health
tcpSocketTCP 포트 연결 확인포트 8080 연결
exec명령어 실행 결과 확인curl localhost:8080

🚦 Readiness Probe (준비 상태 확인)

🤔 질문: “이 컨테이너가 일할 준비 됐나요?”

🏠 실생활 비유

graph TD
    A[식당 오픈 전] --> B[재료 준비]
    B --> C[청소 완료]
    C --> D[손님 받기 시작]
    
    E[컨테이너 시작] --> F[데이터 로딩]
    F --> G[초기화 완료]
    G --> H[트래픽 받기 시작]

📋 구체적인 시나리오

데이터베이스 연결 시나리오

  1. 상황: 웹서버가 시작되었지만 DB 연결은 아직 진행 중
  2. Readiness Probe: /ready 엔드포인트 호출 → 실패 응답
  3. 쿠버네티스 조치: 로드밸런서에서 해당 파드 제외
  4. DB 연결 완료: /ready 엔드포인트 → 성공 응답
  5. 트래픽 복구: 로드밸런서에 파드 다시 추가

💻 실제 설정 예시

# 🚦 Readiness Probe 상세 설정
readinessProbe:
  httpGet:
    path: /ready                # 준비 상태 확인 URL
    port: 8080
  initialDelaySeconds: 5        
  periodSeconds: 5              # 더 자주 확인 (5초마다)
  timeoutSeconds: 2
  failureThreshold: 3           
  successThreshold: 1

🚀 Startup Probe (시작 완료 확인)

🤔 질문: “이 컨테이너가 완전히 시작 됐나요?”

🏠 실생활 비유

오래된 컴퓨터 부팅

  • 오래된 컴퓨터 부팅 → 5분 소요 → 그 동안은 건드리지 말기
  • 레거시 애플리케이션 시작 → 10분 소요 → 완전 시작 후 건강검진 시작

🚨 문제 상황과 해결

graph TD
    A[문제: 시작에 5분 걸리는 앱] --> B[Liveness Probe 30초마다 확인]
    B --> C[1분 후 응답 없다 판단]
    C --> D[컨테이너 재시작 반복]
    
    E[해결: Startup Probe 사용] --> F[시작 완료까지 기다림]
    F --> G[완료 후 정상적인 건강검진 시작]

💻 실제 설정 예시

# 🚀 Startup Probe for Legacy Applications
startupProbe:
  tcpSocket:
    port: 8080
  initialDelaySeconds: 10       # 10초 후 시작
  periodSeconds: 10             # 10초마다 확인
  failureThreshold: 30          # 30번 실패까지 허용 (총 5분)
  timeoutSeconds: 5

주의사항

  • Startup Probe가 성공할 때까지 Liveness/Readiness Probe는 비활성화됩니다.
  • 너무 관대한 설정은 실제 장애를 놓칠 수 있으니 주의하세요.

2. Init Container: 영화 시작 전 예고편

핵심 개념

메인 프로그램이 실행되기 전에 반드시 해야 할 일들을 담당하는 컨테이너입니다.

🎭 실생활 비유

graph LR
    A[영화관: 예고편] --> B[메인 영화 상영]
    C[식당: 재료 준비] --> D[요리 시작]
    E[콘서트: 무대 셋팅] --> F[공연 시작]
    G[Init Container] --> H[Main Container]

📋 주요 특징

특징설명예시
순차 실행정의된 순서대로 하나씩 실행DB 마이그레이션 → 데이터 시딩
완료 필수모든 Init Container 성공 필요하나라도 실패하면 메인 앱 시작 안 됨
일회성한 번 실행 후 종료설정 파일 다운로드

🔧 실제 사용 예시

📊 시나리오 1: 데이터베이스 준비

apiVersion: v1
kind: Pod
metadata:
  name: web-app-with-db-setup
spec:
  # 🎬 Init Containers (순서대로 실행)
  initContainers:
  - name: db-migration            # 1단계: 스키마 업데이트
    image: migrate:latest
    command: 
    - /bin/sh
    - -c
    - |
      echo "🔄 Starting database migration..."
      migrate -database $DATABASE_URL up
      echo "✅ Database migration completed!"
    env:
    - name: DATABASE_URL
      value: "postgres://user:pass@db:5432/myapp"
    
  - name: seed-data              # 2단계: 기초 데이터 입력  
    image: seeder:latest
    command:
    - /bin/sh
    - -c  
    - |
      echo "🌱 Seeding initial data..."
      ./seed-script.sh
      echo "✅ Data seeding completed!"
      
  # 🚀 Main Application
  containers:
  - name: web-app               # 3단계: 웹앱 실행
    image: my-web-app:latest
    ports:
    - containerPort: 8080

🔗 시나리오 2: 설정 파일 다운로드

spec:
  # 📁 공유 볼륨 정의
  volumes:
  - name: config-volume
    emptyDir: {}
    
  initContainers:
  - name: config-downloader
    image: alpine/curl:latest
    command:
    - /bin/sh
    - -c
    - |
      echo "📥 Downloading configuration..."
      curl -o /config/app-config.json \
        https://config-server.company.com/prod/app-config.json
      
      curl -o /config/database.yaml \
        https://config-server.company.com/prod/database.yaml
        
      echo "✅ Configuration files downloaded!"
      ls -la /config/
    volumeMounts:
    - name: config-volume
      mountPath: /config
      
  containers:
  - name: main-app
    image: my-app:latest
    volumeMounts:
    - name: config-volume          # 다운로드된 설정 사용
      mountPath: /app/config
      readOnly: true

🔐 시나리오 3: 보안 설정 및 권한 체크

spec:
  initContainers:
  - name: security-setup
    image: busybox:latest
    securityContext:
      runAsUser: 0                 # root 권한으로 실행
    command:
    - /bin/sh
    - -c
    - |
      echo "🔐 Setting up security..."
      
      # 디렉토리 권한 설정
      mkdir -p /app/data /app/logs
      chmod 755 /app/data
      chmod 777 /app/logs
      
      # 사용자 계정 생성
      adduser -D -u 1001 appuser
      chown -R appuser:appuser /app
      
      echo "✅ Security setup completed!"
    volumeMounts:
    - name: app-volume
      mountPath: /app
      
  containers:
  - name: main-app
    image: my-secure-app:latest
    securityContext:
      runAsUser: 1001              # 일반 사용자로 실행

📈 Init Container 모니터링

디버깅 팁

# Init Container 상태 확인
kubectl describe pod <pod-name>
 
# Init Container 로그 확인  
kubectl logs <pod-name> -c <init-container-name>
 
# 실행 중인 Init Container 접속
kubectl exec -it <pod-name> -c <init-container-name> -- /bin/sh

3. 멀티 컨테이너 패턴: 팀워크의 예술

핵심 개념

하나의 파드 안에 여러 컨테이너를 함께 배치하여 기능을 확장하거나 문제를 해결하는 디자인 패턴들입니다.

🏍️ Sidecar Pattern (사이드카 패턴)

패턴 개요

“메인 오토바이 옆에 붙은 사이드카” 처럼, 메인 컨테이너의 핵심 기능에 영향을 주지 않으면서 부가적인 기능을 추가하는 패턴입니다.

🎯 사용 목적

graph TD
    A[메인 앱] --> B[핵심 비즈니스 로직]
    C[사이드카] --> D[부가 기능]
    
    D --> E[로그 수집]
    D --> F[메트릭 수집] 
    D --> G[설정 동기화]
    D --> H[프록시 역할]

💻 실제 구현: 웹서버 + 로그 수집기

apiVersion: v1
kind: Pod
metadata:
  name: webapp-with-logging
  labels:
    app: webapp
spec:
  # 📁 공유 볼륨
  volumes:
  - name: shared-logs
    emptyDir: {}
  - name: log-config
    configMap:
      name: fluentd-config
      
  containers:
  # 🌐 메인 애플리케이션
  - name: nginx-server
    image: nginx:latest
    ports:
    - containerPort: 80
    volumeMounts:
    - name: shared-logs
      mountPath: /var/log/nginx    # 로그 파일 저장 위치
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi" 
        cpu: "500m"
        
  # 📊 사이드카: 로그 수집기
  - name: log-shipper
    image: fluent/fluentd:latest
    volumeMounts:
    - name: shared-logs
      mountPath: /var/log/nginx    # 같은 로그 디렉토리 접근
      readOnly: true
    - name: log-config
      mountPath: /fluentd/etc
    env:
    - name: FLUENTD_CONF
      value: "fluentd.conf"
    - name: LOG_DESTINATION
      value: "elasticsearch.logging.svc.cluster.local"

📊 사이드카 설정 파일 (ConfigMap)

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluentd.conf: |
    # 🔍 Nginx 액세스 로그 파싱
    <source>
      @type tail
      path /var/log/nginx/access.log
      pos_file /var/log/fluentd-nginx-access.log.pos
      tag nginx.access
      format nginx
    </source>
    
    # 🚨 에러 로그 파싱  
    <source>
      @type tail
      path /var/log/nginx/error.log
      pos_file /var/log/fluentd-nginx-error.log.pos
      tag nginx.error
      format /^(?<time>[^ ]* [^ ]*) \[(?<level>[^\]]*)?\] (?<message>.*)$/
    </source>
    
    # 📤 Elasticsearch로 전송
    <match nginx.**>
      @type elasticsearch
      host "#{ENV['LOG_DESTINATION']}"
      port 9200
      index_name nginx
      type_name _doc
    </match>

✅ 사이드카 패턴의 장점

장점설명예시
분리된 관심사각 컨테이너가 단일 책임웹서버 vs 로그 수집
재사용성다른 앱에도 같은 사이드카 사용모든 웹앱에 같은 로그 수집기
독립적 업데이트메인 앱 수정 없이 사이드카 업그레이드로그 형식 변경 시
기술 다양성다른 언어/기술 스택 사용 가능Java 앱 + Go 모니터링

🏢 Ambassador Pattern (대사 패턴)

패턴 개요

“외교관이 복잡한 외교를 대신 처리” 하듯이, 메인 애플리케이션이 외부 서비스와 통신하는 방식을 단순화시켜주는 프록시 역할을 합니다.

🌐 네트워크 복잡성 해결

graph TD
    A[메인 앱] --> B[localhost:3000]
    B --> C[Ambassador 컨테이너]
    C --> D[서비스 디스커버리]
    C --> E[로드 밸런싱]  
    C --> F[인증/인가]
    C --> G[재시도 로직]
    C --> H[외부 서비스들]

💻 실제 구현: 데이터베이스 연결 프록시

apiVersion: v1
kind: Pod
metadata:
  name: app-with-db-ambassador
spec:
  containers:
  # 💼 메인 애플리케이션
  - name: main-app
    image: my-simple-app:latest
    env:
    - name: DB_HOST
      value: "localhost"          # 단순하게 localhost 사용
    - name: DB_PORT  
      value: "5432"
    - name: DB_NAME
      value: "myapp"
    ports:
    - containerPort: 8080
    
  # 🌐 Ambassador: 데이터베이스 프록시
  - name: db-ambassador
    image: envoyproxy/envoy:latest
    ports:
    - containerPort: 5432         # 로컬 DB 포트로 위장
    volumeMounts:
    - name: envoy-config
      mountPath: /etc/envoy
    command: 
    - /usr/local/bin/envoy
    - -c
    - /etc/envoy/envoy.yaml
    
  volumes:
  - name: envoy-config
    configMap:
      name: envoy-db-config

🔌 Adapter Pattern (어댑터 패턴)

패턴 개요

“220V를 110V로 변환하는 어댑터” 처럼, 메인 컨테이너의 출력을 외부 시스템이 요구하는 표준화된 형식으로 변환합니다.

📊 데이터 형식 변환

graph LR
    A[레거시 앱] --> B[비표준 로그]
    B --> C[Adapter 컨테이너]
    C --> D[표준 JSON 형식]
    D --> E[모니터링 시스템]

4. Pod Affinity: 파드들의 인간관계

핵심 개념

파드를 다른 파드와의 관계에 따라 특정 노드에 함께 배치하거나, 혹은 떨어뜨려 배치하는 스케줄링 규칙입니다.

👫 Pod Affinity (친구 관계)

"친한 친구와 같은 동네에 살고 싶어!"

특정 라벨을 가진 파드가 실행 중인 노드에 새로운 파드를 함께 배치합니다.

🏙️ 실생활 비유

graph TD
    A[카페 + 베이커리] --> B[같은 건물에 있으면 시너지]
    C[웹서버 + 캐시서버] --> D[같은 노드에 있으면 지연 최소화]
    E[앱 + 데이터베이스] --> F[네트워크 성능 최적화]

📊 성능 최적화 시나리오

Redis 캐시 서버와의 Affinity

  • 문제: 웹서버와 Redis가 다른 노드에 있어서 네트워크 지연 발생
  • 해결: Pod Affinity로 같은 노드에 배치
  • 결과: 응답시간 50ms → 5ms로 단축

💻 실제 구현: 웹서버 + Redis 배치

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app-with-redis-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
        tier: frontend
    spec:
      # 🧲 Pod Affinity 설정
      affinity:
        podAffinity:
          # 🔥 필수 조건: 반드시 함께 배치
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - redis            # Redis 파드와 함께
              - key: role
                operator: In  
                values:
                - cache
            topologyKey: kubernetes.io/hostname  # 같은 호스트에
            
          # 🎯 선호 조건: 가능하면 함께 배치  
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100            # 우선순위 (1-100)
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: monitoring  # 모니터링 파드와도 가능하면 함께
              topologyKey: kubernetes.io/hostname

📍 토폴로지 키 옵션

토폴로지 키의미사용 시나리오
kubernetes.io/hostname같은 노드네트워크 지연 최소화
topology.kubernetes.io/zone같은 가용영역중간 수준 분산
topology.kubernetes.io/region같은 리전지역별 서비스 분리

👎 Pod Anti-Affinity (라이벌 관계)

"경쟁사와는 다른 동네에 살고 싶어!"

특정 라벨을 가진 파드가 실행 중인 노드를 피해 다른 노드에 배치합니다.

🏦 실생활 비유

graph TD
    A[은행 지점들] --> B[같은 지역에 너무 몰리면 안 됨]
    C[웹서버 복제본들] --> D[같은 노드에 다 있으면 노드 장애 시 전체 다운]
    E[마이크로서비스] --> F[의존성 최소화를 위해 분산 배치]

🚨 고가용성 확보 시나리오

웹서버 고가용성 구성

  • 목표: 3개 복제본을 서로 다른 노드에 배치
  • 효과: 1개 노드 장애 시에도 서비스 지속
  • 결과: 99.9% → 99.99% 가용성 향상

5. Topology Spread Constraints: 완벽한 균형

핵심 개념

파드들을 여러 지역/노드에 최대한 고르게 분배하는 가장 정교하고 유연한 방법입니다.

🎯 토폴로지 분배의 필요성

🏢 실생활 비유

graph TD
    A[프랜차이즈 매장] --> B[서울 3개, 부산 2개, 대구 2개]
    C[파드 배치] --> D[Zone A 3개, Zone B 2개, Zone C 2개]
    E[고른 분배] --> F[장애 시 영향 최소화]

🎚️ 핵심 개념 상세 분석

📊 maxSkew (최대 불균형도)

"각 지역 간 파드 개수 차이가 이 값 이하로 유지되어야 함"

graph TD
    A["maxSkew: 1"] --> B[Zone A: 3개 파드]
    A --> C[Zone B: 2개 파드 ✅]  
    A --> D[Zone C: 4개 파드 ❌]
    
    E["차이 계산"] --> F[3-2=1 ✅]
    E --> G[4-2=2 ❌]
maxSkew 값의미예시 배치
1최대 1개 차이3-2-2 ✅, 3-1-2 ❌
2최대 2개 차이4-2-2 ✅, 4-1-2 ❌
0완전 동일3-3-3 ✅, 3-2-3 ❌

🗺️ topologyKey (분배 기준)

토폴로지 키범위사용 목적
kubernetes.io/hostname노드별CPU/메모리 부하 분산
topology.kubernetes.io/zone가용영역별네트워크/전력 장애 대비
topology.kubernetes.io/region리전별자연재해/대규모 장애 대비
node.kubernetes.io/instance-type인스턴스 타입별성능 특성별 분배

🚦 whenUnsatisfiable (제약 위반 시 동작)

graph TD
    A[새 파드 스케줄링 요청] --> B{제약 조건 만족 가능?}
    B -->|Yes| C[정상 배치]
    B -->|No| D{whenUnsatisfiable 설정}
    D -->|DoNotSchedule| E[배치 거부]
    D -->|ScheduleAnyway| F[강제 배치]

💻 실제 구현: 완전 분산 시스템

apiVersion: apps/v1
kind: Deployment
metadata:
  name: distributed-microservice
  labels:
    app: user-service
spec:
  replicas: 9
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
        component: backend
        tier: microservice
    spec:
      # ⚖️ 토폴로지 분배 제약 조건
      topologySpreadConstraints:
      
      # 🏢 1단계: 존(Zone) 레벨 분배
      - maxSkew: 1                    # 존 간 최대 1개 차이
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: user-service
        minDomains: 3                 # 최소 3개 존 사용
        
      # 🖥️ 2단계: 노드 레벨 분배  
      - maxSkew: 2                    # 노드 간 최대 2개 차이
        topologyKey: kubernetes.io/hostname  
        whenUnsatisfiable: ScheduleAnyway  # 노드 부족 시 강제 배치
        labelSelector:
          matchLabels:
            app: user-service
            
      containers:
      - name: user-service
        image: user-service:v2.1
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi

🎯 실전 조합 예시: 엔터프라이즈급 고가용성 웹서비스

종합 시나리오

모든 고급 파드 관리 기법을 조합하여 완벽한 고가용성 웹서비스를 구축해보겠습니다.

🏗️ 아키텍처 개요

graph TD
    subgraph "🌍 Internet"
        USER[사용자]
    end
    
    subgraph "⚖️ Load Balancer"
        LB[Nginx Ingress]
    end
    
    subgraph "🖥️ Kubernetes Cluster"
        subgraph "Zone A"
            WEB1[Web Pod 1]
            CACHE1[Redis 1]
        end
        
        subgraph "Zone B" 
            WEB2[Web Pod 2]
            CACHE2[Redis 2]
        end
        
        subgraph "Zone C"
            WEB3[Web Pod 3] 
            CACHE3[Redis 3]
        end
    end
    
    subgraph "💾 Database"
        DB[(PostgreSQL Cluster)]
    end
    
    USER --> LB
    LB --> WEB1
    LB --> WEB2  
    LB --> WEB3
    WEB1 --> CACHE1
    WEB2 --> CACHE2
    WEB3 --> CACHE3
    WEB1 --> DB
    WEB2 --> DB
    WEB3 --> DB

🎯 최종 결과

달성된 목표들

  • 🏥 고가용성: 99.99% 업타임 (3존 분산, 노드 장애 대응)
  • 📊 모니터링: 실시간 메트릭 및 로그 수집
  • 🚀 성능: Redis 사이드카로 응답시간 최적화
  • 🔒 보안: 최소 권한 원칙 적용
  • ⚡ 확장성: HPA 연동 가능한 구조
  • 🔄 무중단 배포: Rolling Update 전략

📚 참고 자료

추가 학습 자료