Controller 플러그인 — Deployment
볼륨 생성·삭제·확장·스냅샷. 클러스터에 하나.
CRI · CNI · CSI · CRD
핵심 기능 중 상당수가 인터페이스만 정의되어 있고 구현은 밖에 있다.
flowchart TB
K8S["Kubernetes 코어<br/>인터페이스만 정의한다"]
K8S --> CRI["CRI<br/>컨테이너 실행"] --> I1["containerd · CRI-O"]
K8S --> CNI["CNI<br/>Pod 네트워크·IP"] --> I2["Calico · Cilium · Flannel"]
K8S --> CSI["CSI<br/>볼륨 생성·마운트"] --> I3["EBS CSI · Ceph · local-path"]
K8S --> CRD["CRD + 컨트롤러<br/>새로운 리소스 종류"] --> I4["cert-manager · Prometheus Operator"]
NONE["플러그인이 없으면<br/>아무것도 안 된다 ⚠️"] -.- K8S
classDef core fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef iface fill:#fef3c7,stroke:#d97706,color:#78350f
classDef warn fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class K8S core
class CRI,CNI,CSI,CRD iface
class NONE warn
class I1,I2,I3,I4 mute
| 인터페이스 | 무엇을 위임하나 | 구현체 예 |
|---|---|---|
| CRI (Container Runtime) | 컨테이너 실행 | containerd, CRI-O |
| CNI (Container Network) | Pod 네트워크·IP | Calico, Cilium, Flannel |
| CSI (Container Storage) | 볼륨 생성·마운트 | EBS CSI, Ceph, local-path |
| CRD + 컨트롤러 | 새로운 리소스 종류 | cert-manager, Prometheus Operator |
이 구조 덕분에 같은 API로 온프렘과 클라우드가 모두 동작한다. 그리고 **“플러그인이 없으면 아무것도 안 된다”**는 트러블슈팅 포인트가 생긴다.
unix:///run/containerd/containerd.sock# crictl 설정sudo crictl config --set runtime-endpoint=unix:///run/containerd/containerd.sockcat /etc/crictl.yaml
sudo crictl ps -a # 컨테이너sudo crictl pods # Pod 샌드박스sudo crictl images # 이미지sudo crictl logs <container-id>sudo crictl inspect <container-id>sudo crictl rmi --prune # 안 쓰는 이미지 정리kubelet이 Pod 샌드박스를 만들 때 CNI 플러그인을 실행한다.
flowchart LR
KL["kubelet"] -->|"ADD 호출"| CNI["CNI 플러그인"]
CONF["/etc/cni/net.d/<br/>JSON 설정 · 사전순 첫 번째를 쓴다"] -.-> CNI
BIN["/opt/cni/bin/<br/>바이너리"] -.-> CNI
CNI --> S1["① 인터페이스 생성"]
S1 --> S2["② IP 할당 · IPAM"]
S2 --> S3["③ 라우팅 설정"]
S3 --> OK["Pod 이 통신 가능 ✅"]
FAIL["CNI 가 없거나 고장"] -.->|"ContainerCreating 에서 멈춘다<br/>노드는 NotReady ❌"| CNI
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class CNI key
class OK ok
class FAIL bad
class KL,CONF,BIN,S1,S2,S3 mute
모든 CNI가 지켜야 하는 규칙
kubectl get csidriverskubectl get csinodeskubectl get storageclasskubectl get volumeattachmentsCSI 드라이버는 보통 두 부분으로 배포된다.
Controller 플러그인 — Deployment
볼륨 생성·삭제·확장·스냅샷. 클러스터에 하나.
Node 플러그인 — DaemonSet
노드에서 attach·mount. 노드마다 하나.
주변에 sidecar 컨테이너들이 붙는다 —
external-provisioner, external-attacher, external-resizer, external-snapshotter, node-driver-registrar
flowchart LR
PVC["PVC 가 Pending"] --> Q{"StorageClass 는 있는가"}
Q -->|없다| A1["StorageClass 를 만든다"]
Q -->|있다| A2["프로비저너 Pod 이 살아 있는가<br/>kubectl get pods -n kube-system | grep csi"]
A2 --> A3["로그 확인<br/>kubectl logs … -c csi-provisioner"]
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class A2 key
class A1 bad
class PVC,Q,A3 mute
PVC가 Pending인데 StorageClass는 있다면
프로비저너 Pod이 살아 있는지부터 확인한다.
kubectl get pods -n kube-system | grep csikubectl logs -n kube-system <csi-controller-pod> -c csi-provisionerapiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: { name: backups.ops.example.com } # 반드시 <복수형>.<그룹> 형식spec: group: ops.example.com scope: Namespaced # Namespaced | Cluster names: { plural: backups, singular: backup, kind: Backup, shortNames: ["bk"] } versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object required: ["schedule"] properties: schedule: { type: string } retention: { type: integer, default: 7 } subresources: { status: {} } additionalPrinterColumns: # kubectl get 의 출력 열을 정한다 - { name: Schedule, type: string, jsonPath: .spec.schedule }flowchart LR
CRD["CRD 적용"] --> EP["API 서버에 새 엔드포인트<br/>/apis/ops.example.com/v1/backups"]
EP --> A["kubectl get/create/delete backup<br/>즉시 동작 ✅"]
EP --> B["kubectl explain backup.spec<br/>스키마가 있으니 동작 ✅"]
EP --> C["검증도 API 서버가 해준다 ✅"]
EP --> D["그런데 아무 일도 일어나지 않는다 ⚠️<br/>보고 행동하는 컨트롤러가 없다"]
D --> OP["→ 그 컨트롤러가 오퍼레이터"]
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class A,B,C ok
class D warn
class OP key
class CRD,EP mute
kubectl apply -f backup-crd.yamlkubectl get crdkubectl api-resources | grep backupskubectl explain backup.spec # ★ 스키마가 있으니 explain도 동작한다apiVersion: ops.example.com/v1kind: Backupmetadata: name: nightlyspec: schedule: "0 3 * * *"운영자의 지식을 코드로 옮긴 컨트롤러.
“이 상태여야 한다”를 CR로 선언하면, 오퍼레이터가 그 상태를 만들고 유지한다.
flowchart LR
U["사용자"] -->|"CR 생성<br/>kind: Certificate"| API["kube-apiserver"]
API -->|"watch"| OP["Operator Pod<br/>cert-manager"]
OP -->|"Secret · Ingress 등 생성"| API
OP -->|"status.conditions 갱신"| API
API -->|"describe 로 읽는다"| U
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class OP key
class U,API mute
| 오퍼레이터 | CR 예 |
|---|---|
| cert-manager | Certificate, Issuer |
| Prometheus Operator | Prometheus, ServiceMonitor, PrometheusRule |
| CloudNativePG | Cluster (Postgres 클러스터) |
| Istio | VirtualService, Gateway |
# 대개 Helm 또는 매니페스트 하나로 설치된다helm install cert-manager jetstack/cert-manager \ --namespace cert-manager --create-namespace --set crds.enabled=true설치 확인은 3단계다.
CRD가 등록됐는가
kubectl get crd | grep cert-manager컨트롤러가 도는가
kubectl get pods -n cert-manager권한이 있는가
kubectl get clusterrole,clusterrolebinding | grep cert-managerCR을 만들었는데 아무 일도 안 일어날 때
flowchart TD
S["CR 을 만들었는데 반응이 없다"] --> C1["① describe 의 status.conditions 와 Events"]
C1 -->|"단서가 없다"| C2["② 오퍼레이터 로그"]
C2 -->|"단서가 없다"| C3["③ 오퍼레이터 Pod 이 살아 있는가"]
C3 -->|"죽었다"| C4["Pod 로그 · RBAC 확인"]
C1 -.-> CMD1["kubectl describe certificate my-cert"]
C2 -.-> CMD2["kubectl logs -n cert-manager deploy/cert-manager --tail=100"]
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class C1 key
class S,C2,C3,C4,CMD1,CMD2 mute
kubectl describe certificate my-cert # ★ status.conditions 와 Eventskubectl logs -n cert-manager deploy/cert-manager --tail=100kubectl get events -n <ns> --sort-by=.lastTimestampflowchart LR
API["kube-apiserver"]
API --> W["Admission webhook<br/>요청을 가로채 고치거나 거부 · 14장"]
API --> AG["API Aggregation<br/>별도 API 서버를 /apis 아래에<br/>metrics-server 가 이 방식"]
KL["kubelet"] --> DP["Device Plugin<br/>GPU 등을 노드 리소스로 노출"]
SCH["scheduler"] --> EX["Scheduler Extender<br/>다중 스케줄러 · 7장"]
CLOUD["Cloud Controller Manager<br/>클라우드별 노드·LB·라우트"] --> API
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class API,KL,SCH key
class W,AG,DP,EX,CLOUD mute
| 확장 | 무엇 |
|---|---|
| Admission webhook | 요청을 가로채 고치거나 거부 (14장) |
| API Aggregation | 별도 API 서버를 /apis 아래에 붙인다 (metrics-server가 이 방식) |
| CRI/CNI/CSI | 이 장에서 본 것들 |
| Device Plugin | GPU 등 특수 하드웨어를 노드 리소스로 노출 |
| Scheduler Extender / 다중 스케줄러 | 배치 로직 교체 (7장) |
| Cloud Controller Manager | 클라우드별 노드·LB·라우트 관리 |
kubectl get apiservices | grep -v Local # 집계된 외부 API 서버들# v1beta1.metrics.k8s.io kube-system/metrics-server TrueAPIService가 False면 그 API 전체가 죽는다 —
kubectl top이 안 되는 원인 중 하나다 (8장).
crictl/etc/cni/net.d/ + /opt/cni/bin/. 없으면 노드가 NotReadydescribe의 conditions와 오퍼레이터 로그