도커 컨테이너 까보기(4) – Docker Total Architecture

Docker architecture 전체를 큰 그림으로 살펴보는 시간을 가져보려 한다.
인터넷을 항해하다 보니 아래 그림을 찾았다.
전체 그림을 제법 잘 보여주는 그림이 아닌가 한다.
물론 2014 년에 작성된 그림이기 때문에 최신 버전과 어느 정도 차이는 있지만, Docker 의 세부 기술들간의 연관 관계를 파악하는데 더 없이 좋은 자료라고 생각한다.
아래 그림의 각 구성요소들은 물리적으로 분리되어 있는 것들이 아니라, docker 를 구성하는 각 역할 별 모듈을 논리적으로 구분지어 그린 것이다.
이러한 모듈들은 각자 자신의 역할을 담당하면서 느슨한 결합(Loosely Coupled)을 통해 하나의 통합된 docker 를 만들어낸다.
눈에 보이는 docker engine 의 모습을 먼저 알고 싶다면 ‘도커 컨테이너 까보기(3) – Docker Process, Binary‘라는 글을 먼저 읽어보면 좋을 듯 하다.

Docker total architecture (그림출처: Docker source code analysis (1): Docker architecture)

위의 전체 그림을 바탕으로 주요한 모듈들을 살펴보기로 하겠다.

Docker daemon

Docker daemon 은 docker engine 내에서 주로 client 및 registry, driver 의 중심에서 작업의 분배를 담당하는 중심점이라고 보면 되겠다.
docker daemon 이라는 용어때문에 다소 혼선이 있을 수 있는데, 여기에서 docker daemon은 그냥 docker engine 중에서 특정 모듈이라고 이해하자.
client 로부터의 HTTP 요청을 내부 job 단위(가장 기본적인 작업 실행 단위)로 처리할 수 있도록 분배한다.
즉, HTTP server 의 역할과 함께 client 요청을 분배(route and distribute), scheduling 하고, 요청에 대한 적합한 Handler 를 찾는다.
요청에 대해 실질적인 처리는 Handler 를 통해 다른 모듈 들에게 전달하여 수행하고 그 결과를 응답으로 작성하여 client 에게 제공한다.

Driver

Docker Driver 는 크게 세 가지 범주로 나눌 수 있다.

  • graphdriver : container image 관리
  • networkdriver : 가상 bridge 등 container 의 network 관리
  • execdriver : container 생성 관리

graphdriver

graphdriver는 Storage Driver 라고 이해하면 되겠다.
/var/lib/docker 내에 저장되어 있는 container image 관련 정보들을 이용하여 사용자에게 layered filesystem으로 제공하는 driver 이다.
built-in graphdriver 로는 btrfs, vfs, auts, devmapper, overlay2 등이 있다.
이러한 graphdriver 는 pluggable 한 구조를 가지고 있기 때문에, 다른 graphdriver 로 변경하여 사용하기가 쉽다.
built-in driver 이외에 plugin 형태의 external graphdriver 를 설치하여 사용할 수도 있다.  

graphdriver 가 /var/lib/docker 아래 저장된 많은 image 와 container 정보 등을 어떻게 검색하고 관리할까?
graphdriver 는 사실 저장된 image 들에 대한 wrapper 라고 봐도 무방하다.
실질적으로 모든 image 들의 관계 및 정보를 저장하고 있는 것은 GraphDB 이다.
이러한 image graph 정보를 이용하여 사용자에게 어떤 편의성을 가진 filesystem 을 제공할 것인가가 graphdriver 의 몫인 것이다.
아래 그림은 storage driver 중 aufs 의 구조를 보여주는데, 각 image layer, container layer 정보들은 모두 /var/lib/docker/aufs 디렉토리에 저장되어 있다.
각 layer 별 관계를 조회하고 key 를 통해 특정 image 를 검색하는 등, 이러한 일련의 정보 검색 및 관리 부분은 graphdb 를 통해 처리한다고 생각하면 되겠다.

graphdriver 는 graphdb 의 도움으로 graph 형태로 저장된 image 들을 layered filesystem 형태로 포장하여 제공할 뿐이다.
데이터 간의 관계를 가장 잘 표현할 수 있는 GraphDB 를 사용하기 때문에, 각 image layer 들의 의존 관계를 파악하기 좋을 뿐 아니라, 이를 관리하기 위한 간단한 interface 까지 제공한다.
graphdriver 는 이러한 GraphDB 의 interface 를 이용하여 구현된다.
(GraphDB는 SQLite를 기반으로 구축 된 작은 맵 데이터베이스이다.)
마치 특정 Database 가 제공하는 API 를 이용하여 business layer 를 구현하듯이, graphdriver 도 graphdb 의 API 를 이용하여 구현된 layer 라고 이해하면 되겠다.

networkdriver

Docker 에서의 network 에 대해 이해하려면 그 design 철학을 먼저 이해해야 한다.
CNM(Container Network Model).
CNM 은 container 를 사용하는 환경에서 사용자가 network 설계를 쉽게 하기 위한 것이다.
즉, 복잡한 물리적인 환경을 고려할 필요없이 사용자는 추상적인 개념 만을 이용해서 network 를 설계할 수 있게 하자는 것이다.
추상적인 개념을 이용할 경우 좋은 점이 무엇인가?
복잡한 하부 환경과 분리하여 논리적 설계가 가능할 뿐 아니라, 이렇게 구현된 시스템의 이식성 또한 높아진다.
OS 나 인프라 환경에 구애받지 않고 Application 은 동일한 추상화 환경 위에서 구현하면 되니까.

CNM 을 구성하는 3 가지 요소가 있다.

  • Sandbox
    Sandbox 는 하나의 Container 의 Network Stack 설정을 포함한다. 즉, Container 의 Network Interface, 라우팅 테이블 및 DNS 설정 관리가 모두 Sandbox 에 포함된다. Sandbox 는 Linux network namespace 또는 이와 유사한 개념으로 구현된다. Sandbox 는 여러 Network 의 많은 Endpoint 를 포함할 수 있다.
  • Endpoint
    Endpoint는 Sandbox를 Network 에 연결한다. Endpoint 의 구현은 veth pair 일 수 있다. 즉, Container 내의 eth 와 외부의 vth 의 pair 를 Endpoint 의 실체로 이해할 수 있다. 이러한 veth pair 는 docker0 라는 bridge network 에 연결될 수 있다.
    Endpoint 는 하나의 Network 에만 연결될 수 있고, 연결된 상태라면 하나의 Sandbox 에만 속할 수 있다.
  • Network
    CNM은 OSI 모델 측면에서 네트워크를 명시하지 않는다. 네트워크의 구현은 리눅스 브리지, VLAN 등이 될 수 있다. 네트워크는 직접적으로 통신을 할 수 있는 엔드포인트의 모음이다. Network 에는 많은 Endpoint 들이 연결될 수 있다.

다음 그림을 보자.

그림출처: libnetwork design

2 개의 Sandbox 안에 각각 Endpoint 요소를 하나 씩 만들고, 그 Endpoint 둘을 Network 이라는 요소에 연결한다.
그러면 두 개의 Endpoint 요소는 Network 라는 요소를 통해서 통신이 가능하다.
이 얼마나 아름다운 개념인가?
하드웨어 환경을 전혀 생각할 필요가 없는 설계 방식 아닌가?
CNM 은 이렇게 Sandbox, Endpoint, Network 이라는 3 개 Component 로 이루어지며, 사용자는 이러한 Component 들을 조합하여 전체 network 설계를 수행할 수 있다.
실제로 사용자가 이러한 개념으로 network 을 구성할 수 있도록 기능을 제공하는 driver 가 networkdriver 이다.
docker 소스 코드 중에서 libnetwork component 가 이러한 networkdriver 가 구현된 곳이다.
즉, libnetwork 는 CNM 의 구현이다.
맨 위쪽 전체 아키텍처 그림에서 networkdriver 는 libnetwork 이라고 봐도 무방할 듯 하다.

이러한 CNM 의 사상은 내부적으로는 각종 driver 에 의해 구현되는데, 이러한 driver 들은 pluggable 한 구조를 가지고 있다.
용어가 좀 혼동될 수 있는데, 위쪽에서 언급했던 networkdriver(libnetwork)는 ‘Control and Management Plane’ 단계의 driver 를 의미한다.
즉, 상위 layer(Docker Engine)에 network 제어를 위한 추상화 API 를 제공하는 driver(library)라고 말할 수 있다.
network 에 대한 실체가 구현된 driver 가 아니다.
단지 추상화 layer 라고 보면 된다.
CNM 의 사상을 API 로 구체화하고 이러한 API 를 통해 사용자가 network 을 관리할 수 있게 만든 것이다.
이러한 API layer 아래에서 실무(실제적인 network 관련 작업)를 담당하는 놈들을 libnetwork 에 갈아끼울 수 있다.
이러한 driver 들은 바로 위 그림에서 ‘Data Plane’ 단계에 그려진 driver 들이다.
이러한 pluggable 한 driver 들은 상당히 다양한데 아래와 같이 구분된다.
아래 category 에 대한 설명은 이곳이곳에서 확인할 수 있으니 참고하시라.
보통 국내 인터넷 사이트에서 쉽게 찾아볼 수 있는 Docker network 에 대한 글들이 있는데, 해당 글들은 대부분 아래 category 중 Native Network Drivers(bridge, host, none 등)에 대한 내용이다.

  • Network Drivers : 네트워크를 작동시키는 실제 구현을 제공
    – Native Network Drivers
    – Remote Network Drivers
  • IPAM Drivers : IP 주소 관리 드라이버
    – Native IPAM Drivers
    – Remote IPAM Drivers

CNM 의 구체적인 내용(구현)에 대한 것은 다음 글에서 더 자세히 살펴볼 것이다.

execdriver

execdriver 는 container 생성 및 관리에 관한 역할을 담당한다.
즉, kernel 의 격리 기술 등을 이용하여 container 를 생성하고 실행하는 역할을 한다.
위에서 살펴본 networkdriver 와 마찬가지로 execdriver 도 하위 runtime driver 를 pluggable 하게 선택하여 사용할 수 있는 구조이다.
Docker native runtime driver 가 생기기 전에 사용했던 LXC 나 libvirt 를 선택하여 사용할 수도 있고, native driver 고 불리는 libcontainer(현재는 runc)를 선택하여 사용할 수도 있다.
native runctime driver(libcontainer, runc)가 생긴 이유와 그 실체에 대해서는 ‘도커 컨테이너 까보기(3) – Docker Process, Binary‘ 에서 이미 상세히 다루었다.

execdriver 에서 선택된 LXC 또는 native driver 는 Linux Kernel 에서 제공하는 cgroups, namespace 등의 기능을 이용할 수 있는 interface 를 제공하고, 이를 통해 docker 는 container 생성 및 관리에 필요한 실질적인 기능들을 제공한다.
docker run 을 실행하면 이는 결국 execdriver -> runtime driver -> container 관련 kernel 기능에 의해 container 환경이 마련되고 기동되는 것이다.


위에서 기술한 component 관련 설명들을 바탕으로 아래의 두 가지 그림을 살펴보자.
추가로 설명하지 않더라도 번호대로 따라가보면 docker 의 명령어 처리 흐름을 이해할 수 있을 것이다.

첫번째는 docker pull 을 수행할 경우의 처리 흐름이다.

다음은 docker run 을 수행했을 경우의 처리 흐름이다.
docker run 은 container create 와 start 의 두 단계 처리 과정을 거친다.
(물론 local 에 base image 가 없다면 image pull 을 먼저 수행할 것이다.)
image 에 대해 container layer 를 생성하고(graph driver), network 환경을 준비하고(networkdriver), runtime driver 를 통해 container 를 실행(execdriver)한다.


References
확장성 있고, 이식성 있는 도커 컨테이너 네트워크 설계
도커 소스코드 분석 – 아키텍처
libnetwork design
DOCKER NETWORK PART OF THE IMPLEMENTATION OF FLOW ANALYSIS (LIBNETWORK SOURCE CODE INTERPRETATION)

You may also like...