CloudNative
Docker Swarm Cluster 구성과 Service 운영
Manager와 Worker Cluster 구성, Service 배포, Routing Mesh, 확장, Rolling Update 및 Stack 배포
1 ) 클러스터 실습 환경 구성
Ubuntu 24.04 VM 세 대를 같은 Network에 연결하고 한 대는 Manager, 두 대는 Worker로 사용한다.
Windows Host
LAPTOP
192.168.0.99
│
├── LAB_MASTER
│ hostname: master
│ 192.168.0.100
│
├── LAB_WORKER_1
│ hostname: worker1
│ 192.168.0.101
│
└── LAB_WORKER_2
hostname: worker2
192.168.0.102
각 VM은 Bridge Adapter를 사용하여 같은 Network에 연결하고 Host에서 SSH로 접속한다. 세 VM 모두 Docker Engine과 Compose Plugin을 설치해 환경을 준비했다.

sudo apt-get update
sudo apt-get install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
설치 후 Version과 Service 상태를 확인한다.
sudo docker version
sudo systemctl enable --now docker
sudo systemctl status docker
현재 사용자가 sudo 없이 Docker를 실행하려면 docker Group에 추가한 뒤 다시 로그인한다.
sudo usermod -aG docker "$(whoami)"
Docker Socket의 권한을
666으로 변경하면 모든 사용자가 Docker Daemon을 제어할 수 있으므로 사용하지 않는다.
Docker Engine 설치는 모든 Node에서 수행하지만 Cluster를 생성하고 관리하는 명령은 Manager에서 수행한다.
Kubernetes도 Container Runtime과 Node 구성 요소는 모든 Machine에 필요하지만 Cluster 제어 명령은 Control Plane을 중심으로 수행한다.
2 ) Swarm Cluster 구성
Manager 초기화
Manager로 사용할 master에서 Swarm Mode를 초기화한다.
docker swarm init
# docker swarm init --advertise-addr 192.168.0.100
docker swarm init으로만 실행하면 Docker가 해당 Host의 IP Address를 자동으로 선택해 Swarm Manager를 초기화한다.
--advertise-addr는 다른 Node가 Manager에 접속할 주소를 지정한다.
다른 Swarm 노드들에게 “이 Manager에는 계속 192.168.0.100으로 접속해”라고 명시하는 것이다.
초기화가 끝나면 현재 Node가 Manager가 되고 Worker가 Cluster에 참여할 때 사용할 docker swarm join 명령이 출력된다.
Kubernetes에서는
kubeadm init이 Control Plane을 초기화하고 Worker가 사용할kubeadm join명령을 출력한다. Cluster의 제어 주체를 먼저 만들고 다른 Node가 Token을 이용해 참여한다는 흐름은 비슷하다. 다만 Swarm은 Docker Engine에 Orchestrator가 내장되어 있고 Kubernetes는 API Server, Scheduler, Controller Manager 등 별도의 Control Plane 구성 요소를 사용한다.
Worker 참여
Manager에서 출력된 명령을 worker1, worker2에서 실행한다.
docker swarm join \
--token SWMTKN-1-WORKER_TOKEN \
192.168.0.100:2377

Manager에서는 Worker Token을 다시 확인할 수 있다.
docker swarm join-token worker

docker swarm join-token은 Manager가 가진 Cluster 상태를 조회해야 하므로 Worker에서는 실행할 수 없다.
Kubernetes의 Join Token도 Control Plane에서
kubeadm token create --print-join-command로 다시 만들거나 조회한다.
Join Token은 Node를 Cluster에 참여시킬 수 있는 인증 정보이므로 공개 저장소나 게시물에 실제 값을 남기지 않는다. 노출되었다면 Token을 교체해야 한다.
물론 참여하기 위해서는 Manager와 네트워크 통신이 가능해야 한다. 하지만 토큰이 공개되어 있으면 공격자가 내부에 접근했을 때 필요한 인증 정보를 이미 확보한 상태가 되기에 인증 정보를 담고 있는 토큰은 비밀 정보로 취급해야 한다.
Swarm Port 확인
Manager에서 Docker Daemon이 사용하는 Port를 확인한다.
sudo netstat -nlp | grep dockerd

Swarm Node 사이에는 다음 Port가 필요하다.
| Port | Protocol | 역할 |
|---|---|---|
2377 |
TCP | Manager의 Cluster 관리 통신 |
7946 |
TCP/UDP | Node Discovery와 통신 |
4789 |
UDP | Overlay Network Traffic |
Kubernetes도 Control Plane API, kubelet, Service Network 등에 별도의 Port가 필요하지만 Swarm과 Port 번호 및 구성 요소는 다르다.
Node 상태 확인
Manager에서 Node 목록을 조회한다.
docker node ls

같은 명령을 Worker에서 실행하면 This node is not a swarm manager 오류가 발생한다. docker node ls는 Cluster 전체 상태를 조회하는 관리 명령이므로 Manager에서만 사용할 수 있다.
Kubernetes의
kubectl get nodes도 API Server를 통해 Cluster 상태를 조회한다. 명령 자체가 Control Plane Machine에서만 실행되어야 하는 것은 아니지만, 외부 Machine이나 Worker에서 실행하려면 API Server 주소와 인증 정보가 담긴 Kubeconfig가 필요하다. Swarm Worker가 관리 API를 제공하지 않는 것과 달리 Kubernetes Client는 인증 설정이 있으면 어느 Machine에서도 API Server에 요청할 수 있다.
docker info
docker network ls
docker info의 Swarm: active와 Network 목록의 ingress를 확인한다.
Node 제거
Worker를 정상적으로 제거할 때는 제거할 Worker에서 Swarm을 떠난 뒤 Manager에서 Node 정보를 정리한다.
# 제거할 Worker에서 실행
docker swarm leave
# Manager에서 실행
docker node rm worker1
Manager가 Swarm을 떠나면 Manager Quorum에 영향을 줄 수 있다. 특히 마지막 Manager에서 --force로 떠나면 Cluster를 더 이상 관리할 수 없으므로 실습 종료 외에는 신중하게 사용한다.
3 ) Service 생성과 조회
Manager에서 반복적으로 Message를 출력하는 Service를 생성한다.
docker service create \
--name swarm-start \
alpine:3 \
/bin/sh -c "while true; do echo 'Docker Swarm Start'; sleep 3; done"
Service 상태와 Task 배치를 확인한다.
docker service ls
docker service ps swarm-start
docker service logs -f swarm-start
docker service ls, docker service ps, docker service logs는 Cluster Service를 관리하는 명령이므로 Manager에서 실행한다. Worker에서는 자신에게 배치된 Container를 docker container ls로 확인할 수 있지만 Service의 전체 Replica 상태는 조회할 수 없다.
Kubernetes에서는 Deployment를 생성한 뒤
kubectl get deployments,kubectl get pods,kubectl logs로 비슷한 정보를 확인한다. Swarm의 Service는 원하는 Replica 상태를 선언하는 단위이며 Kubernetes에서는 주로 Deployment가 이 역할을 담당한다.
Service를 삭제한다.
docker service rm swarm-start
4 ) Nginx Service와 Routing Mesh
Worker에만 Nginx Task가 배치되도록 Constraint를 지정하고 Service Port를 공개한다.
docker service create \
--name web-alb \
--replicas 2 \
--publish published=8001,target=80 \
--constraint 'node.role==worker' \
nginx
docker service ps web-alb
node.role==worker Constraint 때문에 Nginx Task는 Manager에 배치되지 않는다. 그러나 Service의 8001번 Port를 공개하면 Manager와 두 Worker를 포함한 모든 Node가 동일한 8001번 Port로 들어오는 Traffic을 받는다.
curl http://192.168.0.100:8001
curl http://192.168.0.101:8001
curl http://192.168.0.102:8001
세 주소 모두 같은 Service에 연결된다. 이것은 모든 Node가 공개 Port를 감시하는 Swarm Routing Mesh의 동작이다. 요청을 받은 Node에 Nginx Task가 없더라도 Ingress Network가 실제 Task가 실행 중인 Node로 Traffic을 전달한다.
Kubernetes에서는 Deployment만 생성해도 모든 Node에 같은 외부 Port가 자동으로 열리지 않는다. 외부 접근에는 NodePort, LoadBalancer 또는 Ingress 같은 별도의 Service 노출 방식이 필요하다. NodePort를 사용하면 여러 Node의 동일 Port로 접근할 수 있다는 점은 Swarm Routing Mesh와 비슷하지만, Kubernetes Service와 kube-proxy 또는 CNI가 Traffic 전달을 담당한다는 차이가 있다.
임시 Page 변경
Task가 배치된 Worker에서 테스트용 index.html을 만든 뒤 특정 Container에 복사할 수 있다.
<h1>Swarm Worker 1</h1>
docker container ls
docker cp index.html CONTAINER_ID:/usr/share/nginx/html/index.html
이 방법은 특정 Task의 쓰기 Layer만 변경한다. 다른 Replica에는 적용되지 않고 Task가 재생성되면 변경 내용도 사라지므로 Load Balancing을 눈으로 확인하는 임시 실습에만 사용한다. 실제 배포에서는 Page를 포함한 새로운 Image를 Build하여 Service를 Update한다.
5 ) Overlay Network와 Service Discovery
Swarm의 Overlay Network는 여러 Docker Host에 걸쳐 있는 Service를 하나의 가상 Network로 연결한다. 같은 Overlay Network의 Service는 IP를 직접 관리하지 않고 Service 이름으로 통신한다.
docker network create \
--driver overlay \
dailylog-net
Service에는 Virtual IP가 할당되며 내장 DNS가 Service 이름을 이 주소로 해석한다. Client가 Virtual IP로 요청하면 Swarm이 실행 중인 Replica 중 하나로 전달한다. Replica가 늘거나 다른 Node로 이동해도 Application은 같은 Service 이름을 사용한다.
Kubernetes도 Service 이름을 Cluster DNS로 조회하고 고정된 ClusterIP를 통해 여러 Pod로 요청을 전달한다. Service Discovery 관점은 비슷하지만 Swarm은 Service와 Overlay Network를 Docker Engine이 직접 관리하고 Kubernetes는 Service, EndpointSlice, kube-proxy 또는 Network Plugin이 역할을 나누어 담당한다.
6 ) Service 확장과 배포 Mode
Replicated Mode
실행 중인 Service의 Replica 수를 변경한다.
docker service scale web-alb=5
Node 수보다 Replica가 많으면 한 Node에 여러 Task가 배치될 수 있다. Swarm은 Node 수와 Replica 수를 일치시키는 것이 아니라 가용 Resource와 Constraint를 기준으로 Task를 배치한다.
Kubernetes의
kubectl scale deployment ... --replicas=5도 원하는 Pod 수를 변경한다. 두 도구 모두 선언한 수와 실제 실행 수가 같아지도록 조정한다.
Global Mode
조건에 맞는 모든 Node에 Task를 하나씩 배치한다.
docker service create \
--name global-nginx \
--mode global \
nginx
Global Mode는 각 Node에서 동작해야 하는 Monitoring Agent나 Log Collector에 적합하다. Kubernetes의 DaemonSet이 같은 목적을 담당한다.
7 ) 장애 복구와 Desired State
Replica가 세 개인 Service를 생성한다.
docker service create \
--name recovery-test \
--replicas 3 \
nginx
Task가 배치된 Worker에서 Container 하나를 강제로 삭제한다.
docker container rm -f CONTAINER_ID
Manager에서 일정 시간 뒤 Task 상태를 다시 확인한다.
docker service ps recovery-test
실행 중인 Replica가 세 개보다 적어졌으므로 Swarm은 새로운 Task를 생성한다. 사용자가 선언한 Desired State와 실제 상태를 지속적으로 비교하고 차이를 복구하는 동작이다.
Kubernetes의 Deployment Controller도 Pod가 삭제되면 ReplicaSet을 통해 새로운 Pod를 생성한다. 자동 복구의 기본 원리는 같지만 Swarm은 Service Task를, Kubernetes는 Pod와 Controller Resource를 중심으로 상태를 관리한다.
8 ) Rolling Update와 Rollback
Redis 6.0 Service를 생성한 뒤 Image Version을 변경한다.
docker service create \
--name my-database \
--replicas 3 \
redis:6.0-alpine
docker service update \
--image redis:6.2.5-alpine \
my-database
docker service ps my-database
Rolling Update는 기존 Task를 한꺼번에 교체하지 않고 정해진 단위와 간격으로 교체한다.
| Option | 역할 |
|---|---|
--update-parallelism |
동시에 Update할 Task 수, 기본값 1 |
--update-delay |
한 Update Group이 끝난 뒤 다음 Group까지 대기 시간 |
--update-order |
stop-first 또는 start-first |
--update-failure-action |
실패 시 pause, rollback, continue |
--update-max-failure-ratio |
허용할 Update 실패 비율 |
기본 Update 순서는 기존 Task를 먼저 중지하는 stop-first이다. 새 Task를 먼저 실행하여 중단 시간을 줄이려면 start-first를 명시한다.
docker service update \
--image redis:6.2.5-alpine \
--update-parallelism 1 \
--update-delay 10s \
--update-order start-first \
--update-failure-action rollback \
my-database
실패한 Update를 이전 Service Spec으로 되돌린다.
docker service rollback my-database
docker info
docker swarm update --task-history-limit 10
Task History Limit은 이전 Task 기록의 보존 수를 조정한다. Rollback 자체가 여러 Version의 Image 이력을 원하는 만큼 보관하는 기능이라는 의미는 아니다.
Kubernetes Deployment도 RollingUpdate Strategy,
maxUnavailable,maxSurge와kubectl rollout undo를 제공한다. 두 도구 모두 단계적 교체와 Rollback을 지원하지만 Option 이름과 상태를 기록하는 Resource 구조는 다르다.
Resource와 Service 설정 변경
실행 중인 Service의 설정을 변경할 수 있다.
docker service update \
--limit-cpu 0.5 \
--limit-memory 512M \
--env-add MY_VAR=value \
--env-rm OLD_VAR \
--publish-add published=8002,target=80 \
--replicas 5 \
web-alb
docker service inspect --pretty web-alb
9 ) Node Drain과 유지 보수
Node를 유지 보수할 때 Drain으로 변경하면 기존 Task를 다른 Active Node로 옮기고 새로운 Task도 배치하지 않는다.
docker node update \
--availability drain \
worker1
docker service ps web-alb
유지 보수가 끝나면 다시 Active로 변경한다.
docker node update \
--availability active \
worker1
docker node update는 Manager에서 실행한다. Worker는 자신의 Availability를 직접 변경할 수 없다.
Kubernetes에서는
kubectl cordon NODE로 새로운 Pod Scheduling을 막고kubectl drain NODE로 기존 Workload를 다른 Node로 이동시킨다. Swarm의Drain은 이 두 작업을 하나의 Availability 상태 변경으로 처리한다.
10 ) Docker Stack 배포
Swarm에서는 여러 Service, Network와 Volume을 하나의 Stack으로 배포할 수 있다. docker stack 명령은 Manager에서 실행한다.
Overlay Network 생성
docker network create \
--driver overlay \
dailylog-net
Stack File 작성
docker stack deploy는 현재도 Legacy Compose v3 형식을 사용하므로 Stack File에는 version을 유지한다.
version: "3.9"
services:
mongodb:
image: dbgurum/dailylog:db_1.0
ports:
- "27017:27017"
networks:
- dailylog-net
deploy:
placement:
constraints:
- node.role != manager
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
backend:
image: dbgurum/dailylog:back_1.0
ports:
- "8000:8000"
networks:
- dailylog-net
environment:
MONGO_URL: mongodb://mongodb:27017
deploy:
replicas: 2
placement:
constraints:
- node.role != manager
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
frontend:
image: dbgurum/dailylog:front_1.0
ports:
- "3000:8000"
networks:
- dailylog-net
environment:
PORT: "8000"
DAILYLOG_API_ADDR: backend:8000
deploy:
replicas: 2
placement:
constraints:
- node.role != manager
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
networks:
dailylog-net:
external: true
Stack 배포에서는 각 Node가 Image를 내려받을 수 있어야 하므로 Image를 Registry에 Push한 상태여야 한다. depends_on은 Stack Service의 준비 순서를 제어하는 기능으로 의존하지 않고, Backend가 Database 연결 실패를 재시도하도록 Application과 Restart Policy를 구성한다.
배포와 확인
docker stack deploy \
--compose-file docker-compose.yaml \
dailylog
docker stack ls
docker stack services dailylog
docker stack ps dailylog
Stack을 삭제한다.
docker stack rm dailylog
Kubernetes에서는 여러 Resource가 작성된 YAML을
kubectl apply로 배포한다. Swarm Stack과 마찬가지로 선언형 File로 여러 구성 요소를 배포하지만 Kubernetes는 Deployment, Service, ConfigMap, Secret 등 Resource 종류를 각각 정의한다.
11 ) Docker Swarm의 Volume
Swarm Service의 Container도 데이터를 영속적으로 보관하기 위해 Volume을 사용한다. 다만 기본 local Driver로 만든 Named Volume은 각 Node의 Local Disk에 독립적으로 생성된다는 점을 고려해야 한다.
Named Volume과 재배치
다음 Service가 worker1에서 실행되면 db-data Volume도 worker1에 생성된다.
services:
database:
image: mariadb:11
volumes:
- db-data:/var/lib/mysql
volumes:
db-data: {}
Service Task가 장애나 Node Drain으로 worker2에 다시 배치되면 Docker는 worker2에 같은 이름의 새 Local Volume을 생성한다. Volume 이름은 같지만 worker1의 기존 데이터가 자동으로 복사되거나 공유되지는 않는다.
worker1 worker2
┌────────────────────┐ ┌────────────────────┐
│ db-data │ │ db-data │
│ 기존 Database Data │ │ 새로 생성된 Volume │
└────────────────────┘ └────────────────────┘
같은 이름이지만 서로 다른 Local Data
Node Placement로 고정
단일 Database처럼 특정 Node의 Local Data를 계속 사용해야 한다면 Node에 Label을 지정하고 Placement Constraint로 Task를 고정할 수 있다.
Manager에서 Storage를 보유한 Node에 Label을 지정한다.
docker node update --label-add storage=db worker1
Stack File에서 해당 Label을 가진 Node에만 Database를 배치한다.
services:
database:
image: mariadb:11
volumes:
- db-data:/var/lib/mysql
deploy:
placement:
constraints:
- node.labels.storage == db
volumes:
db-data: {}
이 방식은 Local Data를 유지하지만 worker1에 장애가 발생하면 다른 Node에 자동으로 재배치할 수 없다. Node 고정은 공유 Storage가 아니며 고가용성을 제공하지 않는다.
NFS와 Network Storage
여러 Node에서 같은 Data에 접근해야 한다면 NFS 같은 Network File System이나 Cluster 전체에서 사용할 수 있는 Storage Driver를 구성한다.
services:
database:
image: mariadb:11
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
driver: local
driver_opts:
type: nfs
o: addr=192.168.0.110,rw
device: ":/srv/swarm-data"
모든 Swarm Node가 NFS Server에 연결할 수 있어야 하며 NFS Directory, 접근 권한과 방화벽을 미리 구성해야 한다. Public Cloud에서는 Network File System이나 Swarm과 호환되는 외부 Volume Driver를 사용할 수 있다.
| 방식 | Data 위치 | 다른 Node 재배치 | 적합한 경우 | 주의 사항 |
|---|---|---|---|---|
| Local Named Volume | 각 Node의 Local Disk | 기존 Data 자동 연결 불가 | Node와 Data의 결합이 중요하지 않은 경우 | 같은 이름의 별도 Volume 생성 가능 |
| Placement Constraint | 지정 Node의 Local Disk | 지정 Node에서만 실행 | 단일 Stateful Application | Node 장애 시 Service 중단 가능 |
| NFS·Network Storage | Network Storage | 같은 Data에 접근 가능 | 여러 Node에서 Data 공유 | Storage 가용성·성능·권한 관리 필요 |
Stateful Service를 운영할 때는 Container Replica 수만 늘리는 것으로 Data 고가용성이 완성되지 않는다. Database 자체의 Replication과 Backup, Storage 장애 복구 방식까지 함께 설계해야 한다.
최종 정리
Manager에서 Swarm을 초기화하고 Worker는 Join Token으로 Cluster에 참여한다.
Cluster 전체의 Node와 Service 관리 명령은 Manager에서 실행하며 Worker는 할당된 Task를 실행한다.
Service Port를 공개하면 Task 배치 여부와 관계없이 모든 Node가 같은 Port로 Traffic을 받는다.
Overlay Network와 내장 DNS를 통해 Service 이름으로 통신하며 Routing Mesh가 실행 중인 Task로 요청을 전달한다.
Replicated Mode는 지정한 수의 Task를 유지하고 Global Mode는 조건에 맞는 모든 Node에 Task를 배치한다.
Swarm은 Desired State, Rolling Update, Rollback과 Node Drain으로 Service 운영을 지원한다.
docker stack deploy는 여러 Service와 Network를 하나의 Application Stack으로 배포한다.Swarm의 기본 Named Volume은 Node Local Storage이므로 Task 재배치 시 Data가 자동으로 따라가지 않는다.
댓글