LabHub
배우기 러닝패스 코스

CRDとオペレータ

古いバージョンを消したらオブジェクトが消えた - 保存バージョン移行の手順

LabHub 에서 이어서 보기

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

목표

두 버전 CRD 를 만들어 저장 버전을 올리고, 모든 오브젝트를 다시 쓴 뒤 status.storedVersions 를 정리하고 옛 버전을 내리는 이전 절차를 처음부터 끝까지 걸어 본다. 중간에 API 서버가 어디서 막아 주는지도 직접 본다.

왜 중요한가

CRD 는 코드가 아니라 API 계약이다. 필드를 하나 바꾸려면 새 버전을 내고, 옛 버전을 쓰던 사람들이 옮겨 갈 시간을 준 뒤에야 지울 수 있다. 이때 사람들이 가장 자주 빠뜨리는 것이 이미 저장된 오브젝트를 다시 쓰는 일이다. 저장 버전을 바꾸는 것은 '앞으로 저장할 표현' 만 바꾸는 일이라, 이미 etcd 에 들어 있는 바이트는 그대로 옛 표현이다. 그 상태에서 옛 스키마를 지우면 저장된 오브젝트를 읽을 방법이 없어진다. status.storedVersions 는 바로 그 사고를 막으라고 있는 기록이고, API 서버는 이 목록에 남아 있는 버전을 빼려는 시도를 실제로 거절한다. 이 실습은 그 안전장치를 한 번 부딪혀 본 다음, 올바른 순서로 통과한다.

단계

  1. /root/crd-version/tunnel-crd.yaml 에 CRD tunnels.net.labhub.io 를 쓰세요 — 그룹 net.labhub.io, 종류 Tunnel, 복수형 tunnels, 버전 두 개입니다. v1alpha1served: true·storage: true 이고 deprecated: truedeprecationWarning 을 붙이며 스키마에 spec.endpoint(string)·spec.port(integer) 만 둡니다. v1beta1served: true·storage: false 이고 두 필드에 더해 spec.mtu(integer, default: 1400)를 둡니다. 적용한 뒤 두 버전의 상태를 /root/crd-version/versions-initial.txt<버전> served=<참거짓> storage=<참거짓> 두 줄로 저장하세요.
  2. 네임스페이스 crd-version 를 만들고 /root/crd-version/tunnel-east.yaml(이름 t-east, endpoint 10.30.0.11, port 4789)과 /root/crd-version/tunnel-west.yaml(이름 t-west, endpoint 10.30.0.12, port 4789)을 v1alpha1 로 적용하세요. 적용할 때 돌아오는 경고를 표준 오류까지 받아 /root/crd-version/deprecation-warning.txt 에 저장하고, 그 시점의 status.storedVersions/root/crd-version/stored-initial.txt 에 한 줄로 저장하세요.
  3. 같은 오브젝트를 새 버전 창으로 읽으세요 — kubectl -n crd-version get tunnels.v1beta1.net.labhub.io t-east -o yaml 의 출력을 /root/crd-version/as-v1beta1.yaml 에 저장합니다. 그리고 /root/crd-version/conversion-note.txt 에 두 줄을 적으세요 — apiVersion=<읽은 apiVersion>mtu=<spec.mtu 값, 없으면 none>.
  4. /root/crd-version/tunnel-crd-beta-storage.yaml 에 1단계 CRD 를 복사하되 v1alpha1storage 를 거짓으로, v1beta1storage 를 참으로 바꿔 적용하세요. 적용한 뒤 status.storedVersions/root/crd-version/stored-after-flip.txt 에 한 줄로 저장하세요.
  5. 저장 버전을 실제로 옮기세요 — crd-version 의 모든 Tunnel 을 v1beta1 로 읽어 그대로 다시 쓰면 됩니다(kubectl get … -o json | kubectl replace -f -). 두 오브젝트를 모두 다시 쓴 뒤 kubectl -n crd-version get tunnels.v1beta1.net.labhub.io -o yaml 출력을 /root/crd-version/after-rewrite.yaml 에 저장하세요.
  6. 옛 버전을 spec.versions 에서 빼 보세요 — kubectl patch crd tunnels.net.labhub.io --type=json -p '[{"op":"remove","path":"/spec/versions/0"}]'. 명령의 출력과 종료 코드를 /root/crd-version/remove-blocked.txt 에 모으세요 — 첫 줄은 remove-rc=<종료 코드> 이고 그 아래에 서버가 낸 문장을 그대로 붙입니다.
  7. 이제 모든 오브젝트가 v1beta1 로 저장돼 있으니 기록을 정리하세요 — kubectl patch crd tunnels.net.labhub.io --subresource=status --type=mergestatus.storedVersions["v1beta1"] 하나로 만듭니다. 정리한 뒤의 값을 /root/crd-version/stored-pruned.txt 에 한 줄로 저장하세요.
  8. /root/crd-version/tunnel-crd-retire.yaml 에 4단계 CRD 를 복사하되 v1alpha1served 를 거짓으로 바꿔 적용하세요. 그다음 kubectl -n crd-version get tunnels.v1alpha1.net.labhub.io t-east 을 실행해 결과를 /root/crd-version/retired.txt 에 모으세요 — 첫 줄은 get-rc=<종료 코드> 이고 그 아래에 서버 문장을 붙입니다. 마지막으로 /root/crd-version/version-check.sh 를 만드세요 — 이 CRD 의 served·storage·stored 를 각각 한 줄씩 <이름>=<값> 으로 찍고, storedVersions 에 저장 버전이 아닌 값이 있으면 PENDING <버전> 을 찍고 0 이 아닌 코드로, 없으면 MIGRATED 를 찍고 0 으로 끝나야 합니다. 그 출력을 /root/crd-version/version-check.txt 에 저장하세요.

참고

한 타입에 두 버전을 함께 제공한다

/root/crd-version/tunnel-crd.yaml 에 CRD tunnels.net.labhub.io 를 쓰세요 — 그룹 net.labhub.io, 종류 Tunnel, 복수형 tunnels, 버전 두 개입니다. v1alpha1served: true·storage: true 이고 deprecated: truedeprecationWarning 을 붙이며 스키마에 spec.endpoint(string)·spec.port(integer) 만 둡니다. v1beta1served: true·storage: false 이고 두 필드에 더해 spec.mtu(integer, default: 1400)를 둡니다. 적용한 뒤 두 버전의 상태를 /root/crd-version/versions-initial.txt<버전> served=<참거짓> storage=<참거짓> 두 줄로 저장하세요.

한 CRD 안의 버전들은 같은 오브젝트를 다른 창으로 보는 것이라 저장은 딱 하나의 표현으로만 이루어집니다. 그래서 storage 가 참인 버전은 정확히 하나여야 하고, 나머지는 served 만 켜서 읽고 쓰는 창으로 남깁니다. deprecationWarning 은 그 버전으로 요청할 때 클라이언트에게 돌아가는 문장입니다.

폐기된 버전으로 만들면 경고가 돌아온다

네임스페이스 crd-version 를 만들고 /root/crd-version/tunnel-east.yaml(이름 t-east, endpoint 10.30.0.11, port 4789)과 /root/crd-version/tunnel-west.yaml(이름 t-west, endpoint 10.30.0.12, port 4789)을 v1alpha1 로 적용하세요. 적용할 때 돌아오는 경고를 표준 오류까지 받아 /root/crd-version/deprecation-warning.txt 에 저장하고, 그 시점의 status.storedVersions/root/crd-version/stored-initial.txt 에 한 줄로 저장하세요.

폐기 경고는 표준 출력이 아니라 표준 오류로 나옵니다. storedVersions 는 spec 이 아니라 status 에 있는 기록이고, '이 CRD 로 지금까지 실제로 저장된 적이 있는 버전' 을 뜻합니다. 아직 한 버전으로만 저장했다면 무엇이 들어 있을지 예상해 보세요.

새 버전으로 읽으면 무엇이 달라지나

같은 오브젝트를 새 버전 창으로 읽으세요 — kubectl -n crd-version get tunnels.v1beta1.net.labhub.io t-east -o yaml 의 출력을 /root/crd-version/as-v1beta1.yaml 에 저장합니다. 그리고 /root/crd-version/conversion-note.txt 에 두 줄을 적으세요 — apiVersion=<읽은 apiVersion>mtu=<spec.mtu 값, 없으면 none>.

conversion.strategy 를 따로 적지 않으면 None 이고, None 은 저장된 바이트를 그대로 두고 apiVersion 딱지만 바꿔 보여 줍니다. v1beta1 스키마에는 기본값이 있는 필드가 있는데, 기본값은 '저장할 때' 채워집니다. 이 오브젝트는 아직 어느 버전으로 저장돼 있는지 생각해 보세요.

저장 버전을 올린다

/root/crd-version/tunnel-crd-beta-storage.yaml 에 1단계 CRD 를 복사하되 v1alpha1storage 를 거짓으로, v1beta1storage 를 참으로 바꿔 적용하세요. 적용한 뒤 status.storedVersions/root/crd-version/stored-after-flip.txt 에 한 줄로 저장하세요.

이 한 줄을 바꾸면 '앞으로 저장할 때 쓸 표현' 만 달라집니다. 이미 etcd 에 들어 있는 오브젝트의 바이트는 그대로입니다. 그래서 storedVersions 는 줄어들지 않고 오히려 늘어납니다 — 그 목록이 무엇을 뜻하는지 다시 읽어 보세요.

모든 오브젝트를 한 번씩 다시 쓴다

저장 버전을 실제로 옮기세요 — crd-version 의 모든 Tunnel 을 v1beta1 로 읽어 그대로 다시 쓰면 됩니다(kubectl get … -o json | kubectl replace -f -). 두 오브젝트를 모두 다시 쓴 뒤 kubectl -n crd-version get tunnels.v1beta1.net.labhub.io -o yaml 출력을 /root/crd-version/after-rewrite.yaml 에 저장하세요.

다시 쓰기가 정말 일어났는지는 새 버전에만 있는 기본값이 증거가 됩니다. 저장 버전으로 쓰는 순간 API 서버가 그 필드를 채우기 때문입니다. 오브젝트가 수천 개인 실제 운영에서는 이 작업을 한꺼번에 돌리지 않고 나눠 돌립니다 — 매번 etcd 에 쓰기가 발생하기 때문입니다.

아직 지울 수 없다고 API 서버가 막는다

옛 버전을 spec.versions 에서 빼 보세요 — kubectl patch crd tunnels.net.labhub.io --type=json -p '[{"op":"remove","path":"/spec/versions/0"}]'. 명령의 출력과 종료 코드를 /root/crd-version/remove-blocked.txt 에 모으세요 — 첫 줄은 remove-rc=<종료 코드> 이고 그 아래에 서버가 낸 문장을 그대로 붙입니다.

이 거절은 잔소리가 아니라 안전장치입니다. 목록에 남아 있는 버전의 스키마를 지우면 그 표현으로 저장된 오브젝트를 읽을 방법이 없어집니다. 오류 문장이 어느 필드를 지목하는지 정확히 읽어 보세요 — 고쳐야 할 곳이 거기에 적혀 있습니다.

기록을 정리해야 문이 열린다

이제 모든 오브젝트가 v1beta1 로 저장돼 있으니 기록을 정리하세요 — kubectl patch crd tunnels.net.labhub.io --subresource=status --type=mergestatus.storedVersions["v1beta1"] 하나로 만듭니다. 정리한 뒤의 값을 /root/crd-version/stored-pruned.txt 에 한 줄로 저장하세요.

이 정리는 사람이 '다시 쓰기를 끝냈다' 고 선언하는 행위입니다. API 서버는 다시 쓰기를 대신 세어 주지 않습니다 — 그래서 5단계를 건너뛰고 여기부터 하면 그대로 사고가 됩니다. status 는 별도 창이라 보통의 patch 로는 닿지 않습니다.

옛 창을 닫고 이전 완료를 증명한다

/root/crd-version/tunnel-crd-retire.yaml 에 4단계 CRD 를 복사하되 v1alpha1served 를 거짓으로 바꿔 적용하세요. 그다음 kubectl -n crd-version get tunnels.v1alpha1.net.labhub.io t-east 을 실행해 결과를 /root/crd-version/retired.txt 에 모으세요 — 첫 줄은 get-rc=<종료 코드> 이고 그 아래에 서버 문장을 붙입니다. 마지막으로 /root/crd-version/version-check.sh 를 만드세요 — 이 CRD 의 served·storage·stored 를 각각 한 줄씩 <이름>=<값> 으로 찍고, storedVersions 에 저장 버전이 아닌 값이 있으면 PENDING <버전> 을 찍고 0 이 아닌 코드로, 없으면 MIGRATED 를 찍고 0 으로 끝나야 합니다. 그 출력을 /root/crd-version/version-check.txt 에 저장하세요.

served 를 끄면 그 버전의 엔드포인트 자체가 사라져서 옛 apiVersion 으로 보내는 요청은 리소스를 찾을 수 없다는 답을 받습니다. 저장된 오브젝트는 멀쩡히 있는데 그 창으로만 안 보이는 것입니다. 점검 스크립트는 표준출력에만 쓰고 파일을 직접 건드리지 않게 하세요 — 그래야 몇 번을 돌려도 같은 답이 나옵니다.