k8s의 선언형 모델은 강력하다. Deployment에 replicas: 5를 선언하면 컨트롤러가 알아서 파드를 유지한다. 이 모델을 기본 리소스(Deployment, Service 등)만이 아니라 도메인 특화 리소스에도 적용할 수 있다면 어떨까.
CRD(Custom Resource Definition)는 k8s에 새 리소스 타입을 추가하는 메커니즘이다. PostgresCluster나 Certificate 같은 리소스를 정의하면 kubectl get postgresclusters 처럼 기본 리소스처럼 다룰 수 있다. Operator 패턴은 이 CRD와 컨트롤러를 결합해, 복잡한 운영 작업을 자동화하는 방법이다.
CRD — 새 리소스 타입 정의
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: widgets.example.com # <복수명>.<그룹>
spec:
group: example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
color:
type: string
size:
type: integer
minimum: 1
maximum: 100
status:
type: object
properties:
ready:
type: boolean
message:
type: string
scope: Namespaced # 또는 Cluster
names:
plural: widgets
singular: widget
kind: Widget
shortNames:
- wg
CRD를 클러스터에 적용하면 이제 Widget 오브젝트를 만들 수 있다.
apiVersion: example.com/v1
kind: Widget
metadata:
name: my-widget
spec:
color: blue
size: 42
kubectl get widgets
kubectl describe widget my-widget
CRD만 있으면 리소스를 저장하고 읽을 수 있다. 하지만 이 리소스가 실제로 무언가를 하게 만들려면 컨트롤러가 필요하다.
Operator — 컨트롤러와 CRD의 결합
Operator 패턴은 CoreOS(현 Red Hat)가 제안한 개념이다. 운영자(Operator)가 수동으로 하던 작업 — DB 클러스터 확장, 백업, 장애 복구, 버전 업그레이드 — 을 소프트웨어로 자동화한다.
구조는 단순하다.
- CRD로 원하는 상태를 선언하는 리소스를 정의한다.
- 컨트롤러가 그 리소스를 감시하고, desired state와 actual state의 차이를 없애는 Reconcile 루프를 실행한다.
사용자: Widget CR 생성/수정/삭제
↓
API Server에 이벤트 발생
↓
Controller: Watch → Reconcile 함수 호출
↓
현재 상태 확인 (k8s 리소스, 외부 시스템)
↓
desired state와 diff 계산
↓
필요한 작업 수행 (파드 생성, DB 설정 변경 등)
↓
status 업데이트
Reconcile 함수는 멱등성을 가져야 한다. 같은 상태를 여러 번 적용해도 결과가 같아야 한다. 컨트롤러는 오류가 생기면 재시도하고, 재시작 후에도 상태를 보고 올바른 방향으로 수렴한다.
실제 사례: cert-manager
cert-manager는 TLS 인증서 발급과 갱신을 자동화하는 Operator다.
# cert-manager가 제공하는 CRD: Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-tls
namespace: production
spec:
secretName: api-tls-secret # 인증서를 저장할 Secret 이름
duration: 2160h # 90일
renewBefore: 360h # 만료 15일 전에 갱신
dnsNames:
- api.example.com
- www.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
이 YAML을 적용하면 cert-manager 컨트롤러가:
- Let’s Encrypt에 인증서 발급 요청
- ACME 챌린지 수행 (HTTP-01 또는 DNS-01)
- 발급된 인증서를
api-tls-secretSecret에 저장 - 만료 15일 전에 자동으로 갱신
기존에는 Certbot을 cron으로 돌리고 nginx를 재시작하는 등의 수동 작업이었다. cert-manager는 이 모든 것을 Certificate 리소스 하나로 선언적으로 관리한다.
실제 사례: Postgres Operator (CrunchyData PGO)
apiVersion: postgres-operator.crunchydata.com/v1beta1
kind: PostgresCluster
metadata:
name: my-postgres
namespace: production
spec:
postgresVersion: 15
instances:
- name: instance1
replicas: 3 # Primary 1 + Standby 2
dataVolumeClaimSpec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
backups:
pgbackrest:
repos:
- name: repo1
s3:
bucket: my-postgres-backups
endpoint: s3.amazonaws.com
region: ap-northeast-2
schedules:
full: "0 2 * * 0" # 매주 일요일 새벽 2시 풀 백업
incremental: "0 2 * * 1-6" # 평일 새벽 2시 증분 백업
이 선언 하나로 Operator가:
- Primary + Standby PostgreSQL 클러스터 구성
- Replication 설정
- S3 자동 백업 스케줄
- Standby 장애 시 자동 Failover
- 버전 업그레이드 시 Rolling 방식으로 처리
수십 줄의 쉘 스크립트와 cron 작업이 YAML 선언 하나로 대체된다.
Kubebuilder — Operator 개발 도구
직접 Operator를 만들 때는 Kubebuilder 또는 Operator SDK를 쓴다. Kubebuilder는 Go 기반 스캐폴딩을 제공한다.
# 프로젝트 초기화
kubebuilder init --domain example.com --repo github.com/my-org/my-operator
# API (CRD + Controller) 생성
kubebuilder create api --group apps --version v1 --kind Widget
이 명령이 CRD 스캐폴드, 컨트롤러 파일, 테스트 파일을 자동 생성한다. 개발자는 Reconcile 함수만 채우면 된다.
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
widget := &appsv1.Widget{}
if err := r.Get(ctx, req.NamespacedName, widget); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// desired state 구현: 필요한 Deployment, Service 생성/수정
// ...
// status 업데이트
widget.Status.Ready = true
r.Status().Update(ctx, widget)
return ctrl.Result{}, nil
}
트레이드오프
Operator는 강력하지만 복잡도 비용이 있다. 컨트롤러 코드가 버그를 가지면 리소스가 잘못된 상태로 수렴할 수 있다. Reconcile이 외부 시스템(DB, 클라우드 API)을 변경하는 경우 롤백이 어렵다.
오픈소스 Operator를 쓸 때는 버전과 CRD 스키마 변경을 주의해야 한다. CRD의 spec 구조가 버전마다 달라지는 경우가 있고, 업그레이드 시 기존 CR이 새 스키마와 호환되지 않으면 문제가 된다. 업그레이드 전 마이그레이션 가이드를 반드시 확인한다.
단순한 배포 자동화는 Helm이나 Kustomize로 충분하다. Operator는 운영 지식(장애 복구, 백업, 버전 업그레이드 절차) 을 자동화해야 할 때 가치가 있다. 상태가 없는 단순 서비스에 Operator를 만드는 것은 과한 복잡도다.