홈랩은 잘 돌아갈 땐 조용하지만, 한 번 터지면 새벽까지 붙잡게 됩니다. 이번 글은 제가 실제로 겪고 해결한 두 가지 사건 — “빌드하다 디스크가 꽉 참”과 “좀비 프로세스 307개” — 을 그대로 복기합니다. 둘 다 홈랩에서 흔히 만나는 함정이라, 같은 벽에 부딪힌 분께 도움이 될 겁니다.
사건 1. 컨테이너 빌드 중 “disk quota exceeded”
Docker 이미지를 새로 빌드하는데 갑자기 실패했습니다.
failed to solve: ... write ...: disk quota exceeded
원인을 찾아보니 해당 LXC의 rootfs가 8GB였는데, 그 안에서 Playwright(헤드리스 크롬) 이미지를 재빌드하려니 공간이 부족했습니다. 크로미움을 포함한 이미지는 통째로 1GB를 훌쩍 넘고, 여기에 빌드 캐시가 눈덩이처럼 쌓여 순식간에 rootfs를 채운 겁니다.
해결은 두 단계였습니다. 먼저 쌓인 빌드 캐시를 비우고,
# 안 쓰는 빌드 캐시 전량 정리 (공간 즉시 회수)
docker builder prune -af
그래도 빠듯해서 Proxmox 호스트에서 LXC rootfs를 온라인으로 확장했습니다(무중단).
# LXC 113의 rootfs를 8G → 16G 로 확장
pct resize 113 rootfs +8G
배운 것: 크로미움·플레이라이트처럼 무거운 이미지를 빌드하는 컨테이너는 rootfs를 처음부터 넉넉히(최소 16GB) 잡아야 합니다. 그리고 docker builder prune을 주기적으로 돌리지 않으면 빌드 캐시가 조용히 디스크를 잠식합니다. 빌드 전 df -h / 확인은 습관으로.
사건 2. 좀비 프로세스가 307개까지 쌓였다
어느 날 컨테이너 상태를 보니 좀비(zombie) 프로세스가 307개나 쌓여 있었습니다.
# 좀비 프로세스 개수 확인
ps aux | awk '$8 ~ /Z/ {c++} END {print c}'
범인은 헤드리스 크롬이었습니다. Playwright가 크로미움을 반복 실행/종료하는데, 자식 프로세스가 끝나면 부모가 그 종료 상태를 거둬(reap)줘야 합니다. 그런데 컨테이너의 PID 1(내 파이썬 앱)은 그 일을 하지 않습니다. 일반 리눅스라면 init이 고아 프로세스를 거두지만, 컨테이너 안엔 그 init이 없어서 죽은 크롬 프로세스가 계속 좀비로 남은 겁니다.
해결은 가벼운 init(tini)을 PID 1로 넣는 것이었습니다. Docker Compose라면 한 줄이면 됩니다.
services:
app:
image: my-bot:latest
init: true # tini가 PID 1이 되어 좀비를 자동 수거
init: true를 켜자 좀비가 더 이상 쌓이지 않았습니다. tini가 PID 1로 앉아 자식들의 종료를 대신 거둬 주기 때문입니다.
배운 것: 컨테이너 안에서 자식 프로세스를 반복 생성하는 앱(브라우저 자동화, 이미지 변환, 서브프로세스 호출 등)을 돌린다면 init: true는 선택이 아니라 필수입니다. 컨테이너의 PID 1은 “프로세스 청소부” 역할을 하지 않는다는 걸 꼭 기억하세요.
정리 — 홈랩 삽질에서 얻은 두 습관
| 사건 | 근본 원인 | 예방 습관 |
|---|---|---|
| 디스크 풀 | 빌드 캐시 누적 + 작은 rootfs | rootfs 넉넉히, builder prune 주기화, 빌드 전 df -h |
| 좀비 307개 | 컨테이너 PID 1이 자식 미수거 | 서브프로세스 앱엔 init: true |
둘 다 “알고 나면 별것 아니지만, 모르면 새벽을 태우는” 문제였습니다. 홈랩의 실력은 이런 삽질을 하나씩 기록하며 쌓이는 것 같습니다.