땡글이LAB

[도커] 스웜 모드(Swarm mode) - 서비스(create, replicated/global) 본문

Devops/도커, 쿠버네티스

[도커] 스웜 모드(Swarm mode) - 서비스(create, replicated/global)

땡글이B 2022. 8. 30. 20:42

 이전 포스팅에서 계속 사용해온 도커 명령어의 제어 단위는 컨테이너였다. docker run 명령어는 컨테이너를 생성하고, docker rm 명령어는 컨테이너를 삭제했던 것처럼 도커 클라이언트에서 사용하는 명령어가 제어하는 것은 컨테이너이다. 

 

 하지만, 스웜 모드에서 제어하는 단위는 컨테이너가 아닌 서비스(Service)이다!

 

 서비스(Service)는 같은 이미지에서 생성된 컨테이너의 집합이며, 서비스를 제어하면 해당 서비스 내의 컨테이너에 같은 명령이 수행된다. 서비스 내에 컨테이너는 1개 이상 존재할 수 있으며, 컨테이너들은 각 워커 노드와 매니저 노드에 할당된다. 이러한 컨테이너들은 '태스크(Task)'라고 한다.  

 

 스웜 스케줄러(Scheduler)는 서비스의 정의에 따라 컨테이너를 할당할 적합한 노드를 선정하고, 해당 노드에 컨테이너를 분산해서 할당한다. 위의 그림은 각 노드에 컨테이너가 하나씩 할당된 경우를 보여주지만 반드시 각 노드에 하나씩 할당되지 않을 수 있다.

 

이처럼 함께 생성된 컨테이너를 레플리카(replica)라고 하며, 서비스에 설정된 레플리카의 수만큼의 컨테이너가 스웜 클러스터 내에 존재해야 한다.

 

 

 스웜은 서비스의 컨테이너들에 대한 상태를 계속 확인하고 있다가 서비스 내에 정의된 레플리카의 수만큼 컨테이너가 스웜 클러스터에 존재하지 않으면 새로운 컨테이너 레플리카를 생성한다. 아래의 그림에서 컨테이너가 할당된 노드가 다운되면 매니저는 사용 가능한 다른 노드에 같은 컨테이너를 생성한다. 서버가 다운되지 않더라도 서비스 내의 컨테이너 중 일부가 작동을 멈춰 정지한 상태로 있다면 이 또한 레플리카의 수를 충족하지 못하는 것으로 판단해 스웜 매니저는 새로운 컨테이너를 클러스터에 새롭게 생성한다. 

 

 서비스는 롤링 업데이트(Rolling update) 기능도 제공한다. 서비스 내 컨테이너들의 이미지를 일괄적으로 업데이트해야 할 때, 컨테이너들의 이미지를 순서대로 변경해 서비스 자체가 다운되는 시간 없이 컨테이너의 업데이트를 진행할 수 있다.

 

서비스 생성

 서비스를 제어하는 도커 명령어는 전부 매니저 노드에서만 사용할 수 있다. 따라서 다음 예제는 모두 매니저 노드에서 입력한다는 전제 하에 진행한다. 

 

 서비스를 사용하기 위한 명령어는 "docker service"로 시작한다. 서비스를 생성하려면 docker service create 명령어를 사용한다. 아래의 스크립트는 ubuntu:14.04 이미지로 서비스 내의 컨테이너를 생성하며 컨테이너가 시작할 때 실행할 명령어로 'hello world'를 출력하는 셸 명령어이다.

$ docker service create \
ubuntu:14.04 \
/bin/sh -c "while true; do echo hello world; sleep 1; done"

 

 서비스 내의 컨테이너는 detached 모드로, 즉 docker run 명령어의 -d 옵션을 사용해 동작할 수 있는 이미지를 사용해야 한다. 위 스크립트 예제는 우분투 컨테이너 내에서 계속 'hello world'를 출력하기 때문에 컨테이너가 서비스로서 정상적으로 동작한다. 그러나 다음과 같이 서비스를 생성하면 컨테이너 내부를 차지하고 있는 프로세스가 없어 컨테이너가 정지될 것이고, 스웜 매니저는 서비스의 컨테이너에 장애가 생긴 것으로 판단해 컨테이너를 계속 반복해서 생성할 것이다.

 

 서비스를 생성했다면, 서비스의 목록을 확인해 방금 생성한 서비스가 정상적으로 구동하고 있는지 확인할 수 있다. 스웜 클러스터 내의 서비스 목록을 확인하는 명령어는 "docker service ls" 이다. 서비스 이름은 따로 정의하지 않았기에 서비스의 이름이 determined_raman으로 무작위로 설정된 것을 알 수 있다.

 

 서비스의 자세한 정보를 확인하려면 docker service ps [서비스 이름]과 같이 입력하면 된다. 이 명령어로 서비스 내의 컨테이너의 목록, 상태, 컨테이너가 할당된 노드의 위치를 알 수 있다. 

 

생성된 서비스를 삭제하려면 docker service rm 명령어를 입력한다. docker rm 명령어는 컨테이너가 실행 중이면 삭제할수 없던 것과 달리, docker service rm 명령어를 사용하면 서비스의 상태에 관계없이 서비스의 컨테이너를 바로 삭제한다.

 

// 서비스 목록 조회
$ docker service ls

// 특정 서비스의 자세한 정보 조회
$ docker service ps [서비스 이름]

// 서비스 삭제
$ docker service rm [서비스 이름]

 

Nginx 웹 서버 서비스 생성하기

 아래의 스크립트 코드는 2개의 레플리카 컨테이너를 정의하고, 서비스의 이름을 myweb으로 설정하며 컨테이너의 80번 포트를 각 노드의 80번 포트로 연결하는 서비스를 생성한다. 

 

$ docker service create --name myweb \
--replicas 2 \
-p 80:80 \
nginx

 docker service create 명령어도 docker run과 마찬가지로 도커 데몬에 이미지가 없다면 자동으로 pull하므로 시간이 조금 걸릴 수 있다. 컨테이너가 정상적으로 생성되면 스웜 클러스터 내의 노드 중 하나를 선택해 80번 포트로 접근해 Nginx 웹 서버가 구동되고 있는 것을 확인할 수 있다. 

 

 위 예시에서 각 nginx 컨테이너는 swarm-manager와 swarm-worker1 노드에 생성됐다. 그렇다고해서 꼭 두 노드의 IP 주소로 접근해야만 Nginx 웹 서버에 접근할 수 있는 것은 아니다. docker service create 명령어에서 -p 옵션에 80:80 을 입력함으로써 스웜 클러스터 자체에 포트를 개방했다고 생각하면 쉽게 이해할 수 있다. 스웜 클러스터 내의 어떠한 노드로 접근해도 위 서비스의 웹 서버에 접근할 수 있다. 이를 직접 확인해보기 위해 swarm-worker2 노드의 IP 주소로 접근해본다. 

 

 실제로 nginx가 구동된 서버는 manager 와 worker1 노드에서 구동되었지만, worker2 노드에서도 nginx컨테이너로 접속이 가능하다. 

AWS EC2 instance로 실험할 때에는, security group에서 UDP 포트를 열어줘야 앞서 말한 worker2 노드에서 nginx 컨테이너로 접근 가능하다. 
EC2 instance (worker1 노드를 포함하는 인스턴스의 security group)

 

 

만약 여기서 서비스 내의 Nginx 컨테이너를 4개로 늘리면 어떻게 될까? docker service scale 명령어를 이용하면 레플리카셋의 수를 늘리거나 줄일 수 있다.

$ docker service scale myweb=4

 

 출력의 NODE 항목을 보면, 이전에 manager, worker1 노드와는 달리 worker2 노드에 2개의 컨테이너가 할당된 것을 알 수 있다. 컨테이너가 각 컨테이너들이 호스트의 80번 포트에 연결된 것이 아니라 실제로는 각 노드의 80번 포트로 들어온 요청을 위 4개의 컨테이너 중 1개로 리다이렉트(redirect)하기 때문이다. 따라서 각 호스트의 어느 노드로 접근하든 4개의 컨테이너 중 1개에 접근하게 된다.

 

스웜 모드는 라운드 로빈(Round-Robin) 방식으로 서비스 내에 접근할 컨테이너를 결정한다. 각 노드의 트래픽이나 자원 사용량 등을 고려해 로드 밸런싱을 해야 한다면 이 방식은 적합하지 않을 수 있다.

 

Global 서비스 생성하기

 서비스의 모드는 두 가지가 있다. 하나는 위에서 생성한 Nginx 웹서버 서비스와 같이 레플리카셋의 수를 정의해 그만큼의 같은 컨테이너를 생성하는 복제 모드(replicated)로서 실제 서비스를 제공하기 위해 일반적으로 쓰이는 모드이다.

 다른 하나는 글로벌(global) 모드이다. 글로벌 서비스는 스웜 클러스터 내에서 사용할 수 있는 모든 노드에 컨테이너를 반드시 하나씩 생성한다. 따라서 글로벌 모드로 생성한 서비스는 레플리카셋의 수를 별도로 지정하지 않는다. 

 글로벌 서비스는 스웜 클러스터를 모니터링하기 위한 에이전트 컨테이너 등을 생성해야 할 때 유용하다. 

 

 글로벌 서비스는 다음과 같이 docker service create 명령어에 --mode global을 추가해 생성할 수 있다. --mode 옵션이 없으면 default로 복제 모드로 지정된다.

$ docker service create --name global_web \
--mode global \
nginx

 확인해보면 알 수 있듯이, 각 노드에 컨테이너가 하나씩 생성되어있다. 

 

스웜 모드의 서비스 장애 복구

 앞에서 이야기한 것처럼, 복제 모드로 설정된 서비스의 컨테이너가 정지하거나 특정 노드가 다운되면 스웜 매니저는 새로운 컨테이너를 생성해 자동으로 이를 복구한다. 위에서 생성한 myweb 서비스 중 컨테이너 하나를 삭제해보자.

 

 뭔가 새로운 컨테이너가 생성되었다. 추가적으로 3개의 노드 중 swarm-worker1 노드의 도커 데몬 프로세스를 종료해 임의로 노드 장애 상태를 만들어본다. 

(worker1 노드) $ service docker stop

manager 노드

 매니저 노드에서 docker node ls 명령어를 통해, worker1 노드의 상태가 Down으로 바뀐 것을 확인할 수 있다. docker service ps 명령어로 다시 컨테이너의 목록을 확인해보면 종료(shutdown)됐으며, 이를 복구하기 위한 컨테이너가 manager 노드에 생성되었음을 알 수 있다. [아래 이미지 참고!]

 

 

 다운됐던 노드를 다시 시작해 정상적인 상태를 회복해도 장애를 복구하기 위해 다른 노드로 옮겨진 컨테이너가 해당 노드에 자동으로 할당되지는 않는다. 위 예에서 worker1 노드에서 도커 엔진을 다시 시작해 컨테이너를 실행할 수 있는 환경을 복구해도 myweb.1 컨테이너가 다시 worker1 노드로 돌아가는 재균형(rebalance) 작업이 일어나지는 않는다는 뜻이다.

 즉, 새로운 노드를 추가하거나 다운됐던 노드를 다시 복구했을 때 서비스의 컨테이너 할당의 균형을 맞추기 위해서는 scale 명령어를 이용해 컨테이너의 수를 줄이고 다시 늘려야 한다.

// 복구 혹은 서비스 컨테이너 할당의 균형 맞추기 위한 프로세스
$ docker service scale myweb=1
$ docker service scale myweb=4

 

 

 

References

 

Comments