도커 컨테이너 까보기(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...

0 0 votes
Article Rating
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
두리몽실

2시간 동안 도커 컨테이너 까보기 시리즈를 정독했습니다
정말 어려운 내용을 쉽고 재밌게 정리해주셔 도커에 대해 이해하는데 많은 도움이 되었네요 정말 글을 잘쓰십니다!!
저도 언젠가 이런 수준의 글을 쓰는 날이 올 수 있게 열심히 공부해야겠습니다 ㅎㅎ

1
0
Would love your thoughts, please comment.x
()
x