CPU를 다 쓰지도 않았는데 스로틀링에 걸렸습니다
컨테이너에 CPU limit을 걸면 커널은 100ms마다 예산을 끊어서 줍니다. 한 번에 몰아주는 게 아니라요.
그리고 앱·런타임·스레드풀은 “코어 몇 개”를 정수로만 읽어서 병렬도를 정합니다. 0.6코어짜리 병렬도 같은 건 없으니까요.
이 둘이 맞물리면 이상한 일이 생깁니다. CPU 사용률은 limit의 3분의 1인데, period의 3할이 잘립니다.
그래프는 내내 여유로워 보이는데 워크로드는 계속 멈춰 서 있는 거죠.
특정 언어나 프레임워크의 함정이 아닙니다. 컨테이너에 CPU limit을 건 워크로드면 누구나 밟을 수 있는 것입니다.
발단은 트래픽이 몰리는 이벤트였습니다.
그날 CPU가 스로틀링에 걸리면서 장애가 났습니다.
잘린 쪽은 클러스터 컨트롤 플레인 컴포넌트 하나였고요.
그런데 메트릭을 열어보니 이상했습니다.
파드별 CPU 최대가 0.207코어. limit 600m의 34%밖에 안 됐습니다.
limit을 다 쓰지도 않았는데 왜 잘렸을까요?
장애 당일 09:30–11:00, 파드별 최대치를 1분 해상도로. 주황 음영은 그 분에 스로틀로 잘린 period 비율입니다 — 진할수록 많이 잘렸습니다.
파란 선이 limit 근처에도 못 갔는데, 주황으로 물든 시간은 계속 잘리고 있었습니다. 피크(10:40)에선 CPU가 limit의 31%를 쓰는 동안 period의 30%가 잘렸습니다 — 일한 period마다 quota를 끝까지 태웠다는 뜻입니다.
가로축은 장애 당일의 한 시간 반이고, 툴팁의 p99는 이 컴포넌트가 처리하는 작업의 지연입니다.
원인을 찾는 과정에서 정리한 내용들을 순서대로 적어보겠습니다.
CPU 사용률은 단독으로 읽으면 안 됩니다
우선 CPU 그래프를 어떻게 읽어야 하는지부터 짚고 가겠습니다.
CPU 사용률은 응답 시간과 겹쳐 봐야 의미가 생깁니다. 같은 그래프라도 응답 시간이 어떻게 움직이느냐에 따라 전혀 다른 상황이 됩니다.
CPU가 트래픽을 따라 완만히 움직이고 응답 시간은 낮게 눕는다.
계산이 아니라 무언가를 기다리는 중. 느려진 DB·락·고갈된 커넥션 풀이 흔한 이유다. 그런데 컨테이너에는 겉모습이 이것과 똑같은 경우가 하나 더 있다 — 주황으로 물든 구간이 그것이고, 본문에서 따로 다룬다.
톱니가 사라지면 포화. 트래픽도 같이 올랐나로 증설과 무한루프를 가른다.
같은 "CPU 그래프"라도 응답 시간을 겹쳐 놓으면 셋은 완전히 다른 상황입니다. CPU 90%가 문제인지 아닌지는 CPU 그래프가 아니라 응답 시간이 말해줍니다.
두 번째가 가장 헷갈리는 상황입니다. CPU는 20~30%로 한가한데 응답 시간만 폭증하죠.
이건 스레드들이 계산이 아니라 무언가를 기다리고 있다는 뜻입니다. 느려진 DB, 락, 고갈된 커넥션 풀, 외부 API 타임아웃 같은 것들요.
이럴 땐 CPU 그래프를 붙잡고 있어봐야 답이 안 나옵니다.
세 번째는 반대로 명확해 보이지만 갈래가 둘입니다. 트래픽 그래프를 같이 봐야 용량 초과(증설)인지 코드 무한 루프인지 갈립니다.
그런데 컨테이너에는 여기에 네 번째 패턴이 있습니다. 그래프 모양은 두 번째와 똑같은데 원인이 셋 중 어디에도 없는 것 — CPU는 한가하고, 기다리는 것도 없는데, 느린 경우입니다.
이번 사례가 여기 해당합니다.
평균이 스로틀을 가립니다
CFS quota는 100ms짜리 period 단위로 채워집니다.
100ms마다 새로 충전되고, 남아도 이월되지 않습니다. 1분 평균 같은 건 커널 입장에서 존재하지 않고요.
가장 험했던 1분을 열어보겠습니다. 충전량은 period당 60ms(limit 600m), 평균 사용량은 20.7ms(0.207코어)였습니다.
34%. 대시보드에는 그렇게 찍힙니다.
그런데 같은 1분에 period의 28%가 스로틀로 카운트되고 있었습니다.
스로틀됐다는 건 그 period에서 60ms를 다 쓰고도 모자랐다는 뜻입니다 ← 그러니 그 period들의 사용량은 정확히 60ms입니다. 천장이니까요.
여기까지 오면 나머지가 역산됩니다. period 100개로 놓고 보죠.
스로틀된 28개 × 60ms(천장) = 1680ms
멀쩡한 72개 × 5.4ms = 389ms
합계 = 2069ms ⇒ period당 20.7ms ← 우리가 본 "평균"네 번에 한 번꼴로 천장에 머리를 박고 있었고, 나머지는 5ms 남짓 쓰고 놀고 있었습니다.
60ms 아니면 5ms. 그 사이는 거의 없습니다.
정작 대시보드가 가리키는 34%(20.7ms)짜리 period는 하나도 없습니다.
평균 207m은 이 두 상태를 섞어 만든, 어느 쪽도 아닌 숫자였을 뿐이었습니다. 그래프가 여유로워 보인 건 그래서였고요.
요청 하나 관점으로 바꿔 보면 더 분명해집니다.
quota를 다 쓴 순간부터 다음 구간이 시작될 때까지 애플리케이션 전체가 멈춘다. 평균 사용률은 0.1코어로 limit(0.2코어) 아래인데도 요청은 80ms 더 걸린다.
여기서 평균 CPU 사용률은 0.1코어입니다. limit(0.2코어)의 절반이죠. 대시보드에는 여유로운 값으로 나옵니다.
그런데 요청 하나는 80ms를 멈춰 있었습니다.
그리고 도는 코어가 여러 개면 이 예산은 더 빨리 마릅니다.
limit이 1코어(quota 100ms/period)여도, 코어가 많아 동시 실행이 늘면 그 100ms를 더 빨리 태운다. 1코어는 꽉 채워 안 잘리지만 2코어는 절반·4코어는 ¾ 이 멈춤 — throttle은 CPU 총량이 아니라 병렬도(코어 수)에서 터진다.
quota는 “동시에 몇 코어가 도느냐”에 비례해서 소진됩니다. limit 1코어면 period당 CPU-time은 100ms인데, 1코어면 그걸 딱 채우고 끝나 안 잘리고, 2코어면 50ms·4코어면 25ms 만에 태워버립니다.
그 “동시 개수”는 노드 코어가 받쳐줘야 나옵니다. 같은 limit이라도 코어 많은 노드에 올릴수록 더 잘 잘리는 거죠 — 직관과 반대입니다.
그리고 quota가 마르는 순간 돌던 스레드가 다 같이 멈춥니다. 하나만 느려지는 게 아니라 컨테이너 전체가 정지하는 거죠. 요청 하나하나의 꼬리가 길어지는 이유입니다.
그런데 이번 사례는 반대쪽이었습니다.
병렬도가 1이라 quota를 빨리 태울 수 있는 조건이 아니었는데도 잘렸거든요. 이유는 조금 뒤에 나옵니다…
같은 문제가 보고된 사례도 있습니다.
kubernetes#67577을 보면 ingress controller에서 CPU limit을 걷어냈더니 p99 지연이 12~20배 줄었습니다.
평균 CPU는 30m 남짓이었고, limit을 1500m까지 올려봐도 별로 나아지지 않았는데, 아예 없애자 p99·p999가 60ms·100ms에서 ~5ms로 떨어졌다고요.
⇒ limit을 높이는 게 아니라 없애야 풀렸다는 게 이 문제의 성격을 그대로 보여줍니다. 총량이 모자란 게 아니라 쓸 수 있는 타이밍이 잘린 거니까요.
결론 : CPU 사용률 그래프만 보고 limit을 사이징하면 안 됩니다. throttled_periods / periods 분율을 같이 보지 않으면 이 상태는 아예 안 보입니다.
quota가 명목대로 안 쓰이는 구간도 조금 있습니다
지금까지 짚은 주된 원인은 버스트입니다. period 안에서 몰아 쓰다 예산이 마르는 것.
거기에 2차 요인이 하나 더 붙습니다.
CFS는 quota를 통째로 주는 게 아니라 CPU별로 5ms 슬라이스씩 떼어서 넘깁니다.
그리고 슬라이스를 받아간 CPU에서 스레드가 잠들면 1ms만 남기고 나머지는 글로벌 풀로 돌아갑니다.
돌아가긴 하는데, 그 1ms씩은 남습니다.
CPU를 여러 개 훑을수록 조금씩 새는 거죠(kubernetes#67577 ).
이렇게 돌아가지 못하고 남는 몫을 좌초분이라고 부르겠습니다.
이 워크로드가 정확히 그 조건이었습니다. OS 스레드가 17개였거든요.
병렬도는 1인데도요. (그 1이 어디서 왔는지는 다음 절에서 다룹니다)
병렬도가 1이라는 건 계산을 도는 자리가 하나라는 뜻일 뿐이니까요.
프로세스가 띄우는 런타임 보조 스레드들은 그대로 남아 여러 CPU를 훑습니다.
그리고 전부 같은 quota에 청구됩니다.
다만 규모는 크지 않았습니다.
좌초분이 전혀 없다고 보고(quota 60ms 그대로) 계산했을 때 앞뒤가 안 맞는 표본은 6.5%뿐이었습니다. 나머지는 60ms로 설명이 되고요.
⇒ 주된 원인은 버스트고, 슬라이스 잔류는 2차 요인입니다. 비중을 뒤집어 읽으면 안 됩니다.
앱이 믿는 코어 수와 커널이 주는 quota는 다릅니다
요즘 런타임과 스레드풀은 자기 몫 코어 수를 노드에서 세지 않습니다.
cgroup에 걸린 CPU limit을 직접 읽거나 오케스트레이터가 주입해준 값을 받아서 정하는데, 여기서 그 값은 0.6입니다.
앱이든 런타임이든 스레드풀이든, 병렬도는 결국 정수 코어 수로 정합니다.
워커를 몇 개 띄울지, 스레드를 몇 개 만들지를 정하는 자리에 0.6을 넣을 수는 없으니까요.
그래서 1코어 미만이면 병렬도는 최소 1로 올라갑니다.
올리는 방향도 구현마다 다릅니다 — 올림이면 1.4코어는 2가 되고, 내림이면 1로 남습니다.
반면 커널 quota는 소수점을 그대로 씁니다. limit이 600m이면 period 100ms당 60ms고요.
| Option | Type | Description |
|---|---|---|
| 앱이 준비한 병렬도 | ceil(0.6) = 1 — 1코어를 태울 준비를 함 | |
| CFS quota | 0.6 × 100ms = 60ms — 커널은 0.6코어만 허용 |
런타임과 커널이 서로 다른 값을 기준으로 동작하는 셈입니다.
⇒ 읽는 쪽은 정수로 올리고, 주는 쪽은 소수점 그대로입니다.
말로는 잘 안 와닿으니 움직여 보겠습니다. 1코어에 맞게 요청을 처리하도록 구성한 평범한 앱 하나로 단순화하죠. 아래 그림에서 셋만 따라가시면 됩니다.
왼쪽에서 요청이 계속 도착합니다. 가운데 CPU 슬롯은 하나뿐이라 한 번에 하나씩 처리하고요.
그런데 CFS가 이 슬롯에 주는 시간은 1코어(100ms 전부)가 아니라 period 100ms당 60ms뿐입니다.
위에서 아래로 흐르는 period 시간축에서 60ms를 다 쓰면 슬롯이 닫히고(스로틀), 그동안 도착한 요청은 큐에 그대로 쌓입니다.
다음 period가 와야 다시 열립니다.
앱은 1코어를 쓸 생각으로 요청을 받지만, CFS가 실제로 주는 건 period 100ms당 60ms뿐이다. 60ms를 다 쓰면 남은 40ms 동안 슬롯이 닫히고 — 그 사이 도착한 요청은 큐에 쌓여 다음 period를 기다린다. 평균 사용률은 그대로인데 꼬리만 길어지는 이유다.
큐가 쌓였다 빠지기를 반복하는 게 보이시죠. CPU 슬롯은 노는 게 아닙니다 — quota는 매번 다 씁니다. 다만 하나짜리 슬롯이 주기적으로 닫히니 요청이 그때마다 밀립니다.
평균은 한가한데 꼬리가 길어지는 게, 그림으로 보면 이겁니다.
도입에서 “이 둘이 맞물리면”이라고 한 게 이 슬롯입니다.
앞에서 계산한 24%의 period가 이 슬롯이 60ms를 끝까지 태운 순간들이고요.
나머지 76%는 8ms만 쓰고 지나간 한가한 period였습니다.
위 그림이 보여주는 게 그 섞임입니다.
limit을 올리거나 period를 늘리면 되나
먼저 떠오르는 답이 둘 있는데, 둘 다 절반만 듣습니다.
limit을 올린다. 실제로 효과는 있습니다.
다만 request = limit으로 맞춰 쓰고 있었다면 limit을 올리는 만큼 request도 올라가면서 노드에 파드가 덜 들어갑니다. 클러스터 전체 사용률이 떨어지죠.
request는 그대로 두고 limit만 올리면 이번엔 오버커밋이 됩니다. 평소엔 괜찮지만 부하가 몰리는 시점에 문제가 됩니다.
period를 늘린다. 앞의 0.2코어 그림(발표 자료 예시)으로 다시 말하면, quota 20ms/period 100ms를 40ms/200ms로 바꾸면 같은 0.2코어인데 스로틀 횟수는 절반이 됩니다.
그런데 한 번 잘릴 때 더 오래 잘립니다. 80ms 멈추던 게 160ms 멈추는 거죠. 지연에 민감한 서비스라면 오히려 나빠집니다.
게다가 Linux는 period 상한이 1000ms라 크게 키울 수도 없습니다.
⇒ 둘 다 “얼마나 주느냐”를 건드리는 답입니다. 문제는 총량이 아니라 타이밍이었는데요.
CPU Burst — 안 쓴 quota를 모아뒀다 빌려 씁니다
타이밍을 직접 건드리는 방법이 커널에 있습니다.
사실 2024년에 컨테이너 격리를 정리하면서 한 번 지나갔던 기능입니다. 그때는 “이런 게 있다”까지만 적고 넘어갔는데, 이렇게 직접 밟고 나서 다시 보게 됐네요…
Linux 커널 문서의 표현이 정확합니다.
This feature borrows time now against our future underrun, at the cost of increased interference against the other system users.
한가할 때 안 쓰고 남긴 quota를 버퍼에 적립해뒀다가, 바쁠 때 당겨 쓰는 겁니다.
안 쓴 quota를 버퍼에 모아뒀다 필요할 때 당겨 쓴다. 모아둔 게 없는 1구간은 그대로 잘리지만, 2구간의 미사용분 덕에 3구간은 스로틀 없이 지나간다 — 긴 구간 평균은 여전히 limit을 넘지 않는다.
여기서 중요한 건 계약이 깨지지 않는다는 점입니다.
Burst 없음 : CPUTime ≤ Quota
Burst 켬 : CPUTime ≤ Quota + Buffer ← 한 구간에서는 넘을 수 있지만
버퍼가 과거의 미사용분에서만 오므로
: ΣCPUTime ≤ Quota × Periods ← 긴 구간 평균은 그대로순간적으로는 limit을 넘지만, 버퍼가 과거의 미사용분에서만 채워지니 긴 구간 평균은 limit으로 수렴합니다. “0.2코어”라는 약속은 지켜지고, 그 0.2코어를 언제 쓸지만 자유로워지는 거죠.
다만 이건 설계 의도지 강한 보장이 아닙니다. 2021년 커널 메일링리스트에 이런 리포트가 올라왔거든요.
Tasks might get more cpu than quota in persistent periods… Once this group gets a free period which leaves enough runtime, it has a chance to get computing power more than its quota for 10 periods or more
— sched/fair: prevent cpu burst too many periods , Honglei Wang, 2021-11
quota 100ms에 버퍼 100ms를 주고 평균 수요가 105ms쯤 되는 그룹이면, 한 번 한가한 구간이 생기는 순간 그걸로 10주기 넘게 계속 초과할 수 있습니다. 보고자의 표현으로는 태스크가 버스트 몫을 “훔쳐서 평상시 일에 쓰는” 셈이죠.
⇒ 버퍼를 크게 잡을수록 “가끔 튀는 걸 흡수한다”에서 “상시 초과한다”로 성격이 바뀝니다. quota의 몇 배씩 주는 건 그래서 위험합니다.
설정과 관측은 이렇습니다.
# cgroup v1
cpu.cfs_burst_us # 적립 상한. 기본 0 = 적립 안 함
# quota보다 크게는 못 잡는다
# cgroup v2
cpu.max.burst
# 얼마나 쓰였나 — cpu.stat에 두 항목이 붙는다
nr_bursts # burst가 발생한 period 수
burst_time # quota를 초과해 쓴 누적 시간기본값이 0이라는 데 주의해야 합니다. 커널이 지원해도 명시적으로 켜지 않으면 아무 일도 일어나지 않습니다.
효과는 발표 자료에 실측이 있습니다. 어떤 워크로드에서 평균 응답 30ms대 → 9.6ms, p99 500ms대 → 27.3ms. 다른 케이스에선 평균 1310ms → 952ms로 27% 감소.
같은 수치가 커널 커밋 메시지에도 있습니다 — Java 워크로드 p99 500ms+ → 27ms, schbench p99 933us → 12us. 발표만의 주장이 아니라 패치를 머지할 때 제시된 근거였다는 뜻입니다.
Alibaba의 Apache HTTP Server 벤치마크(limit 4코어)도 같이 보면 좋습니다.
| Option | Type | Description |
|---|---|---|
| p99 응답 | 111.69ms | 71.30ms (−36%) |
| 스로틀된 비율 | 33% | 0% |
| 평균 CPU 사용률 | 32.5% | 33.8% |
평균 CPU는 거의 그대로입니다. 더 많이 쓴 게 아니라 쓰는 타이밍만 바뀌었는데 꼬리가 36% 깎였습니다. 이 기능의 성격이 이 표에 잘 드러납니다.
공짜는 아닙니다
커널 문서에 적힌 “at the cost of increased interference against the other system users”가 그 대가입니다.
내 컨테이너가 버퍼를 당겨 쓰는 동안, 같은 노드의 다른 컨테이너는 그만큼 밀립니다. 원래대로면 지켰을 deadline을 놓칠 수 있고, 그게 반복되면 계속 밀리기만 하는 상태에 빠질 수도 있습니다.
발표자들은 이걸 큐잉 이론과 몬테카를로 시뮬레이션으로 따져봤는데, 결론이 “노드 평균 CPU 사용률이 70% 아래면 안전하다” 였습니다.
다만 조건이 붙습니다 — cputime 수요가 지수분포, 컨테이너 20개, 버퍼 = quota 100% 기준입니다.
컨테이너 수가 많을수록, 버퍼가 작을수록 안전한 쪽이고요.
⇒ 여유 있는 노드에서 튀는 워크로드에는 잘 맞고, 이미 꽉 찬 노드에서는 쓰면 안 되는 기능입니다.
운영 함정도 몇 개 문서화돼 있습니다. Alibaba의 koordinator 문서에 적힌 것들인데, 세 번째가 특히 이 글과 맞물립니다.
- 반응이 늦습니다. quota 조정 방식은 스로틀이 실제로 감지된 뒤에야 움직입니다. 첫 번째 스파이크는 이미 맞고 난 뒤죠.
- 한 파드의 남용이 노드 전체로 번집니다. 노드 CPU가 임계(기본 50%)를 넘으면 그 노드의 모든 파드 quota가 일괄 리셋됩니다.
cfs_quota_us를 읽어 스레드 수를 정하는 앱은 오작동할 수 있습니다. quota 값이 요동치니까요.
세 번째를 다시 읽어보시죠.
이 글이 내내 다룬 게 정수 병렬도와 소수점 quota의 어긋남이었습니다.
quota를 동적으로 흔드는 방식과, 그 quota 값을 읽어 스레드 수를 정하는 앱을 같이 쓰면 둘이 서로를 밟습니다.
Alibaba Cloud Linux에도 비슷한 함정이 있습니다 — 자식 cgroup은 기본적으로 burst가 꺼져 있어서 개별로 켜줘야 합니다. 부모에 걸었다고 상속되지 않습니다.
쓸 수 있나
커널은 준비돼 있는데 위층이 안 따라왔습니다.
- Linux 5.14+ 메인라인에 들어가 있습니다
- OCI runtime-spec에는
burst필드가 있습니다. 2023년 1월에 머지됐어요 - 그런데 Kubernetes에는 이걸 켤 방법이 없습니다. #104516 이 2021년 8월에 열렸는데 5년째 담당자도 연결된 PR도 없이 그대로입니다
⇒ 커널에도 있고 런타임 스펙에도 있는데 파드 스펙에서 닿을 수가 없는 상태입니다.
그래서 지금 쓰려면 노드에서 cgroup 파일을 직접 만지거나(DaemonSet으로 흉내 내는 사례들이 있습니다), Alibaba ACK처럼 벤더가 별도 애노테이션(koordinator.sh/cpuBurst)을 얹어준 환경이어야 합니다.
2024년 글에서 이 기능을 적으면서 “쿠버네티스는 그닥 관심을 보이지 않는듯 합니다” 라고 썼는데, 2년 지나 다시 찾아보니 그 말이 그대로 맞았습니다.
관심을 보인 쪽은 여전히 Alibaba였고요.
확인은 파일 존재 여부로 합니다.
kubectl exec <pod> -- cat /sys/fs/cgroup/cpu.max.burst # cgroup v2
# "0" 이면 커널은 지원하는데 꺼져 있다는 뜻
# 파일이 없으면 커널이 5.14 미만저희 환경은 아직 확인 전입니다. 다만 이 글의 그 워크로드처럼 평균은 한가한데 버스트에서 잘리는 모양이라면, 이게 정확히 겨냥하는 케이스입니다.
그래서 CPU limit을 걸 때
이번 일을 일반화하면 규칙 몇 개가 나옵니다.
소수점 CPU limit은 어긋나기 쉽습니다. 1코어 미만이면 병렬도는 최소 1로 올라가는데 quota는 그 값 그대로니까요.
600m은 어느 쪽으로 반올림해도 1이라 무조건이고, 올림하는 구현이라면 1500m·2500m도 같은 문제입니다. 정수 코어로 맞춰두면 둘이 어긋날 자리가 없습니다.
1코어 미만 limit에 CPU-bound 워크로드를 올리는 건 피하는 게 좋습니다. 병렬도를 1보다 아래로 내릴 방법이 없어 애초에 맞출 수가 없습니다.
그렇다고 limit을 아예 빼는 것도 답은 아닙니다. 런타임은 cgroup을 읽어 병렬도를 정하니, limit이 없으면 노드 전체 코어 수를 그대로 잡습니다. 64코어 노드에서 병렬도 64는 그것대로 낭비입니다.
대시보드를 고쳐두는 게 먼저입니다. rate(container_cpu_cfs_throttled_periods_total) / rate(container_cpu_cfs_periods_total) 를 CPU 그래프와 같은 패널에 겹쳐 두세요.
그리고 알람은 사용률이 아니라 이 분율에 거는 게 맞습니다. 사용률 임계는 이 상태에서 영원히 안 울리니까요.
정리
- CPU 그래프는 혼자 읽으면 안 됩니다. 응답 시간과 겹쳐야 “한가한데 느린” 상태가 보이고, 컨테이너에서는 거기에 스로틀이라는 네 번째 패턴이 하나 더 있습니다.
- CPU 사용률은 최대 limit의 34%였는데, 잘린 period는 최대 30%였습니다. quota는 100ms 단위로 소진되지 평균으로 판정되지 않기 때문입니다.
- 앱은 1코어를 믿고 커널은 0.6코어를 줍니다. 소수점 limit은 읽는 쪽이 올림하는 순간 이렇게 어긋납니다.
- 문제가 총량이 아니라 타이밍이라면, limit을 올리거나 period를 늘리는 건 반쯤만 듣습니다. CPU Burst는 안 쓴 quota를 적립해 그 타이밍을 직접 풀어주되, 누적 평균은 그대로 지킵니다. 대신 같은 노드의 이웃을 밀어내니 여유 있는 노드에서만 쓸 것.
이 글에 크게 빚진 자료 둘 :
- 서버 모니터링 분석 가이드 (kciter) — “CPU 사용률이라는 말의 함정” 절. 이 글 앞부분의 CPU 그래프 세 패턴은 여기서 얻은 관점입니다. 지표를 혼자 읽지 말라는 이야기를 이보다 잘 정리한 걸 못 봤습니다.
- CPU Burst: Getting Rid of Unnecessary Throttling — KubeCon + CloudNativeCon China 2021, Huaixin Chang & Tianchen Ding (Alibaba Cloud). 0.2코어 / 30ms → 110ms 예시, CPU Burst 설계와 WCET 시뮬레이션 결과가 전부 이 발표에서 나왔습니다. 발표 영상 · 슬라이드 PDF · 커널 반영은 commit f4183717b370
두 자료의 도해는 그대로 쓰지 않고, 이 글의 색 체계에 맞춰 전부 새로 그렸습니다. 수치와 논지는 원본을 따르되 그림은 제 것입니다.
그 밖에 :
- 컨테이너의 모든것 — 2024년에 cgroup 격리를 정리하면서 CPU Burst와 CPU 고정코어 할당을 짚어둔 글. 이번 글은 그 각주를 실제로 밟은 이야기입니다
- Alibaba Cloud 기술블로그 3부작 — Kill the Annoying CPU Throttling · 2부 · 확률론으로 다시 보는 CPU Burst . 같이 보시면 흥미롭습니다
- sched/fair: prevent cpu burst too many periods — 버퍼를 크게 잡으면 버스트를 “훔쳐” 상시 초과할 수 있다는 리포트
- Linux / CFS Bandwidth Control — quota·period·슬라이스,
min_cfs_rq_runtime, 그리고cpu.cfs_burst_us - kubernetes/kubernetes#67577 — 저사용률 스로틀 보고, ingress controller p99 12~20배 사례
- kubernetes/kubernetes#104516 — 파드 스펙에서 burst buffer를 켜는 논의
그래프는 내내 여유로워 보였습니다.
limit의 34%. 이 숫자만 들고 있으면 CPU는 용의자 목록에도 안 올라갑니다.
이 상태를 못 알아본 이유가 하필 평균이라는 가장 익숙한 렌즈 때문이었다는 게 오래 남습니다…
limits.cpu를 정수로 올리고 나면 스로틀 분율과 꼬리 지연이 어떻게 달라지는지는, 다시 재보고 남기겠습니다.