컨테이너화된 마이크로서비스와 전체 자동화

발행: (2025년 12월 10일 오전 06:18 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

전체 개요

우리가 만들고 있는 것

프로덕션 수준의 TODO 애플리케이션, 구성 요소:

  • 여러 마이크로서비스 (5개의 서로 다른 프로그래밍 언어)
  • 자동화된 인프라 프로비저닝 (Terraform)
  • 자동화된 설정 관리 (Ansible)
  • 드리프트 감지를 포함한 CI/CD 파이프라인
  • Traefik을 이용한 자동 HTTPS
  • 제로 트러스트 보안 모델

아키텍처

graph TD
    A[Traefik (HTTPS/Proxy)] --> B[Frontend (Vue.js)]
    A --> C[Auth API (Go)]
    A --> D[Todos API (Node.js)]
    C --> E[Users API (Java Spring)]
    C --> F[Redis Queue]
    C --> G[Log Processor (Python)]

애플리케이션 이해하기

서비스들

1. 프론트엔드 (Vue.js)

  • 사용자 인터페이스, 로그인 페이지 → TODO 대시보드
  • 백엔드 API와 통신
  • Traefik을 통해 포트 80/443으로 노출

2. Auth API (Go)

  • 사용자 인증을 처리하고 JWT 토큰 발급
  • 엔드포인트: /api/auth

3. Todos API (Node.js)

  • TODO 항목을 관리 (CRUD)
  • 유효한 JWT 토큰 필요
  • 엔드포인트: /api/todos

4. Users API (Java Spring Boot)

  • 사용자 관리 및 프로필 작업
  • 엔드포인트: /api/users

5. Log Processor (Python)

  • 백그라운드 작업을 처리하고 Redis 큐에서 소비, 감사 로그 작성

6. Redis Queue

  • 비동기 작업을 위한 메시지 브로커

1단계: 컨테이너화

프론트엔드 Dockerfile (Vue.js)

# Multi‑stage build for optimized production image

# Stage 1 – build the application
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2 – serve with nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

멀티‑스테이지 빌드가 왜 필요한가?
빌더 단계는 약 800 MB(빌드 도구 포함)이고, 최종 단계는 약 25 MB(nginx + 정적 파일만)로, 97 % 크기가 감소합니다.

프론트엔드 nginx 설정

server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;

    # SPA routing – send all requests to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

Auth API Dockerfile (Go)

# Multi‑stage build for Go

# Stage 1 – build the binary
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Stage 2 – minimal runtime image
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1

EXPOSE 8080
CMD ["./main"]

왜 이런 접근 방식을 쓰는가?
빌더 단계는 약 400 MB, 최종 단계는 약 15 MB(정적 바이너리만, 런타임 의존성 없음). 시작이 빠르고 공격 표면이 작아집니다.

Todos API Dockerfile (Node.js)

FROM node:18-alpine
WORKDIR /app

# Install dependencies first (better caching)
COPY package*.json ./
RUN npm ci --only=production

# Copy application code
COPY . .

# Create non‑root user for security
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 && \
    chown -R nodejs:nodejs /app

USER nodejs

HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

EXPOSE 3000
CMD ["node", "server.js"]

보안 참고: 비루트 사용자로 실행하면 컨테이너가 침해당했을 때 피해를 제한할 수 있습니다.

Users API Dockerfile (Java Spring Boot)

# Multi‑stage build for Java

# Stage 1 – build with Maven
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml ./
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# Stage 2 – runtime
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --quiet --tries=1 --spider http://localhost:8080/actuator/health || exit 1

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Java‑특화 최적화

ENTRYPOINT ["java",
  "-XX:+UseContainerSupport",
  "-XX:MaxRAMPercentage=75.0",
  "-XX:+ExitOnOutOfMemoryError",
  "-jar", "/app/app.jar"]

Log Processor Dockerfile (Python)

FROM python:3.11-slim
WORKDIR /app

# Install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Create non‑root user
RUN useradd -m -u 1001 processor && \
    chown -R processor:processor /app

USER processor

HEALTHCHECK --interval=30s --timeout=3s \
  CMD ps aux | grep processor.py || exit 1

CMD ["python", "processor.py"]

Docker Compose – 전체 오케스트레이션

version: '3.8'

services:
  # Traefik reverse proxy
  traefik:
    image: traefik:v2.10
    container_name: traefik
    command:
      - "--api.dashboard=true"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      # Let's Encrypt (example configuration)
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=you@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "letsencrypt:/letsencrypt"

  # Frontend (Vue.js)
  frontend:
    build: ./frontend
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.frontend.rule=Host(`todo.example.com`)"
      - "traefik.http.routers.frontend.entrypoints=websecure"
      - "traefik.http.routers.frontend.tls.certresolver=myresolver"

  # Auth API (Go)
  auth:
    build: ./auth
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.auth.rule=Host(`auth.todo.example.com`)"
      - "traefik.http.routers.auth.entrypoints=websecure"
      - "traefik.http.routers.auth.tls.certresolver=myresolver"

  # Todos API (Node.js)
  todos:
    build: ./todos
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.todos.rule=Host(`api.todo.example.com`)"
      - "traefik.http.routers.todos.entrypoints=websecure"
      - "traefik.http.routers.todos.tls.certresolver=myresolver"

  # Users API (Java Spring Boot)
  users:
    build: ./users
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.users.rule=Host(`users.todo.example.com`)"
      - "traefik.http.routers.users.entrypoints=websecure"
      - "traefik.http.routers.users.tls.certresolver=myresolver"

  # Log Processor (Python)
  logprocessor:
    build: ./logprocessor

  # Redis Queue
  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - "redis-data:/data"

volumes:
  letsencrypt:
  redis-data:
Back to Blog

관련 글

더 보기 »