Docker Swarm 클러스터 가이드 — 서비스, 스택, 고가용성

Docker Swarm이란?

Docker Swarm은 Docker에 내장된 컨테이너 오케스트레이션 도구입니다. 여러 대의 Docker 호스트를 하나의 클러스터로 묶어 서비스를 배포하고 관리합니다. Kubernetes보다 설정이 간단하고, Docker CLI만으로 운영할 수 있어 중소 규모 서비스에 적합합니다.

비유하면, 단일 Docker 호스트가 한 명의 요리사라면 Swarm은 여러 요리사가 협력하는 주방입니다. 주방장(매니저 노드)이 주문(서비스)을 배분하고, 요리사(워커 노드)가 음식(컨테이너)을 만듭니다. 한 요리사가 아프면 다른 요리사가 대신합니다.

구성 요소역할
Manager 노드클러스터 관리, 스케줄링, 서비스 정의 저장
Worker 노드컨테이너 실행
Service배포 단위 (이미지 + 복제 수 + 네트워크 설정)
Task서비스의 개별 컨테이너 인스턴스
Stack여러 서비스를 묶은 애플리케이션 단위

클러스터 초기화

Swarm 클러스터를 구성하는 과정입니다.

# === 매니저 노드에서 실행 ===
# Swarm 초기화 (현재 호스트가 매니저)
docker swarm init --advertise-addr 192.168.1.10
# Swarm initialized: current node (abc123) is now a manager.
#
# To add a worker to this swarm, run the following command:
#   docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377
#
# To add a manager to this swarm, run 'docker swarm join-token manager'

# 워커 조인 토큰 확인
docker swarm join-token worker
# SWMTKN-1-xxx...

# 매니저 조인 토큰 확인 (고가용성을 위해 매니저 3대 권장)
docker swarm join-token manager

# === 워커 노드에서 실행 ===
docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377
# This node joined a swarm as a worker.

# === 매니저 노드에서 클러스터 상태 확인 ===
docker node ls
# ID           HOSTNAME    STATUS  AVAILABILITY  MANAGER STATUS  ENGINE VERSION
# abc123 *     manager-1   Ready   Active        Leader          24.0.7
# def456       worker-1    Ready   Active                        24.0.7
# ghi789       worker-2    Ready   Active                        24.0.7

고가용성을 위해 매니저 노드는 홀수 개(3, 5, 7)로 구성합니다. Raft 합의 알고리즘으로 리더를 선출하며, 과반수 이상의 매니저가 살아있어야 클러스터가 정상 운영됩니다.

서비스 배포

Swarm에서 컨테이너는 서비스 단위로 배포합니다. docker service 명령어로 관리합니다.

# 서비스 생성 (Nginx 웹서버 3대)
docker service create \
  --name web \
  --replicas 3 \
  --publish 80:80 \
  --update-delay 10s \
  --update-parallelism 1 \
  --restart-condition on-failure \
  nginx:alpine

# 서비스 목록 확인
docker service ls
# ID           NAME  MODE        REPLICAS  IMAGE
# xyz123       web   replicated  3/3       nginx:alpine

# 서비스 상세 상태 (각 태스크가 어느 노드에서 실행 중인지)
docker service ps web
# ID         NAME    IMAGE          NODE       DESIRED STATE  CURRENT STATE
# aaa111     web.1   nginx:alpine   manager-1  Running        Running 2 minutes ago
# bbb222     web.2   nginx:alpine   worker-1   Running        Running 2 minutes ago
# ccc333     web.3   nginx:alpine   worker-2   Running        Running 2 minutes ago

# 서비스 로그 확인 (모든 태스크의 로그 통합)
docker service logs -f web

# 서비스 스케일링 (3대 → 5대)
docker service scale web=5
# web scaled to 5
# overall progress: 5 out of 5 tasks

# 서비스 상세 정보
docker service inspect --pretty web

Swarm의 라우팅 메시(Routing Mesh) 덕분에 클러스터의 어떤 노드에 요청해도 서비스에 접근할 수 있습니다. 예를 들어 web 서비스가 worker-1과 worker-2에서만 실행 중이더라도, manager-1의 80번 포트로 요청하면 자동으로 적절한 노드로 라우팅됩니다.

롤링 업데이트

서비스 중단 없이 이미지를 업데이트하는 과정입니다.

# 서비스 이미지 업데이트 (롤링 업데이트)
docker service update \
  --image nginx:1.25-alpine \
  --update-delay 15s \
  --update-parallelism 1 \
  --update-failure-action rollback \
  --update-order start-first \
  web

# 업데이트 진행 상황 확인
docker service ps web
# ID         NAME      IMAGE               NODE       DESIRED STATE  CURRENT STATE
# ddd444     web.1     nginx:1.25-alpine   manager-1  Running        Running 10 seconds ago
# aaa111     \_web.1   nginx:alpine        manager-1  Shutdown       Shutdown 15 seconds ago
# bbb222     web.2     nginx:alpine        worker-1   Running        Running 5 minutes ago
# ccc333     web.3     nginx:alpine        worker-2   Running        Running 5 minutes ago

# 업데이트 실패 시 수동 롤백
docker service rollback web
# web rolled back

# 롤백 상태 확인
docker service inspect --pretty web
# UpdateStatus:
#  State: rollback_completed

업데이트 옵션 설명입니다.

옵션설명
--update-parallelism 1한 번에 1개 태스크씩 업데이트
--update-delay 15s각 태스크 업데이트 사이 15초 대기
--update-failure-action rollback실패 시 자동 롤백
--update-order start-first새 태스크를 먼저 시작한 후 기존 태스크 종료

스택 배포

docker stack은 Docker Compose 파일 형식으로 여러 서비스를 한 번에 배포합니다.

# stack.yml — 웹 애플리케이션 스택
version: "3.8"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3

  api:
    image: my-registry.com/my-app:latest
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    networks:
      - frontend
      - backend
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M
      update_config:
        parallelism: 1
        delay: 15s
        order: start-first

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      replicas: 1
      placement:
        constraints:
          # DB는 매니저 노드에서만 실행 (볼륨 일관성)
          - node.role == manager

  redis:
    image: redis:7-alpine
    networks:
      - backend
    deploy:
      replicas: 1

networks:
  frontend:
    driver: overlay
  backend:
    driver: overlay
    internal: true

volumes:
  pgdata:
# 스택 배포
docker stack deploy -c stack.yml myapp

# 스택 목록
docker stack ls
# NAME    SERVICES  ORCHESTRATOR
# myapp   4         Swarm

# 스택의 서비스 확인
docker stack services myapp
# ID         NAME          MODE        REPLICAS  IMAGE
# aaa        myapp_nginx   replicated  2/2       nginx:alpine
# bbb        myapp_api     replicated  3/3       my-registry.com/my-app:latest
# ccc        myapp_db      replicated  1/1       postgres:16-alpine
# ddd        myapp_redis   replicated  1/1       redis:7-alpine

# 스택의 모든 태스크 확인
docker stack ps myapp

# 스택 업데이트 (stack.yml 수정 후 같은 명령 실행)
docker stack deploy -c stack.yml myapp

# 스택 삭제
docker stack rm myapp

노드 관리

클러스터 노드의 상태와 역할을 관리하는 방법입니다.

# 노드 상태 확인
docker node ls

# 노드를 유지보수 모드로 전환 (해당 노드의 태스크가 다른 노드로 이동)
docker node update --availability drain worker-1
# worker-1의 모든 태스크가 다른 노드로 재배치됨

# 유지보수 완료 후 활성화
docker node update --availability active worker-1

# 노드에 라벨 추가 (배치 제약 조건에 사용)
docker node update --label-add zone=ap-northeast-2a worker-1
docker node update --label-add ssd=true worker-2

# 라벨 기반 배치 (SSD 노드에서만 DB 실행)
docker service create \
  --name db \
  --constraint 'node.labels.ssd == true' \
  postgres:16-alpine

# 노드를 Swarm에서 제거
# 워커 노드에서 실행:
docker swarm leave
# 매니저에서 제거:
docker node rm worker-1

헬스체크와 서비스 디스커버리

Swarm은 내장 헬스체크와 로드 밸런싱을 제공합니다.

# Dockerfile에 헬스체크 추가
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev

# 30초마다 헬스체크, 5초 타임아웃, 3회 실패 시 unhealthy
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

USER node
EXPOSE 3000
CMD ["node", "server.js"]
# 서비스 생성 시 헬스체크 설정
docker service create \
  --name api \
  --replicas 3 \
  --health-cmd "wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1" \
  --health-interval 30s \
  --health-timeout 5s \
  --health-retries 3 \
  my-app:latest

# 서비스 상태에서 헬스체크 결과 확인
docker service ps api
# healthy 태스크만 트래픽을 받음

실전 팁

  • 매니저 노드 수: 프로덕션에서는 매니저를 3대 이상(홀수) 구성하세요. 1대면 단일 장애점(SPOF)이 되고, 2대면 1대 장애 시 합의를 달성할 수 없습니다. 3대면 1대 장애를 견딥니다.
  • 매니저 노드의 워크로드: 매니저 노드에서도 태스크를 실행할 수 있지만, 대규모 클러스터에서는 매니저를 drain 모드로 설정하여 관리 작업에만 집중시키는 것이 안정적입니다.
  • 시크릿 관리: docker secret으로 암호화된 시크릿을 Raft 로그에 저장하고, 컨테이너의 /run/secrets/ 경로로 마운트합니다. 환경변수보다 안전합니다.
  • 롤링 업데이트 전략: --update-order start-first를 사용하면 새 태스크가 정상 시작된 후 기존 태스크를 종료하므로 다운타임이 발생하지 않습니다. --update-failure-action rollback으로 실패 시 자동 롤백을 설정하세요.
  • Swarm vs Kubernetes: 10대 미만의 노드, Docker Compose에 익숙한 팀, 빠른 설정이 필요한 경우 Swarm이 적합합니다. 대규모 클러스터, 복잡한 스케줄링, 풍부한 에코시스템이 필요하면 Kubernetes를 선택하세요.

이 글이 도움이 되었나요?