You raised the replicas and nothing happened - the scale subresource contract
한국어 원문으로 표시합니다.
목표
CRD 의 subresources.scale 을 세 경로로 제대로 맺고, 셀렉터 경로를 빠뜨렸을 때와 경로에 오타가 있을 때 각각 어떤 침묵이 생기는지 직접 만들어 본 뒤, 그 침묵을 잡아내는 점검 스크립트를 만든다.
왜 중요한가
kubectl scale 과 HPA 는 대상의 종류를 모른다. 이 둘이 아는 것은 오직 scale 하위 리소스라는 공통 창 하나뿐이고, CRD 는 그 창을 세 개의 JSONPath 로 열어 준다. 그래서 오퍼레이터를 만드는 사람이 이 세 줄을 제대로 쓰면 자기 타입이 쿠버네티스의 기존 자동 확장 도구 전부와 공짜로 이어지고, 잘못 쓰면 그 도구들이 오류 없이 아무 일도 하지 않는다. 이 침묵이 이 모듈의 주제다. 경로에 오타가 있으면 kubectl scale 은 성공했다고 답하면서 값을 바꾸지 않고, 셀렉터 경로가 없으면 HPA 는 대상을 읽는 데 성공한 채로 영원히 조정하지 않는다. 둘 다 사람이 화면을 들여다보기 전에는 드러나지 않으므로, 배포 파이프라인에 넣을 수 있는 점검 방법을 함께 갖춰야 한다.
단계
/root/op-scale/shard-crd.yaml에 CRDshards.scale.labhub.io를 쓰세요 — 그룹scale.labhub.io, 종류Shard, 복수형shards, 버전 v1 하나(served·storage 모두 참),spec.replicas(integer)·status.replicas(integer)·status.selector(string) 를 스키마에 두고,subresources에status: {}와scale을 켭니다. scale 의 세 경로는 각각.spec.replicas·.status.replicas·.status.selector입니다. 적용한 뒤 클러스터에 등록된 세 경로를/root/op-scale/scale-paths.txt에<이름>=<값>세 줄로 저장하세요(specReplicasPath=...,statusReplicasPath=...,labelSelectorPath=...).- 네임스페이스
op-scale를 만들고/root/op-scale/shard-a.yaml에 Sharda를 쓰세요 —spec.replicas는 2 입니다. 적용한 뒤kubectl -n op-scale get shard a --subresource=scale -o yaml의 출력을 그대로/root/op-scale/scale-initial.yaml에 저장하세요. kubectl -n op-scale scale shard a --replicas=5로 Sharda의 원하는 수를 5 로 올리세요. 그다음 scale 하위 리소스에서 두 숫자를 꺼내/root/op-scale/after-scale.txt에spec.replicas=5와status.replicas=0두 줄로 저장하세요.- 컨트롤러가 할 일을 대신해 status 를 쓰세요 —
kubectl -n op-scale patch shard a --subresource=status로status.replicas를 5 로,status.selector를app=shard-a로 채웁니다. 그다음 scale 하위 리소스 출력을/root/op-scale/scale-ready.yaml에 저장하세요. /root/op-scale/shard-hpa.yaml에autoscaling/v2HPAshard-a-hpa를 쓰세요 —scaleTargetRef는 apiVersionscale.labhub.io/v1, kindShard, namea이고 minReplicas 2, maxReplicas 10, 지표는 Resource cpu 의 Utilization 70 입니다. 적용하고 조건이 채워질 때까지 기다린 뒤/root/op-scale/hpa-status.txt에 두 줄로 저장하세요 —AbleToScale=<상태>/<이유>와REFERENCE=<kubectl get hpa 의 REFERENCE 칸>./root/op-scale/block-crd.yaml에 CRDblocks.scale.labhub.io(종류Block, 복수형blocks)를 쓰되 scale 에labelSelectorPath를 넣지 않습니다./root/op-scale/block-b.yaml로 Blockb(spec.replicas3)를,/root/op-scale/block-hpa.yaml로 HPAblock-b-hpa(minReplicas 1, maxReplicas 6, cpu 70)를 만들어 적용하고, 조건이 채워지면/root/op-scale/noselector.txt에ScalingActive=<상태>/<이유>와MESSAGE=<조건 메시지>두 줄로 저장하세요./root/op-scale/relay-crd.yaml에 CRDrelays.scale.labhub.io(종류Relay, 복수형relays)를 쓰되specReplicasPath를 일부러.spec.replica(끝의 s 를 뺀 오타)로 적습니다./root/op-scale/relay-r.yaml로 Relayr(spec.replicas2)을 만들어 적용하고,kubectl -n op-scale scale relay r --replicas=4와kubectl -n op-scale get relay r --subresource=scale -o yaml을 차례로 실행해 그 결과를/root/op-scale/typo-report.txt에 모으세요 —scale-rc=<종료 코드>,spec.replicas=<명령 뒤 실제 값>, 그리고 두 번째 명령의 오류 문장을 그대로 담은 줄이 있어야 합니다./root/op-scale/scale-audit.tsv에<CRD 이름><탭><기대 분류>세 줄을 적으세요 —shards.scale.labhub.io는ok,blocks.scale.labhub.io는nosel,relays.scale.labhub.io는ok입니다./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에 저장하세요.
참고
- 세 경로는
specReplicasPath·statusReplicasPath·labelSelectorPath이고 값은 점으로 시작합니다. - status 를 쓰려면
kubectl patch … --subresource=status가 필요합니다. - HPA 의 판정은
status.conditions의AbleToScale·ScalingActive두 조건에 남습니다. - 이 환경에는 지표 서버가 없어 HPA 의 TARGETS 는 계속 알 수 없음으로 남습니다 — 정상입니다.
- 흔한 실수: 셀렉터 경로를 빠뜨리고 HPA 가 붙었다고 안심한다.
- 흔한 실수:
kubectl scale의 종료 코드만 보고 값이 바뀌었다고 믿는다. - 참고: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
- 참고: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
세 경로로 계약을 맺는다
/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) 를 스키마에 두고, subresources 에 status: {} 와 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.txt 에 spec.replicas=5 와 status.replicas=0 두 줄로 저장하세요.
kubectl scale 은 Deployment 전용 명령이 아닙니다 — scale 하위 리소스가 켜진 모든 타입에 그대로 동작합니다. 두 숫자가 다르게 나오는 이유를 생각해 보세요. specReplicasPath 는 사용자가 쓰는 자리이고 statusReplicasPath 는 컨트롤러가 쓰는 자리인데, 이 클러스터에는 Shard 를 돌보는 컨트롤러가 없습니다.
컨트롤러 자리를 손으로 채운다
컨트롤러가 할 일을 대신해 status 를 쓰세요 — kubectl -n op-scale patch shard a --subresource=status 로 status.replicas 를 5 로, status.selector 를 app=shard-a 로 채웁니다. 그다음 scale 하위 리소스 출력을 /root/op-scale/scale-ready.yaml 에 저장하세요.
status 는 별도 엔드포인트라 보통의 patch 로는 써지지 않습니다. --subresource=status 를 붙여야 그 창으로 들어갑니다. 셀렉터는 문자열 한 줄이고 라벨 셀렉터 문법(키=값)을 그대로 씁니다 — 이 값이 나중에 HPA 가 파드를 세는 기준이 됩니다.
HPA 를 커스텀 리소스에 건다
/root/op-scale/shard-hpa.yaml 에 autoscaling/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.txt 에 ScalingActive=<상태>/<이유> 와 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=4 와 kubectl -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.io 는 ok, blocks.scale.labhub.io 는 nosel, relays.scale.labhub.io 는 ok 입니다. /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 하위 리소스를 한 번 읽어 봐야 알 수 있습니다. 스크립트가 파일을 직접 쓰면 다시 돌릴 때 산출물을 덮어쓰니 표준출력으로만 내보내세요.