쿠버네티스 운영 실무 · etcd 스냅샷이 답이 아닐 때 · 이론
스냅샷으로는 못 하는 복구
한 줄 요약
etcd 스냅샷은 클러스터 전체를 한 시점으로 되돌리는 도구라서 "이 네임스페이스만" 이나 "다른 클러스터로"
에는 쓸 수 없다. 그 자리를 오브젝트 수준 백업이 메우는데, 내보낸 것을 그대로 되돌려 넣으면 안 되고
특히 ownerReferences 를 남기면 복원한 오브젝트가 조용히 사라진다.
왜 스냅샷만으로는 모자란가
운영에서 실제로 오는 요청은 대개 이런 모양이다. "어제 오후에 실수로 지운 shop 네임스페이스만
되살려 주세요." 또는 "스테이징 클러스터에 운영과 같은 구성을 세워 주세요." 두 요청 모두 etcd
스냅샷으로는 답할 수 없다. 스냅샷 복구는 클러스터 전체를 그 시점으로 되돌리는 일이라, 그 사이에
일어난 다른 모든 변경까지 함께 사라진다. 지운 네임스페이스 하나를 위해 그날 하루의 배포를 전부
되돌릴 수는 없다. 그리고 스냅샷은 그 클러스터의 신원과 묶여 있어서 다른 클러스터로 옮길 수 없다.
두 방식의 자리가 다르다고 보는 편이 정확하다.
| | etcd 스냅샷 | 오브젝트 백업 |
| --- | --- | --- |
| 단위 | 클러스터 전체 | 고른 오브젝트 |
| 시점 | 통째로 한 시점 | 오브젝트마다 내보낸 시점 |
| 다른 클러스터로 | 안 된다 | 된다 |
| 부분 복구 | 안 된다 | 된다 |
| 컨트롤 플레인 설정 | 함께 복구된다 | 대상이 아니다 |
어떻게 동작하나
오브젝트 백업의 핵심은 내보내기가 아니라 정리다. kubectl get -o yaml 은 사용자가 쓴 것과 서버가
채운 것을 구분하지 않고 전부 내보낸다. 그중 되돌려 넣으면 안 되는 것들이 있다.
| 지울 것 | 왜 |
| --- | --- |
| metadata.uid | 오브젝트마다 새로 발급된다. 예전 값을 넣을 수 없다 |
| metadata.resourceVersion | 낙관적 잠금용 값이라 복원 시 충돌만 만든다 |
| metadata.creationTimestamp·generation | 서버가 정한다 |
| metadata.managedFields | 필드 소유권 이력이라 다른 클러스터에서 뜻이 없다 |
| metadata.ownerReferences | 복원한 클러스터에 그 uid 를 가진 주인이 없다 |
| status | 컨트롤러가 관측해서 다시 쓴다 |
| Service 의 spec.clusterIP·nodePort | 그 클러스터가 할당한 주소다 |
여기에 metadata.namespace 를 함께 지우는 습관이 유용하다. 복원할 곳을 매니페스트가 아니라 명령에서
정할 수 있게 되고, 그래야 같은 백업으로 검증용 네임스페이스에 먼저 세워 보는 절차를 만들 수 있다.
가장 위험한 것은 ownerReferences 다. 쿠버네티스는 이 참조로 의존 관계를 관리하고, 가비지 컬렉터는
주인이 없어진 오브젝트를 지운다. 그런데 참조는 이름만이 아니라 uid 까지 포함한다. 복원한
클러스터에서 그 uid 를 가진 오브젝트를 찾지 못하면 가비지 컬렉터는 주인이 사라진 것으로 판단하고
복원한 오브젝트를 지운다. 오류도, 알림도, 대부분의 경우 이벤트도 없다. ReplicaSet 을 통째로
백업했다가 복원 몇 초 뒤에 조용히 사라지는 사고가 여기서 나온다.
복원에는 순서도 있다. 네임스페이스가 먼저 있어야 그 안에 오브젝트를 넣을 수 있고, CRD 가 먼저
등록돼야 커스텀 리소스를 받아 준다. 순서를 틀리면 API 서버는 아예 종류를 모른다고 거절한다 —
매니페스트가 잘못된 것이 아니라 그 종류가 아직 없는 것이다.
마지막으로 PVC 는 데이터가 아니다. PersistentVolumeClaim 을 백업했다면 복원한 것은 "이만큼의
볼륨을 달라" 는 요청서뿐이고, 그 안에 있던 파일은 어디에도 없다. 데이터 백업은 볼륨 스냅샷이나
애플리케이션 수준 백업이라는 다른 문제다. 이 구분을 흐리면 복구 훈련을 통과하고도 실제 사고에서
데이터를 잃는다.
현장에서 만나는 모습
가장 흔한 사고는 복원했는데 몇 초 뒤 사라지는 것이다. 위의 ownerReferences 때문인데, 증상이
"복원이 실패했다" 가 아니라 "복원은 성공했는데 없다" 여서 원인을 찾는 데 오래 걸린다.
두 번째는 복원 순서를 적어 두지 않은 것이다. 평소에는 CRD 가 이미 있으니 아무 문제가 없다가,
새 클러스터에 통째로 복원하는 날에 처음 만난다. 그날은 시간이 없다.
세 번째는 검증을 사람이 눈으로 하는 것이다. 오브젝트가 있는지만 세면 레플리카가 2에서 1로 줄어든
복원도 성공으로 보인다. 그래서 복원 절차의 마지막 단계는 언제나 기계가 값을 대조하는 것이어야 한다.
이 실습 환경의 한계
이 클러스터에는 PersistentVolume 도 CSI 드라이버도 없어서 **PVC 와 데이터의 차이를 실물로 보여 줄 수는
없다.** 그 부분은 위의 설명으로 대신한다. 반대로 가비지 컬렉터는 진짜로 돌아서, 낡은 소유자 참조를
남긴 채 복원하면 그 오브젝트가 실제로 사라지는 것을 눈으로 볼 수 있다 — 실습에서 재 보니 몇 초 만에
사라졌다. 또 이 실습은 etcdctl 을 한 번도 쓰지 않는다. 같은 코스의 etcd 실습과 겹치지 않게 하려는
것이기도 하고, 오브젝트 백업이 실제로 etcd 를 건드리지 않는 작업이기도 하다.
다음 실습에서 할 것
CRD 하나와 네임스페이스 한 벌(Deployment·Service·ConfigMap·커스텀 리소스)을 만들고, 날것 그대로
내보내 무엇이 들어 있는지 본다. 그다음 지워야 할 필드를 걷어 내는 스크립트를 만들고, 네임스페이스를
통째로 지운 뒤 다른 네임스페이스로 되살린다. 이어서 낡은 소유자 참조를 남긴 오브젝트가 실제로
사라지는 것을 확인하고, CRD 없이 커스텀 리소스를 먼저 넣었을 때의 오류를 읽고 복원 순서를 적는다.
마지막으로 복원 목록과 실제 클러스터를 대조하는 검증 스크립트를 만든다.
참고 문서:
- https://kubernetes.io/docs/concepts/architecture/garbage-collection/
- https://kubernetes.io/docs/concepts/overview/working-with-objects/