도커스웜을 이용해 배포하는 방법과 배포시 라벨 등록을 이용하는 경우에 대한 설명입니다.

스웜 생성 및 노드간 ingress network 형성

일단 도커스웜을 만들어 줍니다.

$ docker swarm init
Swarm initialized: current node (d4phl1t6rj5kvcpmezngrua9d) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-510716h1vbba

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

$ docker node ls
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
d4phl1t6rj5kvcpmezngrua9d *   docker-desktop   Ready     Active         Leader           20.10.6

물리서버 1대당 node 가 1대라고 생각하시면 됩니다. 도커스웜은 manager node 와 worker node 로 구성됩니다. init 명령어를 사용하여 한대를 생성하였기 때문에 manager node 한대가 만들어집니다.

간단한 http 서버를 하나 만들고 docker build 로 이미지를 만듭니다.

package main

import (
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		w.Write([]byte("Hello World!"))
	})

	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		return
	}
}
FROM golang:latest

ENV GO111MODULE=off

WORKDIR /go/src/app
COPY . /go/src/app

RUN go build -o simple_program

ENTRYPOINT ["/go/src/app/simple_program"]
docker build -t simple_program .

도커로 배포시 deploy mode 는 global 과 replicated 가 있습니다. global 은 노드 한대당 한개의 컨테이너를 올립니다 replicated 는 컨테이너 개수를 조절할 수 있습니다.

예를들어 현재 노드가 1개인 상황에서 global 로 지정한다면 simple_program 컨테이너를 1개 밖에 생성하지 못하지만 replicated 로 설정한다면 여러대를 생성할 수 있습니다.

docker-compose.yml

version: '3.7'

services:
  simple_program:
    image: simple_program
    deploy:
      mode: replicated
      replicas: 2

    ports:
      - 8080:8080
$ docker stack deploy --compose-file docker-compose.yml local_deploy_stack
Creating network local_deploy_stack_default
Creating service local_deploy_stack_simple_program

$ docker service ls
ID             NAME                                MODE         REPLICAS   IMAGE                   PORTS
o40ngptp1epi   local_deploy_stack_simple_program   replicated   2/2        simple_program:latest   *:8080->8080/tcp

$ docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED          STATUS          PORTS     NAMES
a6dd528dfd5a   simple_program:latest   "/go/src/app/simple_…"   21 seconds ago   Up 19 seconds             local_deploy_stack_simple_program.2.qpnrss8vnj1nz3r291gpdv79r
7d05a56a280b   simple_program:latest   "/go/src/app/simple_…"   21 seconds ago   Up 19 seconds             local_deploy_stack_simple_program.1.yl8hbedpk7wxatrvi90wxaepc

컨테이너가 2대 생성된걸 알 수 있습니다. 그러면 외부에서 접근시 어떤 컨테이너로 갈지 궁금하실수 있는데요.

https://docs.docker.com/engine/swarm/ingress/

해당 문서를 보면 스웜에서 노드끼리 내부네트워크를 형성하고 로드밸런스가 있다는것을 알 수 있습니다.

ingress network

관련된 stackoverflow 질문도 추가합니다.

https://stackoverflow.com/questions/42510944/how-is-load-balancing-done-in-docker-swarm-mode

만약에 외부 라우터를 사용한다면 endpoint_mode 값을 dns 라운드로빈으로 설정을 할 수도 있습니다.

endpoint_mode 값은 디폴트가 vip 이고 vip, dnsrr 두개 설정값을 가집니다.

deploy:
      mode: replicated
      replicas: 2
      endpoint_mode: dnsrr 

vip 와 dnsrr 설정의 동작방식은 아래 문서에서 확인할 수 있습니다.

https://sreeninet.wordpress.com/2016/07/29/service-discovery-and-load-balancing-internals-in-docker-1-12/

https://blog.octo.com/en/how-does-it-work-docker-part-3-load-balancing-service-discovery-and-security/

도커스웜 노드간 포트 공유

ingress network 로 형성이 되어있기때문에 포트를 binding 하게 되면 컨테이너가 떠있지 않은 node 에서도 해당 포트는 사용 할 수 없습니다.

예를들어 node 1, node 2 를 스웜으로 형성하고 node 1 에는 8000번 포트 node 2 에는 9000번 포트를 사용하고 두포트를 host 포트와 binding 을 시킨다면 node 1에서 9000번 포트를 사용할수 없고 node 2 에서 8000번 포트를 사용할 수 없습니다.

제가 했던 실수는 node 1, node 2 에 레디스를 네이티브로 설치해두었었는데 node 1 에 컨테이너로 레디스를 설치한 순간 port already in use 에러가 떴습니다.

도커 스웜 노드 라벨링

개별 도커노드들을 서버 종류에 따라서 배포하고 싶을 수 있습니다.

이를테면 api 서버, redis 서버 등등 으로 구분하고 배포하고 싶을 수 있는데 이때 사용할 수 있는 방법 중 하나는 도커 노드들에 라벨을 붙이는 것 입니다.

라벨 추가

docker node update –label-add 라벨key=라벨value 호스트명

ex) docker node update –label-add server_type=api docker-desktop

라벨 삭제

docker node update –label-rm 라벨key 호스트명

ex) docker node update –label-rm server_type docker-desktop

라벨링이 잘 되었는지 확인을 하려면 아래 명령어를 사용하면 됩니다.

docker node inspect 호스트명 –pretty

$ docker node update --label-add server_type=api docker-desktop
docker-desktop

$ docker node inspect docker-desktop --pretty
ID:                     d4phl1t
Labels:
 - server_type=api
Hostname:               docker-desktop

라벨링이 되어있다면 아래 명령어를 이용해 배포할 노드에 원하는 종류의 서버를 배포할 수 있습니다.

version: '3.7'

services:
  api_server:
    image: simple_program
    deploy:
      mode: replicated
      replicas: 2
      placement:
        constraints:
          - node.labels.server_type == api
    ports:
      - 8080:8080

만약 mode 를 global 로 한다면 server_type 이 api 인 노드들에 각각 한대씩 배포하게 됩니다.

deploy:
      mode: global
      placement:
        constraints:
          - node.labels.server_type == api

한가지 노드에 2가지 이상의 역활을 맡기는 경우에 노드들에 라벨링을 여러개 줍니다.

$ docker node update --label-add aggregator_role=true docker-desktop
docker-desktop

$ docker node update --label-add log_server_role=true docker-desktop
docker-desktop

$  docker node inspect docker-desktop --pretty
ID:                     d4phl1t6rj5kvcpmezngrua9d
Labels:
 - aggregator_role=true
 - log_server_role=true

이런식으로 라벨링을 하면 한 노드에 여러가지 역할을 부여할 수 있습니다.

constraints 을 여러개 걸었을 경우 모두 만족하는 node 에만 배포 됩니다.

deploy:
      mode: global
      placement:
        constraints:
          - node.labels.aggregator_role=true
          - node.labels.log_server_role=true

위와 같이 2가지 제약조건을 건다면 aggregator_role 도 true 이고 log_server_role 도 true 인 node 에만 배포하게 됩니다.

처음에 조건이 and 가 아닌 or 라고 생각해서 stackoverflow 에 질문을 올렸었는데 docs 에 모든조건 만족이라고 나와있다고 친절한 답변을 받았습니다. ( docs 를 좀 더 꼼꼼히 읽어봤어야 했네요 )

https://stackoverflow.com/questions/67761268/docker-swarm-a-problem-that-does-not-work-when-deploying-with-multiple-labels-i/67762721#67762721