쿠버네티스 운영 실무 · etcd 백업과 복구 · 이론
etcd — 스냅샷이 없으면 클러스터도 없다
한 줄 요약
kubectl get 으로 보이는 모든 것은 etcd 에서 읽어 온 값이다. etcd 를 잃으면 클러스터를 잃는다.
왜 이게 필요했나
컨트롤 플레인을 여러 대로 늘리면 안심이 된다. 저자가 홈랩을 3노드에서 7노드로 늘리면서 컨트롤 플레인을 3대로 만들고 etcd 멤버를 3개로 맞춘 기록이 있다. 멤버가 3이면 과반은 2이므로 한 대가 죽어도 클러스터는 살아 있다. 그런데 그 글의 마지막 줄에 이런 메모가 남아 있다. 쿼럼은 장애 대비이지 실수 대비가 아니다.
이 문장이 이 모듈의 출발점이다. 노드가 죽는 사고는 복제가 막아 준다. 그러나 kubectl delete ns payments 를 잘못 친 사고는 복제가 막아 주지 못한다. 삭제는 정상적인 쓰기이고, etcd 멤버 세 대가 그 삭제를 성실하게 세 벌 복제한다. 멤버가 100개여도 결과는 같다. 시간을 되돌리는 유일한 수단은 그 시점 이전의 스냅샷이다.
같은 글에는 이런 사례도 있다. 컨트롤 플레인 3대가 모두 정상인데 첫 노드가 죽자 kubectl 도 kubelet 도 전부 접속이 끊겼다. controlPlaneEndpoint 가 VIP 가 아니라 첫 노드의 물리 IP 로 박혀 있었기 때문이다. etcd 쿼럼과 API 가용성은 별개의 문제였다. 이처럼 "이중화했으니 괜찮다"는 감각은 자주 틀린다. 복구 절차를 손으로 한 번 해 본 사람만이 무엇이 실제로 지켜지는지 안다.
어떻게 동작하나
etcd 백업은 파일 복사가 아니라 일관된 시점 하나를 통째로 떠내는 것이다.
etcdctl snapshot save /backup/snap.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key프로덕션 etcd 는 클라이언트 인증까지 걸린 TLS 로만 말을 받는다. 그래서 인증서 세 개가 항상 따라다닌다. 경로를 외울 필요는 없다. etcd 는 kubeadm 클러스터에서 정적 파드로 뜨므로 kubectl describe pod etcd-<노드> -n kube-system 의 실행 인자에 정답이 그대로 적혀 있다. 이 랩 환경의 etcd 는 클라이언트 TLS 없이 127.0.0.1:2379 에서 평문으로 듣고 있으므로 세 플래그를 생략한다. 대신 정기 백업 CronJob 을 작성하는 단계에서 프로덕션 형태를 그대로 적게 된다.
뜬 다음에는 반드시 검증한다.
etcdutl snapshot status /backup/snap.db -w json# {"hash":3106878859,"revision":12450,"totalKey":1287,"totalSize":5779456}세 숫자가 전부 의미를 가진다. revision 은 이 스냅샷이 담고 있는 시점이고, totalKey 는 담긴 오브젝트의 규모이며, hash 는 파일 무결성이다. 0바이트짜리 파일이 매일 정상적으로 쌓이고 있는 백업 시스템은 생각보다 흔하다. 검증되지 않은 백업은 백업이 아니라 백업에 대한 희망이다.
복구는 그 반대 방향인데, 결정적으로 다른 점이 하나 있다. snapshot restore 는 데이터를 되돌리는 명령이 아니라 새 etcd 클러스터를 하나 만드는 명령이다. 그래서 --name, --initial-cluster, --initial-advertise-peer-urls, --initial-cluster-token 같은 신원 플래그를 받는다. 그리고 --data-dir 은 반드시 비어 있는 새 경로여야 한다. 기존 /var/lib/etcd 를 그대로 주면 실패하거나, 더 나쁘게는 살아 있는 데이터를 건드린다.
복구 순서도 정해져 있다.
| 순서 | 하는 일 | 왜 |
| --- | --- | --- |
| 1 | 스냅샷 검증 | 못 쓰는 파일로 클러스터를 세우면 두 번 죽는다 |
| 2 | kube-apiserver 중지 | 복구 중에 쓰기가 계속되면 안 된다 |
| 3 | snapshot restore | 새 데이터 디렉터리 생성 |
| 4 | etcd 매니페스트의 hostPath 를 새 경로로 교체 | 이걸 빼먹으면 옛 데이터로 다시 뜬다 |
| 5 | apiserver 기동 후 kubectl get 으로 확인 | 되살아났는지는 API 로 본다 |
현장에서 만나는 모습
첫째, 백업 주기가 곧 최대 손실량이다. 6시간마다 백업하면 최악의 경우 6시간치 변경이 사라진다. 복구 목표 시점(RPO)을 정하고 그에 맞춰 주기를 정하는 것이지, 반대가 아니다.
둘째, 백업 파일을 같은 디스크에 두는 실수. 노드 디스크가 죽는 사고에서 스냅샷도 같이 죽는다. 저자의 홈랩에서도 NAS 로 빼는 경로를 따로 만들었다. 보관 정책도 필요하다 — find /backup -mtime +7 -delete 한 줄이 없으면 몇 달 뒤 디스크가 찬다.
셋째, 복구 후 인증서와 시간. 스냅샷은 오브젝트만 담는다. 노드의 kubelet 인증서, 토큰, 그리고 그 사이에 만들어진 실제 컨테이너는 스냅샷 밖의 세계다. 복구 직후 클러스터가 어수선한 것은 정상이고, 잠시 뒤 컨트롤러들이 선언된 상태로 수렴시킨다.
다음 실습에서 할 것
첫 실습에서는 살아 있는 etcd 의 건강 상태와 멤버를 확인하고, 스냅샷을 두 번 떠서 revision 이 늘어나는 것을 보고, 정기 백업 CronJob 과 스냅샷 검증 스크립트를 만든다. 두 번째 실습에서는 오브젝트를 실제로 지운 뒤 스냅샷을 새 데이터 디렉터리로 복구하고, 그 데이터로 두 번째 etcd 프로세스를 직접 띄워 지워진 값이 되살아나는 것을 눈으로 확인한다.