About 도커(Docker)
#Docker
우테코 테코톡의 영상의 내용을, 글로 편하게 볼 수 있도록 정리한 게시글입니다!
참고)
https://youtu.be/IiNI6XAYtrs?si=LQPkjVUi-Ac02etT
도커란?
- 컨테이너 기반 가상화 도구
가상화?
가상화의 등장 배경
6000만원짜리 좋은 서버가 있다. 여기에 쇼핑몰 사이트를 띄운다.
그런데 쇼핑몰 사이트가 좋은 서버의 성능 10%도 못 쓴다.
그래서 이 서버에 반려동물 사료 플랫폼이라는 새로운 프로젝트를 같이 올리려고 한다.
그런데 그냥 올리면 쇼핑몰 사이트에서 쓰고 있는 기술들이랑 충돌이 발생한다.
서버의 성능을 나눠서 사용할 수 없을까? : 가상화
하나의 서버 자원을 나눠 가지고 성능을 분산 시키고, 분산된 서버들은 각기 다른 서비스를 수행할 수 있게 되었다. 가상화를 통해 사용자가 많은 서비스에 많은 자원을 할당해주고, 적은 서비스에는 적게 할당해준다.
가상화의 종류 - 서버 가상화
하나의 물리적 서버 호스트에서 여러 개의 서버 운영 체제를 게스트로 실행할 수 있게 해주는 소프트웨어 아키텍처.

하나의 호스트에 게스트 OS가 할당된다.
하이퍼바이저
서버 가상화 기술을 구현할 수 있게 해주는 소프트웨어. 여러 개의 OS를 하나의 호스트 OS에 생성해서 사용할 수 있게 해준다.
호스트는 가상 머신이라는 단위로 구별된다.
각 가상머신에는 여러 운영체제가 설치된다. 하이퍼바이저에 의해 생성되고 관리되는 운영체제를 게스트 운영체제라고 부른다. 각 게스트 운영체제는 다른 게스트 운영체제와는 완전히 독립된 공간과 시스템 자원을 할당받아 사용한다.
하이퍼바이저는 OS들에게 자원을 나누어주며 조율하는 역할을 한다. OS들의 커널을 번역해서 하드웨어에게 전달한다.

서버 가상화의 단점
- 가상화 작업은 하이퍼바이저를 반드시 거쳐야 하기 때문에 일반 호스트에 비해 성능 손실이 발생한다.
- 가상 머신에는 게스트 운영체제를 사용하기 위한 라이브러리 커널 등을 전부 포함하기 때문에 배포하기 위한 이미지로 만들었을 때 크기 또한 커진다.
- 가상 머신은 완벽한 운영체제를 생성할 수 있는 장점은 있지만, 성능이 느리고 용량상 부담이 있는 트레이드 오프가 있다.
- 작은 Application을 구동하는데 굳이 OS까지 새로 띄워야 할까?
가상화 - 컨테이너 기반 가상화
서버 가상화의 단점을 해결하기 위해 컨테이너 기반 가상화가 등장한다.
컨테이너는 가상화된 공간을 생성하기 위해 리눅스 자체 기능인 chroot, 네임스페이스, cgroup을 사용함으로써 프로세스 단위의 격리 환경을 만든다.

도커 엔진 위에 컨테이너가 할당되어 있다.
- 컨테이너 안에는 Application을 구동하는 데 필요한 라이브러리와 실행 파일만 존재한다.
- 그렇기 때문에 이미지로 만들었을 때 이미지 용량 또한 가상 머신에 비해 대폭 줄어든다.
- 따라서 이미지를 만들어 배포하는 시간이 가상머신에 비해 빠르고, 가상화된 공간을 사용할 때 성능 손실도 거의 없다는 장점이 있다.
컨테이너를 다루는 기술은 Docker 말고도 다른 벤더사도 많다. 컨테이너 기술은 도커만의 기술이 아니다.
컨테이너를 왜 써야할까?
도커는 컨테이너 기술에 여러 기능을 추가한 오픈소스 프로젝트이다.
정확히 컨테이너가 기술적으로 어떤 의미일까?
사전적 의미
- 컨테이너는 어떤 물체를 격리하는 공간이다.
- 각각의 컨테이너는 격리된 상태로 다른 컨테이너들과 분리된다.
기술적 의미
- Servlet Container, IoC Container, Bean Cotainer…
- xxx의 생성 운영 제거까지의 라이프 사이클 관리
- 컨테이너에 담긴 것들의 라이프 사이클을 관리한다.
가상화 관점에서 컨테이너
- 이미지의 목적에 따라 생성되는 프로세스 단위의 격리 환경
- 이미지는 컨테이너를 만들기 위한 틀이다.
- ex) SpringBoot 1개, Nginx 1개를 컨테이너에서 실행한다면, 이미지의 목적은 Spring Boot Application, Nginx Application이다.
- 환경을 제공하고, 프로세스의 생명 주기를 관리

컨테이너는 파일시스템과 격리된 시스템 자원 및 네트워크를 사용할 수 있는 독립된 공간을 갖는다. 컨테이너가 실행되며, 프로세스가 실행되기에 필요한 자원들을 할당받고 프로세스를 실행한다. 이 때 커널을 통해 필요한 자원들을 가져온다.
Host 입장에서는 컨테이터를 어떻게 바라볼까?
앞서 말했듯이 컨테이너는 프로세스의 생명 주기를 관리하며 실행하는 하나의 프로세스이다.
즉, Spring Boot Application 프로세스를 직접 실행하나, 컨테이너로 실행하나 똑같은 프로세스로 바라본다.
돌아와서, 컨테이너를 써야 하는 이유에 대해 정리해보자.
- 컨테이너는 프로세스를 격리된 환경에서 관리한다. Host OS와 격리를 통해 컨테이너에 어떤 설정을 하든 Host Os에 영향을 끼치지 않는다. 즉, 독립된 개발 환경을 보장받을 수 있다.
- 이를 통해 프로세스를 컨테이너 단위로 바라볼 수 있게 되고, 프로세스의 관리, 확장에 용이해진다.
컨테이너를 어떻게 관리할까?
사용자는 Docker Engine을 통해 컨테이너를 관리할 수 있다.
도커엔진?
유저가 컨테이너를 쉽게 사용할 수 있게 하는 주체이다.
- 컨테이너의 라이플 사이클을 관리
- 컨테이너를 생성하기 위한 이미지 관리
- 컨테이너 데이터를 저장하기 위한 저장소 역할을 하는 볼륨의 관리
- 컨테이너의 접속을 관리하기 위한 네트워크 관리
- etc

도커 엔진 - 명령
사용자가 명령어를 쳤을 때 어떤 플로우로 진행될까?

- 먼저 사용자는 docker 명령어로 도커 엔진에게 명령어를 보낸다.
- docker run -it …
- 이를 전달 받은 도커 클라이언트는 /var/run/docker.sock에 위치한 유닉스 소켓을 통해 도커 데몬의 API를 호출한다.
- 도커 데몬은 명령어에 해당하는 작업을 수행하고, 수행 결과를 도커 클라이언트에게 반환하며 사용자에게 결과를 출력한다.
- dockerd는 컨테이너를 생성하고 실행하며 이미지를 관리하는 주체이다.
- 도커 프로세스가 실행되어 입력을 받을 준비가 된 상태를 도커 데몬이라 부른다.
도커 엔진 - 외부 API를 통해 호출
도커 클라이언트를 거치지 않고 데몬에게 명령어를 직접 보낼수도 있다.

이때는 url 요청을 보내 명령어를 쳤을 때와 같은 동작을 수행할 수 있다.
중간 정리

지금까지는 단일 서버에 대한 도커 엔진에 관한 내용이였다. 아래에서는 여러 개의 서버의 도커 엔진에 대해 살펴보자.
컨테이너가 많아져서 자원이 부족하다면?

하나의 호스트 머신에서 컨테이너가 많아져 CPU나 메모리, 디스크 용량과 같은 자원이 부족해 진 경우를 생각해보자. 새로운 컨테이너를 추가하려는데 자원 부족으로 추가하지 못하는 상황에 도달했다. 어떻게 해결할까?
자원을 늘리는 방법 - Scale Up
제일 간단한 방법. “매우 성능이 좋은 서버를 새로 산다”이다. 하지만 자원의 확장성 측면이나 비용 측면에서 좋지 않은 해답이다. 비싸게 주고 샀는데 그만큼 자원이 필요하지 않을 수도 있기 때문이다.
자원을 늘리는 방법 - Scale Out
여러가지 방법이 있지만 가장 많이 사용하는 방법은 여러 대의 서버를 클러스터로 만들어 자원을 병렬적으로 확장하는 것이다. 하나를 키우는게 아니라 병렬적으로 확장한다


만약 4GB 메모리의 서버에 대해 컨테이너가 너무 많이 생성돼있어 컨테이너를 더 이상 사용할 수 없다고 판단이 되면 4GB 메모리가 탑재된 새로운 서버를 추가해 자원을 늘린다. 만약 놀고 있는 자원이 많다면 다시 줄이면 된다.
클러스터?
각기 다른 서버들을 하나로 묶어 하나의 시스템 같이 동작하게 한다. 클라이언트에게 고가용성의 서비스를 제공할 수 있다.

동일한 network 풀을 쓰며, 요청에 따라 트래픽을 분산시킬 수 있다.
클러스터 - 도입의 어려움
하지만 여러 대의 서버를 하나의 자원 풀로 만드는 것은 쉬운 일이 아니다.
새로운 서버나 컨테이너가 추가될 때 이를 발견하는 작업부터, 어떠한 서버에 어떤 컨테이너를 할당할 건지에 대한 작업들 등 처리해야 할 작업이 많다.
다행히 이러한 문제들을 해결하는 여러 솔루션을 오픈소스로 활용할 수 있다. 이 중 대표적인 것이 도커 스웜이다.
도커 스웜 - 스웜 모드
스웜 모드는 마이크로 서비스 아케텍처의 컨테이너를 다루기 위한 클러스터링 기능에 초점을 맞추고 있다.
같은 컨테이너를 동시에 여러 개 생성해 필요에 따라 유동적으로 컨테이너 수를 조절할 수 있다.
컨테이너로의 연결을 분산하는 로드 밸런싱 기능을 자체적으로 지원한다.
scale out은 기본적으로 지원하지만, 자체적으로 인스턴스를 늘리거나 줄이거나 할 수는 없고 개발자가 직접 늘려줘야 한다.
스웜은 도커 엔진 자체에 내장되어 있다.
스웜 모드는 매니저 노드와 워커 노드로 구성돼있다.

- 워커 노드: 실제로 컨테이너가 생성되고 관리되는 도커 서버
- 매니저 노드 : 워커 노드를 관리하기 위한 도커 서버
- 기본적으로 워커 노드의 역할을 포함한다.
- 운영환경에서 다중화 하는 것이 좋다.
- 매니저 노드의 부하를 분산하고, 특정 매니저 노드가 다운됐을 때 정상적으로 스웜 클러스터를 유지할 수 있기 때문이다.
스웜모드 - 클러스터 구축
AWS EC2 인스턴스 예를 들어보자.

4개의 인스턴스가 준비되어 있다.
먼저 매니저 노드를 할 인스턴스를 지정하고, 노드의 역할을 할 인스턴스를 지정해준다.
노드를 지정했으므로, 역할을 할당해줘야 한다.
도커 명령어인 docker swarm init
을 통해 매니저 노드를 설정한다.
그 다음 docker swarm join
을 통해 노드를 추가할 수 있다.

워커 노드를 추가함으로써 하나의 클러스터 단위가 완성된다.
(물론 매니저 노드 하나를 하나의 클러스터라고 할 수도 있다.)
여기서 우리는 이제 서비스를 시작할 수 있게 되었다.
서비스?
일반적인 도커 명령어의 제어 단위는 컨테이너이다. 이는 도커 클라이언트가 제어하는 것은 컨테이너라는 말이다.
도커 스웜에서 제어 단위는 서비스이다. 서비스는 같은 이미지에서 생성된 컨테이너 집합이다.

서비스를 제어하면 해당 서비스 내의 컨테이너에 같은 명령이 수행된다. 이 때 서비스 내의 컨테이너를 Task라 부른다.
서비스의 정의에 따라 테스크를 할당할 적합한 노드를 선정하고, 선택된 노드에 테스크를 분산해서 할당된다.
함께 생성된 테스크를 Replica(레플리카)라고 하고, 서비스에 설정된 레플리카의 수만큼 테스크가 스웜 클러스터 내에 존재해야 한다. 위 그림에서는 아무 노드에 레플리카셋을 3으로 두었다.
감이 안오니 구체적인 예시를 보자.

- Spring boot, nginx 2개의 서비스를 정의했다.
- Spring Boot는 모든 노드에 생성될 테스크의 수를 3개로 설정했다.
- Nginx는 모든 노드에 2개로 설정했다.
- 클러스터 안에 Springboot 컨테이너 3개, Nginx 컨테이너 2개가 할당된 것을 확인할 수 있다.
만약 워커 노드 하나에 장애가 발생한다면?
스웜은 서비스의 테스크에 대한 상태를 계속 확인하다가 서비스 내에 정의된 레플리카 수만큼 스웜 클러스터 내에 존재하지 않으면 새로운 테스크 레플리카를 생성한다.

그림과 같이 하나의 노드가 다운되면, 다른 노드에 테스크가 생성된다.
(매니저 노드가 다운돼버리면 클러스터가 사라진다. 주의!)
스웜 모드가 제공하는 나머지 기능들
- 클러스터 관리
- 서비스 관리
- 네트워크 관리
- 노드 관리
- 등등
도커 엔진에서 제공하는 기능과 유사하다.
스웜 모드는 왜 써야할까?
서비스 확장/관리를 편하게 하기 위해서이다.
컨테이너를 어떻게 효율적으로 생성할까?
각각의 서비스가 동작하기 위해선 컨테이너가 필요하다. 서비스에 필요한 컨테이너가 1~2개 정도라면, 도커 명령어를 직접 입력해 생성할 수 있지만, 서비스가 여러개라면 말이 달라진다.

만약 위 예시처럼 컨테이너 5개, 혹은 그 이상의 컨테이너를 직접 명령어를 통해 생성한다면, 비효율적이다.
어떻게 효율적으로 생성할까? 도커 컴포즈
도커 컴포즈
여러 개의 컨테이너를 하나의 서비스로 정의하고 실행한다. 스웜 모드랑 비슷하다.
설정 파일에 정의된 서비스의 컨테이너 수를 유동적으로 조절할 수 있다. 서비스 디스커버리도 자동적으로 이루어진다.
컴포즈는 도커 엔진 밖에 위치한다. 도커 엔진 밖에 위치하기 때문에 도커에 내장되어 있지 않고, 컴포즈를 직접 설치해야 한다.
도커 컴포즈의 플로우

도커 컴포즈는 컨테이너의 설정이 정의된 yml 파일을 읽어 도커 엔진을 통해 컨테이너를 생성한다. 따라서 도커 컴포즈를 사용하려면 yml 파일을 먼저 작성해야 한다.
파일을 작성한 후 docker-compose 명령어를 통해 컨테이너를 생성할 수 있다.
도커 컴포즈의 구조
도커 컴포즈는 기본적으로 docker-compose.yml이 위치한 디렉터리 이름을 프로젝트 이름으로 사용한다.
즉, yml 파일이 위치한 디렉터리 이름에 따라 프로젝트 이름이 달라진다.(물론 직접 설정도 가능하다)

하나의 프로젝트는 여러개의 서비스로 이루어지고, 하나의 서비스에는 여러 개의 컨테이너가 존재할 수 있다.
각 컨테이너는 번호를 붙여 서비스 내의 컨테이너를 구별한다.
컴포즈는 왜 써야 할까?
컨테이너의 생성을 편리하기 하기 위해서이다.
쿠버네티스
위에서 설명했던 것을 효율적으로 사용해주는 기술.