사용자와 도커가 소통할 수 있도록 하는 소통 창구
우선 사용자가 Docker CLI에 아래와 같이 도커를 생성하는 명령어를 입력한다
e.g. $ docker container run --name ctr1 -it alpine:latest sh
그러면 Docker Client는입력된 명령어를 적절한 API Payload로 변환해 도커 엔진에 있는 Docker Deamon에게 RestAPI로 Post 요청을 하게 된다.
e.g. POST /containers/create HTTP/1.1
도커 클라이언트와 도커 데몬은 /var/run/docker.sock
(Linux), \pipe\docker_engine
(Window) 에 위치한 유니스 소켓을 통해 통신하며, API를 사용하여 명령어를 전달한다.
요청된 API는 Unix Socket을 통해 dockerd에게 전달 된다.
도커가 설치된 OS
도커 호스트 내에는 도커 엔진이 설치되어 컨테이너의 Life Cycle, 이미지 관리, 데이터 저장을 위한 볼륨 관리, 네트워크 관리 등을 한다.
컨테이너를 생성하기 위한 외부 이미지 저장소
도커의 공식 registry인 DockerHub를 통해 다른 도커 사용자들의 이미지를 가지고 오거나 CLI 명령어를 실행하여 필요한 이미지를 가지고 올 수 있다.
또한 개인이 만든 어플리케이션도 이미지로 만들어 안전하게 업로드 및 배포를 할 수 있다.
도커 구조의 뼈대만 알아봤다면 이제 살을 덧붙여 사용되는 도구들도 함께 알아보고 자세히 들여다보자.
도커 엔진은 Docker Daemon, Rest API, API를 통해 도커 데몬과 통신하는 CLI로 모듈식으로 구성되어 있다.
도커 클라이언트는 위에서 설명했으니 생략한다.
도커 엔진의 모듈 중 하나로서 사용자가 입력하는 도커 명령어들인 API 요청을 수신하고, 컨테이너, 이미지 등 도커 객체를 생성하고 관리한다.
dockerd가 컨테이너를 실행하는 순서는 아래와 같다.
containerd
runc
이때 3단계는 High-Level Container Runtime인 containerd
가 수행하며,
4단계는 Low-Level Container Runtime인 runc
가 수행하게 된다.
그래서 3단계를 수행하기 위해서는 dockerd가 containerd과 통신하기 위해 호출하게 된다.
이때 dockerd는 CRUD 스타일의 API를 통해 gRPC로 containerd와 통신한다.
e.g. client.NewContainer(context, ...)
또한 dockerd는 컨테이너 생성뿐 아니라 다른 데몬과 통신하여 도커 서비스를 관리할 수도 있다.
이외에도 dockerd의 역할로는 이미지 관리, 이미지 빌드, REST API, 인증, 보안, 코어 네트워킹, 오케스트레이션 등이 있다.
Container의 생명주기를 관리하고, 이미지를 관리, 압축해제, Low Level Container Runtime으로 전달 등 고수준 작업을 수행한다.
containerd가 생겨나게 된 배경을 정리한 글이 있어서 가지고 와봤다.
초기 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입니다.
https://kr.linkedin.com/pulse/containerd는-무엇이고-왜-중요할까-sean-lee
즉 containerd는 Docker에서 만든 Container Runtime으로 컨테이너 표준인 OCI (Open Container Initiative) 를 따르고 있다.
containerd
containerd는 Dokcer 이미지를 가져와서 컨테이너 구성을 적용해 runc가 실행할 수 있는 OCI 번들로 변환한다.
OCI 런타임 표준을 준수하는 Container Runtime 으로 컨테이너를 생성 및 실행하기 위한 CLI
컨테이너는 namespace와 cgroups를 통해 구현되는데 namespace는 시스템 리소스(Filesystem, Network 등)의 가상화 및 격리를 가능케하고, cgroups는 컨테이너 안에서 사용할 수 있는 리소스의 양을 제한하는데 쓰인다.
Low Level Container Runtime은 이러한 namespace와 cgroup의 설정 및 실행을 담당하고 있다.
현재 다양한 Low Level Container Runtime 중 OCI 표준 스펙을 지키면서 살아남은 건 많지않으며 runC가 사실상 시장을 지배했다고 보면 된다.
컨테이너 프로세스는 runc의 하위 프로세스로 시작되는데, 컨테이너 프로세스가 실행하자마자 runc가 종료exit된다.
컨테이너 안에서 생성되는 프로세스의 부모가 될 상위 프로세스가 필요하다.
이 때, docker-containerd-shim이 새로운 상위 프로세스가 되어 컨테이너의 생명주기를 관리하게 된다.
예를 들어 컨테이너의 로그(stdout/stderr)을 가져오거나, 새로운 프로세스를 생성하는 등의 동작이 가능하다.
📎 참고 링크