주요글: 도커 시작하기

스웜과 오버레이 네트워크

docker network ls 명령어는 네트워크 목록을 보여준다. 스웜에 스택을 배포했다면 범위가 swarm인 오버레이 네트워크를 볼 수 있다. 오버레이 네트워크는 서로 다른 노드에 생성된 컨테이너 간 연결을 처리한다.

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c6ef5b2ea943        bridge              bridge              local
da6c7575bf20        docker_gwbridge     bridge              local
b30ace236245        host                host                local
c8c1e9h5214o        ingress             overlay             swarm
vwes54i1ysyh        simple_default      overlay             swarm

오버레이 네트워크 중 ingress 네트워크는 호스트(노드)에서 서비스로의 포워딩을 담당한다. 외부에 포트를 공개한 서비스를 스웜에 배포하면 ingress 네트워크에 서비스를 참여시키고 다음의 두 IP를 할당한다.

  • 서비스에 대한 가상 IP
  • 서비스의 각 컨테이너에 대한 IP

각 노드는 외부에 개시된 서비스 포트로 요청이 오면 ingress 네트워크의 가상 IP로 전달한다. 가상 IP는 ingress 네트워크에서 서비스에 접근할 때 사용할 IP이다. 가상 IP에 전달된 요청은 다시 컨테이너의 IP로 전달된다. ingress 네트워크에 참여한 서비스의 각 컨테이너는 ingress 네트워크 내에서 고유 IP를 갖는다.

docker network inspect ingress 명령어 실행하면 ingress 네트워크 내에 생성된 서비스의 VIP와 각 컨테이너에 할당된 IP를 확인할 수 있다.

스웜에 스택을 배포하면 스택을 위한 오버레이 네트워크도 생긴다. 다음 컴포즈 파일을 보자.

version: "3.6"
services:
  mysql:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=rootpw
    deploy:
      replicas: 1
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    deploy:
      replicas: 1

이 파일에는 네트워크 설정이 없다. 이 경우 스웜은 스택을 위한 네트워크를 생성한다. 이때 네트워크 이름은 "스택명_default"가 된다. 예를 들어 위 설정을 이용해서 이름이 dbadmin인 스택을 배포하면 dbadmin_default인 네트워크를 생성한다.

스택을 위한 오버레이 네트워크는 스택에 속한 서비스가 서로 통신할 때 사용된다. 위 설정의 경우 mysql 서비스와 adminer 서비스가 서로 통신할 때 스택을 위한 오버레이 네트워크를 사용하게 된다.

오버레이 네트워크와 서비스

default 이름 대신에 직접 네트워크 이름을 지정할 수 있다. 컴포즈 파일에 networks 키를 이용해서 사용할 네트워크를 지정하면 된다. 다음은 설정 예이다.

version: "3.6"
services:
  mysql:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=rootpw
    networks:
      internal:
        aliases:
          - db
    deploy:
      replicas: 1
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    networks:
      - internal
    deploy:
      replicas: 1

networks:
  internal:

가장 하단에 networks 키는 네트워크 이름 목록을 값으로 갖는다. 위 설정에서는 이름이 internal인 네트워크만 설정했다. 여기서 internal은 오버레이 네트워크다.

서비스 설정에 networks 키 값으로 internal을 추가하면 해당 서비스는 internal 네트워크에 묶인다. 위 설정은 mysql 서비스와 adminer 서비스를 둘 다 internal 네트워크에 연결했다.

aliases는 네트워크 내에서 서비스를 참조할 때 사용할 별칭을 추가한다. mysql 설정은 db를 별칭으로 추가했다. 같은 네트워크를 사용하는 서비스는 서비스 이름과 별칭을 사용해서 다른 서비스에 연결할 수 있다. 위 설정에서 adminer 서비스의 컨테이너는 'mysql'이나 'db'를 이용해서 mysql 서비스에 연결할 수 있다. 실제 서비스 이름인 '스택명_mysql'로도 접근할 수 있다. 물론 다른 네트워크에서는 이 이름들로 접근할 수 없다.

외부 오버레이 네트워크 사용

각 스택의 서비스를 같은 네트워크에 참여시키고 싶다면 오버레이 네트워크를 스웜 수준에서 생성하고 스택에서 이 네트워크를 참조하면 된다. 오버레이 네트워크를 생성할 때는 -d (--driver) 옵션 값으로 overlay를 지정하면 된다.

$ docker network create -d overlay service-net

컴포즈 파일에서는 external 옵션을 true로 지정해서 해당 네트워크가 스택 외부 자원임을 명시한다.

version: "3.6"
services:
  mysql:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=rootpw
    networks:
      service-net:
        aliases:
          - db
    deploy:
      replicas: 1
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    networks:
      service-net:
        aliases:
          - web
    deploy:
      replicas: 1

networks:
  service-net:
    external: true

위 설정을 이용해서 스택을 생성하면 스택의 서비스는 service-net 네트워크에 참여한다. service-net에 참여하는 서비스는 서비스 이름과 alias를 이용해서 각 서비스에 연결할 수 있다.

 

 

 

 

서비스 생성과 리플리케이션

도커 스웜 클러스터를 만들었다면 서비스를 생성할 수 있다. 서비스를 만드는 것은 어렵지 않다. 매니저 노드에서 docker create service 명령어를 실행하면 된다. 서비스를 생성할 때 --replicas 옵션을 사용해서 생성할 컨테이너 개수를 지정한다.

docker service create --name simple \
--publish published=8000,target=5000 \
--replicas 2 \
madvirus/simplenode:0.1

서비스를 생성한 뒤 "docker service ps 서비스명" 명령어를 실행하면 각 컨테이너가 어느 도커 노드에서 실행 중인지 확인할 수 있다.

vagrant@docker-node1:~$ docker service ps simple
ID                  NAME                IMAGE                     NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
lecwvwjcs7lm        simple.1            madvirus/simplenode:0.1   docker-node2        Running             Running 5 minutes ago
wap8ua3proil        simple.2            madvirus/simplenode:0.1   docker-node3        Running             Running 6 minutes ago

결과에서 NAME은 컨테이너의 완전한 이름이 아닌 서비스 내에서 구분하기 위한 이름이다. 실제 컨테이너 이름은 simple.2.wap8ua3proil1aqircsy7tifq와 같이 NAME 뒤에 ID를 결합한 문자열을 사용한다.

docker servie scale 명령어를 사용하면 운영 중에 리플리케이션 개수를 변경할 수 있다.

$ docker service scale simple=4
simple scaled to 4
overall progress: 4 out of 4 tasks
1/4: running   [==================================================>]
2/4: running   [==================================================>]
3/4: running   [==================================================>]
4/4: running   [==================================================>]
verify: Service converged

$ docker service ps simple
ID                  NAME                IMAGE                     NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
lecwvwjcs7lm        simple.1            madvirus/simplenode:0.1   docker-node2        Running             Running 18 minutes ago
wap8ua3proil        simple.2            madvirus/simplenode:0.1   docker-node3        Running             Running 19 minutes ago
v90ftwuywm0d        simple.3            madvirus/simplenode:0.1   docker-node1        Running             Running about a minute ago
674upghwilm2        simple.4            madvirus/simplenode:0.1   docker-node1        Running             Running about a minute ago

docker service ps 명령어를 실행하면 simple 서비스의 컨테이너 개수가 4개가 된 것을 확인할 수 있다. 개수를 늘리는 것 뿐만 아니라 줄이는 것도 가능하다.

$ docker service scale simple=1
simple scaled to 1
overall progress: 1 out of 1 tasks
1/1: running   [==================================================>]
verify: Service converged

$ docker service ps simple
ID                  NAME                IMAGE                     NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
lecwvwjcs7lm        simple.1            madvirus/simplenode:0.1   docker-node2        Running             Running 20 minutes ago

리플리케이션 개수를 줄일 때는 가장 최근에 생성한 컨테이너를 먼저 중지한다.

서비스 제거

서비스 제거는 간단하다. "docker service rm 서비스명" 명령어를 사용하면 된다. 서비스를 제거하면 해당 컨테이너도 함께 삭제된다.

simplenode:0.2 버전

롤링 업그레이드와 헬스 체크를 테스트하기 위해 madvirus/simplenode:0.2 버전 이미지를 새로 추가했다. 이 이미지는 다음의 app.js를 실행한다.

const http = require('http');
const url = require('url');

const server = http.createServer().listen(5000);
let slow = false;

server.on('request', (req, res) => {
    console.log('request arrived.');
    const query = url.parse(req.url, true).query;
    if (query.stop === 'true') {
        res.write("Process down: " + process.env.HOSTNAME);
        res.end();
        setTimeout((arg) => { process.exit(1); }, 1000, "shutdown");
    } else if (query.slow === 'true') {
        slow = true;
        res.write("Health check slowdown");
        res.end();
    } else if (req.url === '/health') {
        if (slow) {
            setTimeout((arg) => { res.write("SLOW"); res.end(); }, 5000, "slow");
        } else {
            res.write("OK");
            res.end();
        }
    } else {
        res.write("Hello V0.2: " + process.env.HOSTNAME);
        res.end();
    }
});

server.on('connection', (socket) => {
    console.log("Connected");
});

다음은 이 app.js의 주요 내용이다.

  • 헬스체크를 위한 /health 경로를 처리한다.
    • slow 값이 true면 /health 응답을 5초 뒤에 한다. 이것으로 헬스체크 실패를 흉내낸다.
    • slow 기본 값은 false이다.
    • slow 파라미터가 'true'면 slow를 true로 바꾼다.
  • stop 파라미터가 'true'면 1초 뒤에 프로세스를 중지한다.

롤링 업그레이드

madvirus/simplenode:0.1 버전을 이용한 서비스를 madvirus/simplenode:0.2 버전으로 업그레이드해보자. docker service update 명령어를 사용하면 된다. 다음은 실행 예이다.

$ docker service update \
--update-parallelism 1 \
--update-delay 10s \
--update-order start-first \
--image madvirus/simplenode:0.2 \
simple

업데이트 관련 주요 옵션은 옵션은 다음과 같다.

  • --update-parallelism : 동시에 업데이트할 컨테이너 개수를 지정한다.
  • --update-delay : 업데이트 간 간격을 지정한다.
  • --update-order : start-first면 새 컨테이너를 먼저 생성한 뒤에 기존 컨테이너를 삭제한다. stop-first면 기존 컨테이너를 먼저 삭제하고 그 다음에 새 컨테이너를 생성한다.
  • --update-failure-action : 업데이트에 실패할 경우 이 값이 pause면 업데이트를 멈추고, continue면 업데이트를 계속하고, rollback이면 업데이트를 롤백한다.
  • --update-max-failure-ratio : 실패 비율이 지정한 값 이상이면 업데이트 실패로 간주한다.

서비스 업데이트 후에 docker service ps 명령어로 서비스를 조회하면 다음과 같이 simplenode:0.1 버전은 중지되고 simplenode:0.2 버전이 실행 중인 것을 확인할 수 있다.

vagrant@docker-node1:/vagrant/sample/simplenode$ docker service ps simple
ID                  NAME                IMAGE                     NODE                DESIRED STATE       CURRENT STATE             ERROR               PORTS
sqyy4m1846qu        simple.1            madvirus/simplenode:0.2   docker-node3        Running             Running 35 seconds ago
78c9glgwxr01         \_ simple.1        madvirus/simplenode:0.1   docker-node2        Shutdown            Shutdown 31 seconds ago
1zynj40j8a5v        simple.2            madvirus/simplenode:0.2   docker-node2        Running             Running 19 seconds ago
z0pz24xqpmna         \_ simple.2        madvirus/simplenode:0.1   docker-node1        Shutdown            Shutdown 15 seconds ago

상태 유지와 헬스 체크

simple 서비스를 생성할 때 리플리카의 개수를 2로 지정했다. 도커 스웜은 simple 서비스의 컨테이너 개수를 2개로 유지하기 위해 노력한다. 예를 들어 컨테이너 한 개가 종료되면 새로운 컨테이너를 구동해서 정상 동작하는 컨테이너 개수를 맞춘다.

실제로 컨테이너 한 개를 종료해보자. http://192.168.1.101:8000/?stop=true을 요청하면 컨테이너 중 한 개가 종료된다. 컨테이너가 종료되면 도커 스웜은 새로운 컨테이너를 바로 생성해서 컨테이너 개수를 2로 맞춘다.

헬스 체크 옵션을 주면 컨테이너가 정상 상태가 아닐 때 컨테이너를 중지하고 새로운 컨테이너를 생성할 수 있다. 헬스 체크 관련 옵션을 사용하면 생성한 컨테이너가 정상 상태인지 주기적으로 확인한다. 도커 스웜은 컨테이너의 상태를 주기적으로 확인해서 정상이 아니면 컨테이너를 제거하고 리플리케이션 개수에 맞게 새로 생성한다. 헬스 체크와 관련된 옵션은 --health로 시작하며 다음은 주요 옵션의 사용 예를 보여준다.

docker service create \
--name simple \
--publish published=8000,target=5000 \
--health-cmd 'curl -f http://localhost:5000/health' \
--health-timeout=3s \
--health-retries=3 \
--health-interval=10s \
--health-start-period=10s \
--replicas=2 \
madvirus/simplenode:0.2

주요 옵션은 다음과 같다.

  • --health-cmd : 정상 상태인지 확인할 때 사용할 명령어를 입력한다. 이 명령어는 컨테이너 내에서 실행된다.
  • --health-timeout : 명령어의 실행 시간 제한을 지정한다. 이 시간 내에 명령어 실행이 끝나면 정상이 아닌 것으로 판단한다.
  • --health-retries : 지정한 횟수만큼 연속해서 실패하면 정상이 아닌 것으로 간주한다.
  • --health-interval : 헬스 체크를 실행할 주기를 지정한다.
  • --health-start-period : 컨테이너 시작 후 지정한 시간 동안은 헬스 체크에 실패해도 실패로 간주하지 않는다.

헬스 체크가 잘 되는지 확인해보자. 두 개의 컨테이너가 떠 있다고 가정하고 http://192.168.1.101:8000/?slow=true 요청을 보내자. 앞서 app.js 코드를 보면 slow 파라미터 값이 true이면 /health 요청의 응답 시간을 5초로 바꾼다. 헬스 체크 제한 시간은 3초이므로 이 URL을 실행하면 컨테이너의 헬스체크에 실패한다.

헬스 체크 간격이 10초이고 3회 시도하므로 잠시 뒤에 simple service ps simple 명령어로 컨테이너 목록을 보자. 한 컨테이너가 실패 상태에 있고(Failed) 이를 대체할 새로운 컨테이너를 준비한 것을 확인할 수 있다.

$ docker service ps simple
ID                  NAME                IMAGE                     NODE                DESIRED STATE       CURRENT STATE                   ERROR                              PORTS
qzxmt802h7b9        simple.1            madvirus/simplenode:0.2   docker-node2        Running             Running 2 minutes ago
drmlj2bh7ltw        simple.2            madvirus/simplenode:0.2   docker-node3        Ready               Ready less than a second ago
cdumn8jafb56         \_ simple.2        madvirus/simplenode:0.2   docker-node3        Shutdown            Failed less than a second ago   "task: non-zero exit (143): do…"

서비스와 호스트 연결

서비스를 생성할 때 --publish (또는 -p) 옵션을 사용하면 호스트의 포트에 서비스의 포트를 연결해서 외부에 서비스를 개시할 수 있다.

docker service create \
--name simple \
--publish published=8000,target=5000 \
--replicas=2 \
madvirus/simplenode:0.2

스웜에서 외부에 서비스를 개시하면 스웜에 참여하는 모든 노드가 서비스에 대한 요청을 처리한다. 위 코드는 호스트의 8000 포트를 이용해서 서비스를 개시했는데 이 경우 스웜에 참여한 모든 노드는 8000 포트로 오는 요청을 서비스의 5000 포트로 전달한다. 호스트로 오는 요청을 실제 컨테이너로 전달하는 것은 ingress 네트워크를 통해 처리되는데 이에 대한 내용은 뒤에서 다시 살펴본다.

모드 종류

도커 서비스는 리플리케이션과 글로벌의 두 모드가 있다. 서비스를 생성할 때 --mode 옵션을 이용해서 모드를 지정할 수 있다. 모드에는 다음 두 값이 올 수 있다.

  • replicated : 기본 값으로 지정한 개수만큼 컨테이너를 생성한다.
  • global : 각 노드마다 한 개의 컨테이너를 생성한다.
https://docs.docker.com/engine/reference/commandline/service_create/ 문서에서 더 많은 서비스 관련 옵션을 확인할 수 있다.

관련 글

 

 

도커 스웜 모드를 사용하면 클러스트 환경에서 서비스를 관리할 수 있다. 여기서 서비스는 일종의 네트워크에서 찾고 사용할 수 있는 기능으로 컨테이너로 구성되어 있다. 서비스를 사용하면 실제 운영 중에 필요한 컨테이너 헬스체크, 고가용성을 위한 이중화, 롤링 업그레이드 등을 손쉽게 할 수 있다.

이 글에서는 도커 스웜 클러스터를 구축하고 아주 간단한 서비스를 클러스터에서 실행해보겠다.

서비스 테스트를 위한 간단한 이미지

여기서 사용할 이미지는 호스트 이름을 리턴하는 간단한 노드 웹이다. 코드는 다음과 같다.

const http = require('http');

const server = http.createServer().listen(5000);

server.on('request', (req, res) => {
    console.log('request arrived.');
    res.write("Hello: " + process.env.HOSTNAME);
    res.end();
});

server.on('connection', (socket) => {
    console.log("Connected");
});

이 코드를 실행하는 이미지를 도커 허브에 madvirus/simplenode:0.1로 등록해 놨다. 다음은 이미지를 생성할 때 사용한 Dockerfile이다.

FROM node:8.16-alpine
RUN apk add --no-cache tini curl

WORKDIR /app

COPY app.js .

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "app.js"]

도커 스웜 클러스터 생성

도커 스웜 클러스터를 생성하기 위해 다음의 IP를 가진 세 장비를 준비했다. 괄호 안에 표시한 문자열은 호스트 이름이다.

  • 매니저 : 192.168.1.101(docker-node1)
  • 워커: 192.168.1.102(docker-node2), 192.168.1.103(docker-node3)

101 서버에는 스웜 클러스터의 매니저를 설치하고 나머지 102, 103 서버에는 워커를 설치한다.

먼저 101 서버에서 docker swarm init 명령어로 도커 스웜을 초기화하자. 이때 --advertise-addr 옵션으로 스웜 클러스터에 참여할 IP를 지정한다.

$ docker swarm init --advertise-addr=192.168.1.101
Swarm initialized: current node (t8uoehrwcnzdf4q0poh6thlt4) is now a manager.

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

    docker swarm join --token SWMTKN-1-4pldr32x98ytmo60lso4jkhj2mg2zrdqxo33b2n8f50oz01pwp-8y4jflppx6eelxs7cn6fgfmd2 192.168.1.101:2377

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

docker swram init 명령어는 docker swarm join 명령어를 출력한다. 이 명령을 사용해서 스웜에 워커를 추가할 수 있다. docker swarm join-token worker 명령어를 사용해서 참여 명령어를 조회할 수 있다.

102 서버와 103 서버에서 docker swram join 명령어를 실행하자.

$ docker swarm join --token SWMTKN-1-4pldr32x98ytmo60lso4jkhj2mg2zrdqxo33b2n8f50oz01pwp-8y4jflppx6eelxs7cn6fgfmd2 192.168.1.101:2377
This node joined a swarm as a worker.

스웜 매니저 장비(101 서버)에서 docker node ls 명령어를 실행해보자. 노드 목록을 볼 수 있다. 노드는 스웜에 참여한 도커 인스턴스로 보통 한 호스트에 한 노드를 설치한다.

vagrant@docker-node1:~$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
t8uoehrwcnzdf4q0poh6thlt4 *   docker-node1        Ready               Active              Leader              19.03.2
rmsb5mnofkv8czjgjzpcce1gy     docker-node2        Ready               Active                                  19.03.2
01bul21qtizrxnawg56zngs4v     docker-node3        Ready               Active                                  19.03.2

docker-node1 호스트는 MANAGER STATUS 값이 Leader이다. 도커 스웜은 한 개 이상의 매니저를 가질 수 있는데 이 중에서 리더가 클러스터를 관리한다.

서비스 테스트하기

스웜 클러스터를 만들었으니 이제 클러스터에서 서비스를 실행해보자. docker service create 명령어를 사용해서 서비스를 생성한다.

$ docker service create \
--name simple \
--publish published=8000,target=5000 \
--replicas 2 \
madvirus/simplenode:0.1

ojslsjah1obmlpdyargxp6fg9
overall progress: 2 out of 2 tasks
1/2: running   [==================================================>]
2/2: running   [==================================================>]
verify: Service converged

위 명령어는 madvirus/simplenode:0.1 이미지를 이용해서 이름이 simple인 서비스를 생성한다. --replicas 옵션은 생성할 컨테이너의 개수를 지정한다. 여기서는 2를 주었으므로 클러스터 내에 두 개의 컨테이너를 생성한다. --publish 옵션을 사용해서 외부에서 8000번 포트로 연결하면 컨테이너의 5000번 포트로 연결한다.

서비스를 만들었으니 http://192.168.1.101:8000, http://192.168.1.102:8000, http://192.168.1.103:8000에 각각 연결해보자. 아래 그림처럼 호스트 이름을 응답으로 출력할 것이다(simplenode:0.1이 실행하는 app.js는 호스트 이름을 출력하는 간단한 웹 어플리케이션이다).

스웜 ingress 네트워크

docker service ls 명령어를 실행하면 실행 중인 서비스 목록을 보여준다.

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                     PORTS
ojslsjah1obm        simple              replicated          2/2                 madvirus/simplenode:0.1   *:8000->5000/tcp

REPLICAS 값이 2/2인데 이는 현재 2개가 실행중이고(앞의 2) 설정한 복제수는 2(뒤의 2)개라는 것을 보여준다. 포트 연결은 서비스 단위에서 처리된다. 서비스 수준에서 8000 포트로 오는 요청을 컨테이너의 5000 포트로 연결한다.

8000 포트를 사용하면 어떤 노드에 요청하더라도 서비스의 컨테이너에 연결된다. 예를 들어 simple 서비스의 컨테이너가 docker-node1과 docker-node2에 각각 하나씩 떠 있고 docker-node3에 요청을 보내도 도커 스웜 네트워크가 알맞게 컨테이너에 전달한다. 이를 처리하는 것이 ingress 네트워크이다.

docker network ls 명령어를 실행해보자. 이름이 ingress인 네트워크가 생성된 것을 볼 수 있다.

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
d1a366c95b3a        bridge              bridge              local
da6c7575bf20        docker_gwbridge     bridge              local
b30ace236245        host                host                local
c8c1e9h5214o        ingress             overlay             swarm
918b235ab093        none                null                local

ingress 네트워크는 오버레이(overlay) 드라이버를 사용하고 스웜(swarm) 범위를 갖는다. 오버레이 네트워크는 서로 다른 도커 호스트에서 실행되는 컨테이너 간 통신을 위한 논리적인 네트워크 그룹으로서 ingress 네트워크는 특수한 오버레이 네트워크이다. ingress 네트워크는 스웜 노드로 오는 모든 요청을 알맞게 서비스로 라우팅한다.

오버레이 네트워크와 서비스에 대한 내용은 뒤에서 다시 살펴본다.

관련 글

+ Recent posts