도커(Docker) 컨테이너 메모리 누수 잡는 실무 가이드: cgroups v2와 멀티스테이지 빌드 최적화

Written by

in

🏠 HOME
/
📁 EDITORIAL

AWS ECS나 쿠버네티스 프로덕션 클러스터에서 도커 컨테이너가 아무런 예고도 없이 Exit Code 137 (OOMKilled)을 뿜으며 뻗어버리는 현상은 백엔드 엔지니어가 가장 자주 맞닥뜨리는 치명적인 장애입니다. 자바 JVM이나 Node.js V8 런타임이 호스트 메모리를 오판하는 이유와 cgroups v2 기반 리소스 제어, 그리고 멀티스테이지 빌드로 경량화하는 실전 노하우를 총정리합니다.

1. 왜 컨테이너는 호스트의 RAM을 통째로 먹어치우는가?

컨테이너 환경에서 실행되는 애플리케이션은 기본적으로 호스트 머신의 전체 CPU 코어 수와 총 메모리 용량을 인식합니다. 예를 들어 64GB RAM 서버에서 메모리 제한 없이 Node.js나 자바 컨테이너를 실행하면, 가비지 컬렉터(GC)가 메모리가 충분하다고 판단하여 힙 메모리를 계속 팽창시키다가 리눅스 커널의 OOM Killer에 의해 프로세스가 즉각 사살됩니다.

이를 근본적으로 해결하려면 cgroups v2를 활성화하고, docker-compose나 쿠버네티스 매니페스트에서 메모리 상한선(limits)과 보장선(reservations)을 반드시 명시해야 합니다.

# docker-compose.yml Production Resource Hardening
services:
  backend-api:
    image: my-enterprise-api:2026.1
    environment:
      NODE_OPTIONS: "--max-old-space-size=768"
    deploy:
      resources:
        limits:
          cpus: '2.00'
          memory: 1024M
        reservations:
          cpus: '0.50'
          memory: 512M
    restart: always

2. 1.4GB짜리 무거운 이미지를 82MB로 압축하는 멀티스테이지 빌드

소스코드 컴파일에 필요한 개발 도구(TypeScript 컴파일러, Python 빌드 툴, npm devDependencies)가 최종 프로덕션 이미지에 포함되면 컨테이너 부팅 속도가 느려지고 보안 취약점(CVE) 공격 표면이 넓어집니다.

# Production Multi-Stage Dockerfile
# Stage 1: Build Stage
FROM node:22-alpine AS builder
WORKDIR /build
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src/ ./src
RUN npm run build && npm prune --production

# Stage 2: Minimal Production Runtime
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
USER node
COPY --from=builder --chown=node:node /build/node_modules ./node_modules
COPY --from=builder --chown=node:node /build/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]

3. 좀비 프로세스를 방지하는 Tini Init 프로세스 연동

컨테이너의 PID 1 프로세스가 시그널(SIGTERM, SIGINT)을 제대로 자식 프로세스에 전파하지 못하면, 컨테이너 종료 시 10초간 멈췄다가 강제 종료(SIGKILL)되는 현상이 생깁니다. Dockerfile에 tini(경량 init 시스템)를 추가하면 정상적인 Graceful Shutdown이 보장되어 데이터 유실을 100% 방지할 수 있습니다.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *