LabHub
배우기 러닝패스 코스

Container Internals

Apply Limits and Compare Against What You Observe

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

이 실습은 진짜 VM 에서 돕니다

이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이 따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라 진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가 되고 docker execdocker logs 도 그대로 동작합니다.

예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라 컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어 보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.

알아 둘 것이 둘 있습니다.

목표

메모리·CPU·PID 한도를 직접 걸어 보고, 요청한 값(docker inspect 의 HostConfig)과 컨테이너가 실제로 보는 값(/sys/fs/cgroup/*)을 나란히 놓고 비교합니다. 종료코드 137 을 직접 만들어 보고 OOM 킬과 구분하는 법을 정리합니다.

왜 중요한가

운영에서 나오는 질문은 거의 항상 "제한을 걸었는데 왜 안 지켜지죠"이거나 "CPU 는 한가한데 왜 느리죠"입니다. 첫 번째는 요청값과 강제값이 다를 수 있다는 것(rootless 환경에서는 systemd delegation 이 없으면 요청만 기록됩니다)을 몰라서 생기고, 두 번째는 CPU cgroup 이 프로세스를 죽이지 않고 재운다는 것을 몰라서 생깁니다. 그리고 둘 다 docker stats 나 대시보드가 아니라 cgroup 파일을 직접 읽어야 확정됩니다. 그래서 이 실습은 값을 두 군데서 읽어 대조하는 습관을 만드는 것이 목적입니다. 하나만 읽으면 반드시 틀린 결론에 도달합니다.

단계

  1. /root/int2 디렉터리를 만들고, stat -fc %T /sys/fs/cgroup 의 출력을 그대로 /root/int2/version.txt 에 저장합니다 (cgroup2fs 면 v2, tmpfs 면 v1).
  2. /sys/fs/cgroup/cgroup.controllers 파일 내용을 그대로 /root/int2/controllers.txt 에 저장합니다.
  3. alpine:3.20 이미지로 dk-mem 컨테이너를 메모리 한도 64MiB(67108864 바이트) 로 걸어 백그라운드 실행합니다. 채점 시점에 계속 running 상태여야 합니다.
  4. dk-mem 안에서 /sys/fs/cgroup/memory.max 를 읽어 그 값을 그대로 /root/int2/memmax.txt 에 저장합니다.
  5. alpine:3.20 이미지로 dk-cpu 컨테이너를 CPU 0.5 코어 한도로 걸어 백그라운드 실행합니다.
  6. dk-killed 컨테이너를 띄운 뒤 직접 SIGKILL 을 보내 종료코드 137 로 끝냅니다(OOM 이 아니어야 합니다). 그리고 /root/int2/exit137.txt 에 137 이 128 + 9 로 이루어진다는 분해와, OOM 킬도 같은 137 을 낸다는 점을 함께 적습니다.
  7. alpine:3.20 이미지로 dk-pids 컨테이너를 PID 한도 64 로 걸어 실행합니다.
  8. /root/int2/limits.md 에 세 행을 적습니다. dk-mem 행에는 실제 바이트 값 67108864 를, dk-cpu 행에는 0.5(또는 500m)를, dk-pids 행에는 64 를 포함시킵니다.

참고

cgroup 버전 판별

/root/int2 디렉터리를 만들고, stat -fc %T /sys/fs/cgroup 의 출력을 그대로 /root/int2/version.txt 에 저장합니다 (cgroup2fs 면 v2, tmpfs 면 v1).

마운트된 파일시스템의 종류를 물어보는 stat 옵션이 있습니다. 결과 문자열을 해석해서 v1/v2 라고 적는 것이 아니라, 나온 문자열 그대로 저장해야 합니다.

사용 가능한 컨트롤러 목록

/sys/fs/cgroup/cgroup.controllers 파일 내용을 그대로 /root/int2/controllers.txt 에 저장합니다.

cgroup v2 는 루트 계층에 사용 가능한 컨트롤러 이름을 한 줄로 나열한 파일을 둡니다. 손으로 옮겨 적지 말고 그 파일 내용을 그대로 복사하세요. 공백 차이는 허용되지만 항목이 빠지면 안 됩니다.

메모리 한도 64MiB 요청하기

alpine:3.20 이미지로 dk-mem 컨테이너를 메모리 한도 64MiB(67108864 바이트) 로 걸어 백그라운드 실행합니다. 채점 시점에 계속 running 상태여야 합니다.

메모리 한도 플래그는 m, g 같은 접미사를 받습니다. 64MiB 는 정확히 67108864 바이트이고, 채점은 이 숫자를 봅니다. 컨테이너는 채점 시점에 떠 있어야 하므로 곧 끝나는 명령으로 띄우면 안 됩니다.

컨테이너가 실제로 보는 memory.max

dk-mem 안에서 /sys/fs/cgroup/memory.max 를 읽어 그 값을 그대로 /root/int2/memmax.txt 에 저장합니다.

컨테이너 안에서 /sys/fs/cgroup/memory.max 를 읽습니다. 값이 67108864 가 아니라 max 로 나올 수도 있는데, 그것이 오답이 아니라 이 단계의 핵심입니다 — 요청값과 강제값은 다를 수 있습니다. 본 그대로 저장하세요.

CPU 0.5 코어 한도

alpine:3.20 이미지로 dk-cpu 컨테이너를 CPU 0.5 코어 한도로 걸어 백그라운드 실행합니다.

코어 수를 소수로 주는 플래그가 있습니다. 내부적으로는 quota 가 period 의 절반이 되고, 이는 '코어를 반으로 자른다'가 아니라 '매 주기의 절반만 실행한다'는 뜻입니다.

종료코드 137 만들고 해석하기

dk-killed 컨테이너를 띄운 뒤 직접 SIGKILL 을 보내 종료코드 137 로 끝냅니다(OOM 이 아니어야 합니다). 그리고 /root/int2/exit137.txt 에 137 이 128 + 9 로 이루어진다는 분해와, OOM 킬도 같은 137 을 낸다는 점을 함께 적습니다.

메모리를 터뜨려서 죽이는 것이 아니라 직접 SIGKILL 을 보내야 합니다(OOMKilled 가 true 면 이 단계는 실패합니다). 그리고 메모 파일에는 137 을 128 과 9 로 분해한 설명과, OOM 킬도 똑같이 137 을 낸다는 사실을 함께 적으세요.

PID 한도로 포크 폭탄 막기

alpine:3.20 이미지로 dk-pids 컨테이너를 PID 한도 64 로 걸어 실행합니다.

프로세스 개수 상한을 거는 플래그가 따로 있습니다. 메모리나 CPU 한도로는 포크 폭탄을 막지 못합니다 — 프로세스 하나하나는 작지만 개수로 커널 자원을 고갈시키기 때문입니다.

제한 요약표 작성

/root/int2/limits.md 에 세 행을 적습니다. dk-mem 행에는 실제 바이트 값 67108864 를, dk-cpu 행에는 0.5(또는 500m)를, dk-pids 행에는 64 를 포함시킵니다.

세 행 모두 추측이 아니라 docker inspect 로 확인한 값을 적어야 합니다. 메모리는 바이트 숫자 그대로, CPU 는 0.5 로 알아볼 수 있게, PID 는 숫자 그대로 들어가야 합니다.