LabHub
学习 学习路径 课程

CRD 与 Operator

删了父对象,子对象却还在 - 属主引用与删除传播

在 LabHub 中继续学习

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

목표

소유자 참조의 네 필드가 각각 무엇을 결정하는지 직접 만들어 확인하고, --cascade 세 가지의 차이를 오브젝트로 증명한 뒤, Terminating 에 멈춘 오브젝트를 진단하는 보고서를 만든다.

왜 중요한가

오퍼레이터가 만드는 자원은 혼자 살지 않는다. 부모 하나가 사라지면 그 아래 붙은 것들도 함께 정리돼야 하는데, 쿠버네티스에는 이 일을 하는 참조 카운트가 없다. 대신 자식이 부모를 가리키는 ownerReferences 한 줄이 있고, 가비지 컬렉터가 그 참조를 따라 정리한다. 그래서 참조를 잘못 쓰면 고장이 조용히 난다 — uid 가 틀리면 방금 만든 자식이 몇 초 만에 사라지고, 네임스페이스를 넘으면 경고 이벤트 하나만 남기고 자식이 정리된다. 반대로 파이널라이저는 정리가 끝날 때까지 삭제를 붙잡아 두는 장치라, 그 정리를 할 컨트롤러가 없으면 오브젝트가 영원히 Terminating 에 머문다. 운영자가 실제로 마주치는 화면은 대개 이 둘 중 하나이고, 둘 다 원인을 알아내는 절차가 필요하다.

단계

  1. /root/op-ownership/site-crd.yaml 에 CRD sites.own.labhub.io 를 쓰세요 — 그룹 own.labhub.io, 종류 Site, 복수형 sites, 버전 v1 하나이고 스키마에는 spec.region(string) 만 둡니다. 네임스페이스 op-own 를 만들고 /root/op-ownership/site-alpha.yaml 로 Site alpha(region kr-east)를 적용한 뒤, 그 오브젝트의 metadata.uid/root/op-ownership/alpha-uid.txt 에 한 줄로 저장하세요.
  2. /root/op-ownership/alpha-good.yaml 에 ConfigMap alpha-good(네임스페이스 op-own)을 쓰되 ownerReferences 에 Site alpha 를 적으세요 — apiVersion own.labhub.io/v1, kind Site, name alpha, uid 는 1단계에서 받은 실제 값입니다. /root/op-ownership/alpha-bad.yaml 에는 같은 내용의 ConfigMap alpha-bad 를 쓰되 uid 만 00000000-0000-0000-0000-000000000000 으로 적습니다. 둘 다 적용하고 잠시 기다린 뒤 kubectl -n op-own get cm 의 출력을 /root/op-ownership/gc-uid.txt 에 저장하세요.
  3. 네임스페이스 op-own-remote 를 만들고 /root/op-ownership/alpha-remote.yaml 에 ConfigMap alpha-remote 를 쓰세요 — 네임스페이스는 op-own-remote 인데 ownerReferencesop-own 에 있는 Site alpha 를 (실제 uid 로) 가리킵니다. 적용한 뒤 잠시 기다렸다가 kubectl -n op-own-remote get events 의 출력을 /root/op-ownership/xns-event.txt 에 저장하세요.
  4. /root/op-ownership/site-beta.yaml 로 Site beta(region kr-west)를 만들고, 그 자식으로 ConfigMap beta-1beta-2/root/op-ownership/beta-children.yaml 한 파일에 담아(둘을 --- 로 잇습니다) 실제 uid 로 소유자 참조를 걸어 적용하세요. 그다음 kubectl -n op-own delete site beta --cascade=background 로 지우고, 자식이 사라질 때까지 기다린 뒤 kubectl -n op-own get cm 출력을 /root/op-ownership/cascade-background.txt 에 저장하세요.
  5. /root/op-ownership/site-gamma.yaml 로 Site gamma(region jp-east)를 만들고 자식 ConfigMap gamma-1·gamma-2/root/op-ownership/gamma-children.yaml 에 담아 실제 uid 로 걸어 적용하세요. kubectl -n op-own delete site gamma --cascade=orphan 으로 지운 뒤 kubectl -n op-own get cm gamma-1 -o jsonpath='{.metadata.ownerReferences}' 의 결과를 /root/op-ownership/cascade-orphan.txtgamma-1-ownerrefs=<값, 비어 있으면 none> 한 줄로 저장하세요.
  6. /root/op-ownership/site-delta.yaml 로 Site delta(region us-west)를 만들고, /root/op-ownership/delta-child.yaml 로 ConfigMap delta-1 을 만드세요 — 소유자 참조에 실제 uid 와 함께 blockOwnerDeletion: true 를 넣고, 이 ConfigMap 자신에게 파이널라이저 own.labhub.io/hold 를 답니다. 그다음 kubectl -n op-own delete site delta --cascade=foreground --wait=false 를 실행하고, 잠시 뒤 kubectl -n op-own get site delta -o json/root/op-ownership/foreground-stuck.json 에 저장하세요. 이 Site 는 이 실습이 끝날 때까지 그대로 둡니다.
  7. /root/op-ownership/site-epsilon.yaml 로 Site epsilon(region eu-west)을 파이널라이저 own.labhub.io/drain 과 함께 만들고, 자식 ConfigMap epsilon-data 를 실제 uid 로 걸어 /root/op-ownership/epsilon-child.yaml 로 적용하세요. kubectl -n op-own delete site epsilon --wait=false 를 실행한 직후의 오브젝트를 /root/op-ownership/finalizer-pending.json 에 저장하고, 정리 작업(자식 ConfigMap 을 직접 지우기)을 한 뒤 무엇을 정리했는지 /root/op-ownership/epsilon-cleanup.txt 에 한 줄 이상 적으세요. 마지막으로 파이널라이저를 떼어 Site 가 실제로 사라지게 하세요.
  8. /root/op-ownership/stuck-report.sh 를 만드세요 — op-own 에서 (1) deletionTimestamp 가 찍힌 Site 를 STUCK Site/<이름> finalizers=<쉼표로 이은 목록> 으로, (2) blockOwnerDeletion 이 참인 소유자 참조를 가진 ConfigMap 을 BLOCKER ConfigMap/<이름> owner=<종류>/<이름> finalizers=<목록> 으로 찍고, 두 종류를 합쳐 정렬해 표준출력에만 내보냅니다. STUCK 줄이 하나라도 있으면 0 이 아닌 코드로, 없으면 NONE 을 찍고 0 으로 끝나야 합니다. 출력을 /root/op-ownership/stuck-report.txt 에 저장하고, 누가 무엇을 왜 막고 있는지 /root/op-ownership/diagnosis.txt 에 사람이 읽을 문장으로 적으세요(막고 있는 자식의 이름과 그 파이널라이저 이름이 반드시 들어가야 합니다).

참고

부모가 될 타입을 만든다

/root/op-ownership/site-crd.yaml 에 CRD sites.own.labhub.io 를 쓰세요 — 그룹 own.labhub.io, 종류 Site, 복수형 sites, 버전 v1 하나이고 스키마에는 spec.region(string) 만 둡니다. 네임스페이스 op-own 를 만들고 /root/op-ownership/site-alpha.yaml 로 Site alpha(region kr-east)를 적용한 뒤, 그 오브젝트의 metadata.uid/root/op-ownership/alpha-uid.txt 에 한 줄로 저장하세요.

소유자 참조에서 이름은 사람이 읽는 값이고 실제 열쇠는 uid 입니다. 같은 이름의 오브젝트가 지워졌다 다시 만들어지면 uid 는 달라지는데, 이 성질이 이어지는 단계들의 핵심이 됩니다. uid 는 kubectl get … -o jsonpath 로 꺼내세요.

uid 를 틀리면 가비지 컬렉터가 자식을 지운다

/root/op-ownership/alpha-good.yaml 에 ConfigMap alpha-good(네임스페이스 op-own)을 쓰되 ownerReferences 에 Site alpha 를 적으세요 — apiVersion own.labhub.io/v1, kind Site, name alpha, uid 는 1단계에서 받은 실제 값입니다. /root/op-ownership/alpha-bad.yaml 에는 같은 내용의 ConfigMap alpha-bad 를 쓰되 uid 만 00000000-0000-0000-0000-000000000000 으로 적습니다. 둘 다 적용하고 잠시 기다린 뒤 kubectl -n op-own get cm 의 출력을 /root/op-ownership/gc-uid.txt 에 저장하세요.

가비지 컬렉터는 참조의 이름이 아니라 uid 로 부모를 찾습니다. 찾지 못하면 '부모가 이미 사라진 자식' 으로 보고 정리합니다. 이 동작 때문에 컨트롤러가 uid 를 캐시해 두었다가 부모가 다시 만들어진 뒤 쓰면 방금 만든 자식이 곧바로 사라집니다. 기다릴 때는 고정 sleep 보다 사라질 때까지 도는 짧은 반복문이 안전합니다.

네임스페이스를 넘는 소유는 없다

네임스페이스 op-own-remote 를 만들고 /root/op-ownership/alpha-remote.yaml 에 ConfigMap alpha-remote 를 쓰세요 — 네임스페이스는 op-own-remote 인데 ownerReferencesop-own 에 있는 Site alpha 를 (실제 uid 로) 가리킵니다. 적용한 뒤 잠시 기다렸다가 kubectl -n op-own-remote get events 의 출력을 /root/op-ownership/xns-event.txt 에 저장하세요.

소유자 참조는 같은 네임스페이스 안에서만 맺어지고, 클러스터 범위 오브젝트만 네임스페이스 범위 자식을 가질 수 있습니다. 규칙을 어기면 API 서버가 apply 를 막지 않습니다 — 나중에 가비지 컬렉터가 처리하고 그 흔적을 이벤트로 남깁니다. 이벤트의 이유(REASON) 낱말을 그대로 읽어 보세요.

background 는 부모부터 지운다

/root/op-ownership/site-beta.yaml 로 Site beta(region kr-west)를 만들고, 그 자식으로 ConfigMap beta-1beta-2/root/op-ownership/beta-children.yaml 한 파일에 담아(둘을 --- 로 잇습니다) 실제 uid 로 소유자 참조를 걸어 적용하세요. 그다음 kubectl -n op-own delete site beta --cascade=background 로 지우고, 자식이 사라질 때까지 기다린 뒤 kubectl -n op-own get cm 출력을 /root/op-ownership/cascade-background.txt 에 저장하세요.

background 는 기본값입니다. API 서버가 부모를 즉시 지우고 자식 정리는 가비지 컬렉터에게 맡깁니다. 그래서 명령은 빨리 돌아오는데 자식은 잠깐 남아 있습니다 — 이 시간차가 '지웠는데 아직 보인다' 는 착각의 출처입니다.

orphan 은 자식을 남기고 끈만 끊는다

/root/op-ownership/site-gamma.yaml 로 Site gamma(region jp-east)를 만들고 자식 ConfigMap gamma-1·gamma-2/root/op-ownership/gamma-children.yaml 에 담아 실제 uid 로 걸어 적용하세요. kubectl -n op-own delete site gamma --cascade=orphan 으로 지운 뒤 kubectl -n op-own get cm gamma-1 -o jsonpath='{.metadata.ownerReferences}' 의 결과를 /root/op-ownership/cascade-orphan.txtgamma-1-ownerrefs=<값, 비어 있으면 none> 한 줄로 저장하세요.

orphan 은 자식을 지우지 않습니다. 대신 가비지 컬렉터가 자식에서 그 소유자 참조를 떼어 냅니다 — 안 떼면 부모가 없는 참조가 남아 다음 정리 때 자식이 사라지기 때문입니다. 오퍼레이터를 걷어 내면서 그 자원은 살려 두고 싶을 때 쓰는 방식입니다.

foreground 는 자식을 기다리다 멈춘다

/root/op-ownership/site-delta.yaml 로 Site delta(region us-west)를 만들고, /root/op-ownership/delta-child.yaml 로 ConfigMap delta-1 을 만드세요 — 소유자 참조에 실제 uid 와 함께 blockOwnerDeletion: true 를 넣고, 이 ConfigMap 자신에게 파이널라이저 own.labhub.io/hold 를 답니다. 그다음 kubectl -n op-own delete site delta --cascade=foreground --wait=false 를 실행하고, 잠시 뒤 kubectl -n op-own get site delta -o json/root/op-ownership/foreground-stuck.json 에 저장하세요. 이 Site 는 이 실습이 끝날 때까지 그대로 둡니다.

foreground 는 순서를 뒤집습니다 — 자식이 다 사라진 뒤에야 부모를 지웁니다. 그 대기를 표현하려고 API 서버가 부모에 파이널라이저를 하나 붙이는데, 그 이름이 무엇인지 저장한 JSON 에서 찾아보세요. 자식에게 파이널라이저가 있으면 그 자식이 사라지지 못하므로 대기가 끝나지 않습니다.

파이널라이저를 떼기 전까지 삭제는 끝나지 않는다

/root/op-ownership/site-epsilon.yaml 로 Site epsilon(region eu-west)을 파이널라이저 own.labhub.io/drain 과 함께 만들고, 자식 ConfigMap epsilon-data 를 실제 uid 로 걸어 /root/op-ownership/epsilon-child.yaml 로 적용하세요. kubectl -n op-own delete site epsilon --wait=false 를 실행한 직후의 오브젝트를 /root/op-ownership/finalizer-pending.json 에 저장하고, 정리 작업(자식 ConfigMap 을 직접 지우기)을 한 뒤 무엇을 정리했는지 /root/op-ownership/epsilon-cleanup.txt 에 한 줄 이상 적으세요. 마지막으로 파이널라이저를 떼어 Site 가 실제로 사라지게 하세요.

파이널라이저가 붙어 있으면 삭제 요청은 deletionTimestamp 만 찍고 멈춥니다. 오브젝트는 계속 조회되고 수정도 되지만 새로 만들 수는 없습니다. 컨트롤러가 하는 일이 바로 이 구간입니다 — 바깥 시스템을 정리하고 나서 자기 파이널라이저를 지웁니다. 목록에서 원소 하나를 빼려면 kubectl patch --type=json 의 remove 연산이 편합니다.

Terminating 에 멈춘 것을 진단한다

/root/op-ownership/stuck-report.sh 를 만드세요 — op-own 에서 (1) deletionTimestamp 가 찍힌 Site 를 STUCK Site/<이름> finalizers=<쉼표로 이은 목록> 으로, (2) blockOwnerDeletion 이 참인 소유자 참조를 가진 ConfigMap 을 BLOCKER ConfigMap/<이름> owner=<종류>/<이름> finalizers=<목록> 으로 찍고, 두 종류를 합쳐 정렬해 표준출력에만 내보냅니다. STUCK 줄이 하나라도 있으면 0 이 아닌 코드로, 없으면 NONE 을 찍고 0 으로 끝나야 합니다. 출력을 /root/op-ownership/stuck-report.txt 에 저장하고, 누가 무엇을 왜 막고 있는지 /root/op-ownership/diagnosis.txt 에 사람이 읽을 문장으로 적으세요(막고 있는 자식의 이름과 그 파이널라이저 이름이 반드시 들어가야 합니다).

진단의 핵심은 두 목록을 나란히 놓는 것입니다 — 멈춘 부모와, 그 부모를 붙잡을 수 있는 자식. 나이나 시각처럼 볼 때마다 달라지는 값은 출력에 넣지 마세요. 그래야 이 보고서를 파일로 남겨 두고 나중 결과와 견줄 수 있습니다.