티스토리 뷰

이제 K8s와 컨테이너 런타임 사이에 한단계 더 깊은 흐름을 살펴보겠습니다.

컨테이너 기술의 제일 아래 OS의 커널에서는 격리를 위한 chroot, cgroup, namespace 기술이 활용되고 있습니다.

이 Linux 커널 기술 위에 컨테이너 런타임이 실행되는 구조입니다.

 

초기 컨테이너에는 특별한 규격이나 런타임에 대한 표준화가 없다보니,

Docker가 사실상의 컨테이너 표준을 자처했으며, CoreOS는 또 다른 규격으로 컨테이너 표준화를 추진하려 했었습니다

당시 다양한 타입의 컨테이너가 난립할 가능성이 점점 더 높아지자, 표준의 필요성이 대두되었습니다.

이 문제를 해결하기 위해 많은 플랫폼 기업들이 모여, 새로운 개방형 단체인 OCI(Open Container Initiative)를 구성하였고

컨테이너 런타임이 컨테이너를 만들때 지켜야 하는 표준 규약을 관리하기 시작했습니다.

 

OCI에서는 애플리케이션의 이식성(Protability) 관점에서

컨테이너의 포맷과 런타임에 대한 개방형 표준을 만들고자 노력하였고,

그 결과 컨테이너 런타임 및 컨테이너 이미지에 대한 세부 사항과 관련된 표준이 만들어지게 되었습니다.

이제 OCI의 규격을 맞추어 컨테이너를 만들면 컨테이너 런타임들끼리 서로 공유해서 사용할 수 있게 되었습니다.

 

당시 비공식적 표준 역할을 하던 Docker는 컨테이너 런타임으로서, 3단계를 거쳐 컨테이너를 실행하였습니다.

 

    1단계. 컨테이너 이미지 다운로드

    2단계. 이미지를 압축해제하여, 컨테이너의 파일시스템 'Bundle' 생성

    3단계. 'Bundle'로부터 컨테이너 생성 및 실행

 

이 중 docker는 3단계의 컨테이너 생성과 관련하여 OCI 규격에 맞추어 runC라는 툴로 OCI에 제공하였고,

컨테이너 런타임의 표준화는 모든 단계가 아닌 일부 단계인 3단계 컨테이너 실행 부분만이 표준화 되었습니다.

그 결과 컨테이너 런타임은 1단계와 2단계가 함께 포함된 high level Container runtime과 

3단계만 담당하는 low level container runtime으로 나뉘게 되었습니다.

 

    1. Low Level Container Runtime(저수준 컨테이너 런타임)

        실제 컨테이너를 실행하는 런타임으로, LXC, libcontainer, rkt 등이 포함됩니다.

        초기 저수준 컨테이너 런타임인 LXC는 커널레벨의 기술(namespace, cgroup)을 기반으로 만들어졌습니다.

        Docker사에서는 이를 바탕으로 Docker만의 library인 libcontainer를 개발하여 사용하였으며,

        OCI가 생기면서 libcontainer code를 standalone utility로 바꾸어 기부하게 되었습니다.(현재의 runC)

        runC가 기존의 libcontainer와 다른 점은 LXC를 거치지 않고 커널레벨의 가상화를 직접 사용한다는 점입니다.

 

    2. High Level Container Runtime(고수준 컨테이너 런타임)

        컨테이너 이미지의 전송 및 관리, 이미지 압축 풀기 등을 실행하는 런타임으로, 

        docker사에서 개발한 사용자 친화적인 고수준 컨테이너 런타임이 바로 'Docker'입니다.

        일반적으로 원격 애플리케이션이 컨테이너를 실행하고 모니터링 하는데 사용할 수 있는 데몬 및 API를 제공하며,

        컨테이너를 실행하기 위해 저수준 컨테이너 런타임 위에 배치됩니다.

 

 

위 그림처럼 Docker 엔진에서는 dockerd라는 컨테이너를 만들어 주는 기본 기능 외에도

CLI를 제공하고, 로그 알림, 스토리지 드라이버, 네트워크 생성등의 많은 부가 기능을 포함하고 있습니다.

그러나 정작 컨테이너를 만드는 일은 dockerd의 요청을 받은 containerd가 처리하며,

contrainerd는 다시 이요청을 저수준 컨테이너 런타임인 libcontainer(runC)을 이용하여 처리합니다.

 

이처럼 컨테이너를 실행하려면 저수준 및 고수준 컨테이너 런타임이 함께 필요합니다.

사실 Docker 입장에서는 Docker만 써도 다양한 기능을 제공하기 때문에 쓰기 좋으나,

K8s 입장에서는 주로 컨테이너 생성 기능만 쓰기 때문에 Docker의 나머지 기능들이 과해보일 수 있습니다.

 

또한 Docker는 LXC의 기술을 이용하지만 Docker와 LXC 사이에는 큰차이가 있습니다.

즉, 각각 컨테이너를 만들어주는 결과물로써는 동일하지만, 그 목적이 다릅니다.

 

    - Docker : app들을 독립적인 환경에서 사용하기 위한 목원

    - LXC : 운영체제를 컨테이너 가상화로 나누어주기 위한 목적, 기존 VM 기술로 했던 것을 컨테이너 기술로 지원

 

 

 

그럼 K8s가 컨테이너 런타임을 어떻게 사용하는지 살펴보겠습니다.

 

일단 K8s는 kube-apiserver와 kublet이라는 컨테이너를 포함하고 있습니다.

그리고 K8s에는 컨테이너 하나를 생성하라는 명령어 대신

하나 이상의 컨테이너로 이루어진 pod를 생성하라는 명령어가 있습니다.

 

예를 들어 컨테이너 2개를 하나의 pod로 만들어달라는 명령을 사용자가 API 호출을 통해 kube-apiserver에 보내면

kube-apiserver는 kubelet에게 해당 명령을 전달하고,

다시 kubelet은 해당 내용을 확인한 후 컨테이너 런타임에게 컨테이너를 만들어 달라는 명령을 2번 호출합니다.

이제 컨테이너 런타임은 kubelet의 요청대로 컨테이너를 2개 생성합니다.

즉, 컨테이너 런타임은 컨테이너를 생성해주는 역할을 담당하고, 그 결과 컨테이너가 생성물로서 만들어집니다.

 

 

조금 더 들어가서 kubelet과 컨테이너 런타임에 대해 이야기 해보겠습니다.

 

초기 K8s v1.0에서의 kublet은 컨테이너 런타임의 종류에 따라 맞춤형 API를 호출하게 되어 있었습니다.

만약 컨테이너 런타임이 Docker라면 Docker에 맞는 API를, rkt이라면 rkt에 맞는 API를 호출합니다

이후 시간이 지나면서 docker에서 containerd가 분리가 되었고, 새로운 컨테이너 런타임이 등장할 때마다,

kubelet에 새로운 컨테이너 런타임에 대한 정의와 API가 추가되어야 했습니다.

 

이 문제를 해결하기 위해 v1.5에서부터는 kubelet 안에 새로운 interface를 추가하여 새로운 규격을 정했으며,

실제 컨테이너 런타임을 호출하는 부분을 CRI(Contrainer Runtime Interface)라 불리는 부분으로 분리하였습니다.

이제 컨테이너 런타임의 제조사들은 CRI 소스에 본인들의 런타임 관련 연동 부분에 대한 기능을 기여할 수 있었습니다.

 

그런데 CRI 내부에 Docker와 연결을 담당하던 dockershim이 관리가 잘안되고, 버그가 많았다고 합니다.

이 당시 Docker사에 수익모델이 좋지 않았고, 회사의 인수설도 자주 언급되고 하다보니 관리가 어려웠다고 합니다.

그 결과 v1.23까지 유지되었던 dockershim이 빠지게 되었습니다.

 

이 혼란한 시기를 거치면서 Docker를 컨테이너 런타임으로 사용하던 사용자들이 다른 런타임으로 많이 이탈하였으며,

현재는 잠시 떨어졌던 호환성을 다시 따라잡고, 표준화된 인터페이스로 다시 합을 맞춰가고 있습니다만, 

과거의 명성을 따라잡기에는 부족해 보입니다.

 

그런데 K8s의 기능 개발과 CRI의 기능 개발은 서로 별도의 개발인데,

둘다 K8s 내부에 있다보니, Docker에 새로운 기능이 생기면, K8s도 같이 패치를 해야하게 되었습니다.

즉, 이 구조에서는 컨테이너 런타임이 변화될 때마다 CRI의 구현체도 수정을 해야하니, K8s도 패치를 하게 된 것입니다.

 

그래서 v1.24부터는 kubelet에서 컨테이너 런타임으로 바로 호출할 수 있게 구조가 바뀝니다.

이런 구조를 지원하기 위해 런타임별로 관련 인터페이스의 추가가 필요하게 되었습니다.

이에 따라 Contrainerd에는 CRI-Plugin이라는 기능이 추가되었고, Docker도 cri-dockerd라는 데몬을 추가했습니다.

Docker의 경우 v1.23까지 이어져 온 dockershim을 외부로 빼서 cri-dockerd라는 어댑터로 분리된 것입니다.

cri-o는 태생부터 red hat에서 이 규격에 맞추어 만든 런타임이라 별도의 기능이 추가되지는 않았습니다.

v1.27부터는 pod를 만들때, 이 구조를 기본으로 따르게 되어 있습니다.

 

현재 고수준 컨테이너 런타임인 cri-o나 containerd나 모두 저수준 컨테이너 런타임에서는 runC를 사용합니다.

즉, 동일한 환경에서 실행되기 때문에 고수준 컨테이너 런타임에 상관없이 잘 돌아갑니다.

 

참고로 v1.5~v1.23 까지 K8s 내 유지되던 CRI와 kubelet 사이의 통신은 gRPC를 사용합니다.

gRPC는 구글에서 개발하여 오픈소스로 제공하는 서버와 클라이언트 같이 떨어져 있는 프로세스간에 호출 기법으로

개발 언어에 상관없이 프로토콜 버퍼에 바이너리로 데이터를 저장하고 전송하여 처리속도가 빠르게 지원하며,

v1.24 이후에도 kubelet과 컨테이너 런타임의 CRI 플러그인 사이에서도 여전히 사용되고 있습니다.

 

-----------------------------------------------------------------------------------------------

이 내용은 인프런의 '쿠버네티스 어나더 클래스 (지상편) - Sprint 1'의 복습으로써 작성되었습니다.

사용된 이미지는 모두 강사님의 허락아래 강의 파일에서 캡쳐되었으며, 원본 강의는 https://inf.run/k7mF 에서 확인하실 수 있습니다.

댓글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함
최근에 올라온 글
Total
Today
Yesterday
링크