전 글에서는 컨테이너란 무엇인지, 어떻게 구성되는 지 살펴봤습니다. 이제 본격적으로 컨테이너의 런타임에 대해서 알아보려 합니다.

Container Runtimes

2013년 Docker 가 출시되면서 end-to-end 에서 컨테이너가 실행될 때의 문제점 대부분이 해결되었습니다.

  • 컨테이너 이미지 포맷
  • 컨테이너 이미지 빌드하는 방법 (Dockerfile/docker build)
  • 컨테이너 이미지를 관리하는 방법 (docker images, docker rm, etc.)
  • 컨테이너 인스턴스를 관리하는 방법 (docker ps, docker rm, etc.)
  • 컨테이너 이미지를 공유하는 방법 (docker push/pull)
  • 컨테이너를 실행시키는 방법 (docker run)

출시 당시 Docker 는 모놀로딕한 시스템이였습니다. 하지만 각 부분 기능들은 서로에게 의존하지 않았고 각 도구들은 컨테이너 기준에 기반하여 동작했습니다. 또한 컨테이너는 기술적으로 매우 혁신적이었지만, 컨테이너 이미지와 런타임은 다양한 형태로 구현되어 있어, 이식성이 떨어지고 보안, 호환성 등의 이슈가 발생했습니다.

이로인해 Docker, Google, CoreOS 등은 Open Container Initiative(OCI) 를 만들었습니다. OCI는 컨테이너 이미지와 런타임의 표준화를 위한 스펙을 제공하고, 이를 준수하는 컨테이너 런타임과 이미지를 인증하는 과정을 거칩니다. 이를 통해 다양한 환경에서 컨테이너를 이식성 있게 사용할 수 있게 되었고, 보안성과 호환성이 향상되었습니다.

하지만 처음 Docker 가 표준을 설립하는 과정에서 컨테이너 런타임에 대한 혼란이 발생합니다. Docker 는 실제 컨테이너의 실행에만 초점을 맞추었습니다. 이미지의 pull/push 포맷 등은 포함되지 않았습니다. 아래는 실제 Docker 컨테이너를 실행할 때의 과정입니다.

  1. 이미지 다운로드
  2. 이미지를 “bundle” 에 압축해제한다.
  3. bundle 로부터 컨테이너를 실행한다.

위 세 과정 중에서 표준화된 것은 오직 3번입니다. 이러한 차이 때문에 컨테이너 실행을 위한 표준을 만드는 OCI와 Docker는 모두 “컨테이너 런타임”이라는 용어를 사용하지만 살짝 다른 의미를 가지고 있습니다. 이것이 오늘날에도 계속되어 컨테이너 런타임을 혼란스럽게 만드는 이유가 되었습니다.

마지막으로 정리해보자면 OCI는 이미지 포맷과 레지스트리와의 상호 작용, 이미지 내 레이어의 정의, 레이어 병합 등과 같은 기능을 다루는 표준을 제공하지만, 실제로 컨테이너 이미지를 다운로드하고 풀어 헤치는 작업은 OCI 스펙의 일부가 아닙니다. 이러한 작업은 OCI와 함께 사용되는 런타임에 의해 처리됩니다.

Docker runtime은 Docker에서 직접 만든 런타임으로, Docker가 OCI에 참여하기 전에 이미 있던 것입니다. 위에서 명시했듯이 컨테이너 이미지를 다운로드하고 풀어 헤치는 작업까지 모두 포함되어있습니다. Docker runtime은 Docker 이미지와 호환되는 Docker-specific한 컨테이너 런타임이며, Docker가 관리하고 배포하는 도구와 밀접하게 통합됩니다.

저수준 및 고수준 컨테이너 런타임

컨테이너 런타임에는 여러 가지 런타임이 존재합니다. runc, lxc, lmctfy, docker(containerd), rkt, cri-o 등은 각각이 서로 다른 상황에 맞게 구축되어있으며 서로 다른 기능을 가지고 있습니다. containerd 및 cri-o 와 같은 일부는 실제 runc 를 사용해 컨테이너를 실행하고 그 위에서 이미지 관리 및 API를 구현합니다. 따라서 이미지 전송, 이미지 관리, 이미지 압축 해제 및 API 등의 기능들은 runc 에 비해 상위 수준이라고 말할 수 있습니다.

image

위의 그림은 각 런타임에 대한 수준을 대략적으로 나타낸 다이어그램입니다.

“Low-level container runtimes”은 컨테이너를 실행하는 데 중점을 둔 런타임을 의미하며, “High-level container runtimes”은 이미지 관리 및 gRPC/Web API와 같은 높은 수준의 기능을 지원하는 런타임을 의미합니다. 이들은 다른 문제를 해결하고 서로 다른 방식으로 동작하므로, “low-level runtimes”과 “high-level runtimes”은 기본적으로 서로 다른 것으로 간주됩니다.

컨테이너는 기본적으로 Linux namespace 와 cgroup 을 사용해 구현됩니다. namespace 를 사용하면 각 컨테이너에 대해 파일 시스템 또는 네트워킹과 같은 시스템 리소스를 가상화할 수 있습니다. Cgroup 은 각 컨테이너가 사용할 수 있는 CPU 및 메모리와 같은 리소스 양을 제한하는 방법을 제공합니다. 이는 컨테이너 실행을 위한 가장 기초적인 단계로써 “Low-level container runtimes” 에서 실행됩니다. 저수준 런타임은 운영 체제 기능을 사용해 네임스페이스 및 cgroup을 설정한 다음 명령을 실행하는 일을 담당합니다.

일반적으로 컨테이너에서 앱을 실행하려는 개발자에게는 저수준 런타임이 제공하는 기능 이상이 필요합니다. 이미지 포맷, 이미지 관리, 이미지 공유 및 API 등이 필요합니다. 이러한 기능은 “High-level container runtimes” 에서 제공됩니다.

저수준 런타임 개발자의 입장에서는 고수준 런타임은 containerd 및 cri-o 는 컨테이너 런타임이 아니라고 말할 수 있습니다. 컨테이너의 실행 구현을 runc 를 통해서 진행하기 때문입니다. 하지만 사용자 관점에서는 고수준 런타임까지 모두 컨테이너를 실행할 수 있는 기능을 제공하는 단일 구성 요소이기에 해당 관점에서 고수준 런타임도 컨테이너 런타임이라 말할 수 있습니다!

업데이트:

댓글남기기