이 글은 리눅스가 어떻게 컨테이너 cpu를 제어하는지 설명합니다.
1. 컨테이너가 실행된다는 의미
컨테이너가 실행된다는 것은 무슨 의미일까요? 컨테이너는 운영체제가 관리하는 프로세스이기 때문에 프로세스가 실행된다는 의미와 같습니다. 결국 컨테이너가 실행된다는 것은 프로세스가 실행된다는 의미와 같습니다.
프로세스가 실행된다는 의미는 CPU가 시간을 할애하여 프로세스의 연산을 수행하는 것입니다. 운영체제는 CPU가 여러 프로세스를 효율적으로 연산할 수 있도록 프로세스를 스케쥴링 합니다. 스케쥴링은 알고리즘에 따라 동작이 다릅니다.
2. 리눅스가 컨테이너 CPU자원 할당을 제어하는 원리
리눅스에서는 모든 프로세스가 최대한 공정하게 CPU를 할당받을 수 있도록 CFS(Completely Fair Scheduler) 알고리즘을 사용합니다.
CFS 알고리즘은 모든 프로세스에게 CPU 시간을 공정하게 분배하려고 노력합니다. 그러나 현실적인 시나리오에서는 특정 프로세스가 CPU 자원을 필요로 하거나, 반대로 제한되어야 하는 경우가 있습니다. 사용자가 시나리오에 맞게 프로세스에 CPU자원할당을 설정할 수 있어야 하는데, 이 역할을 cgroup(Control group)이 합니다.
cgroup은 프로세스를 그룹으로 묶어 cpu, 메모리 등의 자원을 제어합니다. cgroup을 통해 설정된 자원은 CFS 알고리즘 실행에 영항을 줍니다. 예를 들어 A이름을 갖는 cgroup에 cpu를 30%만 사용하도록 제한하면, A cgroup에 속한 프로세스는 cpu 30%만 사용하게 됩니다.
컨테이너는 프로세스이기 때문에, 컨테이너 또한 cgroup을 사용하여 cpu를 제한합니다.
3. cgroup 확인 방법
cgroup 설정은 파일로 관리되고 컨테이너는 cgroup설정을 마운트해서 사용합니다. 따라서 마운트 정보를 확인하면 cgroup파일이 있는 경로가 보입니다.
mount | grep cgroup
cgroup은 v1과 v2가 있는데 2024년을 기준으로 v2로 변경되는 추세입니다. 아래 예제는 cgroup v2를 사용합니다. 마운트 경로는 /sys/fs/cgroup으로 고정되어 있습니다.
cgroup 설정파일은 cpu, memory 등 매우 많습니다.
4. 쿠버네티스 pod cgroup 경로 확인방법
컨테이너가 마운트 한 cgroup파일들은 컨테이너를 실행하는 호스트에서도 찾을 수 있습니다. 이 원리를 사용하여 호스트에서 쿠버네티스 pod가 사용하는 cgroup을 찾을 수 있습니다.
pod cgroup을 찾는 과정은 아래와 같습니다. pod uuid를 찾고 호스트에서 uuid로 cgroup 경로를 찾으면 됩니다.
kubectl get pod <pod_name> -o jsonpath='{.metadata.uid}'
POD_UID_UNDERSCORE=$(kubectl get pod nginx-pod -o jsonpath='{.metadata.uid}' | tr '-' '_')
find /sys/fs/cgroup/ -name "*${POD_UID_UNDERSCORE}*"
아래 예제는 busybox-cpu1 pod의 cgroup을 찾는 예제입니다.
pod는 1개 이상 컨테이너를 실행할 수 있으므로, pod cgroup하위에 컨테이너 갯수만큼 cgroup이 있습니다.
아래 예시는 busybox-cpu1 pod에서 pause컨테이너의 cgroup을 찾는 과정입니다. cgroup 설정파일 중 cgroup.procs파일이 cgroup을 적용할 프로세스 id입니다. 프로세스 id 정보를 조회하면 pause컨테이너라는 것을 확인 할 수 있습니다.
$ cat cgroup.procs
972
$ ps -fp 972
UID PID PPID C STIME TTY TIME CMD
65535 972 940 0 00:32 ? 00:00:00 /pause
5. pod 컨테이너의 request와 cgroup 연관관계
cgroup은 pod 컨테이너의 request를 어떻게 제어할까요? 아래 예제처럼 request.cpu를 0.3으로 설정하는 것은 어떤 뜻일까요? cpu 0.3 core를 사용한다는 의미일까요?
정답은 cpu 시간을 상대적으로 0.3만큼 사용하겠다는 의미입니다. 0.3코어가 아니라 상대적으로 0.3시간만큼 cpu를 쓰겠다는 의미입니다. 쿠버네티스는 request.cpu를 cgroup이 이해하는 값으로 변환하고 cgroup은 변환된 값을 읽어 프로세스에게 cpu 시간을 상대적으로 할당합니다. cgroup v1에서는 cpu share로 변환되고 cgroup v2에서는 cpu weight로 변환됩니다.
상대적으로 cpu 시간이 할당된다는 의미가 매우 중요합니다. request는 코어가 아니라 시간이 할당된다는 의미입니다. 따라서 다른 프로세스가 cpu 시간을 더 사용하게 되면, pod에 정의된 request보다 cpu 시간을 못 쓸 수 있습니다.
6. request.cpu 예제
예제에서는 정말 request.cpu가 코어가 아니라 cpu시간을 상대적으로 쓰겠다는 것을 보여줍니다. 테스트 결과를 쉽게 분석하기 위해 쿠버네티스 노드는 1core로 설정했습니다.
노드에 request 1.5 cpu를 설정한 pod가 있으면 이 pod의 cpu메트릭은 1.5가 아니라 1이 나옵니다. 1core가 아니라 cpu 100% 시간을 pod에 사용했다는 의미로 해석됩니다.
request.cpu 0.5를 갖는 pod를 생성하면 pod cpu는 어떻게 실행될까요? pod는 3:1비율로 cpu시간을 사용합니다. 1.5:0.5는 3:1이므로 pod가 3:1비율만큼 pod가 cpu시간을 사용합니다.
request.cpu는 cgroup값으로 변환됩니다. cgroup v2에서는 cpu.weight입니다. cpu.weight도 kubectl top결과와 마찬가지로 약 3:1비율로 설정됩니다.
7. pod 컨테이너의 limit와 cgroup 연관관계
limit은 cpu가 pod에 할애하는 시간을 절대시간으로 제한합니다. request.cpu와 연관지으면 리눅스 운영체제는 cpu시간을 request를 참조하여 상대적으로 pod에 cpu시간을 할애하고, 할애한 시간이 limit은 못넘게 합니다.
limit.cpu는 cgroup v1에서 cpu.cfs_quota_us로 변환되고 v2에서는 cpu.max값으로 변환됩니다.
limit.cpu는 request와 다르게 계산식이 있습니다. quota / period가 계산식입니다. 예를 들어 아래 예제에서는 limit.cpu가 1.5가 어떻게 quota, period값으로 전환되는지 보여줍니다. quota가 150000ms, period 100000ms로 변환되고 계산식에 의해 1.5가 됩니다.
period는 리눅스 CFS스케쥴러의 주기입니다. 디폴트로 100ms로 설정되어 있고 100ms마다 프로세스에게 얼마나 cpu시간을 할애할지 결정합니다. 그리고 quota는 프로세스에게 cpu시간을 할애하는 절대값입니다.
부록. systemd는 디폴트로 cgroup을 사용
systemd로 실행되는 프로세스는 디폴트로 cgroup을 사용합니다. docker, kubelet 프로세스는 보통 systemd로 실행되기 때문에 cgroup을 사용합니다. 아래 예제는 init프로세스를 systemd로 실행했을 때 cgroup이 적용된 결과입니다.
systemctl status
'전공영역 공부 기록' 카테고리의 다른 글
쿠버네티스 cpu limit을 설정해야할까? 안해야할까? (1) | 2024.10.06 |
---|---|
아키텍처 이야기 - 애플리케이션 개수를 증가시키면 요청 처리속도가 빨라질까? (1) | 2024.10.06 |
맥북 Linux VM에서 perf사용 방법 (0) | 2024.09.21 |
AWS ALB에 여러 도메인 인증서(ACM)를 적용한 경험 (2) | 2024.09.11 |
Pause container는 어떻게 실행될까? (1) | 2024.09.08 |