근묵자흑
Kubernetes Pattern: Controller 본문
Kubernetes는 선언적(Declarative) 리소스 관리를 기반으로 동작합니다. 사용자가 원하는 상태를 정의하면 시스템이 해당 상태를 유지합니다. 이 동작을 담당하는 것이 바로 Controller입니다.
이 글에서는 Controller 패턴의 개념과 구현 방법을 실제 테스트 결과와 함께 정리하고, 최신 동향도 확인해 보겠습니다.
Controller 개요
문제
Kubernetes는 범용 오케스트레이션 플랫폼이므로 모든 애플리케이션 요구사항을 기본 기능으로 충족하지 못합니다. 플랫폼을 수정하지 않고 기능을 확장할 방법이 필요합니다.
해결책
Kubernetes의 이벤트 기반 아키텍처를 활용하여 커스텀 Controller를 작성합니다. Controller는 리소스 상태 변경을 감시하고, 현재 상태를 원하는 상태에 맞추는 조정(Reconciliation) 작업을 수행합니다.
Controller 동작 원리
Controller는 Observe-Analyze-Act 사이클을 반복 실행합니다.
flowchart LR
A[Observe] --> B[Analyze]
B --> C[Act]
C --> A
A -.- A1[리소스 상태 감시]
B -.- B1[현재 상태와 목표 상태 비교]
C -.- C1[조정 작업 수행]
상태 조정 흐름
sequenceDiagram
participant User
participant API as API Server
participant Ctrl as Controller
participant Pod
User->>API: ConfigMap 수정
API->>Ctrl: MODIFIED 이벤트 전송
Ctrl->>Ctrl: Annotation 확인
Ctrl->>API: Pod 삭제 요청
API->>Pod: Pod 종료
Note over API,Pod: ReplicaSet이 새 Pod 생성
API-->>Ctrl: 삭제 완료 응답
내장 Controller와 커스텀 Controller
Kubernetes에는 ReplicaSet, DaemonSet, StatefulSet, Deployment, Service 등 표준 리소스를 관리하는 내장 Controller 컬렉션이 있습니다. 이 Controller들은 컨트롤 플레인 노드에 배포되는 Controller Manager의 일부로 실행됩니다.
중요한 점은 이 Controller들이 서로를 인식하지 못한다는 것입니다. 각자 무한 조정 루프에서 독립적으로 실행되며, 자신이 담당하는 리소스의 실제 상태와 원하는 상태를 모니터링하고 그에 따라 행동합니다.
이러한 기본 Controller 외에도 Kubernetes의 이벤트 기반 아키텍처는 다른 커스텀 Controller를 네이티브하게 연결할 수 있게 해줍니다. 커스텀 Controller도 내부 Controller와 동일한 방식으로 상태 변경 이벤트에 반응하여 추가 기능을 제공할 수 있습니다.
Controller와 Operator의 구분
복잡성과 진화 관점에서 능동적 조정 컴포넌트를 두 그룹으로 분류할 수 있습니다.
Controller는 표준 Kubernetes 리소스를 모니터링하고 조작하는 단순한 조정 프로세스입니다. 주로 플랫폼 동작을 강화하고 새로운 플랫폼 기능을 추가하는 데 사용됩니다.
Operator는 CustomResourceDefinition(CRD)과 상호작용하는 정교한 조정 프로세스입니다. 일반적으로 복잡한 애플리케이션 도메인 로직을 캡슐화하고 전체 애플리케이션 라이프사이클을 관리합니다.
이 글에서는 단순한 Controller에 집중하고, Operator 패턴은 Chapter 28에서 다룹니다.
구현 예제: ConfigMap Watcher Controller
ConfigMap 변경 시 연관된 Pod를 재시작하는 Controller를 구현합니다. ConfigMap을 환경변수로 사용하는 Pod는 ConfigMap 변경만으로는 새 값을 반영하지 못하므로, Pod 재시작이 필요합니다.
아키텍처
flowchart TB
subgraph "Controller Pod"
proxy[kubeapi-proxy<br/>Ambassador Container]
watcher[config-watcher<br/>Controller Script]
proxy <--> watcher
end
subgraph "API Server"
api[Kubernetes API]
watch[Watch API<br/>watch=true]
end
subgraph "Application"
cm[ConfigMap<br/>webapp-config]
deploy[Deployment<br/>webapp]
pod[Pod<br/>app=webapp]
deploy --> pod
cm -.->|환경변수| pod
end
watcher -->|1. Watch ConfigMaps| watch
watch -->|2. MODIFIED 이벤트| watcher
watcher -->|3. DELETE Pod| api
api -->|4. Pod 삭제| pod
Controller Script
Controller는 Shell Script로 구현됩니다. Kubernetes API의 Watch 기능을 사용하여 ConfigMap 변경을 실시간으로 수신합니다.
#!/bin/bash
namespace=${WATCH_NAMESPACE:-default}
base=http://localhost:8001
ns=namespaces/$namespace
start_event_loop() {
echo "::: Starting to wait for events"
# watch=true: Hanging GET으로 이벤트 스트림 수신
curl -N -s $base/api/v1/${ns}/configmaps?watch=true | while read -r event
do
event=$(echo "$event" | tr '\r\n' ' ')
local type=$(echo "$event" | jq -r .type)
local config_map=$(echo "$event" | jq -r .object.metadata.name)
local annotations=$(echo "$event" | jq -r '.object.metadata.annotations')
if [ "$annotations" != "null" ]; then
local pod_selector=$(echo $annotations | \
jq -r 'to_entries | .[] | select(.key == "k8spatterns.com/podDeleteSelector") | .value | @uri')
fi
echo "::: $type -- $config_map -- $pod_selector"
if [ $type = "MODIFIED" ] && [ -n "$pod_selector" ]; then
delete_pods_with_selector "$pod_selector"
fi
done
}
delete_pods_with_selector() {
local selector=${1}
echo "::::: Deleting pods with $selector"
local pods=$(curl -s $base/api/v1/${ns}/pods?labelSelector=$selector | \
jq -r .items[].metadata.name)
for pod in $pods; do
exit_code=$(curl -s -X DELETE -o /dev/null -w "%{http_code}" $base/api/v1/${ns}/pods/$pod)
if [ $exit_code -eq 200 ]; then
echo "::::: Deleted pod $pod"
else
echo "::::: Error deleting pod $pod: $exit_code"
fi
done
}
start_event_loop
주요 구현 포인트는 다음과 같습니다.
| 항목 | 설명 |
|---|---|
watch=true |
API Server와 지속적인 연결을 유지하며 이벤트를 수신합니다 |
localhost:8001 |
Ambassador 컨테이너(kubeapi-proxy)를 통해 API에 접근합니다 |
WATCH_NAMESPACE |
Downward API로 현재 네임스페이스를 주입합니다 |
podDeleteSelector |
ConfigMap Annotation에서 Pod 선택자를 추출합니다 |
Controller Deployment
apiVersion: v1
kind: ServiceAccount
metadata:
name: config-watcher-controller
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: config-watcher-controller
subjects:
- kind: ServiceAccount
name: config-watcher-controller
roleRef:
name: edit
kind: ClusterRole
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:
name: config-watcher-controller
data:
config-watcher-controller.sh: |
# Controller Script 내용
---
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 Server 프록시
- name: kubeapi-proxy
image: k8spatterns/kubeapi-proxy
# 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:
- mountPath: "/watcher"
name: config-watcher-controller
volumes:
- name: config-watcher-controller
configMap:
name: config-watcher-controller
Web Application
Controller가 감시할 대상 애플리케이션입니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: webapp-config
annotations:
# Controller가 사용할 Pod 선택자
k8spatterns.com/podDeleteSelector: "app=webapp"
data:
message: "Welcome to Kubernetes Patterns !"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp # 위 선택자와 일치
spec:
containers:
- name: app
image: k8spatterns/mini-http-server
ports:
- containerPort: 8080
env:
- name: MESSAGE
valueFrom:
configMapKeyRef:
name: webapp-config
key: message
---
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
selector:
app: webapp
테스트 결과
minikube 환경에서 테스트를 수행했습니다.
테스트 환경
Kubernetes: minikube
Namespace: controller-demo
테스트 과정
- Controller와 Web App 배포
- 초기 Pod 확인:
webapp-f4ddf5b47-7mnl2 - ConfigMap 수정
- Controller 동작 확인
- 새 Pod 확인:
webapp-f4ddf5b47-6skwj
Controller 로그
::: Starting to wait for events
::: ADDED -- config-watcher-controller --
::: ADDED -- kube-root-ca.crt --
::: ADDED -- webapp-config -- app%3Dwebapp
::: MODIFIED -- webapp-config -- app%3Dwebapp
::::: Deleting pods with app%3Dwebapp
::::: Deleted pod webapp-f4ddf5b47-7mnl2
Controller가 시작되면 먼저 기존 ConfigMap들에 대한 ADDED 이벤트를 수신합니다. 그 후 ConfigMap이 수정되면 MODIFIED 이벤트를 받아 해당하는 Pod를 삭제합니다.
동작 확인
flowchart LR
subgraph Before
CM1[ConfigMap<br/>message: Welcome...]
Pod1[Pod<br/>webapp-...-7mnl2]
end
subgraph "ConfigMap 수정"
Patch[kubectl patch]
end
subgraph After
CM2[ConfigMap<br/>message: Hello...]
Pod2[Pod<br/>webapp-...-6skwj]
end
CM1 --> Patch
Patch --> CM2
Pod1 -.->|삭제| Patch
Patch -.->|생성| Pod2
| 단계 | 내용 | 값 |
|---|---|---|
| 수정 전 | Pod 이름 | webapp-f4ddf5b47-7mnl2 |
| 수정 전 | MESSAGE | Welcome to Kubernetes Patterns ! |
| 수정 후 | Pod 이름 | webapp-f4ddf5b47-6skwj |
| 수정 후 | MESSAGE | Hello from Controller Pattern Demo - 19:48:56 |
ConfigMap이 수정되자 Controller가 기존 Pod를 삭제했고, Deployment의 ReplicaSet이 새 Pod를 생성했습니다. 새 Pod는 수정된 ConfigMap 값을 환경변수로 가져와 정상적으로 새로운 메시지를 반환합니다.
적용된 패턴
이 Controller 구현에는 여러 Kubernetes 패턴이 적용되어 있습니다.
flowchart TB
Controller[ConfigMap Watcher Controller]
Controller --> P1[Singleton Service<br/>replicas: 1]
Controller --> P2[Ambassador<br/>kubeapi-proxy]
Controller --> P3[Self Awareness<br/>Downward API]
Controller --> P4[Configuration Resource<br/>ConfigMap + Annotation]
P1 -.- D1[동시성 문제 방지]
P2 -.- D2[API Server 접근 단순화]
P3 -.- D3[네임스페이스 자동 인식]
P4 -.- D4[Controller 설정 저장]
Singleton Service 패턴
replicas: 1로 설정하여 단일 인스턴스만 실행합니다. 여러 Controller가 동시에 같은 리소스를 조작하면 충돌이 발생할 수 있습니다.
Ambassador 패턴
kubeapi-proxy 컨테이너가 localhost:8001을 API Server로 프록시합니다. Controller 스크립트는 인증 처리 없이 localhost로 API에 접근할 수 있습니다.
Self Awareness 패턴
Downward API를 사용하여 metadata.namespace를 WATCH_NAMESPACE 환경변수로 주입합니다. Controller가 배포된 네임스페이스를 자동으로 인식합니다.
Configuration Resource 패턴
Controller 설정은 Annotation을 사용합니다. k8spatterns.com/podDeleteSelector Annotation으로 재시작할 Pod의 Label 선택자를 지정합니다.
Controller 데이터 저장 위치
Controller가 사용하는 데이터는 저장 위치에 따라 특성이 다릅니다.
| 저장 위치 | 용도 | 특징 |
|---|---|---|
| Labels | 리소스 식별 및 선택 | 인덱싱됨, 셀렉터 쿼리 가능 |
| Annotations | 비식별 메타데이터 | 인덱싱 안됨, 구문 제약 적음 |
| ConfigMap | 복잡한 설정 데이터 | CRD 대안, 클러스터 권한 불필요 |
이 예제에서는 Annotation을 사용합니다. Pod 선택자는 식별 목적이 아닌 Controller 동작을 위한 설정이므로 Annotation이 적합합니다.
실무에서 사용되는 Controller
cert-manager
TLS 인증서 관리를 자동화하는 Controller입니다. 세 개의 핵심 컴포넌트로 구성됩니다.
- Controller: Certificate → CertificateRequest → Order → Challenge 흐름 관리
- Webhook: Admission 검증 및 변환
- CA Injector: ValidatingWebhookConfiguration 등에 CA 주입
동일 클러스터에서 Let's Encrypt, Vault, HashiCorp, 자체 CA를 혼용할 수 있으며, Issuer(네임스페이스)와 ClusterIssuer(클러스터 전역)로 범위를 구분합니다.
Argo CD
GitOps CD를 위한 Controller입니다. Application Controller가 Git 저장소의 원하는 상태와 클러스터의 실제 상태를 비교하여 Sync 상태를 관리합니다. Sync Waves와 Hooks 패턴으로 PreSync → Sync → PostSync 단계의 복잡한 배포 오케스트레이션이 가능합니다.
Flux
GitOps Toolkit 아키텍처로 5개의 전문화된 Controller로 구성됩니다.
- Source Controller: Git, OCI, Helm, S3 소스 통합
- Kustomize Controller: Kustomization 적용
- Helm Controller: HelmRelease 선언적 관리
- Notification Controller: 알림 및 웹훅 수신
- Image Automation: 컨테이너 이미지 태그 자동 업데이트
dependsOn 필드로 Controller 간 의존성을 선언하여 순차적 조정을 보장합니다.
정리
Controller는 Kubernetes의 선언적 관리를 가능하게 하는 핵심 컴포넌트입니다.
- Controller는 Observe-Analyze-Act 사이클을 반복하며 상태를 조정합니다
- Watch API로 리소스 변경을 실시간으로 수신합니다
- Annotation은 Controller 설정 저장에 적합합니다
- Singleton 패턴으로 동시성 문제를 방지합니다
- 프로덕션 환경에서는 controller-runtime 등 프레임워크를 사용합니다
- 모든 Reconciliation은 멱등하게 설계해야 합니다
- GenerationChangedPredicate로 불필요한 이벤트를 필터링합니다
- Server-Side Apply를 기본 적용 방식으로 채택합니다
- ObservedGeneration과 Conditions로 상태를 명확히 표현합니다
- 외부 리소스 관리 시 Finalizer를 반드시 구현합니다
- 대규모 환경에서는 Sharding과 Watch 최적화를 고려합니다
다음 장에서는 CRD와 결합한 Operator 패턴을 다룹니다.
참고
'k8s > kubernetes-pattern' 카테고리의 다른 글
| Kubernetes Patterns : Operator (Datadog Operator) (0) | 2026.02.13 |
|---|---|
| Kubernetes Patterns: Operator (0) | 2026.02.07 |
| Kubernetes Pattern: Access Control (0) | 2026.01.24 |
| Kubernetes Pattern: Secure Configuration (0) | 2026.01.17 |
| Kubernetes Pattern: Network Segmentation (0) | 2026.01.10 |