CRD 와 오퍼레이터 · 오퍼레이터 운영 · 실습
오퍼레이터 설치와 권한 경계 검증
목표
오퍼레이터의 매니페스트 일습(네임스페이스, RBAC, 컨트롤러 Deployment, 리더 선출 Lease)을 올바른 순서로 설치하고, 그 권한이 실제로 의도한 경계 안에 있는지 직접 질의해 증명합니다.
왜 중요한가
오퍼레이터는 클러스터에서 가장 강한 권한을 가진 워크로드일 수 있습니다. 여러 네임스페이스의 리소스를 만들고 고치므로, 그 서비스 어카운트가 탈취되면 공격자가 그 권한을 그대로 갖습니다. 그래서 두 가지가 핵심입니다. 첫째, 설치 순서는 의존성입니다. 컨트롤러는 뜨자마자 자기 타입을 watch 하므로 CRD 가 먼저 있어야 하고, 목록 조회부터 하므로 권한이 먼저 있어야 합니다. 순서가 틀리면 크래시 루프나 403 으로 나타나는데, 원인이 코드가 아니라 배포 순서라는 것을 알아채는 데 시간이 걸립니다. 둘째, 권한은 동사를 줄이는 일입니다. verbs: ["*"] 는 편하지만, 자식 정리를 소유자 참조에 맡기면 delete 조차 필요 없는 경우가 많습니다. 그리고 RBAC 에서 webservices 와 webservices/status 는 완전히 다른 리소스입니다 — 이 사실을 모르면 "권한을 줬는데 status 를 못 쓴다"는 증상에 갇힙니다. 마지막으로 권한은 문서가 아니라 검증 대상입니다. kubectl auth can-i --as= 로 되는 것과 되면 안 되는 것을 모두 확인해 두면, 그 표가 곧 권한 경계에 대한 증거가 됩니다.
단계
시작 전 준비: 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. 2번과 6번이 CRD webservices.apps.labhub.io 를 요구하고 7번·8번의 권한 질의가 crd-lab 네임스페이스를 대상으로 하므로, kubectl get crd webservices.apps.labhub.io 가 비어 있으면 CRD 를 다시 적용하고 kubectl create ns crd-lab 도 하세요. CRD 는 v1alpha1 과 v1 두 버전을 가져야 하고 저장 버전은 v1 이어야 합니다(6번이 그 보존을 검사합니다).
1. 네임스페이스 labhub-operator 를 만들고 라벨 app.kubernetes.io/part-of=labhub-operator 와 pod-security.kubernetes.io/enforce=restricted 를 붙이세요.
2. /root/op/install/out/install-order.txt 에 설치 순서를 한 단계당 한 줄로 적으세요. 1번 줄은 CustomResourceDefinition, 2번 줄은 ServiceAccount 와 ClusterRole/ClusterRoleBinding, 3번 줄은 Deployment 를 언급해야 하며, 앞 줄에서 Deployment 나 컨트롤러 라는 낱말을 미리 쓰지 마세요(순서 판정이 첫 등장 줄을 기준으로 합니다). CRD webservices.apps.labhub.io 도 클러스터에 있어야 합니다.
3. labhub-operator 에 ServiceAccount webservice-controller 를 만들고, ClusterRole webservice-controller 를 다음 규칙으로 만드세요.
apiGroups: ["apps.labhub.io"],resources: ["webservices"],verbs: ["get","list","watch","update","patch"]apiGroups: ["apps.labhub.io"],resources: ["webservices/status","webservices/finalizers"],verbs: ["get","update","patch"]apiGroups: [""],resources: ["events"],verbs: ["create","patch"]apiGroups: ["coordination.k8s.io"],resources: ["leases"],verbs: ["get","list","watch","create","update","patch"]
동사와 리소스 어디에도 * 를 쓰면 안 됩니다. 그리고 ClusterRoleBinding webservice-controller 를 만들어 subject 를 kind: ServiceAccount, name: webservice-controller, namespace: labhub-operator 로 지정하세요.
4. labhub-operator 에 Deployment webservice-controller 를 만드세요. spec.replicas 는 1 또는 2, spec.template.spec.serviceAccountName 은 webservice-controller, 컨테이너 이미지는 ghcr.io/labhub/webservice-controller:v0.1.0, args 에 --leader-elect=true 를 넣고, 컨테이너에 livenessProbe 와 readinessProbe 를 각각 두고 resources.limits.memory 를 설정하세요. 파드의 securityContext.runAsNonRoot 는 true 여야 합니다.
5. labhub-operator 에 Lease webservice-controller.labhub.io 를 만드세요. spec.holderIdentity 는 리더의 신원 문자열, spec.leaseDurationSeconds 는 5 이상, spec.renewTime 은 마이크로초까지 있는 RFC3339 시각(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)입니다. 그리고 /root/op/install/out/lease-note.txt 에 리더 선출이 없으면 왜 곤란한지(같은 오브젝트를 둘이 동시에 조정하는 문제)를 적으세요.
6. Deployment 의 컨테이너 이미지를 ghcr.io/labhub/webservice-controller:v0.2.0 으로 올리고 롤아웃이 끝날 때까지 기다리세요. CRD 는 버전이 2개 이상 그대로 남아 있어야 하고 status.storedVersions 에 v1 이 있어야 합니다. 업그레이드 전후로 무엇을 확인했는지 /root/op/install/out/upgrade-check.txt 에 적되 storedVersions 확인 내용을 반드시 포함하세요.
7. /opt/lab/fixtures/operator/broken-operator.yaml 을 /root/op/install/fixed-operator.yaml 로 복사해 잘못된 세 군데를 고치고 적용하세요. 그다음 /root/op/install/out/diagnosis.txt 에 무엇이 왜 틀렸는지 한 줄에 하나씩 세 줄 이상 적으세요. 바인딩 주체 이름 문제와 status 서브리소스 권한 누락은 반드시 포함해야 합니다. 수정 후 kubectl auth can-i update webservices/status -n crd-lab --as=system:serviceaccount:labhub-operator:webservice-controller 와 kubectl auth can-i list webservices --all-namespaces --as=... 가 모두 yes 여야 합니다.
8. /root/op/install/out/permissions.json 을 만드세요. 최상위 키는 checks 이고 각 원소는 verb, resource, expected(yes/no)를 갖고 필요하면 namespace 를 갖습니다. namespace 를 적지 않으면 전 네임스페이스 기준으로 확인됩니다. 최소 6건 이상이어야 하고, expected 가 no 인 항목이 2건 이상이어야 하며, 그중 하나는 반드시 resource 가 secrets 여야 합니다. 예를 들어 list webservices(yes), watch webservices(yes), update webservices/status in crd-lab(yes), create events in crd-lab(yes), update webservices/finalizers in crd-lab(yes), get secrets in crd-lab(no), delete webservices in crd-lab(no), create clusterrolebindings(no) 여덟 건이면 충분합니다. 모든 항목의 expected 가 실제 응답과 같아야 합니다.
참고
- 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. 그래도 선언을 파일로 남겨 두면 어느 파드에서든 같은 상태를 다시 세울 수 있습니다 — 이것이 선언형의 실질적 이점이자, 오퍼레이터 설치를 매니페스트로 관리해야 하는 이유입니다.
- 권한 확인의 기준 주체는
system:serviceaccount:labhub-operator:webservice-controller입니다.kubectl auth can-i <동사> <리소스> -n <ns> --as=<주체>형태로 씁니다. - 3번의 규칙에서
delete를 일부러 넣지 않았습니다. 자식 정리는 소유자 참조와 가비지 컬렉터에 맡기는 것이 표준이고, 그러면 침해 시 대량 삭제 경로가 막힙니다. 8번의delete webservices(no)가 이 설계를 검증합니다. - 리더 선출 Lease 는
resourceNames로 그 이름 하나에만 권한을 한정하는 것이 더 좋은 강화이지만, 이 실습에서는 요구하지 않습니다. - restricted 정책을 만족시키려면 파드에
seccompProfile.type: RuntimeDefault, 컨테이너에allowPrivilegeEscalation: false와capabilities.drop: ["ALL"]을 함께 두는 것이 좋습니다. - 흔한 실수 1: 2번에서 첫 줄에 "CRD 를 먼저 넣어야 컨트롤러가 뜬다" 처럼 뒤 단계 낱말을 미리 쓰는 것. 순서 판정이 첫 등장 줄 번호로 이루어지므로 판정이 뒤집힙니다.
- 흔한 실수 2: 3번에서
webservices에만 권한을 주고webservices/status를 빠뜨리는 것. RBAC 에서 서브리소스는 완전히 다른 리소스입니다. - 흔한 실수 3: 5번에서
renewTime을 초 단위 RFC3339 로만 쓰는 것. Lease 의 시각 필드는 마이크로초 여섯 자리를 요구합니다.
단계 8개
- 오퍼레이터 전용 네임스페이스 만들기
- 설치 순서 정리하기
- 와일드카드 없는 최소권한 만들기
- 컨트롤러 Deployment 작성하기
- 리더 선출 임차 만들기
- 이미지 올리고 CRD 버전 보존 확인하기
- 고장난 오퍼레이터 매니페스트 고치기
- 권한 경계 검증표 만들기