| 방식 | 설명 | 예시 |
|---|---|---|
| 명령적 | "Pod를 생성해라" | kubectl run nginx |
| 선언적 | "3개의 Pod가 있어야 한다" | replicas: 3 |
비유: 에어컨 온도 조절(Thermostat)
명령적: "에어컨 켜! 5분 뒤 꺼!" (직접 제어)
선언적: "온도 24도 유지해줘" (목표만 설정, 에어컨이 알아서 조절)
Kubernetes Controller는 에어컨의 온도 센서처럼 현재 상태를 감지하고, 목표와 다르면 자동으로 조정합니다.


| 단계 | 설명 |
|---|---|
| Observe | 리소스 변경 이벤트 감시 |
| Analyze | 목표 상태와 차이점 분석 |
| Act | 실제 상태를 목표 상태로 조정 |
| 구분 | Controller | Operator |
|---|---|---|
| 대상 | 표준 Kubernetes 리소스 | CRDs (Custom Resources) |
| 복잡도 | 단순 | 복잡 |
| 목적 | 플랫폼 기능 향상 | 애플리케이션 라이프사이클 관리 |
여러 Controller가 동일 리소스를 수정할 때 충돌을 방지하기 위해 resourceVersion을 사용합니다.
metadata:
name: my-config
resourceVersion: "12345" # 수정 시 이 값이 일치해야 업데이트 성공
| 저장 위치 | 인덱싱 | 용도 |
|---|---|---|
| Labels | O | 셀렉터 기반 매칭 |
| Annotations | X | 비식별 메타데이터 (권장) |
| ConfigMaps | - | 복잡한 설정 데이터 |
metadata:
labels:
app: webapp # 셀렉터용
annotations:
k8spatterns.io/podDeleteSelector: "app=webapp" # Controller 설정
문제 상황:
ConfigMap을 환경변수로 Pod에 주입하면, ConfigMap이 변경되어도 Pod가 자동으로 재시작되지 않습니다.
(볼륨 마운트 방식은 자동 반영되지만, 환경변수 방식은 Pod 재시작 필요)
해결책
ConfigMap 변경을 감지하여 관련 Pod를 자동으로 삭제 → Deployment가 새 Pod를 생성하면서 변경된 설정 반영

구성 요소
| 구성 요소 | 역할 |
|---|---|
| webapp-config (ConfigMap) | 웹앱 설정, 어노테이션으로 관리 대상 Pod 지정 |
| webapp (Deployment) | Controller가 관리할 대상 애플리케이션 |
| config-watcher-controller | ConfigMap 변경 감지 및 Pod 삭제 수행 |
먼저 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어노테이션과 매칭되어 재시작 대상이 됩니다.
#!/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)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
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
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!
| Controller | 기능 |
|---|---|
| stakater/Reloader | ConfigMap/Secret 변경 시 워크로드 롤링 업데이트 |
| exposecontroller | Service에 expose 어노테이션 시 Ingress 자동 생성 |
| Flatcar Update Operator | Node 어노테이션 감지하여 자동 재부팅 |
단순 Watch의 문제점
Informer 패턴이 해결

프로덕션에서는 Shell Script가 아닌 Go + controller-runtime(Kubebuilder) 사용을 권장합니다.
고가용성을 위해 여러 인스턴스 실행 시 단일 활성 인스턴스를 보장합니다.
spec:
replicas: 2
containers:
- args: ["--leader-elect=true"]
참고: 이는 controller-runtime 프레임워크의 옵션입니다.
Control Plane의 Coordinated Leader Election(kube-controller-manager 등)과는 다른 개념입니다.
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