LabHub
学习 学习路径 课程

CRD 与 Operator

把副本数调大了却什么都没发生 - scale 子资源的约定

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

CRD 의 subresources.scale 을 세 경로로 제대로 맺고, 셀렉터 경로를 빠뜨렸을 때와 경로에 오타가 있을 때 각각 어떤 침묵이 생기는지 직접 만들어 본 뒤, 그 침묵을 잡아내는 점검 스크립트를 만든다.

왜 중요한가

kubectl scale 과 HPA 는 대상의 종류를 모른다. 이 둘이 아는 것은 오직 scale 하위 리소스라는 공통 창 하나뿐이고, CRD 는 그 창을 세 개의 JSONPath 로 열어 준다. 그래서 오퍼레이터를 만드는 사람이 이 세 줄을 제대로 쓰면 자기 타입이 쿠버네티스의 기존 자동 확장 도구 전부와 공짜로 이어지고, 잘못 쓰면 그 도구들이 오류 없이 아무 일도 하지 않는다. 이 침묵이 이 모듈의 주제다. 경로에 오타가 있으면 kubectl scale 은 성공했다고 답하면서 값을 바꾸지 않고, 셀렉터 경로가 없으면 HPA 는 대상을 읽는 데 성공한 채로 영원히 조정하지 않는다. 둘 다 사람이 화면을 들여다보기 전에는 드러나지 않으므로, 배포 파이프라인에 넣을 수 있는 점검 방법을 함께 갖춰야 한다.

단계

  1. /root/op-scale/shard-crd.yaml 에 CRD shards.scale.labhub.io 를 쓰세요 — 그룹 scale.labhub.io, 종류 Shard, 복수형 shards, 버전 v1 하나(served·storage 모두 참), spec.replicas(integer)·status.replicas(integer)·status.selector(string) 를 스키마에 두고, subresourcesstatus: {}scale 을 켭니다. scale 의 세 경로는 각각 .spec.replicas·.status.replicas·.status.selector 입니다. 적용한 뒤 클러스터에 등록된 세 경로를 /root/op-scale/scale-paths.txt<이름>=<값> 세 줄로 저장하세요(specReplicasPath=..., statusReplicasPath=..., labelSelectorPath=...).
  2. 네임스페이스 op-scale 를 만들고 /root/op-scale/shard-a.yaml 에 Shard a 를 쓰세요 — spec.replicas 는 2 입니다. 적용한 뒤 kubectl -n op-scale get shard a --subresource=scale -o yaml 의 출력을 그대로 /root/op-scale/scale-initial.yaml 에 저장하세요.
  3. kubectl -n op-scale scale shard a --replicas=5 로 Shard a 의 원하는 수를 5 로 올리세요. 그다음 scale 하위 리소스에서 두 숫자를 꺼내 /root/op-scale/after-scale.txtspec.replicas=5status.replicas=0 두 줄로 저장하세요.
  4. 컨트롤러가 할 일을 대신해 status 를 쓰세요 — kubectl -n op-scale patch shard a --subresource=statusstatus.replicas 를 5 로, status.selectorapp=shard-a 로 채웁니다. 그다음 scale 하위 리소스 출력을 /root/op-scale/scale-ready.yaml 에 저장하세요.
  5. /root/op-scale/shard-hpa.yamlautoscaling/v2 HPA shard-a-hpa 를 쓰세요 — scaleTargetRef 는 apiVersion scale.labhub.io/v1, kind Shard, name a 이고 minReplicas 2, maxReplicas 10, 지표는 Resource cpu 의 Utilization 70 입니다. 적용하고 조건이 채워질 때까지 기다린 뒤 /root/op-scale/hpa-status.txt 에 두 줄로 저장하세요 — AbleToScale=<상태>/<이유>REFERENCE=<kubectl get hpa 의 REFERENCE 칸>.
  6. /root/op-scale/block-crd.yaml 에 CRD blocks.scale.labhub.io(종류 Block, 복수형 blocks)를 쓰되 scale 에 labelSelectorPath넣지 않습니다. /root/op-scale/block-b.yaml 로 Block b(spec.replicas 3)를, /root/op-scale/block-hpa.yaml 로 HPA block-b-hpa(minReplicas 1, maxReplicas 6, cpu 70)를 만들어 적용하고, 조건이 채워지면 /root/op-scale/noselector.txtScalingActive=<상태>/<이유>MESSAGE=<조건 메시지> 두 줄로 저장하세요.
  7. /root/op-scale/relay-crd.yaml 에 CRD relays.scale.labhub.io(종류 Relay, 복수형 relays)를 쓰되 specReplicasPath 를 일부러 .spec.replica(끝의 s 를 뺀 오타)로 적습니다. /root/op-scale/relay-r.yaml 로 Relay r(spec.replicas 2)을 만들어 적용하고, kubectl -n op-scale scale relay r --replicas=4kubectl -n op-scale get relay r --subresource=scale -o yaml 을 차례로 실행해 그 결과를 /root/op-scale/typo-report.txt 에 모으세요 — scale-rc=<종료 코드>, spec.replicas=<명령 뒤 실제 값>, 그리고 두 번째 명령의 오류 문장을 그대로 담은 줄이 있어야 합니다.
  8. /root/op-scale/scale-audit.tsv<CRD 이름><탭><기대 분류> 세 줄을 적으세요 — shards.scale.labhub.iook, blocks.scale.labhub.ionosel, relays.scale.labhub.iook 입니다. /root/op-scale/scale-audit.sh 는 이 표를 읽어 CRD 를 ok·nosel·broken 으로 분류하고 맞으면 OK …, 틀리면 MISMATCH … 를 표준출력에만 찍으며 한 줄이라도 틀리면 0 이 아닌 코드로 끝나야 합니다. 먼저 고치기 전에 한 번 돌려 /root/op-scale/scale-audit-before.txt 에 남기고, 그다음 /root/op-scale/relay-crd-fixed.yaml 로 오타를 고쳐 적용한 뒤 kubectl -n op-scale scale relay r --replicas=4 가 이번에는 실제로 값을 바꾸는지 확인하고, 다시 돌린 출력을 /root/op-scale/scale-audit.txt 에 저장하세요.

참고

세 경로로 계약을 맺는다

/root/op-scale/shard-crd.yaml 에 CRD shards.scale.labhub.io 를 쓰세요 — 그룹 scale.labhub.io, 종류 Shard, 복수형 shards, 버전 v1 하나(served·storage 모두 참), spec.replicas(integer)·status.replicas(integer)·status.selector(string) 를 스키마에 두고, subresourcesstatus: {}scale 을 켭니다. scale 의 세 경로는 각각 .spec.replicas·.status.replicas·.status.selector 입니다. 적용한 뒤 클러스터에 등록된 세 경로를 /root/op-scale/scale-paths.txt<이름>=<값> 세 줄로 저장하세요(specReplicasPath=..., statusReplicasPath=..., labelSelectorPath=...).

세 경로는 API 서버에게 '이 타입에서 원하는 수는 어디, 실제 수는 어디, 파드를 고르는 셀렉터는 어디' 를 알려 주는 계약입니다. 경로는 점으로 시작하는 JSONPath 이고 점 표기만 허용됩니다(배열 표기는 쓸 수 없습니다). 등록된 값은 kubectl get crd shards.scale.labhub.io -o jsonpath 로 꺼내면 손으로 옮겨 적는 실수를 피할 수 있습니다.

scale 하위 리소스는 무엇으로 보이나

네임스페이스 op-scale 를 만들고 /root/op-scale/shard-a.yaml 에 Shard a 를 쓰세요 — spec.replicas 는 2 입니다. 적용한 뒤 kubectl -n op-scale get shard a --subresource=scale -o yaml 의 출력을 그대로 /root/op-scale/scale-initial.yaml 에 저장하세요.

같은 오브젝트를 다른 창으로 들여다보는 것이 하위 리소스입니다. 돌아오는 것은 Shard 가 아니라 autoscaling/v1 의 Scale 오브젝트이고, spec.replicas 한 칸과 status.replicas 한 칸만 들어 있습니다. 아직 아무도 status 를 쓰지 않았으니 status 쪽 숫자가 무엇일지 예상해 보세요.

kubectl scale 이 무엇을 바꾸는가

kubectl -n op-scale scale shard a --replicas=5 로 Shard a 의 원하는 수를 5 로 올리세요. 그다음 scale 하위 리소스에서 두 숫자를 꺼내 /root/op-scale/after-scale.txtspec.replicas=5status.replicas=0 두 줄로 저장하세요.

kubectl scale 은 Deployment 전용 명령이 아닙니다 — scale 하위 리소스가 켜진 모든 타입에 그대로 동작합니다. 두 숫자가 다르게 나오는 이유를 생각해 보세요. specReplicasPath 는 사용자가 쓰는 자리이고 statusReplicasPath 는 컨트롤러가 쓰는 자리인데, 이 클러스터에는 Shard 를 돌보는 컨트롤러가 없습니다.

컨트롤러 자리를 손으로 채운다

컨트롤러가 할 일을 대신해 status 를 쓰세요 — kubectl -n op-scale patch shard a --subresource=statusstatus.replicas 를 5 로, status.selectorapp=shard-a 로 채웁니다. 그다음 scale 하위 리소스 출력을 /root/op-scale/scale-ready.yaml 에 저장하세요.

status 는 별도 엔드포인트라 보통의 patch 로는 써지지 않습니다. --subresource=status 를 붙여야 그 창으로 들어갑니다. 셀렉터는 문자열 한 줄이고 라벨 셀렉터 문법(키=값)을 그대로 씁니다 — 이 값이 나중에 HPA 가 파드를 세는 기준이 됩니다.

HPA 를 커스텀 리소스에 건다

/root/op-scale/shard-hpa.yamlautoscaling/v2 HPA shard-a-hpa 를 쓰세요 — scaleTargetRef 는 apiVersion scale.labhub.io/v1, kind Shard, name a 이고 minReplicas 2, maxReplicas 10, 지표는 Resource cpu 의 Utilization 70 입니다. 적용하고 조건이 채워질 때까지 기다린 뒤 /root/op-scale/hpa-status.txt 에 두 줄로 저장하세요 — AbleToScale=<상태>/<이유>REFERENCE=<kubectl get hpa 의 REFERENCE 칸>.

HPA 컨트롤러는 대상의 종류를 몰라도 됩니다. scale 하위 리소스만 읽을 수 있으면 붙습니다. 조건은 kubectl -n <ns> get hpa <이름> -o jsonpath 로 꺼낼 수 있고, 지표 서버가 없는 이 환경에서는 TARGETS 가 계속 알 수 없음으로 남습니다 — 그것과 '대상을 읽을 수 있는가' 는 다른 이야기입니다.

셀렉터 경로를 빼면 HPA 가 조용히 멈춘다

/root/op-scale/block-crd.yaml 에 CRD blocks.scale.labhub.io(종류 Block, 복수형 blocks)를 쓰되 scale 에 labelSelectorPath넣지 않습니다. /root/op-scale/block-b.yaml 로 Block b(spec.replicas 3)를, /root/op-scale/block-hpa.yaml 로 HPA block-b-hpa(minReplicas 1, maxReplicas 6, cpu 70)를 만들어 적용하고, 조건이 채워지면 /root/op-scale/noselector.txtScalingActive=<상태>/<이유>MESSAGE=<조건 메시지> 두 줄로 저장하세요.

앞 단계와 무엇이 달라지는지가 핵심입니다. 대상을 읽는 것(AbleToScale)은 성공하는데 조정이 시작되지 않습니다. HPA 는 목표 사용률을 계산하려면 '이 워크로드의 파드가 어느 것인지' 를 알아야 하는데, 그 답을 주는 자리가 바로 빠뜨린 그 경로입니다. 조건 메시지를 그대로 읽어 보세요.

성공했다고 답하면서 아무것도 안 한다

/root/op-scale/relay-crd.yaml 에 CRD relays.scale.labhub.io(종류 Relay, 복수형 relays)를 쓰되 specReplicasPath 를 일부러 .spec.replica(끝의 s 를 뺀 오타)로 적습니다. /root/op-scale/relay-r.yaml 로 Relay r(spec.replicas 2)을 만들어 적용하고, kubectl -n op-scale scale relay r --replicas=4kubectl -n op-scale get relay r --subresource=scale -o yaml 을 차례로 실행해 그 결과를 /root/op-scale/typo-report.txt 에 모으세요 — scale-rc=<종료 코드>, spec.replicas=<명령 뒤 실제 값>, 그리고 두 번째 명령의 오류 문장을 그대로 담은 줄이 있어야 합니다.

이 단계는 '무슨 일이 일어났는가' 가 아니라 '아무 일도 일어나지 않았는데 왜 성공처럼 보이는가' 를 보는 자리입니다. 종료 코드와 실제 값을 따로 적어 두면 둘이 어긋난다는 것이 한눈에 보입니다. 두 번째 명령의 오류는 표준 오류로 나가니 2>&1 로 함께 받으세요.

조용한 고장을 잡아내는 점검표를 만든다

/root/op-scale/scale-audit.tsv<CRD 이름><탭><기대 분류> 세 줄을 적으세요 — shards.scale.labhub.iook, blocks.scale.labhub.ionosel, relays.scale.labhub.iook 입니다. /root/op-scale/scale-audit.sh 는 이 표를 읽어 CRD 를 ok·nosel·broken 으로 분류하고 맞으면 OK …, 틀리면 MISMATCH … 를 표준출력에만 찍으며 한 줄이라도 틀리면 0 이 아닌 코드로 끝나야 합니다. 먼저 고치기 전에 한 번 돌려 /root/op-scale/scale-audit-before.txt 에 남기고, 그다음 /root/op-scale/relay-crd-fixed.yaml 로 오타를 고쳐 적용한 뒤 kubectl -n op-scale scale relay r --replicas=4 가 이번에는 실제로 값을 바꾸는지 확인하고, 다시 돌린 출력을 /root/op-scale/scale-audit.txt 에 저장하세요.

점검표의 값어치는 '고치기 전' 출력이 증명합니다. 고친 뒤 전부 OK 인 것만 남겨 두면 이 검사가 무엇을 잡을 수 있는지 아무도 모릅니다. 분류는 CRD 정의만 봐서는 부족합니다 — 경로가 실제 오브젝트에 닿는지는 scale 하위 리소스를 한 번 읽어 봐야 알 수 있습니다. 스크립트가 파일을 직접 쓰면 다시 돌릴 때 산출물을 덮어쓰니 표준출력으로만 내보내세요.