"내 컴퓨터에서는 되는데?" — 이 문제를 끝내는 기술
개발자라면 한 번쯤 겪어본 상황이 있을 겁니다. 로컬 환경에서 완벽히 동작하던 애플리케이션이 서버에 배포하면 갑자기 오류를 내뿜는 거죠. Node.js 버전이 다르거나, 시스템 라이브러리가 없거나, 환경 변수 설정이 달라서. Docker는 바로 이 "환경 차이" 문제를 근본적으로 해결합니다.
CNCF(Cloud Native Computing Foundation)의 2024 Survey에 따르면, 기업의 84%가 프로덕션 환경에서 컨테이너를 사용하고 있으며, Kubernetes는 컨테이너 오케스트레이션의 사실상 표준으로 자리잡았습니다. 이 기술을 모르면 현대 인프라를 이해하기 어려운 시대가 된 것입니다.
---
Docker 기초 — 컨테이너란 무엇인가
가상 머신 vs 컨테이너
가상 머신(VM)은 하이퍼바이저 위에 게스트 OS 전체를 올립니다. 각 VM마다 수 GB의 OS 이미지가 필요하고, 부팅에 수십 초가 걸리죠. 반면 컨테이너는 호스트 OS의 커널을 공유하면서 애플리케이션과 의존성만 격리합니다. 이미지 크기는 수십~수백 MB, 시작 시간은 밀리초 단위입니다.
이 차이가 왜 중요한가? 같은 서버에서 VM으로는 5~10개의 서비스를 운영할 수 있다면, 컨테이너로는 수십~수백 개를 운영할 수 있기 때문입니다. 리소스 효율성이 근본적으로 다릅니다.
Dockerfile 작성
```dockerfile # 1단계: 빌드 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build
# 2단계: 실행 (멀티 스테이지 빌드) FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production
# 보안: root가 아닌 사용자로 실행 RUN addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 appuser USER appuser
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist COPY --from=builder --chown=appuser:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=appuser:nodejs /app/package.json ./
EXPOSE 3000 CMD ["node", "dist/server.js"] ```
멀티 스테이지 빌드는 필수 패턴입니다. 빌드 도구(TypeScript, Webpack 등)는 빌드 단계에만 필요하고 실행 단계에는 불필요하니까요. 이 패턴으로 이미지 크기를 50~80% 줄일 수 있습니다.
---
Docker 이미지 최적화
.dockerignore
``` node_modules .git .env .env.local dist *.md .vscode .next coverage ```
레이어 캐싱 최적화
Docker는 Dockerfile의 각 명령어를 레이어로 캐싱합니다. 변경 빈도가 낮은 레이어를 위에, 높은 레이어를 아래에 배치하면 빌드 속도가 크게 향상됩니다.
```dockerfile # ✅ package.json을 먼저 복사 → 의존성 캐싱 활용 COPY package*.json ./ RUN npm ci COPY . . # 소스 코드 변경 시에도 npm ci는 캐시 사용
# ❌ 모든 파일을 한 번에 복사 → 소스 변경마다 npm ci 재실행 COPY . . RUN npm ci ```
---
Docker Compose — 다중 컨테이너 관리
실제 서비스는 웹 서버, 데이터베이스, 캐시 등 여러 컨테이너가 함께 동작합니다.
```yaml # docker-compose.yml version: '3.8' services: app: build: . ports:
- '3000:3000'
environment: DATABASE_URL: postgres://user:pass@db:5432/myapp REDIS_URL: redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_started
db: image: postgres:16-alpine volumes:
- postgres_data:/var/lib/postgresql/data
environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: myapp healthcheck: test: ['CMD-SHELL', 'pg_isready -U user -d myapp'] interval: 5s retries: 5
cache: image: redis:7-alpine command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes: postgres_data: ```
`docker compose up -d` 한 줄로 전체 개발 환경이 실행됩니다. 새 팀원이 합류해도 환경 설정에 시간을 쏟을 필요가 없는 거죠.
---
Kubernetes 기초 — 왜 오케스트레이션이 필요한가
컨테이너가 10개일 때는 Docker Compose로 충분하지만, 수십~수백 개의 컨테이너를 운영할 때는 이야기가 달라집니다. 트래픽 증가 시 자동 확장, 장애 발생 시 자동 복구, 무중단 배포, 서비스 간 네트워크 관리 — 이 모든 것을 수동으로 처리하기란 불가능에 가깝습니다. Kubernetes(K8s)가 이 역할을 합니다.
핵심 개념
Pod: 하나 이상의 컨테이너를 묶은 최소 배포 단위. 같은 Pod의 컨테이너는 네트워크와 스토리지를 공유합니다.
Service: Pod에 안정적인 네트워크 엔드포인트를 제공합니다. Pod는 수시로 생성/삭제되지만, Service의 IP와 DNS는 고정입니다.
Deployment: Pod의 선언적 상태를 관리합니다. "이 앱의 Pod를 3개 유지하라"고 선언하면, Kubernetes가 자동으로 그 상태를 보장합니다.
Deployment 예시
```yaml # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: web-app labels: app: web-app spec: replicas: 3 selector: matchLabels: app: web-app strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 업데이트 중 최대 1개 추가 Pod maxUnavailable: 0 # 업데이트 중 모든 Pod 유지 → 무중단 template: metadata: labels: app: web-app spec: containers:
- name: web-app
image: myregistry/web-app:v1.2.0 ports:
- containerPort: 3000
resources: requests: memory: '128Mi' cpu: '100m' limits: memory: '256Mi' cpu: '500m' livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 5 periodSeconds: 10 ```
`livenessProbe`는 컨테이너가 살아있는지 체크하여 죽으면 재시작하고, `readinessProbe`는 트래픽을 받을 준비가 되었는지 확인하여 준비되지 않은 Pod에는 요청을 보내지 않습니다.
---
실전 팁 — 프로덕션을 위한 체크리스트
Docker 이미지는 반드시 특정 태그를 사용하세요(`node:20-alpine`, 절대 `node:latest` 아님). `latest` 태그는 재현 가능한 빌드를 보장하지 못합니다. 환경 변수로 비밀 정보를 관리할 때는 Kubernetes Secrets나 외부 비밀 관리 도구(HashiCorp Vault, AWS Secrets Manager)를 사용하고, 이미지에 비밀 정보를 하드코딩하지 마세요.
로컬 개발에서는 Docker Desktop과 Kind(Kubernetes IN Docker)를 활용하면 실제 K8s 클러스터 없이도 Kubernetes 환경을 테스트할 수 있습니다. 프로덕션 환경에서는 AWS EKS, Google GKE, Azure AKS 같은 관리형 서비스를 권장합니다.
Docker는 "애플리케이션을 어디서나 동일하게 실행"하는 문제를, Kubernetes는 "대규모 컨테이너를 안정적으로 운영"하는 문제를 해결합니다. 처음에는 Docker부터 시작하여 컨테이너화에 익숙해진 후, 서비스 규모가 커지면 Kubernetes를 도입하는 것이 자연스러운 학습 경로입니다.