이전 글에서 준비한 보안 데이터와 설정 객체를 사용하여 스택을 배포해보자.
3개의 노드에 서비스를 분산하고, 장애 발생 시 시스템이 어떻게 스스로를 복구하는지도 살펴본다.
1. Docker Compose와 Docker Stack의 차이점
먼저 도커 컴포즈와 도커 스택의 차이를 알아야 한다.
두 도구는 YAML 문법을 공유하지만, 설계 목적과 동작 환경에서 큰 차이가 있다.
- Docker Compose: 단일 호스트 환경을 위한 도구다. build 지시어를 통해 이미지를 직접 생성할 수 있으나, 여러 노드에 걸친 스케줄링이나 셀프 힐링 기능은 제공하지 않는다.
- Docker Stack: 도커 스웜 모드의 멀티 노드 환경을 위한 도구다. 사용자가 "목표 상태(Desired State)"를 선언하면, 매니저가 클러스터 전체의 노드 상태를 감시하며 이 상태를 강제로 유지한다. 운영 환경의 오케스트레이션에 특화되어 있다.
| 구분 | Docker Compose | Docker Stack |
| 대상 | 단일 호스트 (Single Node) | 멀티 노드 클러스터 (Swarm) |
| 명령어 | docker-compose up | docker stack deploy |
| 목적 | 개발 및 테스트 환경 구축 | 운영 환경의 오케스트레이션 |
| 네트워크 | Bridge 기반 | Overlay 기반 (노드 간 통신) |
| 주요 차이 | build 지시어 사용 가능 | deploy 섹션(replicas 등) 필수 사용 |
docker stack은 스웜 매니저에게 "최종 상태(Desired State)"를 선언한다.
예를 들어 사용자가 "DB 레플리카 3개를 유지해줘"라고 선언하면, 매니저는 클러스터의 상태를 감시하며 이 레플리카의 수가 유지되도록 지속적으로 스케줄링을 수행한다.
2. Docker Stack 배포를 위한 YAML 파일 작성
YAML 파일을 기반으로 배포를 진행한다.
우선 nano 편집기로 docker-stack.yml 파일을 생성해 다음과 같이 작성한다.
version: '3.8'
services:
# 1. 데이터베이스 서비스
db:
image: mysql:5.7
environment:
# 시크릿 파일의 경로를 환경변수로 지정 (공식 MySQL 이미지의 약속)
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/my_mysql_password
MYSQL_DATABASE: practice_db
secrets:
- my_mysql_password
deploy:
replicas: 3
# 매니저 노드에만 배치하도록 강제하는 제약 (여기서는 필요 없으므로 주석처리)
# placement:
# constraints:
# - node.role == manager
# 2. 검증용 도구 서비스
verifier:
image: alpine
command: sleep 3600
secrets:
- my_mysql_password
configs:
- source: my_app_config
target: /etc/config/settings.yml
# 외부에서 이미 생성한(external) 자원들을 정의
secrets:
my_mysql_password:
external: true
configs:
my_app_config:
external: true
1️⃣ 전역 설정 (Global Settings)
version: '3.8' : 도커 컴포즈 파일 형식의 버전을 지정한다. 3.8은 시크릿과 컨피그 기능을 지원하는 최신 표준이다.
2️⃣ 서비스 정의: db (MySQL)
- services: : 실행할 컨테이너들의 묶음을 시작한다.
- db: : 서비스 이름 지정. 스웜 네트워크 안에서 다른 컨테이너들이 이 이름을 통해 통신할 수 있다.
- image: mysql:5.7 : 사용할 도커 이미지.
- environment: : 컨테이너 내부에서 사용할 환경 변수.
- MYSQL_ROOT_PASSWORD_FILE: /run/secrets/my_mysql_password : MYSQL_ROOT_PASSWORD에 직접 패스워드를 쓸 수도 있지만, 뒤에 _FILE을 붙이면 "비밀번호 대신 이 경로에 있는 파일을 읽어라"라는 뜻이 된다. 보안을 위해 시크릿 경로를 지정한 것이다.
- MYSQL_DATABASE: practice_db : 컨테이너가 처음 뜰 때 자동으로 생성할 데이터베이스 이름.
- secrets: - my_mysql_password : 이 서비스가 하단에 정의된 my_mysql_password라는 시크릿에 접근할 수 있도록 허용. 이 설정이 있어야 /run/secrets/ 경로에 파일이 생긴다.
- deploy: : 스웜 모드에서 어떻게 배포할지 결정한다.
- replicas: 3 : 똑같은 MySQL 컨테이너를 3개 띄우라는 뜻.
3️⃣ 서비스 정의: verifier (검증 도구)
- verifier: : 시크릿과 컨피그가 잘 들어갔는지 테스트하기 위한 임시 서비스.
- image: alpine : 5MB도 안 되는 매우 가벼운 리눅스 이미지를 사용한다.
- command: sleep 3600 : 알파인은 실행할 명령이 없으면 바로 종료된다. 따라서 docker exec으로 들어가서 확인하기 위해 1시간(3600초) 동안 유지하라고 명령을 하는 것이다.
- configs: : 일반 설정 파일을 주입.
- source: my_app_config : 아래에 정의된 컨피그 이름을 가져온다.
- target: /etc/config/settings.yml : 컨테이너 내부의 타겟 경로에 파일 형태로 컨피그를 마운트함.
4️⃣ 외부 자원 정의 (External Resources)
파일의 최하단부에서는 이 서비스들이 사용할 리소스들이 어디에 있는지 정의한다.
- secrets: : 시크릿 목록
- my_mysql_password:: 사용할 시크릿
- external: true: "이 시크릿은 이 YAML 파일 안에서 만드는 게 아니라, 이미 docker secret create 명령어로 만들어둔 것을 가져다 쓰라는 뜻.
- configs: : 컨피그 목록
- my_app_config:: 사용할 컨피그
- external: true: 마찬가지로 이미 생성된 외부 컨피그를 사용하겠다는 뜻
2. 스택(서비스) 배포 및 상태 확인
# 1. 스택 배포
docker stack deploy -c docker-stack.yml my_practice
# 2. 서비스 상태 확인 (REPLICAS 3/3 등 확인)
docker stack services my_practice
ID NAME MODE REPLICAS IMAGE PORTS
uusz5o3fjz80 my_practice_db replicated 3/3 mysql:5.7
cnwnr3px3uay my_practice_verifier replicated 1/1 alpine:latest
docker stack deploy 명령은 [스택명(my_practice)]_[서비스명]으로 서비스를 생성한다.
조회 결과에서 db 서비스의 레플리카가 3/3인 것을 통해 3개의 컨테이너가 성공적으로 배치되었음을 알 수 있다.
아래 그림을 통해 my_practice라는 이름의 스택이 클러스터 내에서 어떻게 구성되는지 직관적으로 알 수 있다.

3. 노드별 데이터 검증
1️⃣ 검증 도구(verifier) 확인
먼저 매니저 노드 터미널에서 스웜 매니저가 verifier 서비스를 어느 노드로 배치했는지 조회한다.
docker stack ps my_practice
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
f22vyk4wug4b my_practice_db.1 mysql:5.7 master Running Running 2 minutes ago
mwkstnzdkm7m my_practice_db.2 mysql:5.7 slave2 Running Running 2 minutes ago
1mtdar13uyok my_practice_db.3 mysql:5.7 slave1 Running Running 2 minutes ago
1xvnfz0292y1 my_practice_verifier.1 alpine:latest slave1 Running Running 2 minutes ago
verifier 서비스가 slave1에서 실행되고 있는 것을 알 수 있다.
이번에는 slave1 노드 터미널에서 verifier 컨테이너에 접속해보자.
# Slave1에서 verifier 컨테이너 ID 조회
docker ps -f "name=my_practice_verifier"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f869eb9f74f alpine:latest "sleep 3600" 5 minutes ago Up 5 minutes my_practice_verifier.1.1xvnfz0292y19fa5b3a6wsjwl
# Slave1에서 verifier 컨테이너 접속
docker exec -it 8f8 sh
/ # cat /run/secrets/my_mysql_password
pswd1234
/ # cat /etc/config/settings.yml
setting: { debug: true }
2️⃣ 데이터베이스(db) 접속 테스트
이번에는 db 컨테이너 ID를 조회한다.
(db 서비스는 레플리카가 3개이므로 master, slave1, slave2 노드에 모두 실행중이므로 아무 노드에서나 조회가 가능하다.)
# slave2에서 db 컨테이너 ID 조회
docker ps -f "name=my_practice_db"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5795c7bf95a2 mysql:5.7 "docker-entrypoint.s…" 8 minutes ago Up 8 minutes 3306/tcp, 33060/tcp
# Slave2에서 db 컨테이너 접속 및 MySQL 로그인
docker exec -it 579 mysql -u root -p
Enter password:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| practice_db |
| sys |
+--------------------+
5 rows in set (0.00 sec)
위에서 stack 파일에 MYSQL_DATABASE 환경변수에 저장한 practice_db 가 데이터베이스에 포함되어 조회되는 것을 확인할 수 있다.
4. 셀프힐링(self-healing) 테스트
도커 스웜의 강력한 기능인 셀프힐링 기능을 확인하기 위해 컨테이너를 강제로 삭제한다.
# 1. Slave2 노드에서 DB 컨테이너 강제 삭제
docker rm -f 579
# 2. 매니저 노드에서 서비스 상태 추적
docker service ps my_practice_db
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
f22vyk4wug4b my_practice_db.1 mysql:5.7 master Running Running 31 minutes ago
a3vfm7qupzxd my_practice_db.2 mysql:5.7 slave2 Running Running 37 seconds ago
mwkstnzdkm7m \_ my_practice_db.2 mysql:5.7 slave2 Shutdown Failed 42 seconds ago "task: non-zero exit (137)"
1mtdar13uyok my_practice_db.3 mysql:5.7 slave1 Running Running 31 minutes ago
docker service ps의 결과를 보면, 강제로 삭제된 태스크는 Failed (non-zero exit 137) 상태로 변하며 종료(Shutdown)된다.
하지만 스웜 매니저는 즉시 새로운 태스크를 생성하여 동일한 노드 혹은 다른 노드에 다시 배치한다.
결과표의 Running 37 seconds ago 상태는 매니저가 선언된 상태(레플리카 3개)를 맞추기 위해 스스로 복구했음을 보여준다.
'AI Journey > 클라우드' 카테고리의 다른 글
| [k8s] Vagrant와 VirtualBox를 사용해서 쿠버네티스 클러스터 구축하기 (0) | 2026.01.29 |
|---|---|
| [k8s] VMware 우분투 머신 세 대로 쿠버네티스 클러스터 구축하기 (+트러블 슈팅) (0) | 2026.01.23 |
| [Docker Swarm] 데이터 관리: 시크릿(secret)과 컨피그(config) (0) | 2026.01.16 |
| [Docker Swarm] Rolling Update - 서비스 업데이트와 복구 (0) | 2026.01.15 |
| [Docker Swarm] 컨테이너 및 노드 장애 복구 메커니즘 (0) | 2026.01.15 |