도커 컨테이너

안녕하세요. kernel360 크루 김민규입니다.

이번 글에선 도커 컨테이너에 대해 이야기해보겠습니다.

컨테이너 = 리눅스 프로세스

도커 컨테이너는 가상머신이 아니라 리눅스 프로세스입니다.

도커는 리눅스의 커널이 제공하는 기술을 활용합니다.

“컨테이너는 리눅스 프로세스이며, 도커는 리눅스 커널이 제공하는 기능을 사용하는 API 데몬이다.”

image

<Red Hat 컨퍼런스 발표 영상>

그래서 윈도우, 맥에서는 다음의 과정을 거쳐 도커가 실행됩니다.

image

  1. 윈도우나 맥OS의 하이퍼바이저 가상화 기술을 사용하여 윈도우/맥OS에서 리눅스 가상머신을 실행.
  2. 실행된 리눅스 가상 머신 커널을 사용해서 컨테이너 환경을 구성합니다.

컨테이너는 리눅스 커널이 제공하는 아래 2가지 기능을 이용해 격리됩니다.

(1) Cgroups

프로세스가 자원을 얼마나 사용하게 할지 결정한다

image

cgroups는 프로세스들의 자원 사용(cpu, 메모리, 네트워크)를 제한하고 격리시키는 Linux 커널 기능입니다.

cgroups를 이용하면, 하드웨어 자원의 사용 범위를 어플리케이션마다 다르게 할당할 수 있습니다. (리눅스 메뉴얼)

(2) Namespace

image

프로세스가 어떤 자원을 볼 수 있는지 결정한다

프로세스의 다양한 측면을 격리하는 Linux 커널 기능입니다.

한 프로세스 집합은 한 리소스 집합을 보고 다른 프로세스 집합은 다른 리소스 집합을 보도록 리소스를 분할하는 기능입니다. (리눅스 메뉴얼)

  • 네임스페이스는 mount, uts, ipc, pid, neet, user까지 6가지 종류가 있습니다.
  • /proc/<pid>/ns 경로로 직접 확인할 수 있습니다.

    image

  • nsenter 명령어로 특정 네임스페이스에 접속할 수 있습니다.

    image

위 6가지 네임스페이스 중, 2가지만 이야기해보겠습니다.

  1. 마운트 네임스페이스

    image

    파일 시스템 마운트 포인트 격리하는 기능입니다.

    • 프로세스는 각자의 root 파일 시스템 (=chroot 와 유사)을 가질 수 있습니다.
    • 프로세스는 각자만의 Private 마운트를 가질 수도, 공유된 마운트를 가질 수 도 있습니다.

    image

    도커 컨테이너의 마운트는 커널의 mnt 네임스페이스 기능입니다.

    따라서 도커 컨테이너에 exec -it bin/bash 명령어로 접속한 뒤, ls 명령어를 실행한 결과는 namesapace로 접속한 마운트 경로와 실제로 동일합니다.

    image

  2. PID 네임스페이스

    PID (Process ID) Numberspace를 격리하는 기능입니다.

    image

    • 특정 PID 네임스페이스에 속한 프로세스는 같은 네임스페이스에 속한 프로세스만 볼 수 있습니다.
    • 각 PID 네임스페이스마다 1번 PID를 가집니다.
    • PID 1 프로세스가 종료되면, 전체 네임스페이스가 종료됩니다.
    • PID 네임스페이스는 중첩된 구조를 가질 수 있기에, 여러개의 PID 를 갖게 됩니다. (네임스페이스당 1개씩, 여러개)

    image

    1번 PID를 가진 프로세스는 특별한 프로세스입니다. pid 1은 init 프로세스로 커널이 생성합니다.

    image

    다음은 1번 프로세스의 특징입니다.

    1. 시그널 처리 기능 (커널이 보내는 시그널을 자식 프로세스에게 전파)
    2. 좀비, 고아 프로세스 정리
    3. 1번 프로세스가 죽으면 시스템 패닉 -> reboot 으로만 해결 가능

    도커 프로세스는 자신의 PID 네임스페이스를 갖게 됩니다.

    따라서 컨테이너 내부로 접속해서 ps 명령어를 실행하면, 컨테이너 실행 태스크가 곧 PID 1번을 갖게 됩니다.

    image

    우리가 컨테이너 Stop 명령어를 실행하면, 도커는 해당 PID 네임스페이스의 1번에게 종료 시그널을 보냅니다. 이로 인해 PID 1번이 종료되고, 해당 네임스페이스가 닫힙니다.

    image

    이때 주의할 점은, Init 프로세스와 다르게 컨테이너의 PID 1번은 자식 프로세스에게 종료 시그널을 전파하지 않는다는 점입니다. 따라서 컨테이너를 생성한 개발자가 직접 시그널을 전파하는 작업을 해줘야 합니다.

    image

    결국 도커는 리눅스의 기능을 활용하는 api 데몬이며, 내부적으로 리눅스 커널이 제공하는 CgroupsNamespace 기능을 이용해 컨테이너를 생성합니다.

    이번 글에서 도커가 내부적으로 어떻게 컨테이너를 생성하는지 간략히 소개해보았습니다.

    앞으로 프로젝트를 하며 도커를 사용함에 있어 작은 도움이 되셨길 바라며 글을 마치겠습니다.