TT Lab
블로그 시작하기
배우기 러닝패스 코스

쿠버네티스 전문가 — 장애를 진단하고 클러스터를 지킨다

컨트롤 플레인은 파일 네 장과 etcd 하나다

TT Lab 에서 이어서 보기

한 줄 요약

kubeadm 클러스터의 컨트롤 플레인은 /etc/kubernetes/manifests 의 YAML 네 장(etcd·API 서버·컨트롤러 매니저·스케줄러)과 그것들이 읽는 인증서·kubeconfig 파일이 전부입니다. kubelet 이 그 파일들을 정적 파드로 띄우므로, 컨트롤 플레인 장애의 진단과 복구는 kubectl 이 아니라 파일과 crictl 로 합니다.

왜 이게 필요했나

클러스터의 모든 상태는 etcd 하나에 있습니다. etcd 가 멈추거나 데이터를 잃으면 모든 컨트롤러가 멈추고, 지워진 객체는 어디에서도 되살아나지 않습니다. 그런데 etcd 와 API 서버를 띄우는 것도 결국 파일입니다. 한 줄이 틀리면 API 서버는 뜨지 못하고, 그 순간 운영자가 늘 쓰던 kubectl 은 connection refused 만 돌려줍니다. 그래서 컨트롤 플레인을 운영한다는 것은 세 가지 손을 갖는 일입니다 — 스냅숏을 뜨고 복원하는 손, kubectl 없이 정적 파드를 읽고 고치는 손, 인증서와 용량처럼 「천천히 다가오는」 고장을 미리 보는 손.

어떻게 동작하나

스냅숏과 복원. 저장은 살아 있는 서버에 붙는 etcdctl snapshot save, 파일의 확인과 복원은 서버 없이 파일을 다루는 etcdutl 입니다. etcd 3.6 에서 etcdctl snapshot restore 는 없어졌습니다(이 클러스터의 etcd 는 3.6.8). 공식 문서의 복원 순서는 API 서버를 모두 내리고 → 복원하고 → 다시 올리는 것입니다. 이 클러스터에서 API 서버를 내리고(30초), 1.5MB 스냅숏을 새 디렉터리에 풀고, etcd 매니페스트의 hostPath 를 바꾸고, API 서버를 되돌려 readyz 가 돌아오기까지 48초가 걸렸고, 지워졌던 네임스페이스는 같은 UID 로 돌아왔으며 사고 뒤에 쓴 ConfigMap 은 사라졌습니다(실측). 복원 뒤에는 etcd 멤버 ID 도 새로 바뀝니다. 하나가 더 남습니다 — kubeadm 은 ConfigMap kubeadm-config 에 etcd 데이터 디렉터리를 기억하고 있어서, 그것을 맞추지 않으면 다음 kubeadm upgrade 가 옛 디렉터리를 가리키는 매니페스트를 다시 씁니다(공식 문서의 경고).

정적 파드를 kubectl 없이 읽는 법. kubelet 은 매니페스트 디렉터리를 주기적으로 다시 읽어 바뀐 파드를 다시 띄웁니다. API 서버가 죽었을 때의 눈은 crictl ps -a --name kube-apiserver(재시작 횟수)와 crictl logs <ID> 입니다. 이 실습의 사고에서 API 서버는 etcd 의 피어 포트(2380)에 붙으려다 failed to connect to {Addr: "127.0.0.1:2380"} 을 되풀이했고, 고친 뒤 응답이 돌아오기까지 사고부터 65초였습니다. 스케줄러가 죽은 사고는 더 조용합니다. 새 파드가 Pending 인데 이벤트가 하나도 없습니다 — 자리가 없어 못 놓는 파드에는 스케줄러가 FailedScheduling 을 남기므로, 아무 말이 없다는 것은 판단할 스케줄러가 없다는 뜻입니다.

인증서. kubeadm 이 만든 leaf 인증서는 1년, CA 는 10년입니다. kubeadm upgrade apply 가 갱신해 주므로 1년에 한 번 이상 업그레이드하면 잊어도 되지만, 업그레이드를 미룬 클러스터는 어느 날 API 서버와 kubelet 이 함께 멈춥니다(인증서 만료로 8시간에서 12시간 복구한 공개 사례가 있습니다). kubeadm certs renew 는 파일만 바꿉니다. 이 클러스터에서 재 보니 API 서버의 서빙 인증서는 다시 띄우지 않아도 30초 안에 새 파일로 바뀌었지만(동적 재적재), kubeconfig 를 시작할 때 한 번 읽는 컨트롤러 매니저·스케줄러는 다시 띄워야 새 자격을 씁니다. 그리고 ~/.kube/config 는 init 때 admin.conf 를 복사한 것이라 갱신되지 않습니다.

용량. etcd 에는 백엔드 상한(기본 2GiB)이 있고, 닿으면 NOSPACE 경보를 걸어 클러스터 전체를 사실상 읽기 전용으로 만듭니다. 이 실습에서 상한을 10MB 로 낮추고 큰 ConfigMap 을 넣자 일곱 개째에 etcdserver: mvcc: database space exceeded 가 났고, 쓰레기 네임스페이스를 지우는 것조차 실패했습니다 — 삭제는 「삭제 중」 표시를 쓰는 데서 시작하기 때문입니다. 그동안 노드는 계속 Ready 로 보였습니다. 노드를 NotReady 로 바꾸는 것도 쓰기이기 때문입니다. etcd 문서의 순서대로 compact → defrag → alarm disarm 을 하자 DB 가 11MB 에서 7.1MB 로 줄고 쓰기가 돌아왔습니다(실측).

현장에서 만나는 모습

플랫폼 엔지니어 채용 공고는 「변경 전 etcd 스냅숏」 과 「정기 복원 훈련」 을 함께 요구합니다. 스냅숏 파일이 쌓여 있는 것과 그 파일로 클러스터가 돌아오는 것은 다른 사실이고, 그 사이에는 이 실습의 함정들(API 서버를 먼저 내리기, 새 디렉터리, kubeadm-config)이 있습니다. 이벤트가 폭주하거나 큰 ConfigMap·CRD 객체가 쌓여 etcd 가 차는 사고는 공개 사례 모음(k8s.af)에 여러 번 나옵니다. endpoint status 의 DB SIZE 를 지표로 보고 있었다면 상한 전에 알 수 있었던 사고들입니다.

다음 실습에서 할 것

etcd 상태를 읽고 스냅숏을 뜬 뒤, 지워진 네임스페이스를 같은 UID 로 복원합니다. API 서버와 스케줄러가 매니페스트 한 줄 때문에 멈춘 것을 crictl 로 진단하고, 인증서를 갱신해 실제로 쓰이게 하고, 꽉 찬 etcd 를 되살립니다.