계기

인스턴스에 새롭게 적용하고 싶은 것들이 생겼습니다.
Cloudflared와 같은 것이 대표적이죠.
Cloudflared를 사용하게 되면서 불필요해진 서비스들이 많습니다.
VPN 같은 서비스가 대표적인 예시죠.

그러다보니 OS를 재설치하고 싶었습니다.
그런데 인스턴스를 지우려니 많은 서비스가 중단이 된다는 것을 깨닳았죠.

그래서 기왕 새로 만드는거 가용성을 확보해보자고 결심했습니다.

가용성 확보해보자

정말 많은 연구를 했습니다.
OCFS2, GFS2, GlusterFS, Ceph
K3s, K8s, Docker Swarm 등등..

이번 편에서는 왜 Docker Swarm으로 구성했는지 알아보고
Swarm과 Traefik을 구현해보았습니다.

container orchestration: Docker swarm vs K8s vs OKD

지금 회사에서 OCP를 하고 있기 때문에 OKD나 rancher, K8s도 생각했으나
개인 사용 목적에는 Swarm이 훨씬 편리하기에 Swarm을 사용하기로 결정했습니다.
경로에 상대경로를 쓸수 없다는 부분이 가장 불편하더라구요.
또한 kubernetes 운영에 필요한 pod들이 docker 데몬보다 더 많은 리소스를 요구합니다.

kubernetes는 yaml 파일에 컨테이너 구성을 명시하는 반면,
Docker는 과정보다 목적을 명시하는 경향이 있는 것 같았습니다.
결정적으로 swarm은 현재 갖고 있는 docker-compose.yml을 거의 그대로 사용 가능하다는 점도 한몫 했지요.ㅎㅎ

docker swarm

docker swarm의 구성은 쉽습니다.

docker 설치

먼저 각 os에 맡게 docker를 설치합니다.

fedora를 예로 들면 다음과 같습니다

sudo dnf config-manager \
    --add-repo \
    https://download.docker.com/linux/fedora/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y

sudo usermod -aG docker $USER

sudo systemctl enable --now docker.socket
sudo systemctl enable --now docker

방화벽 해제

docker node들은 2377/tcp와 7946/tcp,udp로 통신합니다.
(2377포트는 변경할 수 있습니다.)
node들에서 구동되는 container들이 통신할 수 있는 네트워크가 필요합니다.
이 네트워크를 overlay라고 하는데, 4789/udp를 필요로 합니다.

swam init

하나의 node에서 swarm을 시작합니다.
그러고 나서 다른 기기에서 이 swarm에 연결해야 합니다.

docker swarm init  --advertise-addr  --listen-addr :2377
# 2377 포트는 여기에서 바꾸어서 init하면 변경됩니다.
docker swarm join-token manager

node 연결

다른 node들을 swarm init 을 수행한 노드에 연결해야 합니다.

마지막 명령인 docker swarm join-token manager를 입력하면 아래와 같은 형식의 명령이 반환됩니다.

docker swarm join --token SWMTKN-1-cozcuhsdvo9ayshduihv8eoduwl2b3u4h2ktruiqbfacyuzs7x8zviyoa-3u7ad6kvh6dskbd0sef7pe3fz 2377

docker-compose.yml 변경

service로 실행할 때 적용할 수 없는 옵션들을 제거하고, deploy 관련 옵션을 추가합니다.
저는 container_name, restart 옵션을 제거하고 다음을 추가하였습니다.

    deploy:
      mode: replicated
      placement:
        constraints:
          - "node.role==manager"
          - "node.platform.arch==aarch64"
#          - "node.labels.dns==dns
        preferences:
          - spread: node.hostname
          - spread: node.labels.dns
#        max_replicas_per_node: 1
      replicas: 1
#    labels:
#      - com.centurylinklabs.watchtower.depends-on=npm-db,/npm-db

그리고 labels도 deploy 안으로 넣어줬습니다.

서비스 실행

docker sercie deploy -c docker-compose.yml

이제 swarm 자동으로 node를 선택하여 배포하게 됩니다!

Nginx docker service Replica

Loadbalancer가 필요로 해졌습니다.

Gluster 위에서 Linuxserver.io의 Nginx 이미지가 실행헤 15분 가까이 필요로 하더군요.
그래서 순차적으로 컨테이너를 재시작하기 위해 서비스가 여러개의 replica로 작동되도록 설정했습니다.

이 때 발생하는 문제들이 너무 많습니다.

replica 문제 1: sticky session

1번 서비스에 연결된 상태로 로그인을 해도, 나중에 2번이나 3번에 연결되면 로그인 상태가 풀립니다.
4개의 replica를 만들면 4번 로그인 해야 더 이상 로그아웃 되지 않게 되죠.

이 문제를 해결하기 위해 sticky session을 지원하는 LoadBalancer가 필요합니다.
이를 해결하기 위해 Traefik을 사용했습니다.

Healthcheck: 아직 가동되지 않은 nginx로 sticky session

정상이 아닌 container로는 트래픽이 진입되면 안됩니다.
심지어 sticky session까지 구현했기 때문에,
아직 실행된지 15분이 채 안되어 아직 접속 불가능한 container로 traefik이 트래픽을 전달하면, 15분 동안은 계속 접속이 불가능해집니다.
설령 정상인 nginx 컨테이너가 있다고 하더라두요.

따라서 health check를 수행하여, 건강한 container로만 트래픽이 전달되도록 해야 합니다.

fail over: 노드 당 최대 컨테이너 제한

각 노드에 1개씩 2개의 컨테이너가 구동 중입니다.
이 때 하나의 노드가 종료되면서 하나의 노드에 컨테이너가 몰리게 됩니다.
그러고 다시 노드가 켜지면, 각 노드에 1개씩..이
안되더라구요.
그냥 여전히 한 노드에 2개가 뭉쳐져 있더군요.

노드1노드2대기(pending)
노드1 정상, 노드2 정상1개 컨테이너1개 컨테이너0개 컨테이너
노드1 정상, 노드2 down2개 컨테이너-개 컨테이너0개 컨테이너
노드1 정상, 노드2 정상2개 컨테이너0개 컨테이너0개 컨테이너
노드1 down, 노드2 정상개 컨테이너2개 컨테이너(시작 중, 서비스 불가)0개 컨테이너

결국 서비스 불가능한 시점을 맞이하게 됩니다.

node가 복구되면 수동으로 다시 분배할 수 있습니다.
시작시 재분배 명령을 보내도 되었을 것 같은데,
그 때는 생각 못했네요.

아무튼!
저는 노드당 최대 컨테이너를 제한하여 해결했습니다.

그러면 이렇게 됩니다.

노드1노드2대기(pending)
노드1 정상, 노드2 정상1개 컨테이너1개 컨테이너0개 컨테이너
노드1 정상, 노드2 down1개 컨테이너-개 컨테이너1개 컨테이너
노드1 정상, 노드2 정상1개 컨테이너1개 컨테이너0개 컨테이너

fail over: start first – 실패

가동에 15분 가까이 걸리기 때문에, swarm이 fail over를 제공할지라도, 최소 15분은 서비스가 불가능합니다.
사실상 failover가 불가능하죠.
따라서 모든 노드에 container가 존재하도록 하려고 합니다.
문제는 먼저 중지한 후에 서비스가 새로 생성된다는 것입니다.

무언가 변동사항이 있어서 1번 노드의 컨테이너가 재시작 중인데, 갑자기 2번 노드가 연결이 안되거나 꺼져버리면?

그래서 컨테이너를 지우고 새로운 컨테이너를 생성하는 것이 아니라
새로운 컨테이너를 만들고 이전 컨테이너를 생성하도록 했습니다.

이 컨테이너는 15분 정도의 시작 시간을 갖고 있습니다.
즉, 서비스를 수정하면, starting 상태인 서비스가 생성되었다가
약 15분 정도 후에 생성된 컨테이너의 상태가 running으로 변경되면서
기존의 running 서비스가 제거됩니다.

문제는 starting인 이 15분 안에 docker service를 여러번 바꾸는 경우입니다.
이미지를 보면서 설명 드리겠습니다.

start-first: 예상한 동작 1


service update하면 status가 starting인 컨테이너가 생성 됩니다
한번 더 update하면 당연히 starting인 서비스가 죽고, 새로운 starting 서비스가 생성됩니다.
어차피 nginx 서비스가 제공되지 않는 컨테이너니까 지워도 되잖아요.

start-first: 예상한 동작 2

하지만 예상대로 되지 않더군요.
두번째 update하면서 기존의 starting이 제거되지 않고
pending 컨테이너가 추가 되더군요.
그래서 이렇게 되겠구나 생각했습니다.

바로 starting 중인 컨테이너가 running으로 변경되면 기존의 running 컨테이너가 제거되고
pending이 starting으로 바뀌는 것이죠.

start-first: 현실은 시궁창


하지만 그것도 아니었습니다.
분명히 설정 상 replica는 2개인데,
starting이 running으로 바뀌어도
기존의 running은 죽지 않습니다.

영원히 pending이더군요..

최종 nginx 설정: replica=4, stop-first, max_replicas_per_node=2

    healthcheck:
      test: ["CMD-SHELL", "timeout 1 bash -c 'cat < /dev/null > /dev/tcp/localhost/80' || exit 1"]
#      test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]
      interval: 3s
      timeout: 1s
      retries: 5
      start_period: 1800s
    deploy:
      mode: replicated
      placement:
        constraints:
          - "node.role==manager"
          - "node.platform.arch==aarch64"
        max_replicas_per_node: 2
      update_config:
        parallelism: 2
        delay: 720s
        order: stop-first
      replicas: 4

Traefik: stick session Loadbalancer

Traefik으로 Loadbalancer 설정 하였습니다.
이 때 주의 점은.. swarm으로 설정해주는 것과, 사용하지 않을지라도 traefik 서비스를 생성, port를 지정해줘야 한다는 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다


Ads Blocker Image Powered by Code Help Pro

광고 차단 감지됨!

닫기를 누르면 이용하실 수 있지만, 광고 차단은 해제해주시면 좋겠습니다.
Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock