Container Internals - 리눅스 커널부터 살펴보는 컨테이너 기술과 도커의 구조

Container Internals - 리눅스 커널부터 살펴보는 컨테이너 기술과 도커의 구조
Photo by Bernd 📷 Dittrich / Unsplash
💁‍♂️
이 내용은 경북소프트웨어고에서 제가 강의한 "Container & Docker basics" 수업 내용을 일부분 발췌하고 더 전문적인 내용을 추가한 것입니다.

이 포스트에 있는 스케치들은 draw.io 라는 툴을 사용하여 직접 제작하였습니다.

Container 기술 전에는 Virtual Machine(가상 머신) 기술이 있었다. 가상 머신을 통해 우리는 가상화 소프트웨어 시장을 열었고 대표적인 가상화 소프트웨어, 즉 Hypervisor에는 VMWare와 오라클의 VirtualBox, 마소의 Hyper-V 정도가 있다.

가상 머신은 소프트웨어로 하드웨어 자원을 구현한 것으로 완벽하게 하나의 가상 컴퓨터를 구현한다고 보면 된다. 이것은 매우 비효율적인 구성이다. 무려 OS위에 OS를 하나 더 두었기 때문이다.

하지만 Container 기술은 한 OS를 통해 가볍고 빠르게 가상화와 비슷한 작업을 진행할 수 있다. 어떻게 이것이 가능할까?

    VM과 Container의 차이

    VM과 Container의 비교, 사진을 클릭해 더 크게 볼 수 있다.

    새로운 머신을 완벽히 소프트웨어로 구현한 가상 머신 기술과 달리 컨테이너 기술은 어플리케이션을 그대로 호스트의 OS로 돌린다.

    이때 실행된 어플리케이션의 자원을 제한시키고 어플리케이션의 외부 리소스 접근 권한을 제한시키므로서 가상화와 비슷한, 즉 격리화 과정을 통해 어플리케이션을 특정 환경에서 동작하도록 할 수 있다.

    이 격리화 과정에 필요한 기능들은 커널단에서 직접 지원해야 하며 리눅스 커널에는 프로세스 격리 기능이 구현되어 있다.

    이 이유로 가상머신은 Windows, MacOS, Linux 등 다양한 OS위에서 돌릴 수 있지만 Docker와 Container 기술은 사실상 Linux OS위에서만 작동할 수 있다. Linux 커널에 있는 기능을 활용하는 것이기 때문이다.

    Daemon은 Supervisor와 다르게 Linux 커널에게 "이 프로그램을 cpu는 이정도 사용하고, ram은 이정도만 사용할 수 있도록 실행해줘." 라고 명령하는 역할만 할 뿐 어플리케이션과 Linux 커널 사이에 끼어 관여하지는 않는다.

    당연히 Container 기술은 한 OS만을 필요로 하기 때문에 가상 머신과 비교하여 빠르고 가볍다.

    하지만 Container 기술은 완벽한 가상화가 아니기 때문에 호스트의 Linux 커널이 변조되었다면 보안적인 문제가 발생하게 된다.

    Docker internals

    자 그럼 Container 기술 시장에서 가장 많이 사용되고 있는 Docker를 뜯어보자.

    Docker 내부를 뜯어본 그림

    Docker는 대략적으로 dockerd, containerd, container-shim, runc 등으로 이루워져 있다.

    dockerd는 컨테이너 기술을 사용자가 이용하기 편하도록 도커 이미지 관리, 네트워크 관리, 볼륨 계산 등을 하고 containerd에게 gRPC 통신으로 명령을 내린다.

    (여기서 gRPC는 HTTP/2 기반의 cross-language 통신(protobuf IDL)이다. 나중에 한번 정리해서 포스팅 해봐야 겠다.)

    containerd는 dockerd의 명령을 받아 컨테이너들의 메타데이터를 관리하고 스토리지 & 이미지 레이어 계산 등을 한다. 최종적으로 containerd는 여러개 containerd-shim를 실행시키며 컨테이너들의 라이프사이클을 관리한다.

    containerd-shim은 격리된 컨테이너의 부모 프로세스로 컨테이너가 종료될때까지 상주하며 컨테이너의 입력을 처리하고 상태 및 로그를 수집해 gRPC로 containerd에게 전송한다.

    runc는 리눅스 커널의 기능을 사용해 containerd-shim의 자식 프로세스로 격리된 컨테이너를 생성하며 생성후 종료된다.

    Step by Step

    자 그럼 저 구조가 맞는지 프로세스 목록을 보며 확인해보자.

    먼저 Idle 상태에서는 containerd와 dockerd가 켜져있는 것을 볼 수 있다. 여기서 dockerd의 커멘드라인을 잘 보면 containerd의 Unix Socket이 명시되어 있는것을 알 수 있다. 이 Unix Socket을 통해 gRPC 통신을 진행하게 된다.

    자 그럼 docker run -it ubuntu 를 실행해보자

    먼저 이미지가 없으므로 dockerd는 이미지를 다운로드 받고 컨테이너 생성전 필요한 잡다한 연산들을 진행하게 된다. 이때 dockerd의 CPU가 급증하는 것을 볼 수 있다.

    (타이밍을 못 맞춰서 다시 찍었어요)

    이후 dockerd는 containerd에게 컨테이너를 생성하도록 명령하고 containerd는 containerd-shim을 생성하는 것을 볼 수 있다.

    스크린샷에는 안나왔지만 containerd-shim에도 역시 containerd의 Unix Socket이 명시되어 있고 gRPC 통신을 한다.

    이후 containerd-shim은 자식으로 runc를 실행해 컨테이너가 Linux 커널 기능을 이용해 격리화된 프로세스를 만들도록 명령한다.

    격리화를 완료한 runc는 종료되고 격리화된 프로세스인 /bin/bash 를 containerd-shim의 자식으로 만든다.

    [deepdive] cgroup과 namespaces

    유료 구독하고 더 많은 내용을 확인해 보세요!

    이미 가입하셨나요? 여기를 눌러 로그인

    구독하고 더 많은 포스트들을 즐겨보세요!

    무료 가입 후 이메일로도 포스트를 보내드려요!
    your_name@example.com
    구독하기