기술빙자사기

docker swarm 으로 그누보드5 로드밸런싱 흉내내기.

권성재 2020. 3. 25. 11:30

##  docker swarm 으로 그누보드5 로드밸런스 흉내내기

흉내는 흉내일뿐.
실제 서비스에서 로드밸런싱(부하분산)의 문제는 웹서버 프로세스의 접속제한이나
DB 서버의 처리능력, 네트워크 대역폭, 디스크의 I/O 성능등
다양한 부분을 감안한 복잡한 작업이지만, 제가 그런건 알리 없고
다만, 웹서버를 2 개이상 복수로 늘려서 웹접속을 늘려서 부하를 조정하는 .. 
아주 단순한 차원에서, 
도커 스웜에서 제공하는 로드밸런스 기능으로 그누보드5를 설치해 봤습니다.

192.168.219.160 docker-master # 도커 마스터(매니저)
192.168.219.184 docker-node1 # 도커 노동자1

일단, 서버 2 대로 테스트했습니다.
서버는 둘다 우분투리눅스  19.10이고, 기본 도커패키지 docker.io 를 사용하고
hostnamectl 명령어로 각각 호스트이름을 docker-master, docker-node1 으로
지정해 줬습니다.
그누보드5 운영환경은,
웹서버와 mysql DB 서버를 분리하고, DB 서버는 docker-master 에서 1개만
실행하고, 웹서버는 2개이상 실행해서 docker-master 와 docker-node1 에서 
나누어 늘려나가게 했습니다.
mysql DB 서버는 docker-master 서버의 /home/mysql_data 라는 디렉토리에
/var/lib/mysql 디렉토리를 볼륨 연결하고,
아파치 웹서버는 /home/gnuboard_data 에 전체 소스를 넣어서  역시 이 디렉토리를
볼륨으로 /var/www/html 에 웹루트로 연결했습니다.
그런데 문제는, 웹서버를 2대 이상 서버에 분산해서 실행하면, 웹루트의 소스를
동일하게 공유해야 하므로,
네트워크 파일시스템인 nfs 로 노드들 간에 마운트해서 동일한 /home/gnuboard_data 를
접근 하게 했습니다.

우선, 도커 스웜 개념 이해에 집중하기 위해서, 보안상 안좋겠지만 
까다로운 방화벽이 있다면 미리 꺼두고, 그리고 파일 디렉토리 접근 퍼미션은 
대충ㅜㅜ 했습니다. 
그리고, 아래 명령들은 마스터 노드와 노동자노드를 각각 왔다가 갔다하면서 실행하는데
구분은, 프롬프트의 master 와 node1 단어를 보고 잘 구분해서 실행하시길.
docker-node2, docker-node3 등으로 계속 새로운 노드를 추가시 
아래 node1의 작업을 동일하게 합니다.



1. 사전 작업

우선 docker-master에서 DB 서버와 웹서버가 마운트할 디렉토리를 생성합니다.
root 권한으로 해야해서 sudo 로 합니다.
nonots@docker-master:~$ sudo mkdir /home/mysql_data
nonots@docker-master:~$ sudo mkdir -p /home/gnuboard_data/data
nonots@docker-master:~$ sudo chmod 707 /home/mysql_data/
nonots@docker-master:~$ sudo chmod 707 /home/gnuboard_data/
nonots@docker-master:~$ sudo chmod 707 /home/gnuboard_data/data/
그리고 그누보드5 소스 전체를 여기에 복사해서 gnuboard_data에 넣어 줍니다.

docker-node1 에서는 db서버 볼륨은 필요 없으므로 gnuboard_data 만
생성합니다.
nonots@docker-node1:~/mydocker$ sudo mkdir /home/gnuboard_data
nonots@docker-node1:~/mydocker$ sudo chmod 707 /home/gnuboard_data
그리고 docker-node1 의 /home/gnuboard_data 는 마스터에서 마운트하므로
항상 빈디렉토리이어야 합니다.

그리고 마스터에서 nfs 서버를 설치합니다.
nonots@docker-master:~$ sudo apt-get install nfs-common nfs-kernel-server rpcbind portmap

그리고 /etc/exports 파일 제일 하단에 아래 1 줄 추가합니다.
/home/gnuboard_data 192.168.219.184(rw,sync,no_subtree_check)

192.168.219.184(docker-node1)에서 /home/gnuboard_data 를 마운트 가능하게 
하다는 뜻인데, 만약 node2, node3 와 같이 늘어난다면, 그 노드 아이피도 같이
동일하게 추가하면 됩니다.

nonots@docker-master:~$ sudo exportfs -a
nonots@docker-master:~$ sudo systemctl restart nfs-kernel-server
그후 위와 같이 해서 nfs-server 를 실행합니다.
이 nfs 는 111 와 2049 포트를 사용하는데, 추후 방화벽 실행시 이 포트를
열어 줘야 할겁니다.

그리고, 이제 docker-node1 에서는 nfs 클라이언트만 사용하므로
nonots@docker-node1:~$ sudo apt install nfs-common
nonots@docker-node1:~$ sudo mount -t nfs 192.168.219.160:/home/gnuboard_data /home/gnuboard_data
위와 같이 docker-master(192.168.219.160) 에서 gnuboard_data 폴더를
nfs 로 마운트합니다.
만약, 서버 재시작시 자동으로 마운트 되게 하려면 /etc/fstab 에 지정해야 하는데,
그 방법은 알아서 각자.

이렇게 하면
nonots@docker-node1:~$ cd /home/gnuboard_data/
nonots@docker-node1:/home/gnuboard_data$ ls 
하면 그누보드 전체소스가 보여야 정상.

그리고 나서, 모든 서버에서 도커데몬이 정상적으로 실행되어 있는지 확인합니다.



2. 사용할 도커 이미지 생성

웹서버는 아파치2.4에 php가 모듈로 지원되고, php-mysqli php-gd 같은 php 모듈도 사용 하기위해,
hub.docker.com 에 있는 php:7.4-apache 라는 이미지를 불러와서 간단하게 다시 빌드를 했습니다.
이렇게 빌드한걸 hub.docker.com 에 nonots/gnudocker:v1 이라는 이름으로 등록을 해두었습니다.
https://hub.docker.com/r/nonots/gnudocker
이제 docker pull nonots/gnudocker:v1 이라는 명령어로 어디서나 원격으로 사용가능하게 했습니다.

만약, 본인이 새로 이미지를 만들겠다면, 아래 Dockerfile 을 적절하게 수정해서
빌드를 해서 로컬에서 사용하면 됩니다.
그런데, 문제는 이렇게 로컬에 빌드하려면 master뿐만 아니라 다른 노드에도 모두 
동일 이미지로 빌드를 해야 웹서버를 복제시 정상 작동하게 됩니다.

nonots@nonots-master:~/mydocker$ cat Dockerfile 
ROM php:7.4-apache
MAINTAINER nonots
RUN apt update && apt -y install libfreetype6-dev  libjpeg62-turbo-dev zlib1g-dev libpng-dev \
 && docker-php-ext-configure gd --with-freetype --with-jpeg \
 && docker-php-ext-install -j$(nproc) gd mysqli

위와 같이 3줄짜리 Dockerfile 를 간단하게 만듭니다. 
그런후 아래와 같이  nonots/gnudocker:v1 이라는 이미지 이름으로 빌드를 합니다.

nonots@nonots-master:~/mydocker$ docker build . -t nonots/gnudocker:v1

빌드가 성공하면
nonots@nonots-master:~/mydocker$ docker image ls
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
nonots/gnudocker        v1                  78566041ec11        13 seconds ago      435MB

와 같이 이미지 목록에 보입니다.



3. docker swarm 모드 실행

우선 docker-master 에서 docker swarm init  명령어를 아래와 같이 실행합니다.

nonots@docker-master:~/mydocker$ docker swarm init --advertise-addr 192.168.219.160
Swarm initialized: current node (j0m1pxp35cgd9ylu3aaaa3t2j) is now a manager.

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

    docker swarm join --token SWMTKN-1-4m5w9f7sar3xq5xpx0lomsrveqqbm5qda723mi24i2rn8u0ebp-ebuk5n1rcxr21a0ddaln4c7dl 192.168.219.160:2377

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

그러면 위 내용에서 토큰값을 사용하는  docker swarm join 명령줄이 나오는데, 이 명령어를 그대로
복사해서 각 노동자 노드에 실행하면 됩니다.
이 부분은 복사해서 저장해 두는게 좋습니다.

docker-node1 에서 아래와 같이 실행하면, 그 노드는 swarm 패밀리에 포함됩니다.
nonots@docker-node1:~$ docker swarm join --token SWMTKN-1-4m5w9f7sar3xq5xpx0lomsrveqqbm5qda723mi24i2rn8u0ebp-ebuk5n1rcxr21a0ddaln4c7dl 192.168.219.160:2377
This node joined a swarm as a worker.

마스터에서 노드 상태를 보면 아래와 같이 되어 있으면 정상입니다.
이제 본격적으로 로드밸런싱을 사용할수 있습니다.
nonots@docker-master:~/mydocker$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
j0m1pxp35cgd9ylu3aaaa3t2j *   docker-master       Ready               Active              Leader              19.03.6
l0taqdgxz2bfcw65u8zqxbme2     docker-node1        Ready               Active                                  19.03.8



3. 각 서비스 생성

우선 mysql DB 서비스(mymysql)를 생성 실행합니다. 
이건 항상 제일먼저 생성해서 마스터 노드에서만 컨테이너가 위치하게 해야함.
nonots@docker-master:~$ docker service create --name mymysql --replicas 1 --constraint 'node.role==manager' --mount type=bind,src=/home/mysql_data,dst=/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root2349 -e MYSQL_USER=nonots -e MYSQL_DATABASE=nonots_db -e MYSQL_PASSWORD=kwon2349 -p 3306:3306 -d mysql:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

nonots@docker-master:~$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
1kv5g06zn64z        mymysql             replicated          1/1                 mysql:latest        *:3306->3306/tcp

nonots@docker-master:~/mydocker$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                 NAMES
07e9a16b4bab        mysql:latest        "docker-entrypoint.s…"   8 seconds ago       Up 7 seconds        3306/tcp, 33060/tcp   mymysql.1.n6cdjbkvl0xeizy32ciwg9pl1

생성한 후 아래 명령어로 컨테이너 ID 값을 이용해서 mysql 접속시 mysql 프롬프트가 뜨면 정상입니다.
nonots@docker-master:~$ docker container exec -it 07e9a16b4bab mysql -unonots -pkwon2349 nonots_db

그 다음에 아파치 웹서버 서비스(mygnu)를 생성 실행하는데 복제 갯수를 (--replicas 3) 3개
만들게 했습니다.

nonots@docker-master:~$ docker service create --name mygnu --replicas 3 --mount type=bind,src=/home/gnuboard_data,dst=/var/www/html -p 8888:80  -d nonots/gnudocker:v1

이렇게 2 개 서비스를 생성하면 아래와 같이 서비스가 보여야 합니다.
nonots@docker-master:~$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                 PORTS
hu2sel7mozt2        mygnu               replicated          3/3                 nonots/gnudocker:v1   *:8888->80/tcp
tgh1jvrqascz        mymysql             replicated          1/1                 mysql:latest          *:3306->3306/tcp

그리고 그중에서 웹서버 mygnu 는 아래와 같이 master 에 1개, node1 에 2 개가 생성되어 있습니다 
nonots@docker-master:~$ docker service ps mygnu
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
knmz2tgjwkxo        mygnu.1             nonots/gnudocker:v1   docker-master       Running             Running about a minute ago                       
ax7unskk8an0        mygnu.2             nonots/gnudocker:v1   docker-node1        Running             Running about a minute ago                       
7m9antdw76yi        mygnu.3             nonots/gnudocker:v1   docker-node1        Running             Running about a minute ago                       

docker-master 에서 컨테이너를 보면 mygnu 1개와 mymysql 1개가 보이고
nonots@docker-master:~$ docker container ls
CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                 NAMES
81e4fb7f162f        nonots/gnudocker:v1   "docker-php-entrypoi…"   About a minute ago   Up About a minute   80/tcp                mygnu.1.knmz2tgjwkxorhj2dt5zsyzq6
8d48664abbbb        mysql:latest          "docker-entrypoint.s…"   2 minutes ago        Up 2 minutes        3306/tcp, 33060/tcp   mymysql.1.xk40f7u8n0ou83cw95pg5fkhq

docker-node1 서버에서는 mygnu 2개가 보일겁니다.
nonots@docker-node1:~$ docker container ls
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
6cd8f31238b1        nonots/gnudocker:v1   "docker-php-entrypoi…"   2 minutes ago       Up About a minute   80/tcp              mygnu.3.7m9antdw76yi2h362jy52rtzq
d2a1e7cf43cd        nonots/gnudocker:v1   "docker-php-entrypoi…"   2 minutes ago       Up About a minute   80/tcp              mygnu.2.ax7unskk8an0mk3fuktr1qkzh

이제 웹브라우저에서 
http://192.168.219.160:8888 (http://192.168.219.184:8888로 해도 됨)으로 접속하면
그누보드 설치화면이 뜨게 됩니다.
Mysql DB 정보 입력에서  Host 주소는 기본 localhost 가 아니라 
mymysql 혹은 마스터IP 즉 192.168.219.160  둘 다 해도 되더군요.
게시판에 글을 몇개 등록해 봅니다.

이렇게 일단 웹서버가 작동하는건 알겠는데, 과연 로드밸런싱이 되는지 확인하려면
마스터 /home/gnuboard_data 디렉토리에 간단하게 아래와 같이 lb.php 를 만듭니다.
nonots@docker-master:~$ cat /home/gnuboard_data/lb.php 


그리고  웹브라우저에서 http://192.168.219.160:8888/lb.php 를 실행합니다.
시간이 지나서 새로고침을 해보고, 다른 브라우저로 위 주소를 열어 봅니다.
터미널에서 curl 로 테스트를 해보면
아래와 같이 실행시 마다 다른 웹서버를 연결함을 알수 있습니다.
즉 URL 은 동일하게 보여도, 도커 스웜에서 라운드로빈 방식으로 로드밸런스를
하고 있어서 특정 접속마다 자동으로 다른 웹서버에 접속이 됩니다.

nonots@nonots-desktop:~$ curl http://192.168.219.184:8888/lb.php
6cd8f31238b1 - 192.168.219.184 - 10.0.0.109
nonots@nonots-desktop:~$ curl http://192.168.219.184:8888/lb.php
d2a1e7cf43cd - 192.168.219.184 - 10.0.0.110
nonots@nonots-desktop:~$ curl http://192.168.219.184:8888/lb.php
81e4fb7f162f - 192.168.219.184 - 10.0.0.108
nonots@nonots-desktop:~$ curl http://192.168.219.184:8888/lb.php
6cd8f31238b1 - 192.168.219.184 - 10.0.0.109

이제 복제 갯수 늘리거나 줄여보겠습니다.  기존 3개에서 5개로 늘리면
nonots@docker-master:~$ docker service scale mygnu=5
mygnu scaled to 5
overall progress: 5 out of 5 tasks 
1/5: running   [==================================================>] 
2/5: running   [==================================================>] 
3/5: running   [==================================================>] 
4/5: running   [==================================================>] 
5/5: running   [==================================================>] 
verify: Service converged 
nonots@docker-master:~$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                 PORTS
hu2sel7mozt2        mygnu               replicated          5/5                 nonots/gnudocker:v1   *:8888->80/tcp
tgh1jvrqascz        mymysql             replicated          1/1                 mysql:latest          *:3306->3306/tcp

그런후에 각 노드에서 어떻게 웹서버가 분산되어 있는지 보면
아래와 같이 마스트에 2개, node1 에 3개로 각각 분산되어 추가됨을
알수 있습니다.

nonots@docker-master:~$ docker node ps docker-master
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
knmz2tgjwkxo        mygnu.1             nonots/gnudocker:v1   docker-master       Running             Running 12 minutes ago                           
xk40f7u8n0ou        mymysql.1           mysql:latest          docker-master       Running             Running 13 minutes ago                           
xyt8rdnbos5g        mygnu.4             nonots/gnudocker:v1   docker-master       Running             Running about a minute ago                       
nonots@docker-master:~$ docker node ps docker-node1
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
ax7unskk8an0        mygnu.2             nonots/gnudocker:v1   docker-node1        Running             Running 12 minutes ago                           
7m9antdw76yi        mygnu.3             nonots/gnudocker:v1   docker-node1        Running             Running 12 minutes ago                           
6jozbsc02pz0        mygnu.5             nonots/gnudocker:v1   docker-node1        Running             Running about a minute ago                       


4. yaml 파일을 이용한 서비스 실행
이제 위에서 실행한 mygnu mymysql 서비스를 삭제해보겠습니다.
nonots@docker-master:~$ docker service rm mygnu mymysql

너무 간단해서 허무하게 모든 컨테이너가 삭제되어 사라집니다. 
그누보드도 사라졌습니다.
다시 시작하기 위해 위에서 실행한 docker service create 의 기다란 명령어를 
실행해되 되지만, 대신
이번에는 yaml 파일을 이용해서 한 큐에 해보겠습니다

nonots@docker-master:~/mydocker$ cat stack.yaml 
version: "3"
services:
  mymysql:
    image: mysql:latest
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "root2349"  # MYSQL root패스워드 설정 옵션
      MYSQL_USER: "nonots" # 그누보드 사용자
      MYSQL_DATABASE: "nonots_db" # 그누보드 사용 DB
      MYSQL_PASSWORD: "kwon2349" #그누보드 비번
    command: # 명령어 실행
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    volumes:
      - "/home/mysql_data:/var/lib/mysql"
    deploy:
      replicas: 1
      placement:
        constraints:
          - "node.role==manager"

  mygnu:  #apache php 웹서버
    image: nonots/gnudocker:v1
    ports:
      - "8888:80"
    volumes:
      - "/home/gnuboard_data:/var/www/html"
    deploy:
      replicas: 3


위와 같은 stack.yaml 파일을 만든 후
아래와 같이 docker stack deploy 명령어로 sir 이라는 이름의 스택을 생성합니다.

nonots@docker-master:~/mydocker$ docker stack deploy -c ./stack.yaml sir
Creating network sir_default
Creating service sir_mymysql
Creating service sir_mygnu

그러면, 자동으로 서비스명이 sir_ 가 붙어서 실행됩니다. 
(주의:만약 그노보드 초기 설치라면 mysql host 란에 sir_mymysql 을 입력)

nonots@docker-master:~/mydocker$ docker stack ls
NAME                SERVICES            ORCHESTRATOR
sir                 2                   Swarm
nonots@docker-master:~/mydocker$ docker stack ps sir
ID                  NAME                IMAGE                 NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
i174wfc3e0z9        sir_mygnu.1         nonots/gnudocker:v1   docker-node1        Running             Running 7 seconds ago                        
jqhx57uy58h5        sir_mymysql.1       mysql:latest          docker-master       Running             Running 18 seconds ago                       
6g30kt5bmt5n        sir_mygnu.2         nonots/gnudocker:v1   docker-node1        Running             Running 8 seconds ago                        
w4slrrr5d0cs        sir_mygnu.3         nonots/gnudocker:v1   docker-master       Running             Running 14 seconds ago                       
nonots@docker-master:~/mydocker$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                 PORTS
6wybc9w12gfw        sir_mygnu           replicated          3/3                 nonots/gnudocker:v1   *:8888->80/tcp
qss522ijv5rp        sir_mymysql         replicated          1/1                 mysql:latest          *:3306->3306/tcp

위와 같이 웹서버(sir_mygnu) 3개와 sir_mymysql 1개가 실행되고,
웹브라우저로 접속하면 기존 설치한 그누보드 정보와 DB 정보가 그대로 남아있음을
알게 됩니다.

만약, 서비스 종료시에는 
nonots@docker-master:~$ docker stack rm sir
Removing service sir_mygnu
Removing service sir_mymysql
Removing network sir_default
와 같이 역시 한번에 모든 서비스가 종료 됩니다.

클라우드 환경이 만들고 있는 이런 마이크로서비스 세계의 가벼움에서 
민첩함과 허망함이 섞여 느껴집니다.