制限をかけて観測値と照合する
한국어 원문으로 표시합니다.
이 실습은 진짜 VM 에서 돕니다
이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이
따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라
진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가
되고 docker exec 도 docker logs 도 그대로 동작합니다.
예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라 컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어 보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.
알아 둘 것이 둘 있습니다.
- 처음 뜨는 데 1분 남짓 걸립니다. VM 이 부팅하고 도커를 설치하기 때문입니다. 파드 실습(보통 40초)보다 느립니다.
- 브라우저 미리보기가 없습니다. VM 으로 들어오는 연결은 채점 포트
하나만 열려 있습니다. 웹 서버를 띄웠다면 VM 안에서
curl로 확인하세요.
목표
메모리·CPU·PID 한도를 직접 걸어 보고, 요청한 값(docker inspect 의 HostConfig)과
컨테이너가 실제로 보는 값(/sys/fs/cgroup/*)을 나란히 놓고 비교합니다.
종료코드 137 을 직접 만들어 보고 OOM 킬과 구분하는 법을 정리합니다.
왜 중요한가
운영에서 나오는 질문은 거의 항상 "제한을 걸었는데 왜 안 지켜지죠"이거나
"CPU 는 한가한데 왜 느리죠"입니다. 첫 번째는 요청값과 강제값이 다를 수 있다는
것(rootless 환경에서는 systemd delegation 이 없으면 요청만 기록됩니다)을 몰라서
생기고, 두 번째는 CPU cgroup 이 프로세스를 죽이지 않고 재운다는 것을 몰라서
생깁니다. 그리고 둘 다 docker stats 나 대시보드가 아니라 cgroup 파일을 직접
읽어야 확정됩니다. 그래서 이 실습은 값을 두 군데서 읽어 대조하는 습관을
만드는 것이 목적입니다. 하나만 읽으면 반드시 틀린 결론에 도달합니다.
단계
/root/int2디렉터리를 만들고,stat -fc %T /sys/fs/cgroup의 출력을 그대로/root/int2/version.txt에 저장합니다 (cgroup2fs면 v2,tmpfs면 v1)./sys/fs/cgroup/cgroup.controllers파일 내용을 그대로/root/int2/controllers.txt에 저장합니다.alpine:3.20이미지로dk-mem컨테이너를 메모리 한도 64MiB(67108864 바이트) 로 걸어 백그라운드 실행합니다. 채점 시점에 계속 running 상태여야 합니다.dk-mem안에서/sys/fs/cgroup/memory.max를 읽어 그 값을 그대로/root/int2/memmax.txt에 저장합니다.alpine:3.20이미지로dk-cpu컨테이너를 CPU 0.5 코어 한도로 걸어 백그라운드 실행합니다.dk-killed컨테이너를 띄운 뒤 직접 SIGKILL 을 보내 종료코드 137 로 끝냅니다(OOM 이 아니어야 합니다). 그리고/root/int2/exit137.txt에 137 이128+9로 이루어진다는 분해와,OOM킬도 같은 137 을 낸다는 점을 함께 적습니다.alpine:3.20이미지로dk-pids컨테이너를 PID 한도 64 로 걸어 실행합니다./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단계에서 요청한 숫자와 다르더라도 고치려 하지 마세요. 본 그대로 적는 것이 정답입니다.
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 는 숫자 그대로 들어가야 합니다.