Certified Kubernetes Administrator (CKA) #24 Troubleshooting 3: Control plane (apiserver/etcd/scheduler 다운), etcd 복구
앞의 두 트러블슈팅 글은 클러스터가 살아 있다는 전제 위에 있었습니다. #22의 Pod 장애도, #23의 노드 장애도 kubectl이 응답했기 때문에 describe와 logs로 증상을 읽을 수 있었습니다. 그런데 이번 글의 무대는 다릅니다. control plane 자체가 무너지면 kubectl이 통째로 멈춥니다. 진단의 손잡이였던 명령이 사라진 상태에서 원인을 좁혀야 합니다.
다행히 control plane에는 일관된 구조가 있습니다. kubeadm으로 세운 클러스터에서 apiserver, etcd, scheduler, controller-manager는 모두 static Pod로 돕니다. 이 한 가지 사실이 control plane 트러블슈팅 전체를 관통하는 열쇠입니다. static Pod의 동작 방식을 이해하면, apiserver가 죽어 kubectl이 멈춘 상황에서도 무엇을 봐야 하는지가 분명해집니다. 이번 글에서는 그 진단 도구와 증상별 흐름, 그리고 망가진 control plane을 되살리는 법을 정리하겠습니다.
진단의 출발점: control plane은 static Pod다 #
control plane 트러블슈팅을 풀려면 #2에서 다룬 한 가지 사실을 다시 꺼내야 합니다. kubeadm 클러스터에서 control plane 컴포넌트는 Deployment나 DaemonSet이 아니라 static Pod로 뜹니다. static Pod는 apiserver를 거치지 않고, 각 노드의 kubelet이 디스크의 매니페스트 디렉터리를 직접 감시하며 띄우는 Pod입니다. 매니페스트는 다음 위치에 있습니다.
ls /etc/kubernetes/manifests/
# etcd.yaml
# kube-apiserver.yaml
# kube-controller-manager.yaml
# kube-scheduler.yaml이 구조에서 두 가지 결론이 나옵니다. 첫째, kubelet이 이 파일을 직접 읽으므로 파일을 고치면 kubelet이 자동으로 Pod를 재생성합니다. kubectl apply는 필요 없습니다. 둘째, static Pod는 apiserver를 거치지 않으므로 apiserver가 죽어도 kubelet은 매니페스트를 계속 감시합니다. 즉 apiserver가 통째로 다운돼 kubectl이 멈춘 상황에서도, 노드에 들어가 매니페스트를 고치면 kubelet이 컴포넌트를 다시 띄웁니다.
그래서 control plane 트러블슈팅은 거의 항상 같은 자리로 내려갑니다. control plane 노드에 SSH로 들어가, 매니페스트 디렉터리와 kubelet 로그, 그리고 컨테이너 런타임을 직접 들여다보는 일입니다.
kubectl이 멈췄을 때의 진단 도구 #
apiserver가 죽으면 kubectl get도 kubectl describe도 응답하지 않습니다. 다음 같은 메시지가 전형적입니다.
The connection to the server 10.0.0.10:6443 was refused - did you specify the right host or port?이때는 kubectl을 버리고 노드 위의 도구로 내려갑니다. 세 가지가 핵심입니다.
crictl: 컨테이너를 직접 본다 #
crictl은 kubelet이 쓰는 CRI 런타임에 직접 말을 거는 명령입니다. apiserver를 거치지 않으므로 apiserver가 죽어도 동작합니다. control plane 컨테이너가 떠 있는지, 죽고 재시작을 반복하는지를 여기서 봅니다.
# 모든 컨테이너 상태 (중지된 것 포함)
crictl ps -a
# control plane 컨테이너만
crictl ps -a | grep -E 'apiserver|etcd|scheduler|controller'crictl ps에 apiserver 컨테이너가 보이지 않거나 Exited 상태로 재시작을 반복하면, apiserver가 뜨려다 실패하고 있다는 뜻입니다. 컨테이너 ID로 로그를 직접 읽습니다.
# 죽은 컨테이너의 로그 (실패 원인이 여기 찍힘)
crictl logs <container-id>journalctl: kubelet의 눈으로 본다 #
static Pod를 띄우는 주체는 kubelet입니다. 매니페스트에 오류가 있으면 kubelet이 그 사실을 로그에 남깁니다.
journalctl -u kubelet -f매니페스트의 YAML 문법 오류, 잘못된 플래그, 존재하지 않는 파일 경로 같은 문제는 kubelet 로그에 직접 드러납니다.
매니페스트 파일: 원인의 8할이 여기 있다 #
control plane이 뜨지 않는 문제는 대부분 매니페스트 자체의 결함에서 비롯됩니다. 셋을 의심합니다.
- YAML 문법 오타. 들여쓰기 어긋남, 콜론 누락, 따옴표 짝 불일치. kubelet이 파일을 파싱조차 못 하면 Pod가 아예 뜨지 않습니다.
- 잘못된 플래그나 값. 오타가 난 플래그, 틀린 포트, 존재하지 않는 인증서 경로. 컨테이너는 뜨다가 즉시 죽습니다.
- 잘못된 image 태그. 존재하지 않는 버전을 적으면 ImagePull로 막힙니다.
수정 전에 원본을 백업해 두는 습관이 안전합니다.
cp /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/kube-apiserver.yaml.bak증상 1: apiserver 다운 (kubectl 응답 없음) #
가장 다급한 증상입니다. kubectl이 통째로 멈추므로 클러스터를 조작할 수 없습니다. apiserver가 뜨지 않는 원인은 크게 셋입니다.
원인 A: 매니페스트 오류 #
/etc/kubernetes/manifests/kube-apiserver.yaml의 YAML 오타나 잘못된 플래그가 가장 흔합니다. 예를 들어 --etcd-servers 값을 틀리게 적거나, 인증서 경로에 오타가 나거나, 들여쓰기가 어긋난 경우입니다.
# 컨테이너가 떴다 죽기를 반복하면, 죽은 컨테이너 로그를 본다
crictl ps -a | grep apiserver
crictl logs <apiserver-container-id>로그에 어떤 플래그나 경로가 문제인지 찍힙니다. 매니페스트를 고쳐 저장하면 kubelet이 새 인자로 apiserver를 재생성합니다. 반영이 느리면 매니페스트를 디렉터리 밖으로 잠깐 옮겼다 되돌려 강제로 재로딩할 수 있습니다.
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
# 잠시 뒤 되돌린다
mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/원인 B: etcd 연결 불가 #
apiserver는 etcd가 살아 있어야만 뜹니다. etcd가 죽었거나 apiserver의 --etcd-servers 주소가 틀리면, apiserver는 etcd에 붙지 못해 기동 중에 죽습니다. 로그에 connection refused나 etcd 관련 오류가 보이면, 문제의 뿌리는 apiserver가 아니라 etcd일 수 있습니다. 이때는 아래 증상 2의 etcd 진단으로 넘어갑니다.
원인 C: 인증서 문제 #
apiserver는 etcd와 통신할 때, 그리고 클라이언트를 인증할 때 인증서를 씁니다. 인증서 경로가 틀렸거나 인증서가 만료되면 apiserver가 뜨지 못합니다. 매니페스트의 --client-ca-file, --etcd-cafile, --tls-cert-file 같은 플래그가 가리키는 파일이 실제로 존재하는지 확인합니다. 인증서 만료 자체는 #25에서 별도로 다루겠습니다.
증상 2: etcd 다운 #
etcd가 죽으면 클러스터의 상태 저장소가 사라집니다. apiserver는 etcd에 붙지 못해 함께 죽고, 결과적으로 kubectl도 멈춥니다. 즉 apiserver 다운으로 보이는 증상의 진짜 원인이 etcd인 경우가 잦습니다. 그래서 apiserver 로그에 etcd 연결 오류가 보이면 etcd부터 봅니다.
# etcd 컨테이너 상태와 로그
crictl ps -a | grep etcd
crictl logs <etcd-container-id>etcd가 뜨지 않는 원인은 둘입니다.
- 데이터 디렉터리 문제.
--data-dir이 가리키는 디렉터리(보통/var/lib/etcd)가 손상됐거나, 매니페스트의 hostPath가 잘못된 경로를 가리키는 경우입니다. 디스크가 가득 차 etcd가 쓰기를 못 하는 경우도 있습니다. - 인증서 문제. etcd도 자체 인증서로 통신합니다.
/etc/kubernetes/pki/etcd/의 인증서 경로가 틀렸거나 파일이 없으면 etcd가 뜨지 못합니다.
매니페스트 오류로 etcd가 죽었다면 /etc/kubernetes/manifests/etcd.yaml을 고쳐 되살립니다. 데이터 자체가 손상돼 etcd가 살아나지 못한다면, 이때가 #7에서 익힌 스냅샷 복구가 필요한 순간입니다.
etcd 데이터 손상 시 복구 #
스냅샷이 있다면 복구 흐름은 #7과 동일합니다. 떠 둔 스냅샷을 새 data-dir로 풀어낸 뒤, etcd 매니페스트의 hostPath를 그 디렉터리로 바꿔 kubelet이 새 데이터로 etcd를 재기동하게 합니다.
# 1) 스냅샷을 새 디렉터리로 복원 (오프라인 작업, 인증서 불필요)
ETCDCTL_API=3 etcdctl snapshot restore /opt/snapshot.db \
--data-dir=/var/lib/etcd-restore
# 2) /etc/kubernetes/manifests/etcd.yaml의 hostPath를
# /var/lib/etcd에서 /var/lib/etcd-restore로 변경
# 3) 저장하면 kubelet이 etcd를 재생성한다
crictl ps | grep etcd복구 절차의 함정(restore에 인증서를 넣지 않기, 복원 뒤 hostPath 반영 빠뜨리지 않기)은 #7 etcd 백업과 복구에 정리해 두었으므로, 실기 전에 그 글로 손에 익히는 것을 권합니다.
증상 3: scheduler / controller-manager 다운 #
apiserver와 etcd가 죽으면 클러스터가 통째로 멈추므로 증상이 극적입니다. 반면 scheduler와 controller-manager가 죽으면 클러스터는 응답하지만 일부 동작만 조용히 멈춥니다. kubectl이 정상으로 돌기 때문에 오히려 알아채기 어렵습니다.
- kube-scheduler 다운. 새 Pod를 어느 노드에 둘지 결정하는 컴포넌트입니다. 죽으면 새로 만든 Pod가 계속 Pending에 머뭅니다. 이미 떠 있는 Pod는 멀쩡합니다.
kubectl get pods에서 Pending이 풀리지 않고,describe의 이벤트에 스케줄 관련 메시지가 없으면 scheduler를 의심합니다. - kube-controller-manager 다운. reconciliation loop를 돌려 원하는 상태로 수렴시키는 컴포넌트입니다. 죽으면 자가 치유가 멈춥니다. Deployment의 replica를 늘려도 새 Pod가 생기지 않고, 죽은 Pod가 다시 만들어지지 않으며, 노드가 빠져도 Pod가 재배치되지 않습니다.
진단과 복구는 apiserver와 같습니다. 둘 다 static Pod이므로, 해당 매니페스트를 보고 컨테이너 로그를 읽어 원인을 찾습니다.
# scheduler / controller-manager 컨테이너 상태
crictl ps -a | grep -E 'scheduler|controller'
crictl logs <container-id>
# 매니페스트
cat /etc/kubernetes/manifests/kube-scheduler.yaml
cat /etc/kubernetes/manifests/kube-controller-manager.yaml이 둘은 죽어도 클러스터가 즉시 멎지 않으므로 상대적으로 덜 다급하지만, “Pod가 Pending에서 안 풀린다"거나 “Deployment를 스케일해도 반응이 없다"는 증상의 뿌리가 여기에 있을 수 있다는 점을 기억해야 합니다.
static Pod를 고치는 법: 매니페스트 수정 → kubelet 자동 재기동 #
네 컴포넌트 모두 복구 방식이 같습니다. 매니페스트를 고치면 kubelet이 알아서 재기동합니다. 흐름을 한 번 더 정리하면 이렇습니다.
- control plane 노드에 들어가
/etc/kubernetes/manifests/의 해당 파일을 백업한다. crictl logs와journalctl -u kubelet으로 무엇이 문제인지 읽는다.- 매니페스트의 오타,플래그,경로,image를 고쳐 저장한다.
- kubelet이 변경을 감지해 Pod를 재생성한다.
kubectl apply는 필요 없다. - 반영이 느리면 매니페스트를 디렉터리 밖으로 잠깐 옮겼다 되돌려 강제로 재로딩한다.
# 강제 재로딩이 필요할 때
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
# 잠시 뒤
mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/여기서 흔한 실수가 둘 있습니다. 하나는 매니페스트를 고치고 나서 kubectl apply나 systemctl restart를 찾는 것입니다. static Pod는 파일 저장만으로 반영되므로 둘 다 필요 없습니다. 다른 하나는 수정 전에 백업을 안 떠서, 한 번 더 틀렸을 때 원래 값으로 못 돌아가는 것입니다. 고치기 전에 cp로 백업을 떠 두는 한 줄이 시간을 지킵니다.
증상별 진단 표 #
control plane 트러블슈팅을 증상에서 원인으로 좁히는 지도입니다.
| 증상 | 1차 의심 | 진단 명령 | 흔한 원인 |
|---|---|---|---|
| kubectl이 응답 없음(connection refused) | apiserver | crictl ps -a | grep apiserver, crictl logs | 매니페스트 오타, etcd 연결 불가, 인증서 경로 오류 |
| apiserver 로그에 etcd connection refused | etcd | crictl ps -a | grep etcd, crictl logs | etcd 매니페스트 오류, data-dir 손상, 인증서 |
| etcd가 살아나지 못함(데이터 손상) | etcd 데이터 | crictl logs <etcd> | data-dir 손상. 스냅샷 복구 필요(#7) |
| 새 Pod가 계속 Pending | scheduler | crictl logs <scheduler> | scheduler 매니페스트 오류로 다운 |
| 스케일,자가치유가 멈춤 | controller-manager | crictl logs <controller> | controller-manager 다운 |
| 매니페스트를 고쳐도 반영 안 됨 | kubelet | journalctl -u kubelet -f | 파싱 실패, kubelet 자체 다운(#23) |
표를 읽는 순서가 곧 진단 순서입니다. kubectl이 멈췄으면 apiserver를 보고, apiserver 로그가 etcd를 가리키면 etcd로 내려가고, etcd 데이터가 손상됐으면 스냅샷 복구로 넘어갑니다. kubectl이 살아 있는데 Pod만 Pending이거나 자가 치유가 멈췄으면 scheduler와 controller-manager를 의심합니다.
시험 포인트 #
- control plane 네 컴포넌트는 모두 static Pod입니다. 진단도 복구도
/etc/kubernetes/manifests/의 매니페스트를 중심으로 돕니다. - apiserver가 죽으면 kubectl이 멈춥니다. 이때는 노드에 들어가 crictl과 journalctl로 컨테이너와 kubelet 로그를 직접 봅니다.
- apiserver 다운의 흔한 원인은 매니페스트 오타, etcd 연결 불가, 인증서 경로 오류입니다. 로그가 어느 쪽인지 알려 줍니다.
- apiserver 다운으로 보여도 진짜 원인이 etcd인 경우가 많습니다. apiserver 로그에 etcd 연결 오류가 보이면 etcd부터 봅니다.
- etcd 데이터가 손상됐으면 #7의 스냅샷 복구로 되살립니다. restore에는 인증서가 필요 없고, 복원 뒤 hostPath 반영을 빠뜨리지 않는 것이 핵심입니다.
- scheduler가 죽으면 새 Pod가 Pending에 머물고, controller-manager가 죽으면 자가 치유가 멈춥니다. kubectl은 정상이라 알아채기 어렵습니다.
- static Pod는 매니페스트 저장만으로 kubelet이 재기동합니다.
kubectl apply나systemctl restart는 필요 없습니다. 수정 전 백업은 필수입니다.
정리 #
이번 글에서 잡은 것:
- control plane은 static Pod로 돈다는 한 가지 사실이 진단 전체를 관통합니다. kubectl이 멈춰도 노드에서 매니페스트를 고치면 kubelet이 되살립니다.
- 진단 도구: apiserver가 죽으면 kubectl 대신
crictl ps -a/crictl logs로 컨테이너를,journalctl -u kubelet으로 kubelet을, 매니페스트로 오타,플래그,경로를 봅니다. - 증상별 원인: apiserver 다운(매니페스트,etcd,인증서), etcd 다운(data-dir,인증서), scheduler 다운(Pod Pending), controller-manager 다운(자가 치유 정지).
- 복구: 매니페스트를 고쳐 저장하면 kubelet이 재기동합니다. etcd 데이터 손상은 #7의 스냅샷 복구가 답입니다.
control plane을 되살리는 감각은 #2 클러스터 아키텍처에서 다진 “어느 컴포넌트가 무슨 일을 하고, 죽으면 무엇이 멈추는가"라는 지도 위에 서 있습니다. 컴포넌트의 역할을 정확히 알면, 증상만 보고도 어느 매니페스트로 내려가야 할지가 바로 떠오릅니다.
다음: Troubleshooting 4 #
control plane까지 되살렸다면 클러스터의 골격은 다시 섰습니다. 마지막으로 남는 것은 연결과 권한 계층입니다. #25 Troubleshooting 4: Networking, DNS, RBAC, 인증서 만료에서는 Service와 Pod 사이의 통신이 끊기는 문제, CoreDNS 장애로 이름 해석이 안 되는 문제, RBAC 권한 부족으로 요청이 거부되는 문제, 그리고 인증서 만료로 apiserver와 etcd가 서로를 믿지 못해 통신이 끊기는 문제를 증상별로 좁혀 가며 정리하겠습니다. 트러블슈팅 도메인의 마지막 조각입니다.