그림(참고1)
컨테이너 기술은 계층적으로 구성되어 있다. 당연히 컨테이너 기술 활성화에 크게 기여한 도커도 예외가 아니다(참고1). 위 그림의 Docker 박스에서 출발하는 화살표를 따라가 보자. 도커 엔진(from1:도커와 도커 엔진)은 도커 데몬(docker-containerd)에게 일을 시킨다(참고8). 도커 데몬은 고수준 컨테이너 런타임이라고도 불린다(참고2,참고6). 도커의 고수준 컨테이너 런타임인 도커 데몬은 컨테이너 이미지를 빌드하고, 공유하고, 컨테이너 실행을 관리한다(참고7:컨테이너를 실질적으로 실행하는 것이 아니라 실행을 관리한다).
다시 도커 데몬은 저수준 컨테이너 런타임(참고3)이라고도 불리는 ‘진짜’ 도커 컨테이너 런타임(from2)에게 일을 시킨다(참고8). ‘진짜’ 도커 컨테이너 런타임은 격리된 환경을 구성하고(참고17) 컨테이너를 실행한다(참고5,참고9). 위 그림과 조금 다른 방식으로 이 관계를 표현하면 아래와 같다.
그림(참고4)
그렇다면 사람들이 컨테이너 기술을 설명할 때 이렇게 여러 추상 계층으로 나누고 쪼개어 설명하는 이유가 무엇일까? 가령 앞서 설명했던 컨테이너 런타임은 두 가지였다. 고수준 컨테이너 런타임과 저수준 컨테이너 런타임(저수준 컨테이너 런타임을 ‘컨테이너 런타임’ 이라고 부르는 경우가 많아서 이 글에서는 ‘진짜’ 라고 표현한 것)이다. 그런데 컨테이너 기술을 설명한 인터넷의 수많은 글들에서 둘을 가리지 않고 ‘컨테이너 런타임’ 이라고 퉁쳐 부르곤 해서 사람을 헷갈리게나 만드는 것 같다. 하지만 그들의 실수나 잘못이 아니다. 이렇게 세분화된 추상화 수준에서 기술을 다루는 목적을 이해해 보자.
내가 이해한 바에 따르면 가장 큰 목적은 ‘표준화’이다. 다양한 엔지니어링 요구를 충족시켜줄 수 있는 도구를 만들 수 있는 자유를 보장하면서도, 컨테이너의 본래 사용 목적인 이식성을 해치지 않도록 표준을 수립하기 위해서이다. 도커가 세상에 공개되고 컨테이너 기술을 이끌어 갔던 시절에, 컨테이너 런타임에는 원래 고수준 저수준 같은 것들이 없었다(참고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 : 언젠가 이 글에 쓰이면 좋을 것 같은 재료들.