2.3.3 고수준(High-level) 컨테이너 런타임

앞서 우리는 runc나 crun과 같은 저수준 컨테이너 런타임이 실제로 컨테이너 프로세스를 생성하고 격리하는 핵심 작업을 수행한다는 것을 배웠습니다. 마치 자동차 엔진처럼, 실제 구동력을 만들어내는 역할이죠. 하지만 자동차가 엔진만으로 굴러갈 수는 없습니다. 운전자의 조작을 받아 엔진에 명령을 전달하고, 연료를 공급하며, 각종 상태를 관리하는 시스템이 필요합니다. 컨테이너 세계에서도 마찬가지입니다.

저수준 런타임은 OCI 명세에 따라 컨테이너를 실행하는 ‘방법’에 집중합니다. 하지만 쿠버네티스 환경에서는 단순히 컨테이너를 실행하는 것 외에도 더 많은 일들이 필요합니다. 예를 들어, 원격 저장소에서 컨테이너 이미지를 가져오고(pull), 로컬에 저장 및 관리하며, 컨테이너를 위한 네트워크 인터페이스를 설정하고, 볼륨(저장 공간)을 연결하는 등의 복잡한 작업들이 수반됩니다. 또한, 쿠버네티스의 핵심 컴포넌트인 kubelet이 이러한 작업들을 일관된 방식으로 요청하고 제어할 수 있는 표준화된 인터페이스가 필요합니다.

바로 이 지점에서 고수준 컨테이너 런타임이 등장합니다. 고수준 런타임은 kubelet과 저수준 런타임 사이의 중요한 다리 역할을 수행합니다. kubelet으로부터 “이런 명세의 파드(Pod)를 실행해줘”라는 비교적 추상적인 요청을 받으면, 고수준 런타임은 이를 해석하여 다음과 같은 구체적인 작업들을 처리합니다.

  1. 필요한 컨테이너 이미지를 가져와 관리합니다.
  2. 컨테이너 실행에 필요한 네트워크 및 스토리지 설정을 준비합니다.
  3. 저수준 런타임(runc 등)을 호출하여 실제 컨테이너를 생성하고 실행합니다.
  4. 컨테이너의 상태를 모니터링하고 관리하며, kubelet에게 보고합니다.

이러한 상호작용을 표준화하기 위해 등장한 것이 바로 CRI(Container Runtime Interface)입니다.

2.3.3 고수준(High-level) 컨테이너 런타임

CRI는 kubelet과 컨테이너 런타임(고수준 런타임) 사이의 통신 규약(API 명세)입니다. 이 표준 덕분에 쿠버네티스는 특정 컨테이너 런타임 기술에 종속되지 않고, CRI 규격을 만족하는 다양한 런타임들과 유연하게 연동될 수 있게 되었습니다. 이는 클라우드 네이티브 생태계의 개방성과 확장성을 보여주는 대표적인 사례입니다.

이제 쿠버네티스 생태계에서 가장 널리 사용되는 대표적인 고수준 컨테이너 런타임 두 가지, containerd와 CRI-O에 대해 자세히 알아보겠습니다. 이 둘은 CRI를 구현한다는 공통점이 있지만, 탄생 배경과 설계 철학에서 차이를 보입니다.

2.3.3.1 containerd: CNCF Graduated 프로젝트

containerd는 현재 많은 쿠버네티스 환경에서 사실상의 표준처럼 사용되는 매우 인기 있는 고수준 컨테이너 런타임입니다. containerd의 역사를 알면 그 중요성을 더 잘 이해할 수 있습니다. 원래 containerd는 컨테이너 기술을 널리 알린 도커(Docker) 프로젝트 내부에서 컨테이너 실행 및 관리를 담당하던 핵심 컴포넌트였습니다. 즉, 도커 엔진의 심장부와 같은 역할을 했었죠.

하지만 컨테이너 기술이 성숙하고 업계 표준화의 필요성이 대두되면서, 도커는 이 핵심 컴포넌트인 containerd를 독립적인 프로젝트로 분리하여 2017년 CNCF(Cloud Native Computing Foundation)에 기증했습니다. CNCF는 쿠버네티스를 비롯한 다양한 클라우드 네이티브 기술들을 육성하고 표준화하는 중립적인 재단입니다. containerd는 CNCF 내에서 꾸준히 발전하여 가장 높은 등급인 졸업(Graduated) 프로젝트로 인정받았습니다. 이는 containerd가 기술적으로 성숙하고, 안정적이며, 커뮤니티에 의해 활발하게 관리되고, 실제 운영 환경에서 널리 사용되고 있음을 의미합니다.

containerd는 단순히 CRI 구현체로서의 역할만 하는 것이 아니라, 그 자체로 컨테이너의 전체 생명주기를 관리하는 데몬(daemon)으로 설계되었습니다. 주요 기능은 다음과 같습니다.

  • 이미지 관리: 컨테이너 레지스트리(예: Docker Hub, Harbor)에서 이미지를 가져오고(pull), 로컬에 저장하며, 필요시 내보내는(push) 기능을 제공합니다.
  • 스토리지 관리: 컨테이너 이미지 레이어와 컨테이너의 쓰기 가능한(writable) 레이어를 관리하기 위한 스냅샷 기능을 제공합니다.
  • 컨테이너 실행 관리: OCI 호환 저수준 런타임(runc가 기본)을 사용하여 컨테이너를 생성, 실행, 중지, 삭제하는 기능을 제공합니다.
  • 네트워크 관리: 컨테이너의 네트워크 네임스페이스 생성 및 관리, 네트워크 인터페이스 연결 등의 기능을 (주로 CNI 플러그인과 연동하여) 수행합니다.
  • API 제공: CRI 외에도 자체적인 gRPC 기반 API를 제공하여, 쿠버네티스뿐만 아니라 다른 플랫폼이나 도구에서도 containerd를 활용하여 컨테이너를 관리할 수 있도록 합니다. (실제로 현재의 도커 엔진도 내부적으로 이 containerd를 사용합니다.)

containerd의 큰 장점은 오랜 기간 개발되고 수많은 환경에서 검증되었다는 안정성과 성숙도, 그리고 쿠버네티스 외의 다양한 용도로도 활용될 수 있는 범용성입니다. 주요 클라우드 제공사(AWS, GCP, Azure 등)의 관리형 쿠버네티스 서비스(EKS, GKE, AKS)들도 내부적으로 containerd를 널리 사용하고 있습니다.

2.3.3.2 CRI-O: 쿠버네티스 중심 런타임

containerd가 도커에서 분리되어 범용적인 컨테이너 런타임으로 발전했다면, CRI-O는 처음부터 오직 쿠버네티스를 위한 경량 컨테이너 런타임이라는 명확한 목표를 가지고 개발되었습니다. 이름의 ‘CRI’는 Container Runtime Interface를, ‘O’는 OCI(Open Container Initiative)를 의미합니다.

CRI-O의 핵심 설계 철학은 최소주의(Minimalism)와 쿠버네티스 CRI 표준의 엄격한 준수입니다. containerd가 자체 API를 가지며 다양한 기능을 제공하는 것과 달리, CRI-O는 쿠버네티스의 kubelet이 요구하는 CRI 명세만을 정확하게 구현하는 데 집중합니다. 즉, 쿠버네티스 환경 외에서의 사용은 고려하지 않으며, 불필요한 기능이나 데몬을 포함하지 않아 가볍고 단순한 구조를 유지하려는 목표를 가지고 있습니다.

CRI-O의 작동 방식을 좀 더 살펴보면 다음과 같은 특징이 있습니다.

  • kubelet과의 통신: 오직 CRI gRPC 인터페이스를 통해서만 kubelet과 통신합니다. 별도의 자체 API는 제공하지 않습니다.
  • 저수준 런타임 연동: OCI 호환 저수준 런타임(runc 또는 crun 등)을 사용하여 컨테이너를 실행합니다. 어떤 OCI 런타임을 사용할지는 설정으로 지정할 수 있습니다.
  • 이미지 및 스토리지 관리: 이미지 관리에는 containers/image 라이브러리를, 스토리지 관리에는 containers/storage 라이브러리를 사용하는 등, 컨테이너 생태계의 검증된 다른 오픈소스 라이브러리들을 활용하여 기능을 구현합니다. 이는 CRI-O 자체를 가볍게 유지하는 데 도움이 됩니다.
  • 네트워크 관리: CNI(Container Network Interface) 표준을 사용하여 kubelet의 요청에 따라 파드 네트워킹을 설정합니다.

CRI-O의 가장 큰 장점은 쿠버네티스와의 긴밀한 통합과 가벼움입니다. 쿠버네티스 기능 변경에 발맞춰 CRI 구현을 빠르게 업데이트하는 경향이 있으며, 오직 필요한 기능만 포함하므로 잠재적인 공격 표면(attack surface)이 적고 리소스 사용량이 상대적으로 효율적일 수 있습니다. Red Hat의 OpenShift Container Platform 등 쿠버네티스 배포판에서 기본 런타임으로 채택되어 사용되고 있으며, 경량화된 쿠버네티스 환경을 선호하는 경우 좋은 선택지가 될 수 있습니다.

그렇다면 containerd와 CRI-O 중 무엇을 선택해야 할까요? 사실 최종 사용자 입장에서는 어떤 고수준 런타임을 사용하든 쿠버네티스 기능 자체에는 큰 차이를 느끼기 어려울 수 있습니다. 둘 다 CRI 표준을 충실히 구현하기 때문입니다. 선택은 주로 운영 환경의 요구사항, 기술 지원, 익숙함, 또는 특정 쿠버네티스 배포판의 기본 설정 등에 따라 달라질 수 있습니다. containerd는 범용성과 성숙도가 강점이며, CRI-O는 쿠버네티스 중심의 경량화와 단순함이 특징이라고 기억해두시면 좋겠습니다.

2.3.3.3 고수준 컨테이너 런타임의 주요 기능 (이미지 관리, 실행 등)

앞서 우리는 containerd와 CRI-O 같은 고수준 컨테이너 런타임이 쿠버네티스의 kubelet과 저수준 런타임 사이에서 중요한 다리 역할을 한다는 것을 살펴보았습니다. 이번에는 이 고수준 런타임들이 구체적으로 어떤 관리 기능들을 제공하는지, 그 역할과 중요성을 더 깊이 파고들어 보겠습니다. 단순히 컨테이너를 ‘켜고 끄는’ 것을 넘어, 컨테이너의 전체 생애 주기를 매끄럽게 관리하고 쿠버네티스 생태계의 다른 부분들과 유기적으로 협력하는 이들의 핵심 책임들을 자세히 들여다보는 것은 클라우드 네이티브 환경을 이해하는 데 필수적입니다. 고수준 런타임은 kubelet으로부터 받은 지시를 충실히 이행하는 일꾼이자 관리자로서 다음과 같은 중요한 기능들을 책임지고 수행합니다.

1. 컨테이너 이미지 관리 (Image Management): 애플리케이션의 청사진을 다루는 기술

컨테이너 이미지는 애플리케이션과 그 실행 환경을 패키징한 ‘청사진’과 같습니다. 고수준 런타임은 이 청사진을 효율적으로 가져오고, 보관하며, 관리하는 중요한 역할을 담당합니다.

  • 이미지 풀링(Pulling): 원격 저장소에서 청사진 가져오기kubelet이 특정 파드(Pod)를 노드에 배치하라는 명령을 내리면, 가장 먼저 해야 할 일 중 하나는 해당 파드에서 실행될 컨테이너들의 이미지를 확보하는 것입니다. 만약 필요한 이미지가 로컬 노드에 없다면, 고수준 런타임은 파드 명세(Pod Spec)에 지정된 이미지 이름(예: nginx:1.21 또는 my-registry.com/my-app:latest)을 보고 해당 이미지가 저장된 컨테이너 레지스트리(Container Registry)로 이미지를 요청합니다. Docker Hub, Google Container Registry(GCR), Amazon Elastic Container Registry(ECR) 같은 퍼블릭 레지스트리뿐만 아니라, 회사 내부에서 운영하는 Harbor나 Nexus와 같은 프라이빗 레지스트리도 대상이 될 수 있습니다.

    이 과정은 단순히 이미지를 다운로드하는 것 이상을 포함합니다. 만약 프라이빗 레지스트리처럼 인증이 필요한 경우, 쿠버네티스에 imagePullSecrets 등으로 미리 설정된 인증 정보를 사용하여 레지스트리에 안전하게 로그인하고 이미지를 가져옵니다. 또한, 쿠버네티스의 imagePullPolicy 설정(IfNotPresent, Always, Never)에 따라 이미지를 항상 새로 받을지, 로컬에 없으면 받을지, 아니면 절대 받지 않을지를 결정하여 정책에 맞는 동작을 수행합니다. 이는 개발 중에는 항상 최신 이미지를 사용하도록 강제하거나(Always), 운영 환경에서는 안정성을 위해 로컬 이미지를 우선 사용하도록(IfNotPresent) 유도하는 데 활용됩니다.

  • 이미지 저장 및 캐싱: 효율적인 보관과 재사용다운로드한 컨테이너 이미지는 여러 개의 레이어(layer)로 구성된 형태로 저장됩니다. 이는 OCI 이미지 명세(OCI Image Specification)에 따른 표준 방식입니다. 각 레이어는 파일 시스템의 변경 사항을 담고 있으며, 여러 이미지가 동일한 기반 레이어(예: 운영체제 기본 파일 시스템 레이어)를 공유할 수 있습니다. 고수준 런타임은 이러한 레이어들을 컨텐츠 주소 지정 가능 스토리지(Content-Addressable Storage) 방식으로 관리합니다. 즉, 각 레이어는 내용(content)을 해시(hash)한 고유 식별자(digest)를 가지며, 이 식별자를 통해 저장되고 참조됩니다.

    이 방식의 가장 큰 장점은 효율성입니다. 만약 여러 컨테이너 이미지가 동일한 베이스 이미지(예: ubuntu:20.04)를 사용한다면, 해당 베이스 이미지의 레이어들은 디스크에 단 한 번만 저장되고 여러 이미지에서 공유됩니다. 이는 디스크 공간을 크게 절약해주며, 이미 존재하는 레이어는 다시 다운로드할 필요가 없으므로 이미지 풀링 속도도 빨라집니다. 고수준 런타임은 이러한 레이어들을 효율적으로 관리하고, 컨테이너를 실행할 때 필요한 레이어들을 조합하여 컨테이너의 루트 파일 시스템을 구성하는 역할을 합니다. (내부적으로는 OverlayFS, Aufs 등의 스토리지 드라이버 기술을 활용합니다.)

  • 이미지 목록 조회 및 삭제: 청사진 목록 관리와 정리고수준 런타임은 현재 로컬 노드에 저장된 이미지들의 목록을 관리하고 조회하는 기능을 제공합니다. 또한, 더 이상 사용되지 않는 이미지를 정리하여 디스크 공간을 확보하는 역할도 중요합니다. 쿠버네티스의 kubelet은 주기적으로 노드의 디스크 사용량을 확인하고, 설정된 임계값(high/low watermark)을 초과하면 오래된 이미지를 삭제하는 이미지 가비지 컬렉션(Image Garbage Collection)을 수행하도록 고수준 런타임에게 요청합니다. 고수준 런타임은 이 요청에 따라 현재 실행 중인 컨테이너가 사용하지 않는 이미지를 안전하게 식별하고 삭제하는 작업을 수행합니다. 이 기능이 제대로 동작하지 않으면 노드의 디스크 공간 부족으로 새로운 파드를 스케줄링하지 못하는 문제가 발생할 수 있습니다.
2. 컨테이너 실행 및 생명주기 관리 (Execution and Lifecycle Management): 컨테이너의 탄생부터 소멸까지

고수준 런타임은 kubelet의 지시에 따라 컨테이너를 실제로 생성하고, 실행하며, 관리하는 전 과정을 책임집니다.

  • 컨테이너 생성 준비: 실행 환경 조성kubelet으로부터 파드 명세(Pod Spec)를 받으면, 고수준 런타임은 명세에 따라 컨테이너가 실행될 환경을 꼼꼼하게 준비합니다. 이 과정은 여러 단계로 이루어집니다.
    • 루트 파일 시스템 준비: 컨테이너 이미지의 레이어들을 기반으로, 컨테이너가 사용할 루트 파일 시스템(/)을 준비합니다. 여기에는 이미지의 읽기 전용 레이어 위에 컨테이너 내부에서의 변경 사항을 기록할 쓰기 가능한(writable) 레이어를 추가하는 작업이 포함됩니다.
    • 네임스페이스 설정: 컨테이너 격리의 핵심인 리눅스 네임스페이스(Namespaces)를 설정합니다. 특히 파드 내의 컨테이너들은 일반적으로 네트워크 네임스페이스를 공유하는데, 고수준 런타임은 CNI 플러그인과 협력하여 이 네트워크 환경을 구성합니다. (종종 파드의 네트워크 설정을 위한 ‘pause’ 컨테이너를 먼저 생성하기도 합니다.) 또한 PID, Mount, UTS, IPC, User 네임스페이스 등을 파드 명세에 따라 설정하여 컨테이너의 격리 수준을 결정합니다.
    • 볼륨 마운트: 파드 명세에 정의된 볼륨(Volumes)들을 컨테이너 내부의 지정된 경로(volumeMounts)에 마운트합니다. 이 볼륨들은 호스트의 디렉토리일 수도 있고, 네트워크 스토리지일 수도 있으며, 컨피그맵(ConfigMap)이나 시크릿(Secret) 같은 쿠버네티스 오브젝트일 수도 있습니다. 고수준 런타임은 kubelet (및 CSI 플러그인)이 준비한 볼륨을 컨테이너가 접근할 수 있도록 연결해주는 역할을 합니다.
    • 환경 변수 및 명령 설정: 파드 명세에 정의된 환경 변수(Environment Variables), 실행할 명령어(Command), 인자(Arguments) 등을 컨테이너 환경에 설정합니다.
    • 보안 설정 적용: 파드 명세의 securityContext에 정의된 보안 관련 설정들을 적용합니다. 여기에는 컨테이너 프로세스를 실행할 사용자 ID(UID) 및 그룹 ID(GID) 지정, 리눅스 케이퍼빌리티(Capabilities) 조정, Seccomp 프로파일 적용, AppArmor 또는 SELinux 프로파일 적용 등이 포함됩니다. 이는 컨테이너의 보안을 강화하는 데 매우 중요합니다.
  • 저수준 런타임 호출: 실제 실행 요청위의 모든 준비 작업이 완료되면, 고수준 런타임은 이 설정들을 OCI 런타임 명세에 맞는 config.json 파일로 생성합니다. 그리고 준비된 루트 파일 시스템의 경로와 함께 이 설정 파일을 저수준 컨테이너 런타임(runc 또는 crun 등)에게 전달하며 컨테이너 실행을 요청합니다. 즉, 고수준 런타임은 ‘무엇을 어떻게 실행할지’ 상세한 지시서를 만들고, 저수준 런타임은 그 지시서에 따라 실제 격리 환경을 만들고 프로세스를 실행하는 것입니다.
  • 컨테이너 상태 모니터링 및 관리: 생애 주기 추적과 제어컨테이너가 실행된 후에도 고수준 런타임의 역할은 계속됩니다. 실행 중인 컨테이너의 상태(예: Running, Exited, Paused)를 지속적으로 모니터링하고, 이 정보를 kubelet에게 보고합니다. kubelet이 파드의 상태를 판단하고 관리하는 데 이 정보는 필수적입니다.또한, 컨테이너 내부에서 표준 출력(stdout)과 표준 에러(stderr)로 출력되는 로그를 수집하여 kubelet이 kubectl logs 명령 등을 통해 사용자에게 보여줄 수 있도록 전달하는 역할도 수행합니다. 이는 애플리케이션 디버깅 및 모니터링에 매우 중요합니다.

    kubelet이 컨테이너를 정상적으로 종료(graceful shutdown)시키거나 강제로 중지(kill)해야 할 때, 또는 더 이상 필요 없는 컨테이너를 삭제(remove)해야 할 때도 고수준 런타임에게 해당 작업을 요청하며, 고수준 런타임은 이 요청에 따라 컨테이너의 생명주기를 제어합니다.

3. 네트워크 및 스토리지 연동: 외부 세계와의 연결

컨테이너는 격리된 환경이지만, 외부 네트워크와 통신하고 데이터를 영구적으로 저장하기 위해서는 외부 시스템과의 연동이 필요합니다. 고수준 런타임은 이러한 연동을 위한 중요한 연결 고리 역할을 합니다.

  • 네트워크 인터페이스 설정 (CNI 연동): 쿠버네티스에서는 파드의 네트워킹을 위해 CNI(Container Network Interface) 표준을 사용합니다. kubelet은 클러스터에 설정된 CNI 플러그인(예: Calico, Cilium, Flannel 등)을 사용하여 파드의 IP 주소 할당, 네트워크 인터페이스 생성, 라우팅 설정 등을 결정합니다. 고수준 런타임은 kubelet의 지시에 따라, 적절한 파라미터와 함께 해당 CNI 플러그인의 실행 파일(binary)을 호출하여 파드의 네트워크 네임스페이스 내에 네트워크 인터페이스를 실제로 생성하고 설정하는 작업을 수행합니다. 이 과정을 통해 파드는 클러스터 내의 다른 파드나 외부 서비스와 통신할 수 있게 됩니다.
  • 볼륨 마운트 (CSI 연동): 컨테이너가 데이터를 영구적으로 저장하거나 여러 컨테이너가 데이터를 공유하기 위해서는 볼륨(Volume)이 필요합니다. 쿠버네티스는 CSI(Container Storage Interface) 표준을 통해 다양한 스토리지 시스템(예: AWS EBS, GCP Persistent Disk, Ceph, NFS 등)을 일관된 방식으로 사용할 수 있도록 지원합니다. 스토리지 볼륨을 준비하고 노드에 연결(attach)하는 복잡한 과정은 주로 kubelet과 CSI 컨트롤러/노드 플러그인이 담당합니다. 일단 볼륨이 노드의 특정 경로에 준비되면, kubelet은 고수준 런타임에게 이 호스트 경로를 컨테이너 내부의 지정된 경로로 마운트하도록 지시합니다. 고수준 런타임은 이 지시에 따라 컨테이너의 마운트 네임스페이스 내에서 해당 마운트 작업을 수행하여 컨테이너가 볼륨에 접근할 수 있도록 합니다.

결론적으로, 고수준 컨테이너 런타임(containerd, CRI-O 등)은 쿠버네티스가 컨테이너 기반의 애플리케이션을 원활하게 배포하고 관리할 수 있도록 실질적인 실행 및 관리 작업을 총괄하는 매우 중요한 엔진입니다. 이미지 관리, 컨테이너 실행 및 생명주기 관리, 그리고 네트워크 및 스토리지와의 연동에 이르기까지, kubelet의 추상적인 요구사항을 구체적인 컨테이너 작업으로 변환하고 실행하는 핵심적인 역할을 수행합니다.

이 모든 작업이 CRI(Container Runtime Interface)라는 표준화된 약속을 통해 이루어지기 때문에, 쿠버네티스는 특정 런타임 기술에 얽매이지 않고 다양한 구현체들을 유연하게 선택하여 사용할 수 있습니다. 이는 클라우드 네이티브 생태계의 개방성과 혁신을 뒷받침하는 중요한 기반이 됩니다. 따라서 고수준 컨테이너 런타임의 이러한 기능들을 이해하는 것은 쿠버네티스의 내부 동작 원리를 파악하고 클라우드 네이티브 환경을 효과적으로 구축하고 운영하는 데 필수적이라고 할 수 있습니다.