{"id":"IL-0085","org":"Magic Pages","date":"2025-04-14","docs":[],"title":{"en":"A control plane node of Magic Pages' Kubernetes cluster on Hetzner Cloud ran out of memory, the cluster's own calls then used up the Hetzner API rate limit, and all customer websites were down for about 7.5 hours","ko":"Hetzner Cloud 의 k3s 컨트롤 플레인 노드가 메모리 부족으로 꺼진 뒤 클러스터가 Hetzner API 호출 한도를 다 써 버려 모든 고객 사이트가 약 7.5시간 멈췄다."},"domain":["kubernetes"],"impact":{"scope":"All customer websites hosted on Magic Pages (Ghost sites) were unreachable","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The running cluster, not only the deployment tooling, consumed the cloud provider's API quota, and in a failure it consumed it fast enough that the quota never recovered. The default limit of 3600 requests per hour was not enough for failure and recovery scenarios, and API usage from inside the cluster was not monitored.\n\nA tool that hides the provider's API errors slows diagnosis. After a full, non-graceful shutdown, Longhorn needed manual intervention to come back, which lengthened the outage.","ko":"Hetzner 의 기본 API 한도인 시간당 3600건은 장애나 복구 상황에서 클러스터가 쓰는 양에 비해 부족했다. 운영자는 배포에 쓰는 API 사용량만 봤고, 실행 중인 클러스터 내부에서 나가는 호출은 지켜보지 않았다.\n\n네트워킹 같은 핵심 기능이 클라우드 API 하나에 기대고 있으면, 그 API 가 막히는 순간 클러스터 전체가 약해진다. hetzner-k3s 의 추상화는 API 오류를 감춰 문제 해결을 늦췄다. 클러스터가 비정상 종료된 뒤 Longhorn 을 살리는 데 손이 많이 가서 장애가 길어졌다."},"related":[],"sources":[{"url":"https://www.magicpages.co/blog/post-mortem-on-14-april-2025-outage/","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Magic Pages hosts Ghost websites on a Kubernetes cluster on Hetzner Cloud, built with the hetzner-k3s tool, with three control plane nodes for redundancy. When one control plane node ran out of memory, the loss of one node should have been absorbed by the other two. The cluster, however, depends on the Hetzner API for ongoing work such as networking and some storage management, and with the control plane in trouble these components kept calling the API. They used up the project's limit of 3600 requests per hour and kept it at zero, so the failed node could not be replaced and the existing cluster components could not work either, and every customer website went down. The founder knew the limit existed but had not realised how much the running cluster itself relied on the API. Two days later Hetzner told the founder that a bug in a cloud controller that connects Kubernetes clusters to Hetzner Cloud was involved; its root cause had not been found for several months, and Hetzner provided a workaround for faster recovery next time.\n\nTo stop the API calls, the founder powered off every server in the cluster. The ratelimit-remaining value then started to climb, and the founder waited longer to build a buffer and asked Hetzner support for a higher limit. The control plane servers were restarted successfully. When the worker nodes came back, every website container tried to start at the same moment, but the sites need their Longhorn storage volumes first, and Longhorn needs a minimum number of its own components running after a full shutdown. These essential Longhorn components had to be started by hand; after that Longhorn healed itself and made the volumes available over about an hour, and the website containers started as their volumes appeared.\n\nBy about 17:00 UTC around 95% of the sites were back; the rest, including the founder's own blogs, needed manual restarts shortly after. The outage lasted about 7.5 hours. The CDN, when activated, served cached copies of sites, and the databases were unaffected because they had been moved out of the Kubernetes cluster earlier in the year.","ko":"Magic Pages 는 Ghost 사이트를 호스팅하는 서비스이고, Hetzner Cloud 위의 쿠버네티스 클러스터에서 돈다. 이중화를 위해 컨트롤 플레인 노드를 세 대 둔다. 2025-04-14 09:33 UTC 쯤 그중 한 대가 메모리 부족으로 꺼졌다. 운영자는 hetzner-k3s 도구로 더 큰 서버를 띄워 교체하려 했지만 배포가 두 번 연속 실패했다.\n\n그사이 클러스터가 Hetzner API 의 시간당 3600건 한도를 다 써 버렸다. 운영자는 이 한도를 알고 있었지만, 클러스터가 네트워킹과 일부 스토리지 관리 같은 평상시 작업에 Hetzner API 를 얼마나 쓰는지는 제대로 몰랐다. 운영자는 컨트롤 플레인에 문제가 생기자 내부 구성 요소들이 스스로 바로잡으려고 API 호출을 늘렸을 것으로 추정했다. 한도가 바닥나자 새 노드를 띄울 수 없었고 기존 클러스터 구성 요소도 제대로 돌지 못해 장애가 연쇄로 번졌다. 남은 노드들이 API 를 계속 호출해서 한도는 기다려도 회복되지 않았다. 2025-04-16 에 Hetzner 는 쿠버네티스 클러스터와 Hetzner 클라우드 사이를 잇는 클라우드 컨트롤러에 버그가 있다고 알려 왔다. Hetzner 도 이 버그의 근본 원인은 몇 달째 찾지 못했지만, 같은 일이 생겼을 때 훨씬 빨리 복구할 수 있는 우회 방법을 알려 주었다.\n\n운영자는 API 호출을 확실히 멈추려고 서버 전원을 내려 클러스터 전체를 껐다. 그러자 ratelimit-remaining 이 다시 오르기 시작했고, 여유분이 쌓일 때까지 기다린 뒤 컨트롤 플레인 서버를 다시 켰다. 워커 노드를 다시 올리자 모든 웹사이트 컨테이너가 한꺼번에 시작하려 했지만, 사이트가 뜨려면 스토리지 볼륨이 먼저 준비돼야 했다. Longhorn 은 보통 스스로 복구되지만, 클러스터 전체가 비정상 종료된 뒤에는 자기 구성 요소가 최소 몇 개는 돌고 있어야 한다. 그래서 핵심 구성 요소를 손으로 띄웠다. 그 뒤 볼륨이 차례로 붙으면서 한 시간쯤에 걸쳐 사이트가 살아났고, 17:00 UTC 쯤 사이트의 95% 가 돌아왔다. 남은 몇 개는 손으로 다시 시작했다.\n\n장애는 약 7.5시간 이어졌다. 데이터베이스는 그해 초에 쿠버네티스 클러스터 밖으로 옮겨 둔 덕분에 영향이 없었고 데이터 손실도 없었다. CDN 을 켠 사이트는 캐시해 둔 옛 페이지를 계속 내보냈다."},"trigger":{"en":"One of the three control plane nodes ran out of memory and went offline at about 09:33 UTC; the attempts to replace it with hetzner-k3s failed while the cluster's own components were exhausting the Hetzner API rate limit.","ko":"컨트롤 플레인 노드 한 대가 메모리 부족으로 오프라인이 됐다. 이 노드를 교체하는 배포가 실패해 다시 시도하는 동안, 클러스터 내부 구성 요소들이 Hetzner API 를 계속 호출해 시간당 3600건 한도를 소진했다."},"severity":"unknown","symptoms":["service-unavailable","node-not-ready","cloud-api-rate-limited"],"timeline":{"detected_after":null,"resolved_after":"7h 30m"},"date_kind":"incident","detection":{"en":"The founder found the control plane node offline at about 09:33 UTC. Monitoring alerts about unreachable customer websites started around the time the replacement attempts failed, and about half an hour of investigation identified the exhausted Hetzner API rate limit.","ko":"모니터링 경보가 쏟아지면서 고객 웹사이트에 접속할 수 없다는 것을 알았다. API 호출 한도가 원인이라는 것은 30분쯤 조사한 뒤에야 찾았다. hetzner-k3s 가 Hetzner API 오류를 감춰서 원인 파악이 늦어졌다."},"components":["cloud-controller-manager","persistent-volume"],"root_cause":"capacity","remediation":[{"text":{"en":"powered off all servers of the Kubernetes cluster to stop the API requests so the Hetzner rate limit could recover","ko":"API 호출을 확실히 멈추려고 서버 전원을 내려 쿠버네티스 클러스터 전체를 껐다."}},{"text":{"en":"waited until ratelimit-remaining had built up a buffer before starting recovery","ko":"Hetzner API 의 ratelimit-remaining 값이 다시 오르는 것을 지켜보면서, 복구 중에 다시 한도에 걸리지 않도록 여유분이 쌓일 때까지 기다렸다."}},{"text":{"en":"restarted the control plane servers","ko":"API 호출 여유가 생긴 뒤 컨트롤 플레인 서버를 다시 켰다."}},{"text":{"en":"started the essential Longhorn components by hand so Longhorn could heal and make storage volumes available","ko":"워커 노드를 올린 뒤 Longhorn 의 핵심 구성 요소를 손으로 띄워 스토리지 볼륨이 스스로 복구되게 했다."}},{"text":{"en":"restarted the remaining websites by hand","ko":"17:00 UTC 쯤 사이트의 95% 가 돌아온 뒤, 남은 사이트를 손으로 다시 시작했다."}}],"action_items":[{"text":{"en":"Monitor the Hetzner API call rate coming from the Kubernetes cluster nodes themselves to get early warning of high usage","ko":"클러스터 노드에서 나가는 Hetzner API 호출량을 따로 모니터링해서 사용량이 높아지면 미리 알 수 있게 한다."}},{"text":{"en":"Get a higher Hetzner API rate limit; Hetzner support was contacted","ko":"Hetzner 와 협의해 API 호출 한도를 늘린다."}},{"text":{"en":"Investigate how to reduce the cluster's reliance on a single API, including alternatives to hetzner-k3s","ko":"API 하나에 기대는 구조를 줄일 방법을 찾고, hetzner-k3s 로는 그렇게 하기 어렵다면 다른 도구를 검토한다."}},{"text":{"en":"Document and test a procedure for recovering Longhorn after a full cluster shutdown","ko":"클러스터 전체가 꺼진 뒤 Longhorn 스토리지를 복구하는 절차를 더 탄탄하게 문서로 만들고 시험한다."}}],"contributing":["limit-unknown","retry-storm","monitoring-blind-spot","limited-debug-access","single-point-of-failure","no-runbook"],"recurrence_of":null,"symptoms_text":{"en":"At around 09:33 UTC one of the three control plane nodes of the Kubernetes cluster that hosts Magic Pages went offline. Looking at the node showed that it had run out of memory.\n\nThe usual fix is to remove the failed node and create a replacement, so the founder started a replacement on a slightly larger server with the hetzner-k3s tool. The deployment failed, and a second attempt failed too. hetzner-k3s gave no detailed log of why the underlying Hetzner API calls were failing. At about the same time monitoring alerts began to flood in because customer websites were becoming unreachable.\n\nAfter about half an hour of investigation the cause turned out to be the Hetzner Cloud API rate limit of 3600 requests per hour per project. Waiting for the hourly limit to reset did not help: after 30 minutes the API still reported ratelimit-remaining: 0. It took another 15 to 20 minutes to see why. The remaining Kubernetes nodes, struggling with the failing control plane and the lack of API access, kept calling the API, so the quota never refilled. New nodes could not be created, and moving traffic to a separate cluster was not possible either, because that also needed API calls.","ko":"2025-04-14 09:33 UTC 쯤 Magic Pages 의 컨트롤 플레인 노드 세 대 중 한 대가 갑자기 오프라인이 됐다. 노드를 살펴보니 메모리가 바닥나 있었다. 운영자는 평소 절차대로 고장 난 노드를 빼고 메모리가 조금 더 큰 서버로 바꾸려 했다.\n\nhetzner-k3s 도구로 시작한 교체 배포가 실패했다. API 요청은 가끔 일시적으로 실패하기 때문에 다시 실행했지만 또 실패했다. hetzner-k3s 는 Hetzner API 호출이 왜 실패하는지 자세한 로그를 보여 주지 않았다.\n\n비슷한 시각부터 모니터링 경보가 쏟아졌고 고객 웹사이트에 접속할 수 없게 됐다. 30분쯤 조사한 끝에 원인을 찾았다. Hetzner Cloud 가 프로젝트마다 시간당 3600건으로 묶어 둔 API 호출 한도에 걸려 있었다.\n\n처음에는 한도가 풀리기를 기다렸다. 시간당 한도이므로 30분이면 약 1800건이 돌아와야 했지만, 30분 뒤에도 API 상태는 ratelimit-remaining: 0 이었다. 15분에서 20분을 더 보고서야 이유를 알았다. 컨트롤 플레인이 망가져 API 를 쓸 수 없는 상태에서도 남은 노드들이 계속 API 를 호출했고, 그래서 한도가 회복되지 않았다. 별도 클러스터를 띄워 트래픽을 옮기는 방법도 API 호출이 필요해서 쓸 수 없었다."},"updated_at":"2026-11-10T00:00:32.067571Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0085","org_slug":"magic-pages"}