{"id":"IL-0037","org":"Datadog","date":"2019-05-23","docs":[],"title":{"en":"Image registry rate-limited a whole region after a crash-looping DaemonSet with imagePullPolicy Always pulled through three NAT instances","ko":"imagePullPolicy가 Always인 DaemonSet이 크래시 루프를 돌며 이미지를 계속 받아, NAT 세 대를 같이 쓰던 리전 전체가 이미지 레지스트리에서 429로 막혔다"},"domain":["kubernetes","gcp","networking"],"impact":{"scope":"no node in one region could pull images, so new pods and jobs could not start; applications using a Cloud SQL database lost their connections for a couple of hours after the NAT replacement","users":"unknown","region":"one cloud region (not named)"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The speakers' takeaway is to be very careful with DaemonSets in large clusters: they say they had broken everything DaemonSets depend on, including DNS, Vault and the image repository, and that it is very easy to break things when managing them by the thousand. imagePullPolicy: Always made every crash-loop restart pull the image again. Sending all registry traffic through three NAT instances put the whole region behind three source IPs, which the registry rate-limited, and changing those IPs cut the connections to a critical database reached over the internet through them. An admission webhook on pods rejects the pods that controllers in the cluster create, where no user sees the error.","ko":"imagePullPolicy가 Always인 DaemonSet이 크래시 루프에 빠지면 노드 수만큼 이미지 풀이 반복된다. 레지스트리 트래픽이 NAT 몇 대로 모이면 출발지 IP 몇 개가 속도 제한에 걸려 리전 전체가 이미지를 받지 못한다. NAT를 바꾸면 그 IP를 허용해 둔 외부 서비스 연결도 함께 끊긴다. 파드에 거는 어드미션 웹훅은 사용자가 아니라 컨트롤러가 만드는 파드까지 막는다. 발표자들은 대규모 클러스터에서는 DaemonSet을 특히 조심하라고 했다. DaemonSet이 기대는 DNS, Vault, 이미지 저장소도 모두 망가뜨려 본 적이 있다고 했다. 수천 대 단위로 무언가를 다루면 그만큼 망가뜨리기 쉽다는 것이다. kubelet 아래에도 인프라가 있다는 것을 잊지 말라고도 했다."},"related":[],"sources":[{"url":"https://www.youtube.com/watch?v=QKI-JRs2RIE","kind":"talk","license":"unknown","archived":null}],"summary":{"en":"This is one of the failures in the talk \"10 Ways to Shoot Yourself in the Foot with Kubernetes\" by two Datadog infrastructure engineers, whose clusters ran at 1,000 to 2,000 nodes. A permissions change on a storage bucket stopped one application from starting. The application ran as a DaemonSet on about a thousand nodes, and it went into a crash loop; because its pull policy was Always, each restart was a new image pull. All traffic from the region to the image registry left through three NAT instances, so the registry saw an unacceptable pull rate from just three source IPs and rate-limited them with HTTP 429. From then on no node in the region could pull any image: running workloads continued, but new pods and jobs could not start.\n\nThe team replaced the affected NAT instances to get new source IPs, which ended the rate limiting. They had forgotten that some critical services were reached over the internet through those IPs, among them a Cloud SQL database that at the time was not available over a private VPC. Every application using that database lost its connections. After a couple of hours of work on the static IPs and the firewall rules, everything came back.\n\nAs a follow-up they added an admission webhook that disallows floating image tags, starting with latest, and attached it to workload types and to pods. Most pods are created by controllers inside the cluster, not by users, so pods for existing workloads were rejected silently and nothing new could be scheduled. Applying the webhook only to workload objects fixed it; the speaker puts the return to normal at several days to a week after the registry incident.","ko":"Datadog 컴퓨트 팀이 \"10 Ways to Shoot Yourself in the Foot with Kubernetes\" 발표에서 소개한 장애다. Datadog은 수천 대 노드를 쿠버네티스로 운영했고, 가장 큰 클러스터는 약 2,000대, 보통 클러스터는 1,000대에서 1,500대였다. 버킷 권한을 바꾼 뒤 약 1,000대 노드에서 도는 DaemonSet이 크래시 루프에 빠졌다. imagePullPolicy가 Always로 설정돼 있어서 재시작할 때마다 이미지 풀이 일어났다.\n\n이 리전의 이미지 레지스트리 트래픽은 모두 NAT 인스턴스 세 대를 거쳐 나갔다. 레지스트리는 IP 세 개에서 몰려오는 요청을 받아들일 수 없는 양으로 보고 속도 제한을 걸었다. 그 결과 리전의 모든 노드가 이미지를 받지 못했고, 새 파드와 작업이 시작되지 못했다.\n\n팀은 문제의 NAT 인스턴스를 교체해 새 출발지 IP를 얻었고 레지스트리 트래픽은 정상으로 돌아왔다. 그런데 당시 Cloud SQL 데이터베이스는 프라이빗 VPC로 접근할 수 없어서 인터넷으로 접속하고 있었다. 출발지 IP가 바뀌자 이 핵심 데이터베이스를 쓰는 애플리케이션들의 연결이 끊겼다. 고정 IP를 관리하는 곳을 찾아 방화벽 규칙을 다시 설정하기까지 몇 시간이 걸렸다. 후속 조치로 latest 같은 유동 태그를 막는 어드미션 웹훅을 Deployment, StatefulSet, DaemonSet, Job, 파드에 걸었다. 하지만 파드는 사용자가 아니라 클러스터 안의 컨트롤 루프가 만든다. 그래서 노드가 죽어 파드를 재스케줄하거나 스케일 아웃할 때 새로 생기는 파드가 거부됐다. 팀은 웹훅을 워크로드 객체에만 걸도록 바꿨다. 모든 것이 정상으로 돌아온 것은 레지스트리 문제가 시작된 지 약 일주일 뒤였다."},"trigger":{"en":"A change to the permissions on a storage bucket that one application needed to read. The application could no longer start, and its DaemonSet, running on roughly a thousand nodes of the cluster, went into a crash loop with imagePullPolicy: Always, so every restart pulled the image again although it was already on the node.","ko":"애플리케이션 하나가 읽어야 하는 버킷의 권한을 바꾸면서 그 애플리케이션이 시작하지 못하게 됐다. 이 애플리케이션은 약 1,000대 노드에서 도는 DaemonSet이었고 크래시 루프에 빠졌다. imagePullPolicy가 Always여서 재시작할 때마다 노드에 이미 있는 이미지를 다시 받았다."},"severity":"unknown","symptoms":["image-pull-failures","service-unavailable"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"Users reported that their jobs were not starting; monitoring had not caught it first.","ko":"사용자들이 작업이 시작되지 않는다고 알려 와서 알았다. 모니터링보다 사용자 보고가 먼저였다. 팀은 파드 상태, 이미지 풀 지표, 레지스트리 응답 코드를 차례로 확인해 429 속도 제한을 찾았다."},"components":["registry","nat-gateway","database","admission-webhook"],"root_cause":"capacity","remediation":[{"text":{"en":"replaced the affected NAT instances so registry traffic came from new source IPs","ko":"영향을 받은 NAT 인스턴스를 교체해 레지스트리로 나가는 출발지 IP를 바꿨다."}},{"text":{"en":"reconfigured the static IPs and firewall rules so applications could reach the Cloud SQL database again","ko":"Cloud SQL 접속에 쓰던 고정 IP 설정과 방화벽 규칙을 다시 맞춰 데이터베이스 연결을 되살렸다."}}],"action_items":[{"text":{"en":"Add an admission webhook that rejects floating image tags, starting with latest","ko":"latest 같은 유동 이미지 태그를 막는 어드미션 웹훅을 만들어 워크로드 리소스와 파드에 걸었다."}},{"text":{"en":"Apply the image tag webhook to workload objects only, not to pods, after it blocked pods created by in-cluster controllers","ko":"웹훅이 클러스터가 만드는 파드까지 거부하자, 파드 객체에서는 빼고 워크로드 객체에만 적용하도록 바꿨다."}}],"contributing":["automation-amplified","single-point-of-failure","limit-unknown","manual-action-worsened","blast-radius-too-wide"],"recurrence_of":null,"symptoms_text":{"en":"Users reported that their jobs were not starting, with some sign that image pulls were involved. Many pods across the cluster were in error states related to image pulls, while applications that were already running were not affected.\n\nThe image pull metrics showed a sudden jump in the number of pulls, sustained for a while, and then a second jump. The image registry was answering with a large number of HTTP 429 responses: it was rate-limiting Datadog. At that point no node in the whole region could pull from the registry.\n\nReplacing the NAT instances stopped the rate limiting, but it broke something else: applications that reached a critical Cloud SQL database over the internet lost their connections and stopped working, and it took a couple of hours to restore them.\n\nThe follow-up fix caused more trouble. An admission webhook that rejected floating image tags was attached to pods as well as workload objects, so pods that controllers in the cluster created for existing workloads (after node failures or scale-ups) were rejected with no user to see the error, and nothing new could be scheduled. Things were back to normal several days to a week after the registry incident.","ko":"사용자들이 작업이 시작되지 않는다고 알려 왔다. 평소에는 모니터링이 먼저 잡아내는 편이었지만 이번에는 사용자 보고가 먼저였다. 처음부터 이미지 풀과 관련된 흔적이 보였다.\n\n클러스터 전체 파드 상태를 보니 이미지 풀 때문에 오류 상태에 있는 파드가 많았다. 이미 돌고 있던 애플리케이션은 영향을 받지 않았다. 이미지 풀 지표에서는 풀 횟수가 갑자기 크게 뛴 뒤 한동안 그 수준을 유지하다가 다시 한 번 크게 뛰는 모양이 보였다.\n\n이미지 레지스트리의 응답을 확인하니 429가 많이 나오고 있었다. 레지스트리가 속도 제한을 걸었다는 뜻이었다. 이 시점에 팀은 이 리전의 어떤 노드도 레지스트리에서 이미지를 받지 못한다는 것을 알았다.\n\n장애는 조치 과정에서 두 번 더 번졌다. NAT 인스턴스를 교체하자 인터넷으로 Cloud SQL 데이터베이스에 붙던 애플리케이션들의 연결이 끊겼다. 후속 조치로 넣은 어드미션 웹훅은 클러스터가 스스로 만드는 파드까지 거부해서, 클러스터에 새 파드를 하나도 스케줄할 수 없게 됐다."},"updated_at":"2026-11-10T00:00:38.138970Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0037","org_slug":"datadog"}