2024-08-26,   Gun-ha, KANG

이번 포스팅에서는 Docker Swarm dind 구성 및 수동 리밸런싱 테스트를 공유해보려고 합니다.

개요

시스템 메모리 초기화 등을 위해 정기 PM(유지보수) 작업 중, 서버를 하나씩 내렸다 올림

이를 위해, 도커 스웜 클러스터 또한 서버(노드)가 내려갔을 때, Task의 전환 및 노드 복구 후 Task의 재분배(리밸런싱)이 어떻게 이루어지는지 확인이 필요

  • 자동 리밸런싱 제공 X
  • 수동 리밸런싱 방법을 확인

1. Docker Swarm dind 구성

1.1 환경

단일 서버 환경에서의 dind 구성했으며, 매니저 노드 3대만 있을 경우로 가정하여 진행

docker/
├── 1.dind-swarm-cluster
│   ├── docker-compose.yml
│   ├── setup.sh
│   └── volume

Screenshot2

1.2 클러스터 구성

docker-compose.yml

services:
  registry:
    container_name: registry
    image: registry:2.8
    ports:
      - "5000:5000"
    volumes:
      - "./volume/registry-data:/var/lib/registry"

  manager1:
    container_name: manager1
    image: docker:27.1.1-dind
    privileged: true
    tty: true
    ports:
      - "8000:80"
      - "8085:8085"  # visualizer 용
      - "2377:2377"  # Swarm 클러스터 관리 포트
      - "7946:7946"  # 노드 간 통신 포트
      - "7946:7946/udp"  # 노드 간 통신 포트 (UDP)
      - "4789:4789"  # 오버레이 네트워크 포트
    command: ["dockerd-entrypoint.sh", "--experimental", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--insecure-registry=registry:5000"]
    volumes:
      - "./volume/stack:/stack"

  manager2:
    container_name: manager2
    image: docker:27.1.1-dind
    privileged: true
    tty: true
    ports:
      - "8001:80"
      - "2378:2377"  # Swarm 클러스터 관리 포트
      - "7947:7946"  # 노드 간 통신 포트
      - "7947:7946/udp"  # 노드 간 통신 포트 (UDP)
      - "4790:4789"  # 오버레이 네트워크 포트
    command: ["dockerd-entrypoint.sh", "--experimental", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--insecure-registry=registry:5000"]
    volumes:
      - "./volume/stack:/stack"

  manager3:
    container_name: manager3
    image: docker:27.1.1-dind
    privileged: true
    tty: true
    ports:
	  - "8086:8086" 
      - "8002:80"
      - "2379:2377"  # Swarm 클러스터 관리 포트
      - "7948:7946"  # 노드 간 통신 포트
      - "7948:7946/udp"  # 노드 간 통신 포트 (UDP)
      - "4791:4789"  # 오버레이 네트워크 포트
    command: ["dockerd-entrypoint.sh", "--experimental", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--insecure-registry=registry:5000"]
    volumes:
      - "./volume/stack:/stack"

  worker01:
    container_name: worker01
    image: docker:27.1.1-dind
    privileged: true
    tty: true
    ports:
	  - "8087:8087" 
    expose:
      - "7946"       # 노드 간 통신 포트
      - "7946/udp"   # 노드 간 통신 포트 (UDP)
      - "4789/udp"   # 오버레이 네트워크 포트
    command: ["dockerd-entrypoint.sh", "--experimental", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--insecure-registry=registry:5000"]
    depends_on:
      - manager1
      - manager2
      - manager3
      - registry

  worker02:
    container_name: worker02
    image: docker:27.1.1-dind
    privileged: true
    tty: true
    expose:
      - "7946"       # 노드 간 통신 포트
      - "7946/udp"   # 노드 간 통신 포트 (UDP)
      - "4789/udp"   # 오버레이 네트워크 포트
    command: ["dockerd-entrypoint.sh", "--experimental", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--insecure-registry=registry:5000"]
    depends_on:
      - manager1
      - manager2
      - manager3
      - registry

스크립트(setup.sh)를 통해 도커 스웜 클러스터 구축

#!/bin/bash

# 1.
docker-compose up -d

# 2.
docker exec -it manager1 hostname manager1
docker exec -it manager2 hostname manager2
docker exec -it manager3 hostname manager3
docker exec -it worker01 hostname worker01
docker exec -it worker02 hostname worker02

echo "All containers are up and hostnames are set."

sleep 10

# 3.
MANAGER1_IP=$(docker exec -it manager1 hostname -i | tr -d '\r')
docker exec -it manager1 docker swarm init --advertise-addr $MANAGER1_IP

echo "Docker Swarm initialized with manager1 IP: $MANAGER1_IP"

# 4.
MANAGER_JOIN_TOKEN=$(docker exec -it manager1 docker swarm join-token manager -q | tr -d '\r')

docker exec -it manager2 docker swarm join --token $MANAGER_JOIN_TOKEN $MANAGER1_IP:2377
docker exec -it manager3 docker swarm join --token $MANAGER_JOIN_TOKEN $MANAGER1_IP:2377

echo "manager2 and manager3 have joined the swarm as managers."

# 5.
WORKER_JOIN_TOKEN=$(docker exec -it manager1 docker swarm join-token worker -q | tr -d '\r')
docker exec -it worker01 docker swarm join --token $WORKER_JOIN_TOKEN $MANAGER1_IP:2377
docker exec -it worker02 docker swarm join --token $WORKER_JOIN_TOKEN $MANAGER1_IP:2377

echo "worker01 and worker02 have joined the swarm as workers."

1.3 Registry 설정 및 UI 생성

-- (내부가 아닌, 서버에서 수행) tag 붙이기
$ docker tag registry:2.8 localhost:5000/registry:2.8
$ docker push localhost:5000/registry:2.8

‘’‘
registry                   2.8           cfb4d9904335   10 months ago   25.4MB
localhost:5000/registry    2.8           cfb4d9904335   10 months ago   25.4MB
‘’‘

$ curl -X GET http://localhost:5000/v2/_catalog

-- worker 또는 manager 컨테이너 안에서 실행
$ docker pull localhost:5000/registry:2.8 (에러)
$ docker pull registry:5000/registry:2.8 (이름 혹은 IP)
-- registry 컨테이너 설정
$ docker exec -it registry sh
/ # vi etc/docker/registry/config.yml
‘’‘
version: 0.1
log:
  fields:
    service: registry
storage:
  cache:
    blobdescriptor: inmemory
  filesystem:
    rootdirectory: /var/lib/registry
http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]
    Access-Control-Allow-Origin: ["http://localhost:8086"]
    Access-Control-Allow-Methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
    Access-Control-Allow-Headers: ["Authorization", "Content-Type"]
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3
‘’‘

-- 서버
$ docker-compose stop registry
# docker-compose restart registry

-- 매니저 /stack/docker/3.registry-ui
/ # vi docker-compose.yml
‘’‘
services:
  rgst-ui:
    image: joxit/docker-registry-ui:latest
    ports:
      - "8086:80"
    environment:
      - REGISTRY_URL=http://localhost:5000
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.role == test-role
‘’‘

2. 서비스 실행

테스트용

-- 자신의 hostname을 출력하는 웹 어플리케이션 (서비스 실행 = 3개의 Task 실행)
$ docker service create --name testApp -p 4567:4567 --replicas 3 subicura/whoami:1

-- 도커 스웜 visualizer, Global Mode로 실행 (서비스 실행 = 3개의 Task 실행)
$ docker stack deploy -c docker-compose.yml test

services:
  visualizer:
    image: dockersamples/visualizer
    ports:
      - "80:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: global
      placement:
        constraints: [node.role == manager]

--5초마다 카운트 증가하는 어플리케이션 (서비스 실행 = 1개의 Task 실행)
$ docker stack deploy -c docker-compose.yml test
#!/bin/bash
count=0

while true; do
  echo "Hello, World! Count: $count"
  count=$((count + 1))
  sleep 5
done

실행중인 서비스와 Task 확인

Screenshot1

3. 수동 리밸런싱 테스트

3.1 Leader 노드의 서버 중지

노드가 죽었을 떄, Task가 다른 노드로 전환되는지 (승격 여부 포함)

  • 매니저1에 있던 Task(app_helloworld, testApp)가 다른 노드로 전환되어 실행됨
  • Leader의 역할 또한, 승격됨을 확인 (매니저1 -> 매니저3)
$ docker-compose stop manager1

Screenshot3

Task의 전환 (상세 확인)

  • 다른 노드에 새로 Task가 실행되어 서비스의 연속성을 유지함

Screenshot4

3.3 서버 복구 시, Task 이동 확인 (Task 재분배)

클러스터가 자동으로 컨테이너를 해당 노드(복구된)로 재배치시키는 가

  • 실행 중이던 Task들은 원래 위치로 이동하지 않음 (재배치 수행 X)
$ docker-compose start manager1

Screenshot5

도커 스웜은 클러스터가 자동으로 재배치하지않기에 수동으로 실행해야 함

3.3 수동으로 재배치 (1/2)

수동 방법 1. 서비스를 강제로 업데이트하여 컨테이너 재배포

$ docker service update --force app_helloworld

Screenshot6

3.3 수동으로 재배치 (2/2)

수동 방법 2. 복제 수를 조정하여 재배치 유도

$ docker service scale app_helloworld=0
$ docker service scale app_helloworld=3

Screenshot7

결론

도커 스웜에서는 자동으로 리밸런싱되지 않음

  • 자동으로 task를 리밸런싱하면 불필요한 컨테이너 재시작이 발생할 수 있기에, 이는 이미 실행중인 애플리케이션에 영향을 미칠 수 있음 (안정성 우선시)
  • 수동으로 리밸런싱하면, 시기를 정할 수 있으며 효율적 활용 가능

참고


Contact Author. KangGunha Email. zxcvbnm9931@epozen.com

업데이트: