그림(참고1)

Untitled

컨테이너 기술은 계층적으로 구성되어 있다. 당연히 컨테이너 기술 활성화에 크게 기여한 도커도 예외가 아니다(참고1). 위 그림의 Docker 박스에서 출발하는 화살표를 따라가 보자. 도커 엔진(from1:도커와 도커 엔진)은 도커 데몬(docker-containerd)에게 일을 시킨다(참고8). 도커 데몬고수준 컨테이너 런타임이라고도 불린다(참고2,참고6). 도커의 고수준 컨테이너 런타임인 도커 데몬은 컨테이너 이미지를 빌드하고, 공유하고, 컨테이너 실행을 관리한다(참고7:컨테이너를 실질적으로 실행하는 것이 아니라 실행을 관리한다).

저수준 컨테이너 런타임은 컨테이너를 실제 실행하는 역할을 하지만 이미지로부터 컨테이너를 실행하려면 이미지와 관련된 API 같은 기능이 필요합니다. 이러한 기능은 고수준 컨테이너 런타임에서 제공됩니다.(참고5)

다시 도커 데몬은 저수준 컨테이너 런타임(참고3)이라고도 불리는 ‘진짜’ 도커 컨테이너 런타임(from2)에게 일을 시킨다(참고8). ‘진짜’ 도커 컨테이너 런타임은 격리된 환경을 구성하고(참고17) 컨테이너를 실행한다(참고5,참고9). 위 그림과 조금 다른 방식으로 이 관계를 표현하면 아래와 같다.

그림(참고4)

Untitled

그렇다면 사람들이 컨테이너 기술을 설명할 때 이렇게 여러 추상 계층으로 나누고 쪼개어 설명하는 이유가 무엇일까? 가령 앞서 설명했던 컨테이너 런타임은 두 가지였다. 고수준 컨테이너 런타임과 저수준 컨테이너 런타임(저수준 컨테이너 런타임을 ‘컨테이너 런타임’ 이라고 부르는 경우가 많아서 이 글에서는 ‘진짜’ 라고 표현한 것)이다. 그런데 컨테이너 기술을 설명한 인터넷의 수많은 글들에서 둘을 가리지 않고 ‘컨테이너 런타임’ 이라고 퉁쳐 부르곤 해서 사람을 헷갈리게나 만드는 것 같다. 하지만 그들의 실수나 잘못이 아니다. 이렇게 세분화된 추상화 수준에서 기술을 다루는 목적을 이해해 보자.

내가 이해한 바에 따르면 가장 큰 목적은 ‘표준화’이다. 다양한 엔지니어링 요구를 충족시켜줄 수 있는 도구를 만들 수 있는 자유를 보장하면서도, 컨테이너의 본래 사용 목적인 이식성을 해치지 않도록 표준을 수립하기 위해서이다. 도커가 세상에 공개되고 컨테이너 기술을 이끌어 갔던 시절에, 컨테이너 런타임에는 원래 고수준 저수준 같은 것들이 없었다(참고18). 그렇게 고민할 필요도 나눌 필요가 없었기 때문이다. 따라서 그때 당시에 작성된 글들은 고수준 컨테이너 런타임이고 저수준 컨테이너 런타임이고 별로 고민할 것이 없었을 것이다. 하지만 그 이후 컨테이너 사용이 점점 확산되며, 다양한 엔지니어링 수요들이 생겨나기 시작했다. 예를 들어, 빌드된 컨테이너 이미지를 실행하기만 하면 되는 서버에서는 이미지를 빌드하는 기능이 필요가 없는 상황이 있다고 생각해 보자(참고20). 리소스가 충분하지 않은 서버에서는 컨테이너 런타임이 너무 무겁게 느껴질 수 있기에 본질적이지 않은 요소들을 분리할 필요가 있다고 주장할 것이다. 그래서 원래 하나처럼 여겨지던 것들이 다양한 수요를 충족시키기 위해 여러 추상화 단계로 나뉘며 고수준 저수준 따위의 용어가 생겨났을 뿐이다(참고18:Portability). 의도가 선량하다는 것을 이해하면 헷갈리는 단어들을 받아들이기 조금 더 쉬울 것 같다.

그 어떤 데몬이든 OCI(Open container Initiative)스펙만 맞추면 ‘진짜 컨테이너 런타임’(저수준 컨테이너 런타임)에 명령을 내려 OCI스펙을 준수하는 컨테이너를 실행할 수 있게 된 것이다(참고11:당연히 컨테이너 이미지도 OCI스펙을 준수해야 한다). 도커의 RunC는 물론이고, Curn(참고12), Kata(참고13), gVisor(참고14)같은 도구들도 OCI 표준을 준수(OCI-Complient)하는 저수준 컨테이너 런타임과 컨테이너 이미지라고 볼 수 있다.

미니쿠브 설치 문서 스크린샷. 미니쿠브를 설치 시 다양한 (고수준) 컨테이너 런타임을 선택할 수 있도록 제공하고 있다.

미니쿠브 설치 문서 스크린샷. 미니쿠브를 설치 시 다양한 (고수준) 컨테이너 런타임을 선택할 수 있도록 제공하고 있다.

그 어떤 엔진이든 CRI(Kubernetess Container Runtime Interface) 스펙(참고19,참고22:쿠버네티스 표준)만 맞추면 그 어떤 쿠버네티스(from3) 도구들이든 상관없이(참고21) 그 어떠한 고수준 컨테이너 런타임(데몬)에든 명령을 내릴 수 있게 되었다(참고10,참고16:컨테이너 런타임 인터페이스라고 해서 저수준 컨테이너 런타임 인터페이스라고 헷갈리지 말자). 중간에 껴 있는 고수준 컨테이너 런타임은 자연스레 CRI 와 OCI 를 동시에 준수해야 쿠버네티스는 물론 저수준 컨테이너 런타임과도 상호작용할 수 있게 된다. 대표적으로는 CRI-O 가 있다(참고15).

정리하면 아래 표와 같다.

Container engine Interface High level container runtime Interface Low level container runtime
- 표준: CRI 별명: Daemon 표준: OCI 별명: Container runtime
역할: 다양한 기능 역할: 이미지 빌드(참고20:구현체마다 다름), 공유(참고20:구현체마다 다름), 실행명령 역할: 컨테이너 실행
대표적 구현체: docker 대표적 구현체: docker-containerd(참고22) 대표적 구현체: RunC
대체체: LXC(from1) 대체제: CRI-O(참고20) 대체제: Curn, Kata, gVisor

컨테이너 런타임과 컨테이너 엔진의 관계는 커널과 셸에 대응할 수 있지 않을까?


parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료들.

  1. LXC, LibContainer, runC 등은 위에서 설명한 cgroups, namespaces를 표준으로 정의해둔 OCI(Open Container Initative) 스펙을 구현한 컨테이너 기술의 구현체입니다. … Docker는 libcontainer -> runC (libcontainer의 리팩토링 구현체)로 자체 구현체를 갖게 되었습니다. → LXC와 RunC는 다른 추상계층으로 보는 것이 낫지 않나… 그래서 섣불리 못 붙이는 중.
  2. ‣ → 도커가 표준 준수에 순순히 응한 이유가 무엇일까? 오픈소스를 감싸서 사용성 좋게 만든 컨테이너 엔진 정도로는 엄청난 해자를 확보하기 어려울뿐더러, Github같은 플랫폼을 가지고 있는 MS에 비해 너무나도 작고 초라한 docker같은 회사가 표준화에 반대하며 과거의 MS처럼 독자노선을 나섰다가 괜히 손해만 볼지도 모른다(구글에게 MS가 된통 털렸듯)는 공포감도 한몫했겠군.