콘텐츠로 이동

17. 확장 인터페이스와 오퍼레이터

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로 온프렘과 클라우드가 모두 동작한다. 그리고 **“플러그인이 없으면 아무것도 안 된다”**는 트러블슈팅 포인트가 생긴다.

  • kubelet ↔ 런타임 사이의 gRPC 규약이다
  • 소켓 경로: unix:///run/containerd/containerd.sock
  • Docker는 v1.24에서 제거되었다 (dockershim 삭제). 지금은 containerd가 표준
Terminal window
# crictl 설정
sudo crictl config --set runtime-endpoint=unix:///run/containerd/containerd.sock
cat /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가 지켜야 하는 규칙

  • 모든 Pod은 NAT 없이 서로 통신할 수 있다
  • 노드의 에이전트는 그 노드의 모든 Pod과 통신할 수 있다
  • Pod이 보는 자기 IP = 남이 보는 그 Pod의 IP
Terminal window
kubectl get csidrivers
kubectl get csinodes
kubectl get storageclass
kubectl get volumeattachments

CSI 드라이버는 보통 두 부분으로 배포된다.

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이 살아 있는지부터 확인한다.

Terminal window
kubectl get pods -n kube-system | grep csi
kubectl logs -n kube-system <csi-controller-pod> -c csi-provisioner
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: { 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
Terminal window
kubectl apply -f backup-crd.yaml
kubectl get crd
kubectl api-resources | grep backups
kubectl explain backup.spec # ★ 스키마가 있으니 explain도 동작한다
apiVersion: ops.example.com/v1
kind: Backup
metadata:
name: nightly
spec:
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
Terminal window
# 대개 Helm 또는 매니페스트 하나로 설치된다
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace --set crds.enabled=true

설치 확인은 3단계다.

  1. CRD가 등록됐는가

    Terminal window
    kubectl get crd | grep cert-manager
  2. 컨트롤러가 도는가

    Terminal window
    kubectl get pods -n cert-manager
  3. 권한이 있는가

    Terminal window
    kubectl get clusterrole,clusterrolebinding | grep cert-manager

CR을 만들었는데 아무 일도 안 일어날 때

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
Terminal window
kubectl describe certificate my-cert # ★ status.conditions 와 Events
kubectl logs -n cert-manager deploy/cert-manager --tail=100
kubectl get events -n <ns> --sort-by=.lastTimestamp
flowchart 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·라우트 관리
Terminal window
kubectl get apiservices | grep -v Local # 집계된 외부 API 서버들
# v1beta1.metrics.k8s.io kube-system/metrics-server True

APIServiceFalse면 그 API 전체가 죽는다 — kubectl top이 안 되는 원인 중 하나다 (8장).

  • Kubernetes의 핵심 기능은 인터페이스만 있고 구현은 플러그인이다
  • CRI — kubelet ↔ 런타임. Docker는 제거됨. 진단은 crictl
  • CNI/etc/cni/net.d/ + /opt/cni/bin/. 없으면 노드가 NotReady
  • CSI — Controller(Deployment) + Node(DaemonSet). PVC Pending 시 프로비저너를 확인
  • CRD를 만들면 API가 즉시 생기지만, 행동하는 주체는 없다
  • 오퍼레이터 = CRD + 컨트롤러. 설치 확인은 CRD / Pod / RBAC 3단계
  • CR이 반응이 없으면 describe의 conditions오퍼레이터 로그
  • finalizer + 죽은 오퍼레이터 = 삭제가 끝나지 않는다