Docker Compose에서 Swarm까지, 어떻게 설계가 이어지는가
선언형 정의부터 멀티 호스트 오케스트레이션까지, Docker Compose와 Swarm의 공통 철학과 설계 결정을 추적한다.
- 01 컨테이너는 어떻게 격리되면서도 빠른가
- 02 Docker 이미지 크기가 10배 차이 나는 이유
- 03 Docker 네트워크는 어떻게 패킷을 옮기는가
- 04 Docker 스토리지는 어디서 끝나고 어디서 시작되는가
- 05 Docker Compose에서 Swarm까지, 어떻게 설계가 이어지는가
- 06 Docker 보안은 왜 여러 계층이 필요한가
- 07 Docker 컨테이너 리소스 관리의 철학
- 08 Docker는 어떻게 컨테이너를 만드는가
- 09 컨테이너 패턴의 통일된 철학은 무엇인가
- 10 코드 한 줄이 프로덕션에 닿기까지
- 11 컨테이너 디버깅은 왜 이렇게 어려운가
- 12 Docker로 Full-stack 서비스를 운영한다는 것은 무엇인가
- 13 Docker에서 Kubernetes로 — 무엇이 달라지는가
Docker를 처음 배울 때 docker run 한 줄로 컨테이너를 띄운다. 그러다 서비스가 두 개, 세 개로 늘어나면 Compose를 쓴다. 그리고 트래픽이 커지면 Swarm을 쓴다. 이 세 계층은 각각 독립적인 도구처럼 보이지만, 사실 하나의 철학 위에 쌓여 있다 — “원하는 상태를 선언하면, 시스템이 그 상태를 유지한다.” 왜 이 철학이 단순한 편의성을 넘어 운영 신뢰성의 기반이 되는가?
선언형 정의 — Compose의 출발점
docker run은 명령형이다. 네트워크 만들고, 볼륨 만들고, 컨테이너 실행하는 순서를 사람이 직접 제어한다. 재현하기 어렵고, 휴먼 에러가 생긴다.
Compose는 이 문제를 YAML 파일 하나로 뒤집는다. docker-compose.yml에 서비스, 네트워크, 볼륨을 선언하면 docker-compose up -d 한 줄이 나머지를 처리한다. 핵심은 Infrastructure as Code다. 파일이 Git에 들어가고, 코드 리뷰가 가능해지고, 팀 전체가 동일한 환경을 쓴다.
version: '3.8'
services:
db:
image: postgres:14
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
web:
image: nginx
ports:
- "8080:80"
depends_on:
db:
condition: service_healthy
volumes:
db-data:
여기서 이미 설계 원칙 두 가지가 보인다. 환경 변수는 .env로 분리해 시크릿을 코드에서 제거하고, healthcheck + condition: service_healthy로 시작 순서를 의존성이 아닌 준비 상태로 제어한다. depends_on만으로는 DB가 실제로 준비됐는지 알 수 없다 — 컨테이너가 생성됐다는 것과 서비스가 준비됐다는 것은 다르다.
Compose 고급 — 환경 분리의 문제
단일 docker-compose.yml이 커지면 한계가 온다. 개발 환경에는 포트를 열고 디버그 모드를 켜야 하지만, 프로덕션에는 닫아야 한다. 이 요구를 파일 하나로 처리하면 파일이 비대해지고 실수가 생긴다.
Compose의 해답은 파일 병합과 Profiles다.
# 개발
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# 프로덕션
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
나중에 지정된 파일이 우선한다. base.yml에 공통 정의를 두고, dev.yml과 prod.yml에서 필요한 부분만 오버라이드한다. YAML Anchors(&, *)로 파일 내 중복을 제거하면 로깅 설정이나 재시작 정책 같은 공통 패턴을 한 곳에서 관리할 수 있다.
Profiles는 선택적 서비스 실행을 위한 도구다. 모니터링 스택(Prometheus, Grafana)을 항상 띄울 필요는 없다. profiles: [monitoring]으로 태그하면 --profile monitoring 옵션을 줄 때만 해당 서비스가 활성화된다.
파일 분리는 명확성을 주지만 복잡도를 높인다. 파일이 많아질수록 병합 결과를 예측하기 어렵다. docker-compose config로 최종 병합 결과를 항상 확인하라. Makefile이나 쉘 스크립트로 환경별 명령을 래핑하면 실수를 줄일 수 있다.
Swarm — 단일 호스트의 한계를 넘어
Compose는 단일 호스트다. 트래픽이 늘어 서버 한 대로 감당이 안 되거나, 장애 시 서비스가 중단되는 구조는 프로덕션에서 받아들이기 어렵다.
Swarm은 여러 Docker 호스트를 하나의 클러스터로 묶는다. Manager 노드가 Raft 합의 알고리즘으로 클러스터 상태를 관리하고, Worker 노드가 실제 컨테이너를 실행한다.
Manager 노드 장애 허용:
- 1개: 허용 0
- 3개: 허용 1 ← 최소 권장
- 5개: 허용 2
Raft는 과반수 기반이다. Manager 3개 중 1개가 죽어도 2개가 합의를 이룰 수 있다. 2개가 동시에 죽으면 쿼럼을 잃고 클러스터가 읽기 전용으로 전환된다. Manager를 짝수 개로 구성하면 장애 허용이 오히려 줄어드는 이유다 — 4개 Manager는 3개와 동일하게 1대 장애만 허용한다.
서비스와 네트워킹 — Ingress와 Overlay
Swarm의 핵심 추상화는 docker service다. docker run이 단일 컨테이너를 실행한다면, docker service create --replicas 5는 클러스터 어느 노드에든 5개의 태스크를 배치하고, 장애가 나면 자동으로 재생성한다.
외부 트래픽은 Ingress 네트워크가 처리한다. 어느 노드 IP로 접속해도 IPVS 로드 밸런서가 실행 중인 태스크로 라운드 로빈 분배한다. 태스크가 없는 노드도 요청을 받아 다른 노드로 라우팅할 수 있다.
서비스 간 통신은 Overlay 네트워크가 처리한다. VXLAN 터널링으로 서로 다른 호스트의 컨테이너가 같은 L2 네트워크에 있는 것처럼 동작한다. 서비스 이름이 DNS로 해석되어 http://db처럼 직접 참조할 수 있다.
networks:
frontend:
driver: overlay
backend:
driver: overlay
internal: true # 외부 접근 차단
internal: true로 DB 네트워크를 외부와 차단하는 것이 기본 보안 설계다. API가 frontend와 backend 모두에 연결되고, DB는 backend에만 연결된다. Web 서버는 DB에 직접 접근할 수 없다.
롤링 업데이트와 고가용성
Swarm의 업데이트는 선언적이다. “이미지를 바꿔라”라고 선언하면, Swarm이 태스크를 하나씩 교체한다.
docker service create \
--name web \
--replicas 6 \
--update-parallelism 2 \
--update-delay 30s \
--update-order start-first \
--update-failure-action rollback \
--health-cmd "curl -f http://localhost/ || exit 1" \
nginx:1.21-alpine
start-first는 새 태스크를 먼저 시작하고 헬스체크를 통과한 후 구 태스크를 종료한다. 항상 레플리카 수 이상이 가동 중임을 보장한다. rollback은 실패율이 임계값을 넘으면 자동으로 이전 버전으로 복구한다. 이 조합이 무중단 배포의 실질적 구현이다.
정리
- Compose는 선언형 정의로 재현 가능한 환경을 만든다. 핵심은 IaC — 파일이 인프라다.
- 환경 분리는 파일 병합으로 해결한다. 베이스를 분리하고 환경별로 오버라이드한다.
- Swarm은 Raft 합의로 Manager 고가용성을 보장한다. Manager는 반드시 홀수 개, 최소 3개.
- Ingress가 외부 로드 밸런싱을, Overlay + internal이 서비스 간 격리를 처리한다.
- 무중단 배포는
start-first+ 헬스체크 + 자동 롤백의 조합이다. 설정 없이 기본값을 믿으면 다운타임이 생긴다.
Compose에서 Swarm으로 넘어가는 것은 도구 교체가 아니다. 같은 YAML 문법, 같은 선언형 철학이 더 큰 규모로 확장된 것이다.