Docker와 Kubernetes 실전 배포 가이드
Docker와 Kubernetes는 현대적인 애플리케이션 배포의 표준이 되었다. 이 글은 Docker 컨테이너화부터 Kubernetes 클러스터 배포까지 실전 예제와 함께 완벽하게 정리한다.
1. Docker 기초
1-1. Dockerfile 작성
# Node.js 애플리케이션 예시
FROM node:18-alpine AS builder
# 작업 디렉토리 설정
WORKDIR /app
# 의존성 파일 복사
COPY package*.json ./
# 의존성 설치
RUN npm ci --only=production
# 소스 코드 복사
COPY . .
# 빌드
RUN npm run build
# 프로덕션 이미지
FROM node:18-alpine
WORKDIR /app
# 빌드된 파일만 복사
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package*.json ./
# 비root 사용자로 실행
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
# 포트 노출
EXPOSE 3000
# 애플리케이션 실행
CMD ["node", "dist/index.js"]1-2. 멀티 스테이지 빌드
# Go 애플리케이션 예시
# 빌드 스테이지
FROM golang:1.21-alpine AS builder
WORKDIR /build
# 의존성 다운로드
COPY go.mod go.sum ./
RUN go mod download
# 소스 코드 복사 및 빌드
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# 실행 스테이지
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 빌드된 바이너리만 복사
COPY /build/app .
EXPOSE 8080
CMD ["./app"]1-3. Python 애플리케이션
FROM python:3.11-slim
WORKDIR /app
# 시스템 의존성 설치
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Python 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 코드 복사
COPY . .
# 환경 변수 설정
ENV PYTHONUNBUFFERED=1
ENV FLASK_APP=app.py
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]1-4. Docker 이미지 최적화
# .dockerignore 파일
node_modules
npm-debug.log
.git
.gitignore
.env
*.md
.DS_Store
# 최적화된 Dockerfile
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY /app/node_modules ./node_modules
COPY /app/dist ./dist
COPY /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]2. Docker Compose
2-1. 기본 Compose 파일
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/mydb
depends_on:
- db
- redis
volumes:
- ./logs:/app/logs
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:2-2. 개발 환경 Compose
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run dev
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=dev
- POSTGRES_PASSWORD=dev
- POSTGRES_DB=devdb
ports:
- "5432:5432"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
redis:
image: redis:7-alpine
ports:
- "6379:6379"2-3. 프로덕션 Compose
version: '3.8'
services:
web:
build: .
restart: always
ports:
- "80:3000"
environment:
- NODE_ENV=production
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- web
networks:
- app-network
networks:
app-network:
driver: bridge3. Kubernetes 기초
3-1. Pod 정의
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
version: v1
spec:
containers:
- name: app
image: my-app:1.0.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"3-2. Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"3-3. Service
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer3-4. ConfigMap과 Secret
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: "production"
LOG_LEVEL: "info"
API_TIMEOUT: "30"
---
# Secret
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
url: "postgresql://user:password@db:5432/mydb"
username: "user"
password: "password"4. 고급 Kubernetes 리소스
4-1. HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 804-2. Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 804-3. PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd4-4. Job과 CronJob
# Job
apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
template:
spec:
containers:
- name: migrator
image: migrator:1.0.0
command: ["node", "migrate.js"]
restartPolicy: Never
backoffLimit: 3
---
# CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *" # 매일 오전 2시
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup:1.0.0
command: ["sh", "backup.sh"]
restartPolicy: OnFailure5. 배포 전략
5-1. Rolling Update
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:2.0.05-2. Blue-Green 배포
# Blue Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-blue
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: blue
template:
metadata:
labels:
app: my-app
version: blue
spec:
containers:
- name: app
image: my-app:1.0.0
---
# Green Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-green
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: green
template:
metadata:
labels:
app: my-app
version: green
spec:
containers:
- name: app
image: my-app:2.0.0
---
# Service (트래픽 전환)
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
version: blue # green으로 변경하여 트래픽 전환
ports:
- port: 80
targetPort: 30005-3. Canary 배포
# Stable Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-stable
spec:
replicas: 9
selector:
matchLabels:
app: my-app
version: stable
template:
metadata:
labels:
app: my-app
version: stable
spec:
containers:
- name: app
image: my-app:1.0.0
---
# Canary Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-canary
spec:
replicas: 1
selector:
matchLabels:
app: my-app
version: canary
template:
metadata:
labels:
app: my-app
version: canary
spec:
containers:
- name: app
image: my-app:2.0.06. 모니터링과 로깅
6-1. Health Check
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: my-app:1.0.0
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
startupProbe:
httpGet:
path: /startup
port: 3000
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 306-2. 리소스 모니터링
apiVersion: v1
kind: Service
metadata:
name: my-app-service
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3000"
prometheus.io/path: "/metrics"
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 30007. 보안 모범 사례
7-1. SecurityContext
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: my-app:1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE7-2. NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: my-app-network-policy
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: nginx
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
app: db
ports:
- protocol: TCP
port: 54328. 실전 배포 예제
8-1. 완전한 애플리케이션 스택
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: production
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0.0
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
- name: REDIS_URL
valueFrom:
configMapKeyRef:
name: app-config
key: redis-url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-service
namespace: production
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
namespace: production
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 809. CI/CD 통합
9-1. GitHub Actions 예제
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t my-app:${{ github.sha }} .
docker tag my-app:${{ github.sha }} my-app:latest
- name: Push to registry
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker push my-app:${{ github.sha }}
docker push my-app:latest
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/my-app app=my-app:${{ github.sha }} -n production10. 트러블슈팅
10-1. 일반적인 문제
# Pod 상태 확인
kubectl get pods
# Pod 로그 확인
kubectl logs <pod-name>
# Pod 상세 정보
kubectl describe pod <pod-name>
# 컨테이너 내부 접속
kubectl exec -it <pod-name> -- /bin/sh
# 리소스 사용량 확인
kubectl top pods
kubectl top nodes
# 이벤트 확인
kubectl get events --sort-by=.metadata.creationTimestamp10-2. 디버깅 명령어
# Deployment 롤아웃 상태
kubectl rollout status deployment/my-app
# 이전 버전으로 롤백
kubectl rollout undo deployment/my-app
# 특정 리비전으로 롤백
kubectl rollout undo deployment/my-app --to-revision=2
# 리소스 YAML 확인
kubectl get deployment my-app -o yaml
# 포트 포워딩
kubectl port-forward pod/<pod-name> 3000:300011. 결론
Docker와 Kubernetes는 현대적인 애플리케이션 배포의 핵심 도구다. 이 글에서 다룬 내용:
- Docker 기초: Dockerfile 작성, 멀티 스테이지 빌드, 이미지 최적화
- Docker Compose: 개발/프로덕션 환경 구성
- Kubernetes 리소스: Pod, Deployment, Service, ConfigMap, Secret
- 고급 리소스: HPA, Ingress, PVC, Job, CronJob
- 배포 전략: Rolling Update, Blue-Green, Canary
- 모니터링: Health Check, 리소스 모니터링
- 보안: SecurityContext, NetworkPolicy
- CI/CD 통합: GitHub Actions
이러한 도구와 패턴을 적절히 활용하면 안정적이고 확장 가능한 애플리케이션을 배포할 수 있다.