CR一枚から下位リソースと状態を作る
한국어 원문으로 표시합니다.
목표
CR 한 장을 입력으로 삼아 하위 리소스를 만들고, 그 결과를 status 에 되돌려 적어, kubectl get 한 줄이 배포 상태를 말해 주는 구조를 손으로 완성합니다.
왜 중요한가
CR 을 배포 인터페이스로 쓸 때 지켜야 할 규율은 네 가지입니다. 첫째, spec 은 사용자가 쓰고 status 는 컨트롤러가 씁니다. 컨트롤러가 spec 을 건드리면 Git 저장소와 클러스터가 어긋나 다음 동기화가 그것을 지웁니다. 둘째, status 는 반드시 별도 경로로 씁니다. 서브리소스가 켜져 있는데 일반 update 로 쓰면 status 가 조용히 무시되기 때문에, "분명히 썼는데 반영이 안 된다"는 증상으로 나타납니다. 셋째, 자식에는 소유자 참조를 답니다. 부모가 지워질 때 가비지 컬렉터가 자식을 자동으로 정리해 주고, 컨트롤러는 "내가 만든 것"만 관리하면 되므로 정리 로직이 단순해집니다. 이때 연결 고리는 이름이 아니라 uid 입니다 — 같은 이름의 부모를 지웠다 다시 만들면 uid 가 바뀌고 옛 uid 를 가리키는 자식은 즉시 수거됩니다. 넷째, observedGeneration 으로 시차를 드러냅니다. 이 값이 metadata.generation 보다 작으면 "status 는 아직 옛 명세 기준"이라는 뜻이고, 이 한 쌍이 없으면 사용자는 status 를 믿어도 되는지 알 수 없습니다.
단계
시작 전 준비: 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. kubectl get crd webservices.apps.labhub.io 가 비어 있으면 CRD 를 다시 작성해 적용하고 kubectl create ns crd-lab 도 하세요. 이 실습에는 v1 의 additionalPrinterColumns(Image/Replicas/Tier/Age), subresources.status, subresources.scale(.spec.replicas/.status.replicas/.status.selector), 그리고 properties.status 아래 replicas·selector·observedGeneration·conditions 정의가 모두 필요합니다. status 스키마가 없으면 patch 해도 pruning 으로 잘려 나갑니다.
/root/crd/deploy/minimal.yaml에metadata.name: minimal(네임스페이스crd-lab)이고spec.image: nginx:1.27만 있는 WebService 를 쓰고 적용하세요.spec.replicas는 적지 마세요. 저장된 오브젝트의spec.replicas가 1 이어야 합니다./root/crd/deploy/storefront.yaml에metadata.name: storefront,metadata.labels.tier: prod,spec.image: nginx:1.27,spec.replicas: 4,spec.tier: prod인 WebService 를 쓰고 적용하세요.crd-lab에 ConfigMapstorefront-config를 만들고metadata.ownerReferences[0]에apiVersion: apps.labhub.io/v1,kind: WebService,name: storefront,controller: true, 그리고uid는kubectl get webservice storefront -n crd-lab -o jsonpath='{.metadata.uid}'로 조회한 실제 값을 넣으세요.storefront의 status 에replicas: 4와selector: app=storefront를 쓰세요. 반드시--subresource=status를 붙인 patch 로 써야 하며, 사용한 명령줄 전체를/root/crd/deploy/out/status-patch.txt에 저장하세요.- 같은 status 에
conditions를 추가하세요.type: Ready,status: "True",reason: AllReplicasReady,message는 자유 문장,lastTransitionTime은date -u +%Y-%m-%dT%H:%M:%SZ형식의 RFC3339 시각입니다. 함께status.observedGeneration을metadata.generation과 같은 값으로 쓰세요. /opt/lab/fixtures/crd/sample-cr.yaml을crd-lab에 적용해sample을 추가하세요(이로써 WebService 가 3개가 됩니다). 그다음kubectl label webservice minimal -n crd-lab tier=dev로 라벨을 붙이고,kubectl get webservice -n crd-lab -l tier=prod의 출력을/root/crd/deploy/out/selected.txt로 저장하세요. 이 파일에storefront는 있고minimal은 없어야 하며,tier=prod로 걸리는 것은 정확히 하나여야 합니다.kubectl get webservice -n crd-lab -o custom-columns=NAME:.metadata.name,IMAGE:.spec.image,TIER:.spec.tier의 출력을/root/crd/deploy/out/columns.txt로 저장하세요. 머리글 한 줄에 리소스 3줄 이상, 총 4줄 이상이어야 합니다.crd-lab에 ConfigMapstorefront-desired를 만드세요.data.image,data.replicas,data.tier세 키의 값은storefront의spec에서 읽은 값과 문자열로 정확히 같아야 하고, 3번과 같은 방식으로storefront를 소유자로 지정하세요. 이 단계 후에도status.observedGeneration은metadata.generation과 같아야 합니다.
참고
- 실습 파드는 실습마다 새로 뜨므로 앞 실습의 클러스터 상태는 남아 있지 않습니다. 그래도 선언을 파일로 남겨 두면 어느 파드에서든 같은 상태를 다시 세울 수 있습니다 — 이것이 선언형의 실질적 이점입니다.
- status 쓰기 예:
kubectl patch webservice storefront -n crd-lab --subresource=status --type=merge -p '{"status":{"replicas":4}}' - ConfigMap 의
data값은 항상 문자열입니다. 숫자 4 는"4"로 적어야 하며, 그렇지 않으면 적용이 거부됩니다. - 소유자 참조를 넣은 오브젝트는
kubectl apply대신 파일을 만들어 적용하는 편이 편합니다. uid 를 셸 변수로 받아 매니페스트에 끼워 넣으세요. - 흔한 실수 1: 4번에서
--subresource=status없이 patch 하는 것. 서브리소스가 켜져 있으면 status 가 조용히 무시되어 아무 오류도 안 나면서 값이 저장되지 않습니다. - 흔한 실수 2: 3번에서 uid 대신 이름만 맞추는 것. 이름이 같아도 uid 가 다르면 가비지 컬렉터는 그 자식을 고아로 보고 즉시 지웁니다.
- 흔한 실수 3: 8번에서 spec 을 다시 수정해 generation 이 오른 뒤 observedGeneration 을 갱신하지 않는 것.
최소 명세로 CR 만들기
/root/crd/deploy/minimal.yaml 에 metadata.name: minimal(네임스페이스 crd-lab)이고 spec.image: nginx:1.27 만 있는 WebService 를 쓰고 적용하세요. spec.replicas 는 적지 마세요. 저장된 오브젝트의 spec.replicas 가 1 이어야 합니다.
필수 필드 하나만 적습니다. 나머지를 적으면 기본값이 채워진 것인지 확인할 수 없으므로 비워 두세요.
전체 명세를 채운 CR 만들기
/root/crd/deploy/storefront.yaml 에 metadata.name: storefront, metadata.labels.tier: prod, spec.image: nginx:1.27, spec.replicas: 4, spec.tier: prod 인 WebService 를 쓰고 적용하세요.
spec 의 값과 metadata 의 라벨은 다른 자리입니다. 앞의 것은 컨트롤러가 읽는 의도이고 뒤의 것은 셀렉터로 고르기 위한 색인입니다. 둘 다 필요합니다.
소유자 참조로 자식 묶기
crd-lab 에 ConfigMap storefront-config 를 만들고 metadata.ownerReferences[0] 에 apiVersion: apps.labhub.io/v1, kind: WebService, name: storefront, controller: true, 그리고 uid 는 kubectl get webservice storefront -n crd-lab -o jsonpath='{.metadata.uid}' 로 조회한 실제 값을 넣으세요.
소유자 참조는 이름이 아니라 uid 로 연결됩니다. 부모의 uid 를 먼저 조회해 그 값을 넣으세요. apiVersion 은 그룹과 버전을 함께 적고, 주 컨트롤러임을 표시하는 불리언 필드도 필요합니다.
status 서브리소스에 관측값 쓰기
storefront 의 status 에 replicas: 4 와 selector: app=storefront 를 쓰세요. 반드시 --subresource=status 를 붙인 patch 로 써야 하며, 사용한 명령줄 전체를 /root/crd/deploy/out/status-patch.txt 에 저장하세요.
일반 patch 로는 status 가 무시됩니다. status 전용 경로를 지정하는 옵션이 있고, 그 옵션을 쓴 명령 자체를 파일로 남겨야 합니다. 셀렉터는 키=값 형태의 문자열입니다.
표준 conditions 와 observedGeneration 채우기
같은 status 에 conditions 를 추가하세요. type: Ready, status: "True", reason: AllReplicasReady, message 는 자유 문장, lastTransitionTime 은 date -u +%Y-%m-%dT%H:%M:%SZ 형식의 RFC3339 시각입니다. 함께 status.observedGeneration 을 metadata.generation 과 같은 값으로 쓰세요.
Ready 조건에는 기계가 읽는 이유 코드와 사람이 읽는 설명이 둘 다 필요합니다. 시각은 RFC3339 형식이어야 하고, 처리한 세대 번호는 metadata 의 값과 같아야 합니다.
라벨 셀렉터로 CR 고르기
/opt/lab/fixtures/crd/sample-cr.yaml 을 crd-lab 에 적용해 sample 을 추가하세요(이로써 WebService 가 3개가 됩니다). 그다음 kubectl label webservice minimal -n crd-lab tier=dev 로 라벨을 붙이고, kubectl get webservice -n crd-lab -l tier=prod 의 출력을 /root/crd/deploy/out/selected.txt 로 저장하세요. 이 파일에 storefront 는 있고 minimal 은 없어야 하며, tier=prod 로 걸리는 것은 정확히 하나여야 합니다.
spec 의 값이 아니라 metadata 의 라벨로 걸립니다. prod 로 걸리는 것이 정확히 하나가 되도록 다른 CR 에는 다른 값을 붙이세요.
custom-columns 로 원하는 필드만 뽑기
kubectl get webservice -n crd-lab -o custom-columns=NAME:.metadata.name,IMAGE:.spec.image,TIER:.spec.tier 의 출력을 /root/crd/deploy/out/columns.txt 로 저장하세요. 머리글 한 줄에 리소스 3줄 이상, 총 4줄 이상이어야 합니다.
CRD 에 정의한 컬럼과 별개로, 조회할 때 컬럼을 직접 지정할 수 있습니다. 머리글:JSON경로 를 쉼표로 잇습니다.
CR 명세에서 원하는 상태 오브젝트 만들기
crd-lab 에 ConfigMap storefront-desired 를 만드세요. data.image, data.replicas, data.tier 세 키의 값은 storefront 의 spec 에서 읽은 값과 문자열로 정확히 같아야 하고, 3번과 같은 방식으로 storefront 를 소유자로 지정하세요. 이 단계 후에도 status.observedGeneration 은 metadata.generation 과 같아야 합니다.
값을 손으로 베끼지 말고 CR 에서 읽어 그대로 넣으세요. 세 값이 CR 과 하나라도 다르면 안 되고, 부모와의 연결도 필요합니다. spec 을 바꿨다면 처리한 세대 번호도 다시 맞추세요.