{"id":"IL-0029","org":"Buffer","date":"2020-08-31","docs":["https://github.com/kubernetes/kubernetes/issues/67577","https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=763a9ec06c4","https://medium.com/omio-engineering/cpu-limits-and-aggressive-throttling-in-kubernetes-c5b20bd8a718","https://engineering.indeedblog.com/blog/2019/12/unthrottled-fixing-cpu-limits-in-the-cloud/","https://www.youtube.com/watch?v=UE7QX98-kO0","https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/","https://github.com/kubernetes/kops/pull/9283","https://github.com/aws/containers-roadmap/issues/175"],"title":{"en":"Slow user-facing services from CPU throttling on containers that used far less CPU than their limit","ko":"CPU 한도를 건 컨테이너가 사용량이 한도에 한참 못 미치는데도 스로틀링되어 사용자 대면 서비스의 지연 시간이 늘었다."},"domain":["kubernetes","aws"],"impact":{"scope":"user-facing services with CPU limits, including the main API and the buffer.com landing page","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"A CPU limit can slow a service down even when the service uses a small part of it, because of how the kernel enforces CFS quota on older kernels; the throttling metric shows it, the CPU usage graph does not. Upgrading the kernel is the safer fix. Removing limits works for latency, but only together with honest CPU and memory requests, isolation of the unbounded services, and monitoring of node usage, since unbounded containers can starve the kubelet.","ko":"리눅스에서 컨테이너를 돌리면 쿠버네티스든 Mesos 든 Swarm 이든 스로틀링 때문에 컨테이너가 제 성능을 내지 못할 수 있다. 먼저 커널을 수정된 버전으로 올리고 스로틀링 지표를 지켜보는 편이 낫다. CPU 한도를 없애면 지연 시간은 줄지만 위험하다. 한도를 없앤다면 CPU 요청값을 제대로 잡고, 노드의 CPU 와 메모리 사용량을 꼼꼼히 모니터링한다. 사용량이 높으면 Horizontal Pod Autoscaler 로 파드를 늘려 여유 있는 노드에 배치되게 한다. 클러스터에 여유가 없으면 알림을 받거나 노드 오토스케일러로 노드를 늘린다."},"related":[],"sources":[{"url":"https://erickhun.com/posts/kubernetes-faster-services-no-cpu-limits/","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"Kubernetes enforces a container's CPU limit with the kernel's CFS quota: in each period (100ms by default) a container may use only its share of CPU time and is throttled after that. Buffer had set CPU limits on its services because that was the common recommendation, and because without limits a busy container had earlier starved the kubelet and turned nodes NotReady. The team then saw throttling on containers whose usage was far below the limit, and traced it to a Linux kernel bug that throttled containers with CPU limits unnecessarily. The fix for that bug is in kernel 4.19 and later, but the kops version Buffer ran still used an older host image, so upgrading the kernel was not yet possible.\n\nInstead, after long discussion, the team removed CPU limits from every service that sat directly or indirectly on the users' critical path, and did it with safeguards. Those services were moved to dedicated tainted nodes, with tolerations, so that an unbounded service could only disturb other unbounded ones. Each service's CPU request was set from a few months of Datadog usage data: the peak usage plus a margin of more than 20%, with memory requests set the same way, so the scheduler reserved enough room on the node. The cost was lower container density and idle capacity at quiet hours.\n\nAfter a few weeks, latency improved across all the modified services; the main landing page buffer.com became up to 22 times faster. The author adds that upgrading to a fixed kernel should be preferred over removing limits, and that he could not yet confirm the kernel fix removes throttling completely.","ko":"Buffer 는 2016년부터 쿠버네티스를 썼다. kops 로 관리하는 AWS 클러스터에 노드 약 60대, 컨테이너 약 1500개를 운영했다. 컨테이너가 노드의 CPU 를 다 써서 kubelet 같은 핵심 프로세스가 응답하지 않는 일을 막으려고 CPU 한도를 걸었다. CPU 한도는 컨테이너가 한 주기 동안 쓸 수 있는 최대 CPU 시간이고, 주기의 기본값은 100ms 다. 쿠버네티스는 CFS 쿼터로 컨테이너를 스로틀링해 이 한도를 지킨다.\n\n그런데 리눅스 커널 버그 때문에 CPU 한도가 걸린 컨테이너는 사용량이 낮을 때도 스로틀링되었고, 그만큼 서비스가 느려졌다. 수정은 4.19 이상 커널에 들어갔다. 하지만 Buffer 가 쓰던 kops 버전의 기본 호스트 이미지에는 아직 수정이 없었다. kops 는 1.18 부터 Ubuntu 20.04 를 기본 이미지로 쓴다.\n\n팀은 긴 논의 끝에 사용자에게 직접 또는 간접으로 닿는 경로의 서비스에서 CPU 한도를 모두 없앴다. 한도를 없앤 서비스는 테인트를 건 노드로 모아 한도가 있는 서비스와 떼어 놓았다. Datadog 에 쌓인 몇 달 치 사용량을 보고 최대 CPU 사용량에 20% 넘는 여유를 더해 CPU 요청값으로 정했고, 메모리 요청값도 같은 방식으로 정했다. 몇 주 뒤 바꾼 서비스 전반에서 지연 시간이 줄었고, 메인 랜딩 페이지 buffer.com 은 최대 22배 빨라졌다."},"trigger":{"en":"CPU limits on user-facing services, enforced through CFS quota by a Linux kernel with a bug that throttled containers even when they were well under their limit.","ko":"Buffer 는 노드를 지키려고 컨테이너에 CPU 한도를 걸어 두었는데, 노드 커널에는 CPU 한도가 걸린 컨테이너를 필요 없이 스로틀링하는 버그가 남아 있었다."},"severity":"unknown","symptoms":["cpu-throttling","high-latency","node-not-ready"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"While reviewing the container throttling metric, the team noticed many containers being throttled even though their CPU usage stayed far below their limits.","ko":"엔지니어가 컨테이너의 스로틀링 지표를 살피다가, CPU 사용량이 한도보다 훨씬 낮은 컨테이너도 많이 스로틀링되는 것을 보고 알았다."},"components":["cpu-limit","linux-kernel","node-image"],"root_cause":"software-bug","remediation":[{"text":{"en":"removed CPU limits from all services directly or indirectly on the users' critical path","ko":"사용자에게 직접 또는 간접으로 닿는 경로에 있는 모든 서비스에서 CPU 한도를 없앴다."}},{"text":{"en":"moved those services to dedicated tainted nodes and gave them matching tolerations","ko":"한도를 없앤 서비스에 톨러레이션을 더해 테인트를 건 노드에서만 돌게 하고, 한도가 있는 서비스와 떼어 놓았다."}},{"text":{"en":"set each service's CPU request to its peak usage over a few months plus a margin of more than 20%, and set memory requests the same way","ko":"서비스마다 몇 달 치 사용량을 분석해 최대 CPU 사용량에 20% 넘는 여유를 더한 값을 CPU 요청값으로 정했고, 메모리 요청값도 같은 방식으로 정했다."}}],"action_items":[{"text":{"en":"Move nodes to a kernel that contains the CFS throttling fix (4.19 or later), which for Buffer means a kops version whose host image is Ubuntu 20.04","ko":"커널 수정이 들어간 노드 이미지로 올린다. Buffer 는 kops 1.18 이상에서 기본 이미지가 되는 Ubuntu 20.04 를 기다리고 있다."}},{"text":{"en":"After the kernel upgrade, check whether throttling is fully gone and update the post","ko":"수정된 커널로 올린 뒤 스로틀링이 정말 사라졌는지 확인하고, 그 결과로 글을 고친다."}}],"contributing":["known-bug-unpatched","assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"Services on Buffer's kops-managed cluster on AWS (about 60 nodes and 1500 containers) answered more slowly than they should have. The container throttling metric showed that many containers were being throttled whatever their CPU usage was, including containers that never came close to their CPU limit.\n\nOne of the main APIs is the example in the write-up: its CPU limit was 800m, its peak usage was at most 200m, and the throttling graph still showed throttling. On paper the service had four times the CPU it needed, yet the kernel was holding it back.\n\nThe opposite setting had already hurt the team before. When containers ran without CPU limits and used up a node's CPU, the kubelet on that node stopped responding, the node went NotReady, and the pods that were moved elsewhere carried the same problem onto the next nodes. That history is why removing the limits was a long discussion and not a quick fix.","ko":"Buffer 엔지니어가 컨테이너의 스로틀링 지표를 살펴보니, CPU 사용량이 한도 근처에도 가지 않은 컨테이너가 많이 스로틀링되고 있었다.\n\n주요 API 하나는 CPU 한도가 800m 이었고 최대 사용량은 200m 정도였다. 그런데도 스로틀링이 일어났다. 원문은 CPU 사용량 그래프와 스로틀링 그래프를 나란히 놓고, 사용량이 한도보다 훨씬 낮을 때도 스로틀링이 일어나는 모습을 보여 준다.\n\n팀은 GitHub 이슈, Zalando 발표, Omio 글에서 스로틀링이 서비스 성능을 떨어뜨리고 지연 시간을 늘린다는 이야기를 찾았다. 원인은 CPU 한도가 걸린 컨테이너를 필요 없이 스로틀링하는 리눅스 커널 버그였다.\n\n한편 팀은 CPU 한도를 걸지 않았을 때의 위험도 이미 겪었다. 자원을 많이 쓰는 서비스 때문에 노드의 kubelet 이 응답하지 않았고, 노드가 NotReady 가 되었다. 그 노드의 파드가 다른 노드로 다시 스케줄되면서 새 노드에서도 같은 문제가 생겼다."},"updated_at":"2026-11-10T00:00:37.917893Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0029","org_slug":"buffer"}