티스토리 뷰

참조글 :  

https://velog.io/@korjsh/%EB%8F%84%EC%BB%A4%EC%8A%A4%EC%9B%9C-%EA%B8%B0%EC%B4%88-%EB%B0%8F-%EC%98%88%EC%A0%9C

 

도커스웜 기초 및 예제

도커 노드의 클러스터링을 도와주는 강력한 오케스트레이션 툴! 도커스웜에 대해서 배워봅시다

velog.io

 

도커스웜 기초 및 예제

 
 
 
 

클러스터란?

먼저 클러스터 개념을 소개하기전에 라즈베리파이 컴퓨터에 대해서 알아보겠습니다.

라즈베리파이 재단에서 만든 초소형/초저가의 컴퓨터를 라즈베리파이라고 하는데요,
2012년 3월에 첫 버전을 출시하여, 현재 버전 4까지 이어져오고 있습니다.

가격이 저렴한 만큼 성능이 제한적이라서, 개인 실험용이나 임베디드 리눅스 개발보드로 사용되고 있습니다.

(IoT 프로젝트할때, 많이 쓰는 편!)

 

출처: https://www.devicemart.co.kr/goods/view?no=12234534

인간들은 라즈베리파이를 진짜 프로덕션에서 쓸 수 없을까? 성능을 어떻게 끌어올려볼까? 이런 호기심이 생기기 시작합니다.

사람들은 이 작고 가벼운 성능의 라즈베리파이를 괴롭혀보기로 합니다.

다수의 라즈베리파이를 병렬로 묶어서 사용해보자. 즉, 클러스터를 만들어보기로 합니다...

처음에는 3대

조금만 더 ...

인간의 욕심은 끝이 없죠

결국 144노드를 클러스터링한 상용 모듈까지 나오게 됩니다..

심지어 버지니아 공대에서는 SeeMore라는 256노드의 창작품까지 만들었습니다.

seeMore at SXSW from Sam Blanchard on Vimeo.

출처: https://magpi.raspberrypi.com/articles/seemore

하지만 이렇게 클러스터링한다고 해서 성능이 무작정 좋아지는건 아닙니다.
4대의 클러스터와 라즈베리파이 한대의 Run-time과 Speed Up 비교를 보면 라즈베리파이 한대가 오히려 성능이 더 좋게 나옵니다.

이유는 클러스터링된 노드간 통신에 사용하는 이더넷 속도(10/100Mbps)의 한계인 것으로 보입니다.

출처: https://magpi.raspberrypi.com/articles/benchmarking-raspberry-pi-cluster

정리하자면, 컴퓨터의 클러스터라는 개념은 다수의 노드(컴퓨터)를 묶어서, 병렬처리가 가능하게 하여 성능을 올려주는 기술을 말합니다.

도커 스웜이란?

도커에도 클러스터를 위한 도커 스웜이라는 기술이 있습니다.

도커 스웜은 노드들의 집합에 서비스형태로 도커컨테이너를 배포하고 관리할 수 있게 하는 기술입니다.

쉽게 말해서, 클러스터용 컨테이너 서비스 관리 툴이라고 보면 됩니다.

도커스웜을 쓰는 이유?

RAM 8GB 서버로 도커를 운영하고 있는데, 가용램을 넘어서, 더이상 서비스를 올릴 수가 없다면 어떻게 해야할까요?

RAM 8GB 짜리 서버가 한대 더 있다고, 가정해봅시다. 그러면 이 한대를 더 추가해서 운영할 것 입니다. 그런데, 이 두대를 하나의 자원으로 쓰고 싶다고 가정해봅시다.

마치 RAM 16GB의 서버 한대처럼 동작하도록 말이죠. 이럴때 필요한게 병렬확장인 클러스터입니다.

하지만 이런 병렬처리 컴퓨팅을 개인이 만드는 것은 쉽지 않습니다. 하지만 다행하게도 그걸 도와주는 툴이 존재하죠! 바로 도커 스웜 입니다.

스웜모드 구조

도커와 차이점?

스웜모드가 아닌 도커와의 차이는 서비스 개념 입니다.

도커에서는 컨테이너가 단위었다면, 스웜에서는 서비스가 단위입니다.
서비스는 한개의 컨테이너 또는 다수의 컨테이너가 조합될 수 있는 단위입니다.

도커 스웜 설정

이 포스팅에서는 도커 스웜의 기초를 다지기 위해서, 클러스터 사용없이, 단일 노드 스웜 모드를 사용하도록 하겠습니다.

아래 명령어로 스웜 모드 초기화를 진행해봅시다.

docker swarm init

싱글노드로 사용하기 위한 준비는 끝났습니다! 엄청 간단하죠?

도커 스웜용 컴포즈 작성 방법

도커 스웜에서는 서비스를 배포하기 위해서 docker-compose 형식의 파일을 사용합니다.

하지만 도커 컴포즈와는 다르게 deploy라는 옵션이 있고, docker stack에서는 제한되는 옵션들이 있습니다.

제한되는 목록

build
cgroup_parent
container_name
devices
tmpfs
external_links
links
network_mode
restart
security_opt
userns_mode

deploy

도커스택에서 사용하는 기본적인 옵션들에 대해서 알아보겠습니다.

replicas

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

resources

limits: 최대 리소스 성능 제한
reservations: 점유할 리소스 예약
version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 50M
        reservations:
          cpus: '0.25'
          memory: 20M

restart_policy

이 옵션은 컨테이너가 어떻게 다시 켜질 것인지에 대한 옵션입니다.

condition: none, on-failure, any (default: any)
delay: 재시작 시도 시간 간격 (default: 5s)
max_attempts: 재시작 시도 횟수 (default: never give up)
window: 재시작 성공시, 서비스를 다시 실행하는데 걸리는 시간 (default: decide immediately)
version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 10s

update_config

parallelism: 한 번에 업데이트할 컨테이너 수입니다.
delay: 컨테이너 그룹 업데이트 사이에 대기하는 시간입니다.
failure_action: 업데이트에 실패한 경우 수행할 작업. 중 하나 continue, rollback또는 pause 
(기본값 : pause)
monitor: 실패 모니터링을 위한 각 작업 업데이트 후의 기간 (ns|us|ms|s|m|h)(기본값 5초)
(참고 : 0으로 설정하면 기본값 5초가 사용됩니다.)
max_failure_ratio: 업데이트 중 허용되는 실패율입니다.
order: 업데이트 중 작업 순서입니다. 하나는 stop-first(이전 작업은 새로운 하나를 시작하기 전에 정지) 
또는 start-first(새 작업이 먼저 시작되고 실행중인 작업 간략하게 중복) 
(기본값 stop-first) (참고 : compose V3.4 이상에서 지원.)
version: "3.9"
services:
  redis:
    image: redis:alpine
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
  vote:
    image: dockersamples/examplevotingapp_vote:before
    depends_on:
      - redis
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
        delay: 10s
        order: stop-first
        

도커 스웜 기초 명령어

스웜모드가 활성화되면 아래와 같은 명령어를 실행할 수 있습니다.

docker service

docker service create로 컴포즈 파일없이 즉시 서비스를 올릴 수 있다.

예제

docker service create --name registry --publish published=5000,target=5000 registry:2

docker service ls 로 실행중인 서비스들을 확인할 수 있다.

docker stack

docker stack 명령어는 -c or --compose-file 옵션으로 컴포즈파일을 지정해주어야 한다.
예제에 사용할 yaml파일은 아래와 같다.

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

docker stack deploy --compose-file <compose 파일경로> <stack 이름>

예제는 아래와 같다.

docker stack deploy --compose-file docker-compose.yml stackdemo or
docker stack deploy -c docker-compose.yml stackdemo

docker stack services stackdemo 로 실행중인 서비스를 확인할 수 있다.

docker stack rm stackdemo로 스택을 없앨 수 있다.

도커 스웜 기본 예제

로드 밸런싱 (HA Proxy)

var http = require('http');
var os = require('os');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(`<h1>I'm ${os.hostname()}</h1>`);
    console.log(os.hostname())
}).listen(8080);
FROM node
RUN mkdir -p /usr/src/app
COPY index.js /usr/src/app
EXPOSE 8080
CMD [ "node", "/usr/src/app/index" ]

docker-compose.yaml

version: '3.9'
services:
  node:
    image: api-example
    ports:
      - 8080
    environment:
      - SERVICE_PORTS=8080
    deploy:
      replicas: 5
      update_config:
        parallelism: 5
        delay: 10s
      restart_policy:
        condition: on-failure
        max_attempts: 3
        window: 120s
    networks:
      - web

  proxy:
    image: dockercloud/haproxy
    depends_on:
      - node
    environment:
      - BALANCE=leastconn
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 80:80
    networks:
      - web
    deploy:
      placement:
        constraints: [ node.role == manager ]

networks:
  web:
    driver: overlay

위의 세개파일을 만들어 놓고, 순서대로 설정해보자

일단 도커파일로 이미지를 만든다.

docker build -t api-example .

다음으로 스택을 배포 할 수 있도록 도커스웜 초기화 명령어인

docker swarm init을 실행해보자

도커 스웜 init이 제대로 실행되었다면,
docker stack deploy --compose-file=docker-compose.yaml study 으로 서비스를 실행시켜보자

docker ps로 확인해보면 5개의 컨테이너와 하나의 haproxy 서버가 올라가 있는것을 확인할 수 있다.
docker service ls로 조금 더 정리된 화면을 확인할 수 있다.

docker service logs -f study_node로 로그를 확인해보자.

 

*********************************************************

참조문헌 : https://kr.linkedin.com/pulse/containerd%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C-sean-lee

 

 

containerd는 무엇이고 왜 중요할까?

 
2013년 Docker가 처음으로 세상에 나타난 이후 IT업계는 정말 크게 변했습니다. 어플리케이션의 배포단위는 이제 war, jar, zip 등이 아니라 Docker 이미지가 되었고 Docker를 사용할 수 있는 환경이기만 하면 어플리케이션은 Windows에서든, Ubuntu에서든 동일하게 동작하였습니다. Docker의 이러한 특성을 이용해 수백 수천대의 서버를 운영하는 환경에서 Docker를 도입하는 사례들이 늘어나기 시작했고 얼마되지 않아 Docker는 업계의 표준기술중의 하나로 자리잡게 되었습니다.

Docker의 기본 아이디어는 프로세스와 CPU, memory, disk I/O, network 등의 리소스들을 컨테이너라는 하나의 그룹으로 묶어서 관리하자는 것으로 namespace, cgroup 등 Linux의 여러 커널기능들을 조합하여 만들어졌습니다.

이 무렵 구글 내부에서도 컨테이너를 활용해 대규모 서비스를 구축해보는 프로젝트가 진행중이었습니다. 프로젝트의 이름은 Project 7이었으나 나중에 Kubernetes로 바뀌게 됩니다. Kubernetes 역시 등장과 함께 업계의 환영을 받으며 구글을 시작으로 많은 기업들에서 채택되게 됩니다. 참고로 Kubernetes 로고의 방향타 손잡이가 7개인 것은 바로 원래 프로젝트 이름인 Project 7에서 따온 것이라고 합니다.

이렇게 Docker와 Kubernetes가 나란히 성공신화를 써내려가고 있는 가운데 Docker 측에서는 기존 Docker Engine의 Monolithic한 구조를 나누는 작업을 시작했습니다. 초기 Docker를 개발하면서 하나의 완성된 컨테이너 사용자경험을 만드는 것에 집중하다보니 Docker Engine이라는 하나의 패키지에 API, CLI, 네트워크, 스토리지 등 여러 기능들을 모두 담게 되었고, Docker에 의존하고 있던 Kubernetes에서는 Docker 버전이 새로 나올때마다 Kubernetes가 크게 영향을 받는 일들이 생겨났기 때문입니다.

그래서 Docker를 중심으로 구글 등 컨테이너 기술에 관심있는 여러 집단들이 한데 모여 Open Container Initiative, 이하 OCI라는 프로젝트를 시작하여 컨테이너에 관한 표준을 정하는 일들을 시작하게 됩니다. 그래서 Docker에서는 OCI 표준을 준수하는 containerd라는 Container Runtime을 만들고, Kubernetes에서는 OCI 표준을 준수하는 이미지들을 실행할 수 있는 Container Runtime Interface, 이하 CRI 스펙을 버전 1.5부터 제공함으로써 Docker 버전과 무관하게 OCI 표준을 준수하기만 하면 어떤 컨테이너 이미지도 Kubernetes에서 실행가능한 환경이 만들어지게 되었습니다.

요약하자면 컨테이너를 빌드하고, 실행하고 거기에 네트워크, 스토리지, CLI까지 제공해주는 Docker Engine이라는 패키지가 있었는데, 이게 하나의 패키지로 묶여있다보니 여러 불편함들이 생겨났고 이를 해소하기 위해 여러 사람들이 모여 OCI라는 Container Runtime 표준을 만들고 이 표준대로 각자 Container Runtime을 만들기 시작했는데 Docker에서 만든 Container Runtime이 바로 containerd (https://containerd.io/)라는 이야기입니다. 참고로 containerd의 d는 daemon의 d입니다.

한편, Red Hat, Intel, SUSE, Hyper, IBM 쪽에서도 OCI 표준에 따라 Kubernetes 전용 Container Runtime을 만들었는데 이것이 CRI-O (https://cri-o.io/)입니다. containerd와 CRI-O 이 두가지 Container Runtime이 현재 가장 널리 사용되고 있으며 containerd는 Docker Engine에 기본으로 탑재되어 있어서 지금도 Docker를 사용한다면 내부적으로 사용되는 Container Runtime은 containerd 를 사용하게 됩니다. 참고로 `docker build` 커맨드로 생성되는 이미지들 역시 OCI Image Spec을 준수하기 때문에 별도의 작업없이 containerd로 실행시킬 수 있습니다.

이렇게 Container Runtime이 Monolithic 아키텍처에서 분리되어 나오면서 Java 어플리케이션을 배포할 때 JDK보다 가벼운 JRE를 사용하는 것처럼 (물론 이 경우에는 보안과 같은 다른 이유도 있습니다만) 컨테이너를 실행할 때 무거운 Docker Engine이 아닌 containerd나 CRI-O와 같은 가벼운 Container Runtime을 사용하게 되면서 다음과 같은 장점도 나타나게 되었습니다.

쉽게 말해 containerd로의 전환을 통해 Pod은 더 빨리 시작되고, CPU와 메모리의 사용량은 더 줄었다는 이야기로, containerd로의 전환이 왜 일어나고 있는지를 잘 설명해주고 있습니다.

하지만, containerd를 사용하는 방법은 분명히 Docker에 비해 까다롭습니다. 개발자 입장에서는 예전과 같이 docker build만 실행하면 되기 때문에 크게 달라지는 것이 없다고는 하나, Kubernetes 클러스터를 운영하는 입장에서는 Docker로 한번에 모든 것을 제어할 수 있었던 예전과는 달리, 이제는 runc, containerd, ctr 등 낯선 라이브러리들과 익숙해져야 하는 부담이 생겼습니다.

물론 이 부담은 containerd로의 전환을 망설일만한 부담은 아닐 것입니다 게다가 이제 2021년 하반기 출시예정인 kubernetes 1.23과 함께 Container Runtime으로써의 Docker의 지원이 중단되는 것이 결정된만큼 새로운 흐름을 빨리 받아들이고 혜택을 누리는 것이 필요한 것 같습니다.

참고자료

댓글