{"id":"IL-0040","org":"Saltside","date":"2017-04-26","docs":["https://github.com/kubernetes/kops/issues/1808","https://github.com/kubernetes/kops/pull/2260","https://github.com/DataDog/dd-agent/issues/3045","https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/"],"title":{"en":"Parallel Helm installs saturated small gp2 root volumes on a kops cluster and left all three workers NotReady with the kubelet or Docker down","ko":"비운영 쿠버네티스 클러스터에 Helm 차트를 한꺼번에 설치하자 작은 gp2 루트 볼륨의 IOPS 가 바닥나, 워커 노드 세 대에서 kubelet 이나 Docker 가 내려가 세 대 모두 NotReady 가 됐다"},"domain":["kubernetes","aws"],"impact":{"scope":"nonproduction cluster np.k8s.saltside.io: internal services unavailable, no new pods could be created, Helm unusable","users":"developers and CI builds using the nonproduction cluster","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"An ordinary deploy of the application could collapse the cluster, because the default 20GB gp2 root volumes could not keep up with parallel image pulls and container starts. Without node metrics and pod phase metrics, three NotReady workers raised no alert. A single Tiller replica can block the deploy that would fix an outage.","ko":"평소 하던 애플리케이션 배포만으로도 클러스터가 무너질 수 있었다. 워커 노드를 종료하는 방법은 효과가 있었지만 파드를 다른 노드로 옮기지 않으므로 운영 환경에서는 고객이 서비스를 못 쓰게 된다. 그래서 이런 장애의 표준 절차가 될 수 없다. 노드 상태, 파드 단계, 노드 리소스 지표가 없으면 이런 장애를 알아채지 못한다. kops 의 기본 20GB gp2 루트 볼륨은 IOPS 와 디스크 공간이 모두 모자랐다. 새로 만드는 파드 수를 조절할 수단이 있으면 문제를 풀지는 못해도 위험을 줄일 수 있고, 무언가 잘못됐을 때 제 손으로 클러스터를 마비시키는 일을 막을 수 있다."},"related":[],"sources":[{"url":"https://medium.com/saltside-engineering/our-first-kubernetes-outage-c6b9249cfd3a","kind":"blog","license":"unknown","archived":"https://web.archive.org/web/20221127113921/https://engineering.saltside.se/our-first-kubernetes-outage-c6b9249cfd3a"}],"summary":{"en":"Saltside runs several Kubernetes clusters. np.k8s.saltside.io is the nonproduction one, used for developer experiments and as the target of CI builds. The CI process builds the application's Helm charts for four markets, installs them on this cluster and runs helm test on each release. Running it collapsed the cluster's three t2.medium workers: pods went Pending and then Unknown, image pulls failed, Tiller crash-looped, deletes had no effect, and the nodes reported the kubelet or the container runtime as down. The SRE lead ended it by terminating the three workers and letting the auto scaling group replace them; the cluster was normal again after about 10 minutes. The post notes that this cannot be the standard fix, because pods are not drained to other nodes first.\n\nTo find the bottleneck, the team fixed the Datadog integration for the nonproduction AWS account and repeated the installs while watching EBS metrics. The EBS queue length shot up. kops had created the nodes with 20GB gp2 root volumes by default, gp2 IOPS grow with volume size, and df showed 90 percent of the disk in use. kops also did not support EBS optimization. With the root volumes raised to 120GB, and a DaemonSet that ran a sleep loop in common base images such as ruby:2.4 and alpine:3.2 so that they stayed pulled on every node, the charts installed without killing the kubelet. Other things still broke, so the cluster could still not handle that many container operations in parallel.\n\nOther findings from the review: Tiller failed to reschedule because of CPU limits although the nodes showed no CPU request problem, which still needs investigation; helm init runs Tiller as a single replica, so a crash-looping Tiller blocks the chart installs needed to deploy fixes; there was no way to throttle how many new pods are created; the Datadog agent did not yet support the pod phase metrics from kube-state-metrics; and the team did not know what the kubelet checks such as \"Disk Pressure\" test.","ko":"Saltside 의 SRE 팀장 Adam Hawkins 가 공개한 회사의 첫 쿠버네티스 장애 포스트모템이다. 장애가 난 곳은 개발자 실험과 CI 빌드 대상으로 쓰는 비운영 클러스터 np.k8s.saltside.io 였다. 클러스터 안의 서비스를 쓸 수 없었고, tiller 가 내려가 helm 명령이 모두 실패했다. 새 파드를 만들 수 없었고, Pending 이던 파드는 Unknown 이 되었으며, 삭제도 될 때가 있고 안 될 때가 있었다.\n\nCI 과정은 Efritin.com, Tonaton.com, Ikman.lk, Bikroy.com 네 시장의 차트를 만들어 클러스터에 설치하고 릴리스마다 helm test 를 실행한다. 이 과정을 돌리자 파드가 Running 에서 Pending 으로, 두 시간 뒤에는 Unknown 으로 바뀌었다. 담당자는 이미지 풀 과부하로 보고 리소스를 지우려 했지만 Helm 으로는 지울 수 없었고 kubectl delete 로도 지워지지 않았다. 노드를 확인하니 컨테이너 런타임이나 kubelet 이 내려가 있었고 SSH 접속도 되지 않았다. 워커 노드 세 대를 종료하고 오토스케일링 그룹이 새 인스턴스를 띄우게 하자 약 10분 뒤 모든 것이 정상으로 돌아왔다.\n\n이후 DataDog 연동을 고쳐 지표를 받으면서 같은 과정을 다시 돌렸다. 담당자는 디스크나 Docker 데몬을 병목으로 의심했고, 차트를 설치하는 동안 워커 노드의 EBS 대기열 길이가 급격히 치솟는 것을 확인했다. kops 는 기본으로 20GB gp2 EBS 볼륨을 썼고, gp2 의 IOPS 는 디스크 크기에 비례한다. df 로 본 디스크 사용률은 90%였다. 노드에는 IOPS 와 디스크 공간이 모두 모자랐다. kops 가 EBS 최적화를 지원하지 않는다는 사실도 드러났다. 루트 EBS 볼륨을 120GB 로 늘리고 공통 베이스 이미지를 노드에 미리 받아 둔 뒤 다시 돌리자 kubelet 이 죽지 않고 차트가 설치됐다. 하지만 다른 곳에서 문제가 생겨, 클러스터는 여전히 이만큼 많은 컨테이너 작업을 동시에 감당하지 못했다.\n\n팀은 다른 문제도 정리했다. 노드가 NotReady 가 되어도 알려 주는 모니터링이 없었다. 파드 단계별 개수 지표와 노드의 CPU, 메모리, 디스크 지표가 없었고, 이 AWS 계정의 CloudWatch 지표도 DataDog 에 들어오지 않았다. tiller 파드는 CPU 한도 때문에 다시 스케줄되지 못했는데, 노드 수준에서는 CPU 요청에 문제가 없었으므로 이유를 더 조사해야 한다. helm init 은 tiller 를 레플리카 하나로만 띄워서 고가용성을 갖추지 못했다. 팀은 kubelet 검사가 무엇을 확인하는지, 예를 들어 Disk Pressure 가 무엇인지 몰랐고, 새로 만드는 파드 수를 조절할 방법도 없었다."},"trigger":{"en":"A CI run installed the application's Helm charts for all four markets (Efritin.com, Tonaton.com, Ikman.lk and Bikroy.com) on the nonproduction cluster at the same time.","ko":"CI 과정을 로컬에서 돌려 시장 네 곳의 Helm 차트를 비운영 클러스터에 한꺼번에 설치했다. 그 결과 t2.medium 워커 노드 세 대가 여러 컨테이너의 이미지를 동시에 받고 컨테이너를 동시에 만들었다."},"severity":"unknown","symptoms":["pod-pending","image-pull-failures","node-not-ready","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"The SRE lead saw pods go from Running back to Pending while watching kubectl get pods during the CI run; no monitoring raised an alert, even when all three workers were not ready.","ko":"담당자가 CI 과정을 돌리며 watch kubectl get pods 로 지켜보다가 Running 이던 파드가 Pending 으로 바뀌는 것을 보았다. 두 시간 뒤 Unknown 상태의 파드를 보고서야 내부에서 무언가 확실히 잘못됐다고 판단했다. 워커 노드 세 대가 모두 NotReady 였지만 어떤 모니터링도 이를 잡지 못했다."},"components":["node-disk","kubelet","container-runtime","helm"],"root_cause":"capacity","remediation":[{"text":{"en":"terminated the three worker nodes and let the auto scaling group replace them","ko":"워커 노드 세 대를 종료하고 오토스케일링 그룹이 새 인스턴스를 띄우게 했고, 약 10분 뒤 클러스터가 정상으로 돌아왔다."}}],"action_items":[{"text":{"en":"Fix the Datadog integration for the nonproduction AWS account and repeat the installs with telemetry","ko":"비운영 AWS 계정과 DataDog 의 연동 문제를 고쳐 지표가 들어오게 하고, 그 지표를 보며 설치 과정을 다시 돌렸다."}},{"text":{"en":"Increase the worker root EBS volumes from 20GB to 120GB","ko":"워커 노드의 루트 EBS 볼륨을 20GB 에서 120GB 로 늘렸다."}},{"text":{"en":"Keep common base images pulled on every node with a DaemonSet until kops supports user hooks","ko":"ruby:2.4, alpine:3.2 같은 공통 베이스 이미지 안에서 sleep 루프만 도는 DaemonSet 을 배포해 노드에 이미지를 미리 받아 두었다. kops 가 사용자 훅을 지원할 때까지 쓰는 임시 방편이다."}},{"text":{"en":"Configure different allocation limits for the different layers in Kubernetes","ko":"쿠버네티스의 계층마다 할당 한도를 다르게 설정한다."}},{"text":{"en":"Discuss a highly available Tiller with the Helm team","ko":"tiller 가 크래시 루프에 빠지면 차트 설치와 업그레이드로 수정을 배포할 수 없으므로 tiller 고가용성을 Helm 팀과 논의한다."}},{"text":{"en":"Find a way to throttle the number of newly created pods","ko":"새로 만드는 파드 수를 조절할 방법을 찾는다."}}],"contributing":["default-config-unchanged","monitoring-blind-spot","early-signal-ignored","single-point-of-failure"],"recurrence_of":null,"symptoms_text":{"en":"While running the CI process on a local machine to test an application Helm chart, the SRE lead watched the pods with watch kubectl get pods. All pods reached Running, and then some went back to Pending. Two hours later the problem had grown and some pods showed the phase Unknown.\n\nkubectl describe pod showed pod events full of image pull errors from Docker. These were first put down to overload: the cluster was nonproduction, the nodes were t2.medium, and pulls from the official Docker registry had often failed before.\n\nClean-up did not work. Tiller, Helm's server-side component in kube-system, was in a crash loop, so Helm releases could not be deleted. Plain kubectl delete commands returned success but removed nothing. With --force --grace-period=0 the pods disappeared from kubectl get, but nobody checked whether the containers were gone in Docker. In kube-system some pods showed DeadNode. kubectl describe nodes showed some nodes reporting the container runtime down and others the kubelet down after failed checks, kubectl get nodes listed all three workers as not ready, and SSH connections to the instances timed out.\n\nNo monitoring reported any of it. There were no node CPU, memory or disk metrics, no metrics on pod phases such as Pending or Unknown, and no CloudWatch metrics in Datadog for this AWS account.","ko":"SRE 팀장이 애플리케이션 Helm 차트를 고친 뒤 자기 머신에서 CI 과정을 돌렸다. watch kubectl get pods 로 지켜보니 파드가 모두 Running 이 되었는데, 그중 일부가 다시 Pending 으로 바뀌었다. 그는 저절로 풀릴 일시적인 문제라고 보고 회의에 들어갔다.\n\n두 시간 뒤 다시 보니 문제는 더 커져 있었고 Unknown 상태의 파드가 보였다. kubectl describe pod 의 이벤트 로그에는 컨테이너 런타임인 Docker 의 이미지 풀 오류가 가득했다. kube-system 의 tiller 파드는 이유를 알 수 없이 크래시 루프에 빠져 helm 명령이 실패했다. kubectl delete 명령은 성공한 것처럼 끝났지만 실제로 지워진 것은 없었다.\n\nkubectl get pods -n kube-system 으로 보니 파드들이 여러 가지 비정상 상태였고 일부는 DeadNode 였다. kubectl describe nodes 의 출력에서는 어떤 노드는 컨테이너 런타임이 내려갔다고 나왔고, 어떤 노드는 여러 검사에 실패해 kubelet 이 내려갔다고 나왔다. 노드에 SSH 로 접속하려 했지만 연결이 계속 타임아웃됐다. kubectl get nodes 는 워커 노드 세 대가 모두 NotReady 라고 보여 줬지만 어떤 모니터링도 이를 알리지 않았다.\n\n처음에는 이미지 풀 오류를 대수롭지 않게 여겼다. 비운영 클러스터였고, 노드는 t2.medium 이었고, 공식 Docker 레지스트리가 그동안 자주 불안정했기 때문이다. 담당자는 이미지를 한꺼번에 받다가 과부하가 걸렸다고 추정하고 리소스를 지우기로 했다. Helm 릴리스를 지우려 했지만 tiller 가 크래시 루프에 빠져 있어 할 수 없었다. 그래서 kubectl delete 에 --force --grace-period=0 을 붙여 파드를 강제로 지웠다. 파드는 kubectl get 에서 사라졌지만 Docker 에서도 지워졌는지는 확인하지 않았다."},"updated_at":"2026-11-10T00:00:38.304538Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0040","org_slug":"saltside"}