{"id":"IL-0059","org":"NRE Labs","date":"2018-12-04","docs":[],"title":{"en":"Single-master Kubernetes control plane kept dying because the kubelet restarted a healthy etcd on a failing kubeadm liveness probe","ko":"kubeadm이 만든 etcd 정적 파드의 liveness probe가 실패해 kubelet이 약 2분마다 etcd를 재시작했고, 단일 마스터 클러스터의 API 서버가 계속 끊겼다"},"domain":["kubernetes","gcp"],"impact":{"scope":"NRE Labs site; lessons could not be loaded","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Because all of the infrastructure is managed as code, the cluster could be rebuilt quickly, but destroying it before finding the cause also destroyed the evidence. Monitoring matters, especially with a single-node master. Read the restart counts in kubectl output, not just the Running status. Remember the fundamentals instead of expecting the issue to be more complicated than it is, and do not let the urgency of an outage push you into hasty decisions. Put up a maintenance page instead of leaving the site failing halfway.","ko":"모든 인프라를 코드로 관리한 덕에 클러스터를 금방 다시 만들 수 있었다. 하지만 원인을 모른 채 지우면 증거도 함께 사라진다. 단일 마스터로 운영한다면 모니터링이 더 중요하다. kubectl 출력에서 Running만 보지 말고 재시작 횟수까지 읽어야 한다. 복잡한 원인을 기대하기 전에 기본부터 의심하고, 장애의 급박함에 밀려 성급한 결정을 내리지 않는다. 사이트를 반쯤 망가진 채로 두지 말고 점검 페이지로 상황을 알려야 한다."},"related":[],"sources":[{"url":"https://oswalt.dev/2018/12/december-4-nre-labs-outage-post-mortem/","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"NRE Labs, the network automation lab site powered by the Antidote project and then in tech preview, ran on a single-master Kubernetes cluster built with kubeadm on GCP. kubeadm runs every master component, including a single-node etcd, as static pods managed by the kubelet, and gives etcd a liveness probe that reads the key foo with etcdctl.\n\nOn 4 December 2018 that probe began to fail although etcd was serving requests. With failureThreshold 8 and timeoutSeconds 15 the kubelet killed etcd about every two minutes, and the API server went down with it each time, so the site came back for a couple of minutes and then failed again. A kubelet restart looked like a fix for the same reason. The author then destroyed the whole cluster with terraform destroy and rebuilt it with Terraform and Ansible in about 30 minutes, which also destroyed the evidence; the problem returned on the new cluster.\n\nReading the container logs and the kubelet log showed that something outside etcd was sending it SIGTERM, and 8 × 15 seconds matched the two-minute cycle. The author removed the liveness probe from the kubeadm-generated etcd manifest and applied it between restarts, which ended the outage. Why the probe failed in the first place was never found.","ko":"NRE Labs는 Antidote 프로젝트 위에서 도는 네트워크 자동화 교육 사이트이고, 당시에는 SLA가 없는 테크 프리뷰 단계였다. 클러스터는 kubeadm으로 만든 단일 마스터 구성이었고, 마스터는 GCP의 가상 머신 한 대였다. kubeadm은 기본으로 etcd를 kubelet이 관리하는 단일 노드 정적 파드로 띄운다. 쿠버네티스의 모든 상태가 etcd에 있으므로 etcd가 멈추면 클러스터 전체가 멈춘다.\n\n12월 4일 etcd 정적 파드의 liveness probe가 실패하기 시작했다. kubelet은 약 2분마다 etcd에 SIGTERM을 보내 재시작했고, 그때마다 API 서버도 함께 끊겼다. 운영자는 kubelet 재시작 뒤 잠깐 살아난 것을 복구로 착각했고, 재시작 횟수 183도 놓쳤다. terraform과 ansible로 클러스터를 지우고 다시 만들면서 증거까지 없앴다. 새 클러스터에서도 같은 일이 나자 운영자는 etcd 로그와 kubelet 로그를 차례로 봤다. 그리고 probe 설정의 failureThreshold 8과 timeoutSeconds 15를 곱한 2분이 실제로 관찰한 재시작 주기와 같다는 것을 알아냈다.\n\n운영자가 같은 etcdctl 명령을 직접 실행하면 매번 종료 코드 0으로 끝나 실패를 재현하지 못했다. etcd 자체는 정상이고 kubelet만 비정상으로 판단한다고 보고, kubeadm이 만든 매니페스트에서 liveness probe를 통째로 지웠다. probe가 왜 무작위로 실패하기 시작했는지는 답을 찾지 못했다."},"trigger":{"en":"Unknown. The liveness probe that kubeadm writes into the etcd static pod manifest (an `etcdctl get foo` against 127.0.0.1:2379, failureThreshold 8, timeoutSeconds 15) started failing. The author first suspected GCP maintenance the night before, but could not reproduce the probe failure by hand and does not know why it began.","ko":"kubeadm이 etcd 정적 파드 매니페스트에 넣은 liveness probe가 실패하기 시작했다. 이 probe는 etcdctl로 foo 키를 조회한다. failureThreshold가 8, timeoutSeconds가 15라서 약 2분 동안 실패가 이어지면 kubelet이 etcd를 재시작한다. probe가 왜 실패하기 시작했는지는 끝내 밝히지 못했다."},"severity":"unknown","symptoms":["control-plane-unresponsive","readiness-probe-failures","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"The author saw lesson activity drop in the daily NRE Labs statistics during his morning routine.","ko":"운영자가 아침 회의 중 NRE Labs 일일 통계를 보다가 레슨 사용량이 떨어진 것을 보고 알았다. 클러스터 모니터링은 갖추지 않은 상태였고, 원문은 모니터링이 우선순위에서 밀려 있었다고 적었다."},"components":["etcd","kubelet","kube-apiserver"],"root_cause":"unknown","remediation":[{"text":{"en":"removed the liveness probe section from the kubeadm-generated etcd static pod manifest and applied it between etcd restarts","ko":"kubeadm이 만든 etcd 매니페스트에서 liveness probe 부분을 통째로 지우고, 다음 etcd 재시작 때 kubectl apply로 반영했다."}}],"action_items":[{"text":{"en":"Add monitoring, especially for the single-node master","ko":"단일 마스터 클러스터에 모니터링을 갖춘다."}},{"text":{"en":"Run a basic \"down for maintenance\" page in Google Cloud Functions that points to Twitter for updates, to redirect to during outages","ko":"장애가 나면 트위터를 보라고 안내하는 점검 페이지를 Google Cloud Functions에 만든다. 계획된 작업이든 아니든 일이 생기면 트래픽을 그 페이지로 돌린다."}}],"contributing":["single-point-of-failure","default-config-unchanged","manual-action-worsened","early-signal-ignored","monitoring-blind-spot"],"recurrence_of":null,"symptoms_text":{"en":"The daily NRE Labs statistics showed lesson activity collapsing, and kubectl could not reach the cluster: `The connection to the server 1.2.3.4:6443 was refused - did you specify the right host or port?`\n\nOn the master, a GCP virtual machine reachable over SSH, `docker ps` listed no API server container. Restarting the kubelet seemed to fix it: kubectl worked again, the syringe pod was restarted, a few lessons loaded, and an all-clear was posted. Shortly after, lesson activity dropped again.\n\n`kubectl get pods -n=kube-system` showed etcd and kube-apiserver as Running, but each with 183 restarts, which the author did not notice at first. `kubectl get nodes` also showed one worker on v1.11.3 while the others ran v1.12.1, which the author put down to a version typo in one of his playbooks when he removed and added a node earlier that week.\n\nEven on a freshly rebuilt cluster, `docker ps -a` on the controller showed etcd containers starting and exiting; etcd and the API server answered for about 1.5 to 2 minutes at a time. The etcd log showed a normal start, `embed: serving client requests on 127.0.0.1:2379`, and about two minutes later `pkg/osutil: received terminated signal, shutting down...`. The kubelet log said `Container {Name:etcd...truncated....} is dead, but RestartPolicy says that we should restart it.`","ko":"2018년 12월 4일 아침 8시 회의 중 운영자가 NRE Labs의 일일 통계를 보다가 레슨 사용량이 떨어진 것을 알아챘다.\n\nkubectl get pods -n=prod 는 \"The connection to the server 1.2.3.4:6443 was refused - did you specify the right host or port?\" 로 실패했다. SSH로 마스터가 도는 GCE 인스턴스에 들어가 보니 docker ps 출력에 API 서버가 없었다. kubelet을 재시작하자 kubectl이 다시 붙었고, syringe 파드를 재시작한 뒤 레슨 몇 개가 열렸다. 운영자는 밤사이 GCP 유지보수 뒤 마스터가 제대로 올라오지 않았던 것으로 판단하고, 트위터에 복구됐다고 알렸다.\n\n곧 레슨 사용량이 다시 떨어졌다. kubectl get nodes 를 보니 노드 하나만 v1.11.3이고 나머지는 v1.12.1이었다. 운영자는 그 주에 노드를 빼고 새로 넣으면서 플레이북에 버전을 잘못 적었을 것으로 봤다. kube-system 파드 목록에서 etcd와 kube-apiserver 파드의 RESTARTS가 183이었지만, 운영자는 모두 Running인 것만 보고 넘어갔다. 그 상태로 terraform destroy로 클러스터를 통째로 지우고 다시 만들었다. 재구축은 약 30분 뒤 끝났지만 새 클러스터에서도 곧 같은 문제가 나타났다.\n\nAPI 서버에 붙을 수 없어 SSH로 docker ps -a 를 보니 etcd 컨테이너가 계속 떴다가 내려갔다. etcd와 API 서버는 1.5분에서 2분쯤 쓸 수 있다가 끊겼다. etcd 로그에는 \"embed: serving client requests on 127.0.0.1:2379\" 다음 약 2분 뒤 \"pkg/osutil: received terminated signal, shutting down...\" 이 찍혔다. kubelet 로그에는 \"Container {Name:etcd...truncated....} is dead, but RestartPolicy says that we should restart it.\" 가 남았다."},"updated_at":"2026-11-10T00:00:39.003576Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0059","org_slug":"nre-labs"}