{"id":"IL-0503","org":"Binder Project","date":"2017-11-30","docs":["https://status.cloud.google.com/incident/storage/17006","https://github.com/jupyterhub/repo2docker/pull/159","https://github.com/jupyterhub/mybinder.org-deploy/issues/198","https://github.com/jupyterhub/binder/issues/39"],"title":{"en":"mybinder.org launches failed after a GKE node ran out of memory and its nginx-ingress proxy pod crash-looped","ko":"GKE 노드의 메모리가 바닥나 mybinder.org 의 proxy 파드가 OOM 으로 죽고 새 서버 실행이 실패했다"},"domain":["kubernetes","gcp"],"impact":{"scope":"mybinder.org: new server launches failed; restarting the jupyterhub pod also removed all running user pods","users":"unknown","region":"us-central1-a"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":null,"related":[],"sources":[{"url":"https://github.com/jupyterhub/mybinder.org-deploy/blob/main/docs/source/incident-reports/2017-11-30-oom-proxy.md","kind":"github","archived":null}],"summary":{"en":"At 16:22 PST on 30 November 2017 a node in the mybinder.org cluster on GKE ran out of memory and the service started to degrade. The write-up gives a build container as the probable cause: builds had no memory limits, and one likely used all the memory on the node. That node ran the proxy pod, which holds an nginx container and a chp container; the memory exhaustion broke the proxy, its pods went into a crash loop, and launches of new servers failed with an error about creating a temporary user.\n\nOperators noticed the problem at 16:59 because one of them happened to look at Grafana. Restarting the jupyterhub pod did not help and, because of the JupyterHub configuration, deleted all running user pods. After the OOMKilling event was found on the proxy's node, an operator suggested at 17:18 cordoning that node and then deleting the proxy pod, and the node was cordoned. At 17:20 the proxy pod was working again, although by then it had already been running for about 10 minutes, and the node was uncordoned, and at 17:21 service was restored and user pods started running again.","ko":"태평양 시간으로 2017년 11월 30일 16:22 에 mybinder.org 의 GKE 노드 하나가 메모리를 다 쓰면서 서비스에 문제가 생기기 시작했다. 그 노드에서 돌던 proxy 파드가 죽고 재시작하기를 되풀이했고, 새 서버 실행은 임시 사용자를 만들지 못하고 실패했다. 운영팀은 빌드에 메모리 제한이 없어서 빌드 컨테이너가 노드의 메모리를 다 썼을 가능성이 크다고 봤다.\n\n16:59 에 엔지니어가 Grafana 가 이상한 것을 보고 조사를 시작했다. 처음에는 Google Cloud 의 storage 사고를 의심했고, 콘솔에서는 모든 노드가 Ready 였다. 17:04 에 proxy 파드가 CrashloopBackoff 에 빠진 것을 찾았다. 17:12 에 jupyterhub 파드를 kubectl replace --force 로 다시 만들었지만 이 때문에 사용자 파드가 모두 지워졌고 오류는 남았다. 17:14 에 proxy 가 돌던 노드에서 OOMKilling 이벤트를 찾았다. 17:18 에 그 노드를 cordon 하고 proxy 파드를 지우자는 제안이 나왔고, 17:20 에 proxy 파드가 다시 동작하는 것을 알아챘다. 그 proxy 파드는 그때 이미 10분째 돌고 있었다. 노드는 다시 uncordon 했고, 17:21 에 사용자 파드가 다시 뜨며 서비스가 돌아왔다."},"trigger":{"en":"A build container, which ran without a memory limit, most likely used up all the memory on the node that ran the proxy pod.","ko":"GKE 노드 하나의 메모리가 바닥났다. 빌드에 메모리 제한이 없어서 빌드 컨테이너가 노드의 메모리를 다 썼을 가능성이 크다고 봤다."},"severity":"unknown","symptoms":["service-unavailable","crash-loop","readiness-probe-failures","oom-killed"],"timeline":{"detected_after":"37m","resolved_after":"59m"},"date_kind":"incident","detection":{"en":"An operator happened to look at Grafana at 16:59 PST, saw that it looked wrong, and confirmed the error by launching a new server.","ko":"엔지니어가 마침 Grafana 를 보다가 그래프가 이상한 것을 보고 알아챘다."},"components":["ingress","memory-limit","jupyterhub"],"root_cause":"capacity","remediation":[{"text":{"en":"Cordoned the node that had run out of memory","ko":"메모리가 바닥난 노드를 cordon 했다."}},{"text":{"en":"Uncordoned the node once the proxy was working","ko":"cordon 했던 노드를 다시 uncordon 했다."}}],"action_items":[{"text":{"en":"Limit the memory available to build pods","ko":"repo2docker 에서 빌드 파드가 쓸 수 있는 메모리를 제한한다."}},{"text":{"en":"Change the JupyterHub configuration so user pods are not cleaned up when the jupyterhub pod restarts","ko":"jupyterhub 파드가 재시작해도 사용자 파드가 정리되지 않게 설정을 고친다."}},{"text":{"en":"Nominate one person to drive the response, possibly in a shared terminal, so only one person changes the cluster","ko":"장애 대응 때 명령을 직접 실행할 사람 한 명을 정하고 터미널 공유도 고려한다. 클러스터 상태를 바꾸는 명령은 그 한 사람만 내리고, 다른 사람은 그 명령을 알고 있게 한다."}},{"text":{"en":"Alert when something is wrong with the cluster, for example by counting 4xx responses on the jupyterhub pod","ko":"클러스터에 문제가 생겼을 때 울리는 경보를 더 잘 갖춘다. 이번 장애는 jupyterhub 파드의 4xx 응답 수를 세는 경보로 잡을 수 있었다."}}],"contributing":["no-resource-limits","manual-action-worsened","no-alert-on-limit"],"recurrence_of":null,"symptoms_text":{"en":"On 30 November 2017 at 16:59 PST, mybinder.org operators noticed that Grafana looked wrong and that launching a new server failed with an error that a temporary user could not be created. Pods that were already running kept running, but no new ones were being created.\n\nThe launch page showed \"Found built image, launching...\", \"Launching server...\" and then \"Failed to create temporary user for gcr.io/binder-prod/r2d-<repository>:6be6d6329de99168d0a0b68f9d52228ecc61ac50\". The proxy pods were in CrashloopBackoff after about 13 restarts. `kubectl describe` on them showed \"Back-off restarting failed container\" and \"Readiness probe failed: Get http://10.12.5.36:10254/healthz: dial tcp 10.12.5.36:10254: getsockopt: connection refused\" for the nginx-ingress-controller:0.9.0-beta.15 image, and the jupyterhub pod logged many DELETE requests timing out, with tracebacks.\n\nA Google Cloud storage incident (17006) was suspected first, but all nodes showed \"Ready\" in the cloud console. Restarting the jupyterhub pod at 17:12 removed all user pods and the error remained. At 17:14 `kubectl describe` on the node running the proxy showed an OOMKilling event: \"Memory cgroup out of memory: Kill process 8339 (python) score 1576 or sacrific\".","ko":"mybinder.org 에서 새 서버를 실행하면 임시 사용자를 만들지 못했다는 오류가 떴다. 엔지니어가 마침 Grafana 를 보다가 그래프가 이상한 것을 보고 알아챘다.\n\n실행 화면에는 Found built image, launching... 과 Launching server... 다음에 Failed to create temporary user for gcr.io/binder-prod/r2d-<repository>:6be6d6329de99168d0a0b68f9d52228ecc61ac50 이 찍혔다.\n\nproxy 파드는 CrashloopBackoff 상태로 13번쯤 재시작해 있었다. kubectl describe 의 이벤트에는 Back-off restarting failed container 와 Error syncing pod, 그리고 Readiness probe failed: Get http://10.12.5.36:10254/healthz: dial tcp 10.12.5.36:10254: getsockopt: connection refused 가 있었다. 컨테이너 이미지는 gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.15 였다.\n\nproxy 가 돌던 노드를 kubectl describe 로 보니 Warning OOMKilling 58m kernel-monitor, gke-prod-a-default-pool-6ab3d8ec-0h28 Memory cgroup out of memory: Kill process 8339 (python) score 1576 or sacrific 이벤트가 있었다. 그런데 클라우드 콘솔의 Kubernetes clusters 페이지에서는 모든 노드가 Ready 로 보였다.\n\n처음에는 Google Cloud 상태 페이지의 storage 사고 17006 과 관련이 있는지 의심했다. jupyterhub 파드 로그에는 타임아웃 나는 DELETE 요청과 트레이스백이 많았다. 기존 파드는 돌고 있었지만 새 파드가 생기지 않았다. jupyterhub 파드를 강제로 다시 만들었더니 사용자 파드가 모두 지워졌고 오류는 그대로였다."},"updated_at":"2026-11-14T12:34:14.833275Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0503","org_slug":"binder-project"}