[쿠버네티스 패턴] 27장 Controller

bocopile·2026년 1월 31일

쿠버네티스 패턴

목록 보기
25/28

1. Problem: 왜 Controller가 필요한가?

1.1 선언적 vs 명령적 접근

방식설명예시
명령적"Pod를 생성해라"kubectl run nginx
선언적"3개의 Pod가 있어야 한다"replicas: 3

비유: 에어컨 온도 조절(Thermostat)

명령적: "에어컨 켜! 5분 뒤 꺼!" (직접 제어)
선언적: "온도 24도 유지해줘" (목표만 설정, 에어컨이 알아서 조절)

Kubernetes Controller는 에어컨의 온도 센서처럼 현재 상태를 감지하고, 목표와 다르면 자동으로 조정합니다.

1.2 상태 조정(State Reconciliation)

2. Solution: Controller 동작 원리

2.1 Observe-Analyze-Act 사이클

단계설명
Observe리소스 변경 이벤트 감시
Analyze목표 상태와 차이점 분석
Act실제 상태를 목표 상태로 조정

2.2 Controller vs Operator

구분ControllerOperator
대상표준 Kubernetes 리소스CRDs (Custom Resources)
복잡도단순복잡
목적플랫폼 기능 향상애플리케이션 라이프사이클 관리

2.3 동시성 제어: Optimistic Locking

여러 Controller가 동일 리소스를 수정할 때 충돌을 방지하기 위해 resourceVersion을 사용합니다.

metadata:
  name: my-config
  resourceVersion: "12345"  # 수정 시 이 값이 일치해야 업데이트 성공
  • 업데이트 요청 시 resourceVersion이 다르면 409 Conflict 발생
  • Controller는 최신 상태를 다시 읽고 재시도해야 함

3. 데이터 저장 위치 선택

저장 위치인덱싱용도
LabelsO셀렉터 기반 매칭
AnnotationsX비식별 메타데이터 (권장)
ConfigMaps-복잡한 설정 데이터
metadata:
  labels:
    app: webapp           # 셀렉터용
  annotations:
    k8spatterns.io/podDeleteSelector: "app=webapp"  # Controller 설정

4. 실습: ConfigMap 변경 감지 Controller

4.1 시나리오와 목표

문제 상황:
ConfigMap을 환경변수로 Pod에 주입하면, ConfigMap이 변경되어도 Pod가 자동으로 재시작되지 않습니다.
(볼륨 마운트 방식은 자동 반영되지만, 환경변수 방식은 Pod 재시작 필요)

해결책
ConfigMap 변경을 감지하여 관련 Pod를 자동으로 삭제 → Deployment가 새 Pod를 생성하면서 변경된 설정 반영

구성 요소

구성 요소역할
webapp-config (ConfigMap)웹앱 설정, 어노테이션으로 관리 대상 Pod 지정
webapp (Deployment)Controller가 관리할 대상 애플리케이션
config-watcher-controllerConfigMap 변경 감지 및 Pod 삭제 수행

4.2 관리 대상: 웹 애플리케이션 정의

먼저 Controller가 관리할 대상 애플리케이션을 정의합니다.

webapp-config ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
  annotations:
    k8spatterns.io/podDeleteSelector: "app=webapp"  # Controller가 이 어노테이션을 감시
data:
  message: "Welcome to Kubernetes Patterns !"

webapp Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp  # 이 라벨이 podDeleteSelector와 매칭됨
    spec:
      containers:
      - name: app
        image: k8spatterns/mini-http-server
        ports:
        - containerPort: 8080
        env:
        - name: MESSAGE            # ConfigMap을 환경변수로 주입
          valueFrom:
            configMapKeyRef:
              name: webapp-config
              key: message

포인트: app=webapp 라벨을 가진 Pod가 ConfigMap의 podDeleteSelector 어노테이션과 매칭되어 재시작 대상이 됩니다.

4.3 Controller 구현: Shell Script

#!/bin/bash
# config-watcher-controller.sh

namespace=${WATCH_NAMESPACE:-default}
base=http://localhost:8001  # Ambassador 컨테이너가 프록시
ns=namespaces/$namespace

# watch=true: Hanging GET으로 이벤트 스트림 수신
# 운영 Tip: ?labelSelector=app=myapp&watch=true로 대상 ConfigMap 범위 제한 가능
curl -N -s $base/api/v1/${ns}/configmaps?watch=true | \\
while read -r event; do
    type=$(echo "$event" | jq -r '.type')

    # null 방지: // empty로 null을 빈 문자열로 변환
    selector=$(echo "$event" | jq -r \\
        '.object.metadata.annotations["k8spatterns.io/podDeleteSelector"] // empty')

    if [ "$type" = "MODIFIED" ] && [ -n "$selector" ]; then
        # URI 인코딩: app=webapp → app%3Dwebapp
        selector_enc=$(echo "$selector" | jq -Rr @uri)

        echo "[$(date)] ConfigMap 변경 감지, selector: $selector"

        # 매칭되는 Pod 조회 후 삭제
        pods=$(curl -s "$base/api/v1/${ns}/pods?labelSelector=$selector_enc" | \\
            jq -r '.items[].metadata.name')

        for pod in $pods; do
            echo "Deleting pod: $pod"
            curl -s -X DELETE "$base/api/v1/${ns}/pods/$pod"
        done
    fi
done

핵심 포인트:

  • // empty: jq에서 null을 빈 문자열로 변환 (null 방지)
  • @uri: labelSelector를 URL 인코딩 (=%3D)
  • Ambassador 컨테이너가 인증/TLS 처리를 대신함

4.4 RBAC 설정

apiVersion: v1
kind: ServiceAccount
metadata:
  name: config-watcher-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: config-watcher-controller
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: config-watcher-controller
subjects:
- kind: ServiceAccount
  name: config-watcher-controller
roleRef:
  kind: Role
  name: config-watcher-controller
  apiGroup: rbac.authorization.k8s.io

4.5 Controller Deployment (완성본)

apiVersion: v1
kind: ConfigMap
metadata:
  name: config-watcher-controller
data:
  config-watcher-controller.sh: |
    #!/bin/bash
    # (위 스크립트 내용)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: config-watcher-controller
spec:
  replicas: 1  # Singleton 패턴
  selector:
    matchLabels:
      app: config-watcher-controller
  template:
    metadata:
      labels:
        app: config-watcher-controller
    spec:
      serviceAccountName: config-watcher-controller
      containers:
      # Ambassador: API 프록시 (인증 처리 위임)
      - name: kubeapi-proxy
        image: k8spatterns/kubeapi-proxy
      # Main: Controller 스크립트
      - name: config-watcher
        image: k8spatterns/curl-jq
        env:
        - name: WATCH_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace  # Downward API
        command: ["sh", "/watcher/config-watcher-controller.sh"]
        volumeMounts:
        - name: config-watcher-controller
          mountPath: /watcher
      volumes:
      - name: config-watcher-controller
        configMap:
          name: config-watcher-controller

4.6 테스트 및 결과 확인

Step 1: 리소스 배포 (순서 중요)

# 1. RBAC 먼저 (Controller가 권한을 가져야 함)
kubectl apply -f rbac.yaml

# 2. Controller 배포
kubectl apply -f controller-deployment.yaml

# 3. 관리 대상 웹앱 배포
kubectl apply -f webapp.yaml

Step 2: 배포 상태 확인

kubectl get pods
NAME                                         READY   STATUS    AGE
config-watcher-controller-5d4b8c9f5-xyz12    2/2     Running   30s  # 2개 컨테이너 (main + ambassador)
webapp-7d4b8c9f5-abc12                       1/1     Running   15s

Step 3: ConfigMap 변경으로 자동 재시작 테스트

# 터미널 1: Pod 상태 실시간 모니터링
kubectl get pods -l app=webapp -w

# 터미널 2: ConfigMap 변경
kubectl patch configmap webapp-config \
  --patch '{"data":{"message":"Hello, Updated World!"}}'

예상 결과 (터미널 1):

NAME                      READY   STATUS              AGE
webapp-7d4b8c9f5-abc12    1/1     Running             1m
webapp-7d4b8c9f5-abc12    1/1     Terminating         1m    # Controller가 삭제
webapp-7d4b8c9f5-def34    0/1     Pending             0s    # Deployment가 새 Pod 생성
webapp-7d4b8c9f5-def34    0/1     ContainerCreating   1s
webapp-7d4b8c9f5-def34    1/1     Running             3s    # 새 설정 반영 완료

Step 4: 변경된 설정 확인

# 새 Pod의 환경변수 확인
kubectl exec deploy/webapp -- printenv MESSAGE
# 출력: Hello, Updated World!

5. 실무 Controller 예제

Controller기능
stakater/ReloaderConfigMap/Secret 변경 시 워크로드 롤링 업데이트
exposecontrollerService에 expose 어노테이션 시 Ingress 자동 생성
Flatcar Update OperatorNode 어노테이션 감지하여 자동 재부팅

6. 운영 관점 추가 내용

6.1 Watch의 한계와 Informer

단순 Watch의 문제점

  • 연결 끊김 시 이벤트 유실 가능
  • 재연결 시 중간 상태 놓칠 수 있음
  • API Server에 부하 발생

Informer 패턴이 해결

  • 로컬 캐시로 API Server 부하 감소
  • 연결 끊김 시 자동 재동기화 (List + Watch)
  • 여러 Controller가 동일 Informer 공유 가능

프로덕션에서는 Shell Script가 아닌 Go + controller-runtime(Kubebuilder) 사용을 권장합니다.

6.2 Leader Election

고가용성을 위해 여러 인스턴스 실행 시 단일 활성 인스턴스를 보장합니다.

spec:
  replicas: 2
  containers:
  - args: ["--leader-elect=true"]

참고: 이는 controller-runtime 프레임워크의 옵션입니다.
Control Plane의 Coordinated Leader Election(kube-controller-manager 등)과는 다른 개념입니다.

6.3 프로덕션 배포 체크리스트

containers:
  - name: config-watcher
    resources:
      requests:
        memory: "64Mi"    # Watch 연결은 장시간 유지되므로 최소값 설정
        cpu: "50m"
      limits:
        memory: "128Mi"
    securityContext:
      runAsNonRoot: true
      readOnlyRootFilesystem: true
    livenessProbe:        # 스크립트/프로세스 정상 동작 확인
      exec:
        command: ["pgrep", "-f", "config-watcher"]
      periodSeconds: 30

7. Discussion

장점

  • 선언적 관리: 목표 상태만 정의
  • 자동 복구(Self-healing): 장애 시 자동 조정
  • 확장 가능: 커스텀 Controller로 기능 확장
  • 느슨한 결합: 이벤트 기반 독립적 동작

주의사항

  • 디버깅 어려움: 비동기 이벤트 흐름 추적 복잡
  • 이벤트 유실: Watch 연결 끊김 시 발생 가능 → Informer로 해결

출처

도서

  • Bilgin Ibryam, Roland Huß, "Kubernetes Patterns, 2nd Edition", O'Reilly Media, 2023

공식 문서

참고 자료

profile
DevOps Engineer

0개의 댓글