목차
[Kubernetes] Kubernetes CRD 심층 분석: 커스텀 리소스 정의 및 활용 전략
Kubernetes CRD는 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션 플랫폼)를 단순히 “파드를 띄우는 도구”에서 끝내지 않고, 우리 조직의 운영 규칙을 클러스터 안으로 끌고 들어오게 만드는 핵심 확장 포인트입니다. 처음엔 저도 이게 그냥 YAML 하나 더 만드는 기능인가 싶었는데, 실제로 써보니까 운영 지식을 API로 승격시키는 도구에 가깝더라고요. 특히 반복되는 인프라 작업이 많거나, 특정 서비스 배포 규칙을 팀 표준으로 강제하고 싶을 때 Kubernetes CRD의 진가가 확실히 드러납니다.
제가 홈랩과 실무 환경에서 Kubernetes를 오래 만지면서 느낀 건, 사람이 문서 보고 수동으로 맞추는 방식은 결국 어딘가에서 틀어진다는 점이었습니다. “이 서비스는 TLS가 꼭 있어야 한다”, “이 애플리케이션은 백업 정책이 필요하다”, “이 데이터베이스는 복구 절차가 정해져 있다” 같은 규칙을 문서로만 남겨두면요. 언젠가는 빠집니다. 근데 Kubernetes CRD와 Operator 패턴(Operator Pattern, 운영 자동화 패턴)을 같이 쓰면, 그 규칙을 선언형(Declarative, 원하는 상태를 선언하는 방식)으로 관리할 수 있거든요.
쿠버네티스 API 서버, etcd, CustomResourceDefinition, 커스텀 컨트롤러의 관계를 한눈에 보여주는 개요 이미지 위치입니다.
Kubernetes CRD란 무엇인가요?
쉽게 말해 CRD(CustomResourceDefinition, 커스텀 리소스 정의)는 쿠버네티스에 새로운 리소스 타입을 추가하는 방법입니다. Deployment, Service, Ingress(인그레스, 외부 트래픽 진입점)처럼 원래 있던 리소스 말고, 예를 들어 Database, BackupPolicy, AppRelease 같은 우리만의 리소스를 만들 수 있는 거죠.
여기서 사람들이 많이 헷갈리는 포인트가 하나 있습니다. CRD 자체는 결국 스키마(schema, 데이터 구조 정의)를 등록하는 일이거든요. 즉, API 서버가 “아, 이제 이런 모양의 리소스도 받을 수 있구나” 하고 이해하게 만드는 단계예요. 그런데 실제로 그 리소스를 보고 뭔가 동작하게 만드는 건 보통 컨트롤러(Controller, 상태를 감시하고 맞추는 제어 루프)가 담당합니다. 저도 처음엔 CRD만 만들면 자동으로 뭔가 굴러갈 줄 알았는데, 아무 일도 안 일어나서 삽질 좀 했습니다 ㅎㅎ
기본 구성 요소
- CRD: 새로운 리소스 타입의 정의
- Custom Resource: 실제로 생성하는 인스턴스
- Controller: 원하는 상태와 현재 상태를 맞추는 로직
- Operator: 특정 운영 지식을 코드로 구현한 컨트롤러 묶음
| 항목 | 역할 | 비유 |
|---|---|---|
| CRD | 새 API 타입 등록 | 양식(template) 만들기 |
| Custom Resource | 실제 선언 데이터 | 양식에 값 채워 제출 |
| Controller | 선언을 실제 상태로 반영 | 담당자가 요청 처리 |
| Operator | 도메인 운영 자동화 | 숙련 엔지니어의 절차를 자동화 |
왜 Kubernetes 확장에 CRD를 쓰는가
Kubernetes 확장이라고 하면 예전에는 API Aggregation(집계 API)처럼 더 무거운 접근도 있었지만, 대부분의 팀에게는 CRD가 훨씬 현실적입니다. 이유는 분명합니다.
- 기존 kubectl과 선언형 워크플로우를 그대로 활용할 수 있습니다.
- RBAC(Role-Based Access Control, 역할 기반 접근 제어), admission, GitOps와 잘 맞습니다.
- 도메인 개념을 YAML로 표준화할 수 있습니다.
- Operator 패턴과 결합하면 반복 작업을 코드로 고정할 수 있습니다.
예를 들어 운영팀에서 매번 데이터베이스 인스턴스를 만들 때 스토리지 클래스, 백업 주기, 리소스 제한, 비밀 정보(secret) 연결을 공통 규칙으로 적용한다고 해보겠습니다. 이걸 문서로 적어두는 대신 Database라는 커스텀 리소스를 만들고, 컨트롤러가 이를 보고 StatefulSet, Service, Secret 참조, 백업 Job을 구성하게 만들 수 있습니다. 이게 바로 커스텀 리소스와 Operator 패턴이 실무에서 힘을 발휘하는 지점입니다.
혹시 이런 경험 있으신가요? 배포 문서는 있는데 팀원마다 해석이 조금씩 달라서 결과가 달라지는 상황이요. 저는 그걸 몇 번 겪고 나서, 사람이 읽는 문서와 시스템이 읽는 선언을 분리해야겠다고 생각했습니다. Kubernetes CRD는 바로 그 중간을 메워줍니다.
실전 구현 1: CRD 정의하기
이제 가장 단순한 예제로 가보겠습니다. AppClaim이라는 커스텀 리소스를 만들어서, 애플리케이션 배포에 필요한 최소 정보를 선언한다고 가정해보죠. 여기서는 이해를 위해 구조를 단순화했습니다.
- API 그룹(group)과 버전(version)을 정합니다.
- 리소스 이름과 복수형(plural)을 정합니다.
- OpenAPI v3 스키마로 필드를 정의합니다.
- 추가 프린터 컬럼으로 kubectl 가독성을 높입니다.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: appclaims.platform.example.com
spec:
group: platform.example.com
scope: Namespaced
names:
plural: appclaims
singular: appclaim
kind: AppClaim
shortNames:
- ac
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- image
- replicas
properties:
image:
type: string
replicas:
type: integer
minimum: 1
port:
type: integer
minimum: 1
maximum: 65535
expose:
type: boolean
status:
type: object
properties:
phase:
type: string
readyReplicas:
type: integer
subresources:
status: {}
additionalPrinterColumns:
- name: Image
type: string
jsonPath: .spec.image
- name: Replicas
type: integer
jsonPath: .spec.replicas
- name: Phase
type: string
jsonPath: .status.phase
이 YAML의 핵심은 spec과 status를 분리한 거예요. spec은 사용자가 원하는 상태이고, status는 컨트롤러가 관찰한 결과입니다. 이 구분이 흐려지면 나중에 운영이 엄청 꼬입니다. 저도 예전에 status 비슷한 값을 spec에 억지로 넣었다가 reconcile loop(리컨실 루프, 상태 동기화 루프)가 지저분해져서 다시 갈아엎은 적이 있습니다.
kubectl apply -f appclaim-crd.yaml
kubectl get crd appclaims.platform.example.com
정상 적용되면 이제 클러스터는 AppClaim이라는 새 리소스 타입을 이해합니다. 여기서 중요한 포인트! 아직은 타입만 생긴 상태입니다. 배포는 자동으로 안 됩니다.

CRD YAML을 작성하고 kubectl로 적용하는 실전 구성 이미지를 넣는 위치입니다.
실전 구현 2: 커스텀 리소스 생성과 컨트롤러 연결
다음은 실제 인스턴스를 하나 만들어보겠습니다.
apiVersion: platform.example.com/v1alpha1
kind: AppClaim
metadata:
name: demo-web
spec:
image: nginx:stable
replicas: 2
port: 80
expose: true
kubectl apply -f demo-appclaim.yaml
kubectl get appclaims
kubectl describe appclaim demo-web
이제 컨트롤러가 이 리소스를 감시하면서 Deployment와 Service를 생성하도록 만들 수 있습니다. 실제 프로덕션에서는 Go와 controller-runtime을 많이 쓰지만, 여기서는 흐름 이해가 목적이니 의사 코드 수준으로 보겠습니다.
def reconcile(appclaim):
desired_deployment = build_deployment(appclaim.spec)
desired_service = build_service(appclaim.spec)
apply_object(desired_deployment)
if appclaim.spec.get("expose"):
apply_object(desired_service)
update_status(
appclaim,
phase="Ready",
ready_replicas=get_ready_replicas(appclaim.metadata.name)
)
실제로 써보니까 이 구조가 왜 좋은지 금방 체감됩니다. 사용자는 AppClaim만 만들면 되고, 배포 세부 구현은 컨트롤러가 맡습니다. 즉, 사용자 인터페이스는 단순하게, 운영 로직은 중앙집중화되는 거죠. 팀 규모가 커질수록 이 차이가 큽니다.
Operator 패턴과의 연결
Operator 패턴은 단순 생성 자동화를 넘어섭니다. 설치, 업그레이드, 백업, 복구, 장애 대응 같은 운영 절차를 컨트롤러 안에 녹이는 방식입니다. 대표적으로 데이터베이스, 메시지 큐, 모니터링 스택 같은 상태 저장 워크로드에서 자주 쓰입니다. Kubernetes CRD는 이 Operator의 입력 포맷이 되는 경우가 많고요.
만약 앱 수준이 아니라 데이터베이스 운영 자동화까지 가고 싶다면, 다음 글에서 StatefulSet과 Operator 설계 포인트도 이어서 다뤄볼 예정입니다. 이전 글에서 다룬 Helm 차트 구조화 내용이 있다면 그것도 같이 보시면 흐름이 더 잘 잡힙니다.
⚠️ 주의사항과 트러블슈팅
여기서부터가 진짜 실무 구간입니다. CRD는 만들기보다 버전 관리와 호환성에서 더 많이 흔들립니다. 제가 직접 해보니 아래 이슈는 거의 한 번씩 다 밟게 되더라고요.
1. v1alpha1을 너무 오래 끌고 가는 문제
처음엔 빠르게 실험하려고 v1alpha1로 시작합니다. 저도 그랬고요. 근데 이 상태로 사용자 수가 늘어나면 필드 변경이 무서워집니다. 기존 리소스와의 호환성을 고민해야 하거든요. 가능하면 초기에 필드 의미를 명확히 하고, status 구조도 대충 넣지 말고 의도를 분명히 하세요.
2. 스키마 검증을 느슨하게 잡는 문제
“컨트롤러에서 알아서 처리하면 되지”라고 생각하고 schema를 대충 열어두면 나중에 이상한 값이 다 들어옵니다. port에 문자열이 들어오거나, replicas가 0이 되거나, 필수 필드가 비는 식이죠. 그러면 에러 위치가 API 서버가 아니라 애플리케이션 쪽으로 밀려서 디버깅이 더 어려워집니다.
3. status 업데이트 충돌
컨트롤러가 status를 자주 갱신하면 resourceVersion 충돌이 날 수 있습니다. 이럴 때는 status subresource를 쓰고, 재시도 로직과 idempotent(멱등성 있는) 설계를 같이 가져가야 합니다. “한 번 더 실행돼도 결과가 같아야 한다” 이 원칙이 정말 중요합니다.
4. finalizer 처리 누락
외부 리소스를 만들었다면 finalizer(파이널라이저, 삭제 전 정리 훅)를 빼먹으면 안 됩니다. 삭제 이벤트가 왔을 때 외부 DNS, 스토리지, 인증서 같은 부수 리소스를 정리하지 않으면 유령 자원이 남습니다. 저도 예전에 테스트 환경에서 볼륨이 계속 쌓여서 왜 그런가 봤더니 finalizer 처리 누락이 원인이더라고요.
kubectl get appclaim demo-web -o yaml
kubectl api-resources | grep appclaim
kubectl explain appclaim.spec
위 명령어 세 개는 문제 생겼을 때 정말 자주 씁니다. 특히 kubectl explain은 Kubernetes CRD가 의도한 구조대로 등록됐는지 빠르게 확인할 때 유용합니다. 💡 작은 팁인데, 추가 프린터 컬럼을 잘 만들어두면 운영 가시성이 크게 좋아집니다.

스키마 검증 오류, 컨트롤러 동기화, kubectl 진단 흐름을 설명하는 트러블슈팅 다이어그램 위치입니다.
검증과 결과 확인
그럼 무엇을 검증해야 “Kubernetes CRD가 잘 동작한다”고 볼 수 있을까요? 단순히 리소스가 생성됐다는 것만으로는 부족합니다. 저는 보통 아래 순서로 봅니다.
- CRD가 정상 등록됐는지 확인합니다.
- Custom Resource 생성이 스키마에 맞게 통과하는지 봅니다.
- 컨트롤러가 원하는 하위 리소스를 생성하는지 확인합니다.
- status가 실제 상태를 반영하는지 확인합니다.
- 삭제 시 정리 로직이 정상 동작하는지 검증합니다.
kubectl get crd
kubectl get appclaims
kubectl get deployment,service
kubectl describe appclaim demo-web
kubectl delete appclaim demo-web
정상 흐름이라면 AppClaim을 만들었을 때 Deployment와 Service가 생성되고, 준비가 끝나면 status.phase 같은 필드가 업데이트됩니다. 삭제 시에는 관련 리소스가 정리돼야 하고요. 이 지점에서 드디어 “아, 이제 사람이 수작업으로 안 해도 되네” 하는 느낌이 옵니다. 🎉 이거 진짜 편하더라고요.
| 검증 항목 | 확인 방법 | 기대 결과 |
|---|---|---|
| CRD 등록 | kubectl get crd | 리소스 타입 노출 |
| 스키마 검증 | 잘못된 필드로 생성 시도 | API 서버에서 거부 |
| 하위 리소스 생성 | Deployment/Service 조회 | 의도한 객체 생성 |
| Status 반영 | kubectl describe | 상태 필드 갱신 |
| 삭제 정리 | delete 후 잔여 리소스 확인 | 누수 없이 정리 |
커스텀 리소스 상태와 생성된 워크로드를 검증하는 결과 화면 이미지를 넣는 위치입니다.
언제 CRD를 쓰고, 언제 쓰지 말아야 하나
모든 문제를 CRD로 풀 필요는 없습니다. 이건 정말 중요합니다. 저도 한때 뭐든지 커스텀 리소스로 만들면 멋있어 보인다고 생각했었는데, 나중엔 운영 복잡도만 올라간 적이 있었습니다.
- CRD를 쓰면 좋은 경우: 반복되는 운영 절차가 있고, 선언형 API로 표준화할 가치가 있을 때
- CRD가 과한 경우: 단순 스크립트 한 번이면 끝나는 작업일 때
- Operator가 필요한 경우: 설치 이후에도 지속적인 상태 관리, 복구, 업그레이드가 필요할 때
- Helm만으로 충분한 경우: 정적 템플릿 배포가 중심이고 런타임 제어가 크지 않을 때
한 줄로 정리하면 이렇습니다. 새로운 API가 팀의 언어가 될 수준인지 먼저 보세요. 그 정도가 아니면 단순화하는 편이 낫습니다.
정리와 FAQ
Kubernetes CRD는 단순 기능 추가가 아니라, 팀의 운영 지식을 쿠버네티스 API로 모델링하는 방법입니다. CRD, 커스텀 리소스, Kubernetes 확장, Operator 패턴을 한 세트로 이해하면 왜 많은 플랫폼 팀이 이 접근을 쓰는지 감이 오실 겁니다. 저도 처음엔 용어가 너무 많아서 복잡했는데, 결국 핵심은 하나였습니다. “원하는 상태를 잘 정의하고, 그걸 시스템이 계속 맞추게 하자.” 이 철학만 잡히면 훨씬 편해집니다. ✅
다음 단계로는 컨트롤러 프레임워크(controller-runtime), 상태 전이 설계, CRD 버전 업그레이드 전략까지 이어서 보시면 좋습니다. 다음 글에서 다룰 예정입니다.

CRD, 커스텀 리소스, 컨트롤러, Operator의 역할을 요약 비교하는 인포그래픽 위치입니다.
자주 묻는 질문
Q. CRD만 만들면 자동화가 되나요?
아닙니다. CRD는 타입 등록이고, 실제 자동화는 보통 컨트롤러가 담당합니다.
Q. CRD와 Operator는 같은 건가요?
같지 않습니다. CRD는 API 정의이고, Operator는 운영 지식을 담은 자동화 로직입니다. 둘이 함께 쓰이는 경우가 많습니다.
Q. 기존 Deployment나 Helm으로 충분한데도 Kubernetes CRD가 필요할까요?
반복 운영 규칙을 API로 표준화할 필요가 없다면 굳이 안 써도 됩니다. 과한 설계는 오히려 부담이 됩니다.
Q. 처음 만들 때 가장 중요한 한 가지는 뭔가요?
spec과 status의 역할을 명확히 나누고, 스키마 검증을 초기에 엄격하게 잡는 겁니다. 여기서 흔들리면 나중에 계속 고생합니다.
![[Kubernetes] Kubernetes CRD 심층 분석: 커스텀 리소스 정의 및 활용 전략](https://blog.pswq.net/wp-content/uploads/2026/07/kubernetes-crd-deep-dive-custom-resource-definition-and-utilization-strategy-thumbnail.jpg)
![[k8s] Backstage 운영 후기: 개발자 포털 1년 회고와 정착 과정](https://blog.pswq.net/wp-content/uploads/2026/07/backstage-developer-portal-retrospective-thumbnail.jpg)



