Home 컨테이너 런타임, CRI, OCI
Post
Cancel

컨테이너 런타임, CRI, OCI

컨테이너 런타임


Untitled

  • OCI (Open Container Initiative)가 만들어질 당시 비공식적 표준 역할을 하던 도커는 컨테이너 런타임의 표준화를 위해 필요한 모든 단계가 아닌 세 번째 단계인 컨테이너의 실행 부분만 표준화하였습니다.
  • 이로 인해 컨테이너의 런타임은 실제 컨테이너를 실행하는 저수준 컨테이너 런타임인 OCI 런타임컨테이너 이미지의 전송 및 관리, 이미지 압축 풀기 등을 실행하는 고수준 컨테이너 런타임으로 나뉘게 되었습니다.

저수준 컨테이너 런타임(Low-Level Container Runtimes)


  • 컨테이너는 Linux namespace와 cgroup을 사용하여 구현합니다. namespace는 각 컨테이너에 대해 파일 시스템이나 네트워킹과 같은 시스템 리소스를 가상화하고 cgroup은 각 컨테이너가 사용할 수 있는 CPU 및 메모리와 같은 리소스 양을 제한하는 역할을 합니다.
  • 저수준 컨테이너 런타임은 이러한 namespace와 cgroup을 설정한 다음 해당 namespace 및 cgroup 내에서 명령을 실행합니다.
  • OCI를 준수하는 저수준 컨테이너 런타임으로 가장 잘 알려진 것은 runC입니다. runC는 원래 도커에서 컨테이너를 실행하기 위해 개발되었으나, OCI 런타임 표준을 위해 독립적인 라이브러리로 사용되었습니다.

    Untitled

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

고수준 컨테이너 런타임(High-Level Container Runtimes)


  • 일반적으로 고수준 컨테이너 런타임은 원격 애플리케이션이 컨테이너를 논리적으로 실행하고 모니터링 하는데 사용할 수 있는 데몬 및 API를 제공합니다. 또한 컨테이너를 실행하기 위해 저수준 런타임 위에 배치됩니다.
  • 이처럼 컨테이너를 실행하려면 저수준 및 고수준 컨테이너 런타임이 필요하기 때문에 OCI 런타임과 함께 도커가 그 역할을 했습니다. 도커는 docker-containerd라는 가장 잘 알려진 고수준 컨테이너 런타임을 제공합니다. containerd도 runC와 마찬가지로 도커에서 컨테이너를 실행하기 위해 개발되었으나 나중에 독립적인 라이브러리로 추출되었습니다.

    Untitled

CRI & OCI


  • 기본적으로 도커 기반의 쿠버네티스는 다음과 같은 구조로 작동했습니다. Kubelet이 명령을 받으면, Docker 런타임을 통해 컨테이너를 생성하거나 삭제하는 등의 생명 주기를 관리하는 구조를 가졌습니다.

    Untitled

  • 그러나 이후 여러 다양한 컨테이너 기술이 등장하면서, 쿠버네티스는 이러한 다양한 컨테이너 런타임을 지원해야 하는 요구가 생겼습니다. 이에 따라 각 컨테이너 런타임을 지원하려면 Kubelet의 코드를 수정해야 하는 문제가 발생했습니다.

    Untitled

  • 이에 대한 대안으로 Kubelet의 코드 수정 없이도 다양한 컨테이너 런타임을 지원하기 위한 표준화된 인터페이스가 등장했는데, 이것이 “CRI (Container Runtime Interface)“입니다. CRI는 컨테이너의 생성, 삭제 등의 생명 주기를 관리하는 표준 스펙으로, gRPC 기반의 API 스펙을 사용하며, 새로운 컨테이너 런타임은 CRI 스펙에 맞춰 CRI 컴포넌트를 구현하면 됩니다. 쿠버네티스 컨테이너 런타임 인터페이스(CRI)는 클러스터 컴포넌트 kubelet과 container runtime 사이의 통신을 위한 주요 gRPC 프로토콜을 정의합니다.

Untitled

  • 이에 따라 컨테이너 런타임은 CRI 스펙을 준수하여 구현하면, 새로운 컨테이너 런타임을 Kubelet의 코드를 수정하지 않고도 플러그인 형태로 추가할 수 있게 되었습니다. 예를 들어, Docker의 경우 “docker shim”이라는 CRI 인터페이스를 준수하는 구현체를 제공하며, rkt 컨테이너의 경우 “rktlet”이라는 이름의 CRI 구현체를 제공합니다.
  • 그러나 지원되는 컨테이너 종류가 계속 증가하면서 매번 새로운 CRI를 구현해야 하는 문제가 발생했습니다. 이에 대응하여 컨테이너 런타임 자체를 표준화하기 위한 노력이 진행되었는데, 그 결과 “OCI (Open Container Initiative)” 스펙이 만들어졌습니다.
  • OCI 스펙을 준수하여 구현된 컨테이너 런타임은 별도의 CRI 구현이 필요하지 않고, OCI를 지원하는 CRI 구현체를 통해 관리할 수 있게 되었습니다. OCI 스펙을 따르는 컨테이너 런타임을 관리하는 CRI 컴포넌트로는 “CRI-O(Container Runtime Interface - Open Container Initiative)”가 있습니다. 이는 OCI 스펙을 준수한다면, CRI-O를 통해 Kubelet으로부터 명령을 받을 수 있는 구조로 작동하게 됩니다.

CRI-O(Container Runtime Interface - Open Container Initiative)


  • CRI-O 는 레드햇에서 주도적으로 개발하고 있는 오픈소스 프로젝트로 쿠버네티스에서 CRI 구현을 목적으로 만들었기 때문에 “쿠버네티스 전용 런타임”라고 부르기도 합니다.
  • CRI-O는 CRI와 OCI에서 유래된 프로젝트로 컨테이너 런타임 및 이미지가 OCI와 호환되는 것에 중점을 두고 있습니다. CRI 표준 컴포넌트를 최소한의 런타임으로 구현하며 쿠버네티스에서 모든 OCI 호환 런타임 및 컨테이너 이미지를 지원합니다.

    Untitled

  • CRI-O는 컨테이너의 실행을 목적으로 경량화했기 때문에 도커가 제공하는 컨테이너 생성 및 이미지 빌드와 같은 기능은 제공하지 않습니다. 즉, CRI-O 덕분에 쿠버네티스는 컨테이너를 실행할 때 도커가 필요없었으나, 컨테이너의 생성 및 이미지 빌드와 같은 과정에서는 여전히 도커를 필요로 했습니다. 이러한 이유로 CRI-O 개발팀은 도커를 대체할 수 있는 새로운 생태계를 만들기 위해 노력하였습니다.

도커의 문제점


1) High & Low Level Runtime Container

Docker Runtime Container는 High Level & Row Level Runtime Container를 포함하는 완전한 형태를 갖고 있습니다. 이는 장점이라고도 볼 수 있지만, High Level Container 역할을 하는 Docker daemon(Docker Server)와 Row Level Container 역할을 하는 Docker CLI(Client) 간의 통신으로 구성되어 있어 둘 중 하나의 기능만을 원한 경우 또는 Client의 특정 기능(컨테이너 기동, 도커 이미지 빌드, 이미지 pull & push 등)만을 사용하고자 할 경우에도 전체를 구성하여 활용해야 한다는 단점이 존재합니다.

2) SPOF

Docker daemon(Docker Server)은 Daemon 하위에 여러 Container를 기동하고 관리합니다. 특히 Kubernetes의 Master / Worker 노드에 구성된 Docker Daemon에 장애가 발생할 경우 Kubernetes 전체로 장애가 전파 될 수 있습니다. 이는 Single Point Of Failure를 대표하는 장애 포인트가 될 수 있습니다.

3) Audit

Linux Kernel에는 Audit이라는 기능이 존재합니다. 이를 통해 관리자는 시스템의 보안 이벤트를 감시하고 외부로부터의 침입을 감시하는 역할을 수행합니다. 이는 로그인 UID 추적 기능을 통해 감시할 수 있습니다.

/proc/self/loginuid에 저장되는 loginuid 필드는 시스템의 모든 프로세스에 대한 proc 구조체의 일부이다. 이 필드는 한 번만 설정할 수 있다. 설정된 후에는 커널이 프로세스를 재설정하는 것을 허용하지 않는다. 개인의 uid는 다음과 같이 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
id
uid=1000(woobin) gid=1000(woobin) groups=1000(woobin)

cat /proc/self/loginuid
1000

sudo su -
Last login: Tue Jun 20 12:39:20 KST 2023 on tty1

cat /proc/self/loginuid
1000

su - woobin
Last login: Thu Sep 21 20:14:41 KST 2023 on pts/0

cat /proc/self/loginuid
1000

위와 같이 uid(1000)가 발급되고, 발급된 uid는 계정이 변경되어도 그대로 유지된다. (woobin > root > woobin) 이는 초기 로그인 프로세스에서 분기되고 실행되는 모든 프로세스는 자동으로 loginuid를 상속하여, 커널이 로그인 한 사람을 알게 하는 감시 방법이다.

이제 확인하고자 하는 내용은 이 audit을 방해하는 docker에 대한 내용에 대해 알아보고자 한다.

1
2
3
4
5
sudo docker run fedora cat /proc/self/loginuid
4294967295

sudo podman run fedora cat /proc/self/loginuid
1000

위는 docker와 podman으로 각각 기동 후 loginuid를 확인하는 과정이다.

Docker는 클라이언트/서버 모델을 사용한다. 내가 실행한 docker 명령은 Docker 클라이언트 도구이며 클라이언트/서버 작업을 통해 Docker 데몬과 통신한다. 그런 다음 Docker 데몬은 컨테이너를 만들고 Docker 클라이언트 도구에 대한 stdin / stdout 통신을 다시 처리한다. Podman은 컨테이너에 대해 전통적인 fork / exec 모델을 사용하므로 컨테이너 프로세스는 Podman 프로세스의 Child이다.

프로세스의 기본 loginuid (loginuid가 설정되기 전)는 4294967295이다. 컨테이너는 Docker daemon의 child이고 Docker daemon은 init 시스템의 child이므로 systemd, Docker daemon 및 컨테이너가 모두 동일한 loginuid로 처리하는 것을 볼 수 있다. 이는 감사 대상에서 제외됨을 알 수 있다.

이는 Docker Container를 관리하는 주체가 누구인지? Docker를 다운하거나, 악의적으로 Container를 조작하는 등의 일이 발생해도 audit.log를 통해 확인할 수 있는 방법이 없다는 것을 의미한다. Podman의 경우 전통적인 fork / exec 모델을 사용하여 audit.log가 정상적으로 기록된다.

CRI-O 구성 요소 Buildah, Podman, Skopeo


  • Buildah, Podman, Skopeo는 세분화된 Low Level Container 컴포넌트로, 별도의 데몬 없이 전통적인 fork / exec 모델을 사용하며 사용자 네임 스페이스를 이용해 컨테이너를 실행함으로써 단일실패점, audit 보안 기능 사용 및 루트 권한 문제를 해결하였습니다.

Untitled

  • 도커의 서버가 너무 많은 기능을 가지고 있는 단점은 각 툴 별로 다음과 같이 기능을 나누어 제공하는 방식으로 보완하였습니다.

    Untitled

    • Buildah
      • CRI-O에서 이미지를 빌드할 때 도커의 종속성을 제거하기 위해 개발되었고 Dockerfile 없이 다른 스크립트 언어를 사용해 컨테이너 이미지를 빌드하는 것을 목표로 합니다.
    • Podman
      • pull 및 tag 지정과 같은 OCI 컨테이너 이미지를 유지관리하고 수정하는데 도움이 되는 모든 명령 및 기능을 제공합니다. 또한 컨테이너 작성, 실행 및 유지보수도 할 수 있습니다. 즉, Docker CLI에서 수행할 수 있는 명령은 Podman CLI에서도 동일하게 수행 할 수 있습니다.
      • Buildah와 Podman은 일부 겹치는 기능이 있는데 Buildah는 OCI 이미지를 생성하는 효율적인 도구로, Podman은 그러한 이미지와 이미지를 통해 생성한 컨테이너를 유지하고 관리하는 도구로 이해하면 됩니다. 기술적으로 buildah run은 Dockerfile RUN을 에뮬레이트하며 podman run은 docker run을 에뮬레이트 합니다.
    • Skopeo
      • 이미지 저장소에서 다양한 작업을 수행하는 명령줄 도구입니다. 기존 도커가 다른 레지스트리에 이미지를 복사하기 위해 pull, tag, push를 사용했다면 Skopeo는 간단하게 copy 명령으로 해당 기능을 제공합니다. 추가로 저장소에 있는 이미지 이름에 대해 로우 레벨 정보를 제공해줍니다.

Reference


  1. https://www.samsungsds.com/kr/insights/docker.html
  2. https://waspro.tistory.com/679
This post is licensed under CC BY 4.0 by the author.