갇히지 않는 프로세스
컨테이너는 상자가 아니라 커널이 그은 선이다
배포할 때마다 앱이 exit 137로 죽는다.
docker stop은 유예 시간을 다 기다렸다가 컨테이너를 죽이고, Spring Boot의 graceful shutdown 로그는 한 줄도 찍히지 않으며, 그 사이에 처리 중이던 요청은 연결이 끊긴다. 원인은 Dockerfile의CMD java -jar app.jar한 줄이다. 이 한 줄 뒤에는 “프로세스 하나를 어디까지 가둘 수 있는가”를 47년간 다르게 답해온 운영체제의 역사가 있다.
이 책은 docker run은 쳐봤지만 “컨테이너 안은 어떻게 생겼지?”와 “VM이랑 뭐가 다르지?”에
자신 있게 답하지 못하는 백엔드 개발자를 위해 썼다. 인프라·데브옵스로 발을 옮기는
첫 책이다. 명령어를 외우는 대신, 1979년 어느 프로세스에게 가짜 루트 디렉터리를
보여주던 시절에서 출발한다. 감옥(jail)과 구역(zone), 구글이 커널에 밀어 넣은 cgroups,
2013년 5분짜리 데모가 바꾼 포장 방식을 지나, unshare 한 줄로 namespace를 손수 만들고
cgroup 파일을 읽어 오늘의 컨테이너가 왜 그 모양인지를 따라간다.
관통하는 한 문장은 이것이다. 컨테이너는 가상머신도 상자도 아니다. 커널이 한 프로세스 둘레에 그은 선이다. 선은 벽이 아니어서, 어디에 어떻게 그었는지 모르면 시그널도 메모리도 그 선을 넘어 새어 나간다.
이 문장은 명령 몇 줄로 확인된다. 컨테이너 안의 자바는 호스트의 ps에 그대로 보이고,
PID 1이 /bin/sh인 컨테이너는 SIGTERM을 받고도 아무것도 하지 않으며, 메모리 제한을
모르는 JVM은 512MB 컨테이너 안에서 호스트 메모리 기준으로 힙을 잡다가 OOMKilled로
죽는다. 이 책의 모든 실험은 로컬 Docker 하나로 재현되고, 5부에서는 같은 앱을 kind
클러스터에 올려 같은 문제가 Kubernetes에서 어떤 이름으로 다시 나타나는지 본다.
각 장은 배경 → 스토리 → 핵심 세 걸음으로 나아가고, 개념마다 재현 가능한 명령과 그
장을 한 컷으로 요약한 삽화가 함께한다. 예제 앱은 Spring Boot 하나가 3부부터 끝까지
관통한다 — 3부에서 컨테이너에 담고, 4부에서 compose로 DB와 묶고, 5부에서 클러스터에
올린다. 1·2부의 원리 실험은 alpine 셸로 한다. 본문은 리눅스 서버 기준으로 쓰며,
macOS Docker Desktop에서 필요한 우회(--pid=host, --privileged)는 각주로만 둔다.
목차
제1부 · 격리의 역사
- 뿌리를 바꾸다 — chroot는 감옥이었던 적이 없다
- 감옥과 구역 — FreeBSD jail·Solaris Zones
- 구글의 컨테이너 — cgroups·namespace·LXC
- 5분 데모 — Docker가 바꾼 것은 격리가 아니라 포장
제2부 · 커널이 긋는 선 5. 컨테이너는 프로세스다 — VM과 무엇이 다른가 6. 보이는 것을 나누다 — namespace를 unshare로 손수 만들기 7. 쓰는 양을 나누다 — cgroup v2, 제한을 걸고 파일로 읽기 8. 이미지는 켜켜이 쌓인다 — 레이어·OverlayFS·copy-on-write 9. 포트는 열리지 않는다 — veth·bridge·NAT 10. 사라지는 쓰기, 남는 쓰기 — 컨테이너 레이어·볼륨·바인드 마운트
제3부 · Spring Boot를 담다 11. 레이어 캐시와 빌드 순서 — 한 줄 고쳤는데 왜 다 다시 만드나 12. PID 1의 저주 — SIGTERM이 도착하지 않는 이유 13. 512MB 안의 JVM — 컨테이너 인식 JDK와 OOMKilled 14. CPU 제한과 스레드 풀 — CFS quota 스로틀링 15. 살아 있음을 증명하기 — HEALTHCHECK·actuator·로그 드라이버
제4부 · 여럿을 함께 16. 서비스 이름으로 부른다 — compose 네트워크·내장 DNS·depends_on 17. 죽고 다시 살아나기 — exit code·restart 정책·latest 태그
제5부 · Kubernetes로 18. 한 대로 안 될 때 — 오케스트레이션, Pod와 Deployment 19. 트래픽이 찾아오는 길 — Service·ClusterIP·클러스터 DNS 20. 살아 있음과 받을 수 있음은 다르다 — liveness·readiness·startup 21. 요청과 제한, 그리고 롤아웃 — requests/limits·terminationGracePeriodSeconds