사전 컨테이너 런타임
개념

컨테이너 런타임

gabury1고친 사람 github-actions[bot]

컨테이너 런타임은 컨테이너를 띄우고 지키다가 치우는 프로그램입니다. 컨테이너가 쓸 파일을 담아 둔 꾸러미, 곧 컨테이너 이미지를 풀어 놓고 프로세스를 띄웁니다. 그 프로세스는 남의 파일과 프로세스를 못 보고, 쓸 수 있는 자원에도 한도가 걸립니다. 도커나 쿠버네티스에 「컨테이너를 띄워라」라고 시켰을 때 그 일을 손수 하는 것이 이 프로그램입니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 컨테이너 한 개를 띄우고 지켜보다가 치우는 프로그램입니다. 서버에서 컨테이너를 하나 띄우라고 명령하면, 컨테이너가 쓸 파일을 담아 둔 꾸러미(이미지)를 풀고 프로세스를 띄우는 손일은 이 프로그램이 합니다.

왜 이렇게 하나 — 컨테이너를 띄우는 일은 리눅스 커널 기능 여러 개를 정해진 차례로 엮는 잔손 많은 작업입니다. 그 잔손을 한 프로그램에 모아 두면, 위에 있는 도구들은 「이 이미지를 이렇게 띄워라」만 건네면 됩니다.

어떻게 도나

  1. 이미지를 내려받아 디렉터리 하나로 풉니다
  2. 그 디렉터리를 뿌리로 삼고, 커널에 격리와 한도를 걸어 프로세스를 띄웁니다
  3. 그 프로세스가 끝날 때까지 지켜보다가 뒷정리를 합니다

대가 — 컨테이너들이 커널 하나를 나눠 쓰므로 가상 머신만큼 벽이 두껍지 않습니다. 런타임이 커널과 맞물려 돌기 때문에 커널 판이나 설정이 바뀌면 같이 영향을 받습니다.

상세

보이는 것은 프로그램 하나지만, 실무에서 이 이름은 층이 다른 두 물건에 걸쳐 쓰입니다.

컨테이너를 띄운다는 것

컨테이너는 따로 떼어 놓은 기계가 아니라 프로세스 하나입니다. 다른 프로세스와 같은 커널 위에서 돕니다. 다만 볼 수 있는 것과 쓸 수 있는 것이 좁게 잘려 있습니다.

무엇을 못 보게 할지는 네임스페이스가 정합니다. 프로세스 목록 · 파일 시스템의 뿌리 · 네트워크 장치 · 호스트 이름을 종류마다 따로 잘라 줍니다. 그래서 컨테이너 안에서 프로세스 목록을 뽑으면 자기 것 몇 개만 보입니다.

얼마나 쓸 수 있는지는 cgroup(control group, 컨트롤 그룹)이 정합니다. 메모리와 프로세서 시간에 한도를 걸어 두면, 한 컨테이너가 기계 전체를 먹어 치우는 것을 커널이 막습니다.

마지막으로 그 프로세스가 읽을 파일 더미가 있어야 합니다. 컨테이너 이미지를 풀어 놓은 디렉터리가 그것이고, 이것을 루트 파일 시스템이라고 부릅니다.

이 디렉터리를 뿌리 디렉터리로 바꿔치기하면, 컨테이너 안에서는 그 디렉터리가 / 로 보입니다. 이 바꿔치기를 맡는 커널 기능이 pivot_root입니다.

지금까지 본 셋을 한 줄씩 정리하면 이렇습니다.

커널 기능 이 컨테이너에 대해 정하는 것
네임스페이스 무엇을 볼 수 있나
cgroup 얼마나 쓸 수 있나
뿌리 디렉터리 바꿔치기 어느 루트 파일 시스템을 쓰나

컨테이너 런타임은 이 셋을 정해진 차례로 엮어 프로세스를 띄웁니다. 그 프로세스가 끝나면 남은 것을 치웁니다. 런타임이 없다면 컨테이너를 띄울 때마다 이 절차를 손으로 다시 짜야 합니다. 차례를 하나만 어긋나게 짜도 격리가 덜 걸린 채로 프로세스가 떠 버립니다.

한 프로그램이 아니라 두 층

컨테이너 런타임이라고 부르는 물건은 하나가 아닙니다. 어느 층을 말하는지 밝히지 않으면 같은 낱말로 다른 물건을 이야기하게 됩니다.

아래층 — 저수준 런타임 — 은 컨테이너 한 개를 띄우는 일만 합니다. 이미지가 무엇인지도, 그 이미지를 어디서 받아 왔는지도 모릅니다. 받는 것은 디렉터리 하나뿐이고, 이 디렉터리를 번들이라고 부릅니다.

번들에는 두 가지가 들어 있습니다.

번들이 담는 것 무엇이 적혀 있나
루트 파일 시스템 컨테이너 안에서 / 로 보일 디렉터리
설정 파일 어떤 명령을 띄울지 · 무엇을 잘라 낼지 · 한도를 얼마로 걸지

위층 — 고수준 런타임 — 은 그 번들을 만들어 줍니다. 이미지를 저장소에서 내려받습니다. 겹쳐 쌓인 이미지 레이어를 풀어 한 디렉터리로 합치고, 설정 파일을 지어 저수준 런타임에 넘깁니다. 컨테이너가 뜬 뒤에도 로그를 모으고 죽은 컨테이너를 다시 띄우는 일을 맡습니다.

두 층에 붙은 이름과 대표 프로그램은 이렇습니다.

층 맡는 일 대표
저수준 런타임 커널에 격리와 한도를 걸고 프로세스 띄우기 runc · crun
고수준 런타임 이미지 내려받기 · 풀기 · 생명주기 돌보기 containerd · CRI-O

이렇게 가른 까닭은 저수준 런타임을 갈아 끼우기 위해서입니다. 번들의 모양과 저수준 런타임이 받아야 할 명령은 OCI(Open Container Initiative, 오픈 컨테이너 이니셔티브)가 정해 두었습니다. 그 모양만 지키면 다른 저수준 런타임을 같은 자리에 꽂을 수 있습니다.

꽂히는 자리를 그림으로 보면 이렇습니다.

flowchart TD
    A["상위 도구 · 도커 · 쿠버네티스"]
    subgraph HI["고수준 런타임"]
        H1[containerd]
        H2[CRI-O]
    end
    B["번들 · OCI 가 정해 둔 경계"]
    subgraph LO["저수준 런타임 · 같은 번들을 받는다"]
        L1[runc]
        L2[crun]
        L3["가상 머신을 끼우는 런타임"]
    end
    K[커널]
    A --- HI
    HI --- B
    B --- LO
    LO --- K

덕분에 컨테이너를 보통 프로세스로 띄우지 않는 런타임도 이 층에 들어옵니다. 컨테이너마다 작은 가상 머신을 하나씩 띄워 그 안에서 프로세스를 돌리는 Kata Containers가 그렇습니다. 위층이 넘기는 번들은 같고, 그 번들을 무엇으로 실행하느냐만 다릅니다.

위에서 런타임을 부르는 통로

쿠버네티스는 기계마다 kubelet이라는 프로그램을 띄워 두고 그 기계의 컨테이너를 맡깁니다. kubelet 자신은 컨테이너를 못 띄웁니다. 띄우는 일은 고수준 런타임에게 넘깁니다.

둘 사이를 잇는 규약이 CRI(Container Runtime Interface, 컨테이너 런타임 인터페이스)입니다. kubelet 은 이 규약에 적힌 대로 「이 컨테이너를 띄워라」·「이 컨테이너를 죽여라」라고 부르고, 그 아래에서 무엇이 도는지는 묻지 않습니다.

kubelet 이 부른 명령 하나가 커널까지 내려가는 길은 이렇습니다.

sequenceDiagram
    participant K as kubelet
    participant H as 고수준 런타임
    participant L as 저수준 런타임
    participant N as 커널
    K->>H: 이 이미지로 컨테이너를 띄워라
    H->>H: 이미지를 내려받아 번들로 푼다
    H->>L: 이 번들을 띄워라
    L->>N: 격리와 한도를 걸고 프로세스를 띄운다
    N-->>L: 프로세스가 떴다
    L-->>H: 떴다
    H-->>K: 컨테이너가 돈다

규약을 사이에 두면 런타임을 바꿔도 위쪽이 그대로입니다. 한 기계가 쓰던 고수준 런타임을 다른 것으로 옮겨도 kubelet 이 부르는 명령과 인자는 같습니다. 대신 규약의 판이 오르면 런타임도 그 판을 따라가야 합니다. 판이 안 맞으면 kubelet 이 아예 그 런타임에 붙지 못합니다.

가상 머신과 가르는 선

프로그램을 남과 떼어 놓고 돌리는 길이 컨테이너만 있는 것은 아닙니다. 하이퍼바이저로 가상 머신을 만들어 그 안에서 돌릴 수도 있습니다. 하이퍼바이저는 기계를 통째로 흉내 내므로 커널까지 따로 줍니다.

둘을 가르는 선은 커널을 나눠 쓰느냐입니다. 컨테이너 런타임이 띄운 프로세스는 호스트의 커널을 그대로 씁니다. 그래서 띄우는 데 드는 준비가 적고 메모리도 덜 먹습니다. 커널 하나를 여럿이 나눠 쓴다는 것이 값이자 대가입니다.

두 길을 나란히 놓으면 이렇습니다.

flowchart TD
    subgraph 컨테이너["컨테이너 · 커널 하나를 나눠 쓴다"]
        C1[컨테이너 프로세스] --- HK[호스트 커널]
        C2[컨테이너 프로세스] --- HK
    end
    subgraph 가상머신["가상 머신 · 커널을 따로 받는다"]
        V1["가상 머신 · 게스트 커널"] --- HV[하이퍼바이저]
        V2["가상 머신 · 게스트 커널"] --- HV
    end
    컨테이너 ~~~ 가상머신

대가는 벽의 두께입니다. 커널에 구멍이 나면 그 구멍은 그 커널을 쓰는 모든 컨테이너에 열립니다. 격리를 커널이 걸어 주는 이상, 커널 자체가 뚫리는 경우는 런타임이 막지 못합니다. 남이 올린 코드를 받아 돌리는 곳에서 Kata Containers 처럼 컨테이너마다 가상 머신을 끼우는 런타임을 고르는 까닭이 이것입니다.

관련 항목

런타임이 만들고 치우는 대상

컨테이너 · 컨테이너 이미지 · 번들 · 루트 파일 시스템 · 프로세스 · 파드

컨테이너를 가두려고 런타임이 쓰는 커널 기능

네임스페이스 · cgroup · pivot_root · chroot · seccomp · capabilities · SELinux · AppArmor · 커널 · 시스템 콜

프로세스를 직접 띄우는 저수준 런타임

runc · crun · youki · gVisor · Kata Containers

이미지와 생명주기를 맡는 고수준 런타임

containerd · CRI-O · Docker · Podman · LXC

런타임에게 컨테이너를 시키는 상위 도구

Kubernetes · kubelet · 오케스트레이션 · 도커 데몬 · nerdctl · crictl

런타임이 따르는 규격과 층 사이의 규약

OCI · OCI 런타임 명세 · OCI 이미지 명세 · CRI · CNI

이미지를 받아 오는 저장소

컨테이너 레지스트리 · Docker Hub · 이미지 태그 · 이미지 레이어 · 다이제스트

같은 격리를 다른 길로 이루는 기술

가상 머신 · 하이퍼바이저 · KVM · 가상화 · 격리 · 샌드박스

컨테이너를 돌릴 때 나는 장애

OOM 킬 · 컨테이너 탈출 · 좀비 프로세스 · CrashLoopBackOff · 스로틀링

다른 이름: container runtime · 컨테이너런타임