MeetBack 2026.08.23 6 min read

Manager 3대와 Worker 2대로 Docker Swarm을 구성한 이유

Manager quorum, overlay network, Service DNS/VIP, Global Service를 적용한 이유와 Swarm VIP가 애플리케이션 상태를 공유하지 않는 한계를 정리했다.

프로젝트 기간: 2026-08-18 ~ 2026-09-02
글 범위: Manager 3대·Worker 2대 Docker Swarm 구성

MeetBack application을 여러 server에 분산하기 위해 Docker Swarm을 선택했다. Kubernetes를 사용하는 방법도 있었지만, 프로젝트 기간과 팀의 운영 범위를 고려해 image, service, secret, rolling update, overlay network를 비교적 단순한 형태로 경험할 수 있는 Swarm이 맞다고 판단했다.

cluster는 manager 3대와 worker 2대로 구성했다.

Manager 1: Leader
Manager 2: Reachable
Manager 3: Reachable
Worker 1: Application workload
Worker 2: Application workload

Manager를 홀수로 구성한 이유

Swarm manager는 Raft 합의로 cluster state를 관리한다. manager 1대는 단순하지만 장애에 취약하고, 2대는 한 대가 실패하면 과반수가 사라진다. 3대는 한 대 장애까지 quorum을 유지할 수 있다.

manager를 많이 늘린다고 항상 좋아지는 것도 아니다. quorum에 필요한 통신과 운영 대상이 늘어난다. 현재 장비 규모에서는 3대가 가장 작은 실용적인 홀수 구성이었다.

manager node에 application task를 전혀 실행하지 않는 전용 cluster까지는 만들지 못했다. 대신 application service는 worker를 우선하도록 placement constraint를 계획했고, NPM처럼 상태와 local volume에 민감한 service는 특정 manager에 고정했다. 이후 실제 application이 배포되면 resource reservation과 failure 재배치까지 확인할 예정이다.

Overlay network와 Service DNS/VIP

Swarm service는 task가 어느 node에 있든 overlay network를 통해 통신할 수 있다. 나는 edge용 overlay network를 만들고 임시 Nginx service를 배포해 service name으로 접근되는지 확인했다.

client service
  → service DNS name
  → Swarm VIP
  → running task

여기서 VIP는 virtual IP를 통해 살아 있는 task로 연결을 분산하는 역할을 한다. application container의 IP를 직접 고정하거나 특정 worker 주소를 origin으로 쓰지 않아도 된다. Cloudflare Tunnel과 NPM도 최종적으로 application service name/VIP를 바라보도록 설계했다.

하지만 VIP는 session store가 아니다. HTTP session, WebSocket subscription, Spring SimpleBroker state는 각 JVM에 남는다. 이 차이를 놓치면 replica 수만 늘리고 실시간 기능은 오히려 불일치할 수 있다. 이 문제 때문에 별도의 Redis Pub/Sub 공통 module을 만들게 됐다.

Global service와 Replicated service를 구분했다

cloudflared connector와 cAdvisor는 각 Swarm node에 하나씩 필요하다. 이들은 global mode로 배포했다. node가 cluster에 추가되면 해당 조건을 만족하는 node마다 task가 생기고, 제거되면 함께 사라진다.

반면 NPM은 persistent data와 관리 UI를 가진 단일 instance로 구성하고 특정 node에 placement했다. local bind mount를 사용하는 상태에서 task가 다른 node로 이동하면 같은 data가 보장되지 않기 때문이다. NPM을 고가용성으로 과장하지 않고 예비 edge 경로의 단일 component로 기록했다.

실제 application은 replicated service로 여러 task를 배치할 계획이다. 여기에는 다음 정책이 추가로 필요하다.

  • replica 수와 worker placement
  • healthcheck
  • update parallelism과 delay
  • rollback configuration
  • CPU·memory limit/reservation
  • NFS volume과 Docker Secret

application 개발이 끝나지 않았기 때문에 이 부분은 아직 검증 완료로 표시하지 않았다.

Worker에서 관리 명령이 실패한 이유

cloudflared service 상태를 확인하려고 worker에서 docker service psdocker service logs를 실행했을 때 다음 오류가 발생했다.

This node is not a swarm manager.
Worker nodes can't be used to view or modify cluster state.

처음에는 service가 잘못 배포된 것처럼 보일 수 있지만 정상적인 권한 경계다. worker는 task 실행을 담당하고 cluster 전체 service state 조회와 변경은 manager가 담당한다.

나는 manager node로 이동해 다음을 확인했다.

docker service ls
docker service ps <service>
docker service logs <service>
docker node ls

개별 worker에서는 자기 host의 container 상태만 docker ps와 local log로 확인할 수 있다. cluster 관점과 node 관점의 명령을 구분한 것이 해결이었다.

IP 변경 중 quorum을 지킨 방법

구축 도중 모든 node의 service network 주소를 바꿔야 했다. manager 3대를 동시에 변경하면 Raft quorum을 잃을 수 있기 때문에 한 대씩 진행했다.

각 manager 변경 뒤에는 다음 상태를 확인한 후 다음 node로 넘어갔다.

  • node Status=Ready
  • manager Availability=Active
  • leader 1대와 reachable manager 2대
  • overlay network 통신
  • 기존 service task 상태

worker는 drain 후 변경하고 새 주소로 재가입했다. 이때 Prometheus target과 NFS export 같은 IP 종속 설정도 함께 갱신했다.

Container metric을 위한 cAdvisor 배포

host resource만으로는 container별 CPU와 memory 사용량을 알기 어렵다. cAdvisor를 Docker host 전체에 배포하고 Prometheus가 각 endpoint를 scrape하도록 했다.

monitoring server는 Swarm member가 아니지만 Docker container를 실행하므로 별도로 cAdvisor를 구성했다. 결과적으로 Swarm 5개 node와 monitoring host의 container metric을 한 dashboard에서 볼 수 있게 했다.

/dev/kmsg, Docker root directory와 cgroup v2 compatibility를 사전 확인했고, endpoint를 host의 service address에 bind했다. 최종 접근 정책은 firewall 단계에서 monitoring server만 허용하도록 계획했다.

검증과 남은 과제

현재까지 manager quorum, worker ready 상태, overlay 양방향 통신, 임시 service DNS/VIP 응답, global connector와 cAdvisor task 배치를 확인했다.

아직 남은 핵심은 실제 MeetBack application이다.

  • Spring Boot image를 replicated service로 배포
  • NFS mount와 application user 권한 확인
  • Secret 주입
  • task 강제 종료 후 다른 worker로 재배치되는지 확인
  • rolling update 중 WebSocket과 session 영향 확인
  • 잘못된 image 배포 뒤 rollback 확인

Swarm을 선택한 이유는 단순히 container를 여러 대 띄우기 위해서가 아니다. service discovery와 desired state, secret, update policy를 하나의 운영 단위로 다루기 위해서였다. 동시에 VIP가 application state까지 공유하지는 않는다는 한계를 처음부터 architecture에 반영했다.