컨테이너 내부 원리 · cgroup — 얼마나 쓸 수 있는가 · 실습
제한을 걸고 관측값과 대조하기
이 실습은 진짜 VM 에서 돕니다
이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이
따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라
진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가
되고 docker exec 도 docker logs 도 그대로 동작합니다.
예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라
컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어
보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.
알아 둘 것이 둘 있습니다.
- 처음 뜨는 데 1분 남짓 걸립니다. VM 이 부팅하고 도커를 설치하기
- 브라우저 미리보기가 없습니다. VM 으로 들어오는 연결은 채점 포트
때문입니다. 파드 실습(보통 40초)보다 느립니다.
하나만 열려 있습니다. 웹 서버를 띄웠다면 VM 안에서 curl 로 확인하세요.
목표
메모리·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 를 포함시킵니다.
참고
- 메모리 한도는
--memory=64m, CPU 는--cpus=0.5, PID 는--pids-limit=64로 겁니다. - 요청값 확인은
docker inspect <이름> | jq -r '.[0].HostConfig.Memory'처럼 읽습니다. - 직접 신호를 보내는 명령은
docker kill이고 기본 신호가 SIGKILL 입니다. - 종료코드와 OOM 여부는
docker inspect <이름> | jq -r '.[0].State.ExitCode, .[0].State.OOMKilled'로 함께 볼 수 있습니다. - 흔한 실수 1: 3·5·7단계에서
--rm을 붙이면 컨테이너가 사라져 채점할 대상이 없습니다. - 흔한 실수 2: 6단계에서 메모리를 터뜨려 죽이면
OOMKilled가 true 가 되어 실패합니다. 이 단계는 사람이 보낸 SIGKILL 이어야 합니다. - 흔한 실수 3: 4단계 값이 3단계에서 요청한 숫자와 다르더라도 고치려 하지 마세요. 본 그대로 적는 것이 정답입니다.
단계 8개
- cgroup 버전 판별
- 사용 가능한 컨트롤러 목록
- 메모리 한도 64MiB 요청하기
- 컨테이너가 실제로 보는 memory.max
- CPU 0.5 코어 한도
- 종료코드 137 만들고 해석하기
- PID 한도로 포크 폭탄 막기
- 제한 요약표 작성