LabHub
学习 学习路径 课程

Kubernetes 运维实务

请只把昨天删掉的命名空间恢复回来

在 LabHub 中继续学习

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

목표

종류가 섞인 네임스페이스 한 벌을 오브젝트 단위로 내보내고, 그 클러스터에서만 뜻이 있는 필드를 스크립트로 걷어 낸 뒤 다른 네임스페이스로 되살린다. 낡은 소유자 참조가 복원본을 조용히 지우는 것을 실제로 보고, 복원 순서를 틀렸을 때의 오류를 확인하고, 복원 검증을 기계가 하게 만든다.

왜 중요한가

etcd 스냅샷은 클러스터 전체를 한 시점으로 되돌리는 도구다. 그래서 어제 지워진 네임스페이스 하나만 되살려 달라는 요청에는 쓸 수 없고, 다른 클러스터로 옮길 수도 없다. 이럴 때 필요한 것이 오브젝트 수준 백업인데, 여기에는 스냅샷에 없는 어려움이 있다. 내보낸 오브젝트를 그대로 되돌려 넣으면 안 된다. uid·resourceVersion 은 그 클러스터에서만 뜻이 있고, status 는 컨트롤러가 다시 쓸 값이며, ownerReferences 의 uid 는 복원한 클러스터에 존재하지 않는다 — 이 마지막 것이 특히 고약하다. 가비지 컬렉터가 주인 없는 오브젝트로 보고 오류도 이벤트도 없이 지워 버린다.

단계

  1. /root/ops-backup/crd.yaml 에 CustomResourceDefinition widgets.ops.example.com 을 쓰세요 — 그룹 ops.example.com, 종류 Widget(복수형 widgets), 네임스페이스 범위, 버전 v1(served·storage 모두 참)이고 스키마에 정수 spec.size 가 있습니다. 그다음 /root/ops-backup/scene.yaml 에 네임스페이스 ops-shop 의 오브젝트 네 개를 한 파일에 쓰세요 — Deployment catalog(레플리카 2, 라벨 app: catalog, 이미지 nginx:1.27.3), 같은 이름의 ClusterIP Service catalog(셀렉터 app: catalog, 포트 80), ConfigMap catalog-config(currency: KRW, page-size: "20"), Widget w1(spec.size 3). 네임스페이스를 만들고 전부 적용하세요.
  2. 네 오브젝트를 각각 /root/ops-backup/raw/ 아래에 가공하지 않고 그대로 내보내세요 — deploy.yaml(Deployment catalog), svc.yaml(Service catalog), cm.yaml(ConfigMap catalog-config), widget.yaml(Widget w1). kubectl get <종류> <이름> -o yaml 의 출력을 손대지 말고 그대로 저장합니다.
  3. /root/ops-backup/clean.sh 를 만드세요 — 인자로 받은 YAML 파일 하나를 읽어 정리한 결과를 표준출력에만 찍습니다. 지워야 하는 것은 metadatauid·resourceVersion·creationTimestamp·generation·managedFields·ownerReferences·namespace, 최상위 status, 그리고 Service 의 spec.clusterIP·spec.clusterIPs·spec.ports[].nodePort 입니다. 이 스크립트로 raw/ 의 네 파일을 정리해 같은 이름으로 /root/ops-backup/clean/ 에 저장하세요.
  4. 네임스페이스 ops-shop 를 통째로 지우고, 지워진 것을 확인한 뒤 /root/ops-backup/gone.txt 에 한 줄로 기록하세요 — ops-shopNotFound. 지우기 전에 /root/ops-backup/raw/ 의 네 파일이 모두 있는지 먼저 확인하세요.
  5. 네임스페이스 ops-shop-restored 를 만들고 /root/ops-backup/clean/ 의 네 파일을 그 네임스페이스로 복원하세요(정리한 매니페스트에는 네임스페이스가 없으므로 명령에서 지정합니다). Deployment 파드 2개가 Running 이 될 때까지 기다린 뒤 /root/ops-backup/restored.tsv 에 네 줄로 저장하세요 — Deploymentcatalog, Servicecatalog, ConfigMapcatalog-config, Widgetw1 이고 종류 이름 오름차순입니다.
  6. /root/ops-backup/victim.yaml 에 ConfigMap orphan-victim 을 쓰세요 — 네임스페이스 ops-shop-restored, 데이터 note: restored-with-stale-owner, 그리고 metadata.ownerReferences이 클러스터에 없는 소유자를 적습니다(apiVersion: v1, kind: ConfigMap, name: catalog-config-old, uid 는 아무 UUID 나). 적용한 뒤 그 오브젝트가 사라질 때까지 조건 반복문으로 기다리세요. 그다음 /root/ops-backup/fixed.yaml 에 같은 데이터를 가진 ConfigMap orphan-fixed 를 ownerReferences 없이 써서 적용하고, /root/ops-backup/owner.tsv 에 두 줄로 결과를 남기세요 — orphan-victimgone, orphan-fixedalive.
  7. /root/ops-backup/gadget.yaml 에 커스텀 리소스 Gadget g1 을 쓰세요 — apiVersion: ops.example.com/v1, 네임스페이스 ops-shop-restored, spec.colorblue 입니다. 아직 그 종류의 CRD 가 없는 상태에서 먼저 적용해 보고 오류를 /root/ops-backup/order-error.txt 에 저장하세요(표준오류 포함). 그다음 /root/ops-backup/gadget-crd.yaml 에 CRD gadgets.ops.example.com 을 쓰고(그룹 ops.example.com, 종류 Gadget, 복수형 gadgets, 네임스페이스 범위, 버전 v1, 문자열 spec.color) 적용한 뒤 커스텀 리소스를 다시 적용하세요. 마지막으로 /root/ops-backup/restore-order.txt 에 복원 순서를 네 줄로 적으세요 — namespace, crd, custom-resource, workload 를 올바른 순서로 한 줄씩.
  8. /root/ops-backup/inventory.tsv 에 복원 목록을 네 줄로 적으세요 — <종류><이름><기대값> 이고, 기대값은 Deployment 는 spec.replicas, Service 는 spec.ports[0].port, ConfigMap 은 data 의 키 개수, Widget 은 spec.size 입니다. 그다음 /root/ops-backup/verify-restore.sh 를 만드세요 — 이 표를 읽어 ops-shop-restored 의 실제 오브젝트와 대조하고 맞으면 OK <종류>/<이름> <값>, 틀리면 MISMATCH <종류>/<이름> 기대=<값> 실제=<값>표준출력에만 찍고 한 줄이라도 틀리면 0 이 아닌 코드로 끝나야 합니다. 그 출력을 /root/ops-backup/verify.txt 에 저장하세요.

참고

되살릴 한 벌을 만든다

/root/ops-backup/crd.yaml 에 CustomResourceDefinition widgets.ops.example.com 을 쓰세요 — 그룹 ops.example.com, 종류 Widget(복수형 widgets), 네임스페이스 범위, 버전 v1(served·storage 모두 참)이고 스키마에 정수 spec.size 가 있습니다. 그다음 /root/ops-backup/scene.yaml 에 네임스페이스 ops-shop 의 오브젝트 네 개를 한 파일에 쓰세요 — Deployment catalog(레플리카 2, 라벨 app: catalog, 이미지 nginx:1.27.3), 같은 이름의 ClusterIP Service catalog(셀렉터 app: catalog, 포트 80), ConfigMap catalog-config(currency: KRW, page-size: "20"), Widget w1(spec.size 3). 네임스페이스를 만들고 전부 적용하세요.

백업 실습의 절반은 무엇을 백업할지 정하는 일입니다. 종류가 섞인 한 벌을 일부러 만드는 이유가 있습니다 — 워크로드와 설정과 커스텀 리소스는 내보낼 때 지워야 할 필드도, 복원 순서도 서로 다릅니다. CRD 는 클러스터 범위라 네임스페이스를 지워도 남습니다.

있는 그대로 내보낸다

네 오브젝트를 각각 /root/ops-backup/raw/ 아래에 가공하지 않고 그대로 내보내세요 — deploy.yaml(Deployment catalog), svc.yaml(Service catalog), cm.yaml(ConfigMap catalog-config), widget.yaml(Widget w1). kubectl get <종류> <이름> -o yaml 의 출력을 손대지 말고 그대로 저장합니다.

먼저 날것 그대로를 보는 것이 중요합니다. 여기에는 다음 단계에서 지워야 할 것들이 다 들어 있습니다 — 이 클러스터에서만 뜻이 있는 식별자, 컨트롤러가 채운 상태, 관리 필드 이력. 무엇이 왜 문제인지는 지우기 전에 한 번 봐야 압니다.

그 클러스터에서만 뜻이 있는 것을 지운다

/root/ops-backup/clean.sh 를 만드세요 — 인자로 받은 YAML 파일 하나를 읽어 정리한 결과를 표준출력에만 찍습니다. 지워야 하는 것은 metadatauid·resourceVersion·creationTimestamp·generation·managedFields·ownerReferences·namespace, 최상위 status, 그리고 Service 의 spec.clusterIP·spec.clusterIPs·spec.ports[].nodePort 입니다. 이 스크립트로 raw/ 의 네 파일을 정리해 같은 이름으로 /root/ops-backup/clean/ 에 저장하세요.

metadata.namespace 까지 지우는 이유는 복원할 곳을 명령 쪽에서 정하기 위해서입니다 — 그래야 같은 백업을 다른 네임스페이스나 다른 클러스터에 그대로 쓸 수 있습니다. yq 의 del 은 없는 경로를 지우라고 해도 오류를 내지 않으니 종류마다 스크립트를 나눌 필요가 없습니다.

사고를 만든다

네임스페이스 ops-shop 를 통째로 지우고, 지워진 것을 확인한 뒤 /root/ops-backup/gone.txt 에 한 줄로 기록하세요 — ops-shopNotFound. 지우기 전에 /root/ops-backup/raw/ 의 네 파일이 모두 있는지 먼저 확인하세요.

네임스페이스 삭제는 그 안의 오브젝트를 전부 가져갑니다. CRD 는 클러스터 범위라 남지만, 그 CRD 로 만든 커스텀 리소스는 네임스페이스와 함께 사라집니다. 삭제가 끝날 때까지 기다린 뒤 기록하세요 — kubectl delete ns 는 기다려 주지만, 확인은 직접 하는 편이 안전합니다.

다른 네임스페이스로 되살린다

네임스페이스 ops-shop-restored 를 만들고 /root/ops-backup/clean/ 의 네 파일을 그 네임스페이스로 복원하세요(정리한 매니페스트에는 네임스페이스가 없으므로 명령에서 지정합니다). Deployment 파드 2개가 Running 이 될 때까지 기다린 뒤 /root/ops-backup/restored.tsv 에 네 줄로 저장하세요 — Deploymentcatalog, Servicecatalog, ConfigMapcatalog-config, Widgetw1 이고 종류 이름 오름차순입니다.

복원 대상 네임스페이스를 명령에서 정하는 것이 오브젝트 수준 백업의 장점입니다 — 같은 백업으로 검증용 네임스페이스에 한 번 세워 보고 진짜 복구를 하는 절차를 만들 수 있습니다. 새 네임스페이스는 default 서비스 어카운트가 생기기까지 잠깐 걸립니다.

낡은 소유자를 남기면 복원한 것이 사라진다

/root/ops-backup/victim.yaml 에 ConfigMap orphan-victim 을 쓰세요 — 네임스페이스 ops-shop-restored, 데이터 note: restored-with-stale-owner, 그리고 metadata.ownerReferences이 클러스터에 없는 소유자를 적습니다(apiVersion: v1, kind: ConfigMap, name: catalog-config-old, uid 는 아무 UUID 나). 적용한 뒤 그 오브젝트가 사라질 때까지 조건 반복문으로 기다리세요. 그다음 /root/ops-backup/fixed.yaml 에 같은 데이터를 가진 ConfigMap orphan-fixed 를 ownerReferences 없이 써서 적용하고, /root/ops-backup/owner.tsv 에 두 줄로 결과를 남기세요 — orphan-victimgone, orphan-fixedalive.

백업에 ownerReferences 를 그대로 담아 두면 복원한 오브젝트가 조용히 사라집니다. 소유자의 uid 는 클러스터마다 다르고, 복원한 클러스터에서 그 uid 를 가진 오브젝트를 찾지 못하면 가비지 컬렉터는 주인이 없어진 것으로 보고 지웁니다. 오류도 이벤트도 남지 않아서 원인을 찾기 어려운 사고입니다.

순서를 틀리면 복원이 실패한다

/root/ops-backup/gadget.yaml 에 커스텀 리소스 Gadget g1 을 쓰세요 — apiVersion: ops.example.com/v1, 네임스페이스 ops-shop-restored, spec.colorblue 입니다. 아직 그 종류의 CRD 가 없는 상태에서 먼저 적용해 보고 오류를 /root/ops-backup/order-error.txt 에 저장하세요(표준오류 포함). 그다음 /root/ops-backup/gadget-crd.yaml 에 CRD gadgets.ops.example.com 을 쓰고(그룹 ops.example.com, 종류 Gadget, 복수형 gadgets, 네임스페이스 범위, 버전 v1, 문자열 spec.color) 적용한 뒤 커스텀 리소스를 다시 적용하세요. 마지막으로 /root/ops-backup/restore-order.txt 에 복원 순서를 네 줄로 적으세요 — namespace, crd, custom-resource, workload 를 올바른 순서로 한 줄씩.

종류를 모르는 API 서버는 매니페스트를 아예 받아 주지 않습니다. 오류 문장이 무엇을 먼저 설치하라고 말하는지 그대로 읽어 보세요. 복원 절차를 문서로 만들 때 이 순서를 적어 두지 않으면 복구 당일에 같은 오류를 만나고, 그때는 지금처럼 여유가 없습니다.

복원이 됐는지 사람이 눈으로 세지 않게 한다

/root/ops-backup/inventory.tsv 에 복원 목록을 네 줄로 적으세요 — <종류><이름><기대값> 이고, 기대값은 Deployment 는 spec.replicas, Service 는 spec.ports[0].port, ConfigMap 은 data 의 키 개수, Widget 은 spec.size 입니다. 그다음 /root/ops-backup/verify-restore.sh 를 만드세요 — 이 표를 읽어 ops-shop-restored 의 실제 오브젝트와 대조하고 맞으면 OK <종류>/<이름> <값>, 틀리면 MISMATCH <종류>/<이름> 기대=<값> 실제=<값>표준출력에만 찍고 한 줄이라도 틀리면 0 이 아닌 코드로 끝나야 합니다. 그 출력을 /root/ops-backup/verify.txt 에 저장하세요.

복원한 뒤 사람이 화면을 보고 다 있는지 세면 반드시 하나를 빠뜨립니다. 게다가 오브젝트가 있는 것과 값이 같은 것은 다른 문제입니다 — 이름만 세면 레플리카가 2에서 1로 줄어든 복원도 성공으로 보입니다. 스크립트가 파일을 직접 쓰면 채점기가 다시 돌릴 때 학생 산출물을 덮어쓰므로 표준출력으로만 내보내세요.