[k8s 스터디] #01 컨테이너와 도커 + 쿠버네티스
도커 스터디가 끝나고 이제 쿠버네티스 강의로 새로운 스터디가 시작된다.
쿠버네티스 어나더 클래스 (지상편) - Sprint 1, 2 강의 | 일프로 - 인프런
일프로 | , ✅ 광범위한 쿠버네티스 기술을 A~Z까지 넓고 얇게 훑기보다 하나의 개념을 배우더라도 왜 사용하는지 부터 실무에서 어떻게 사용되는지 까지를 다루는 강의✅ 시작은 초급자지만강
www.inflearn.com
강의는 이걸로 하기로 했고 이제 섹션 3. 컨테이너 한방 정리를 본 후 복습이다.
강의 내용에서 처음 접한 개념과 용어가 등장해 정리하는데 꽤 오래 걸렸다.
하지만 쿠버네티스가 뭔지 그 근간이 되는 컨테이너 기반 가상화와 컨테이너 런타임 관련 개념을 정리해야
강의를 따라가면서 길을 잃지 않을 것 같았다.
목차
- 컨테이너란?
- 하이퍼바이저 대비 장점
- 커널 공유와 격리 기술
- 런타임과 Docker
- 런타임의 정의
- Docker의 역할
- 컨테이너 표준화의 흐름
- CRI의 필요성과 등장
- OCI의 표준화 대상 및 영향
- Docker와 Containerd의 분리
- Docker 내부 구조
- containerd와 runc의 역할
- Kubernetes란?
- 등장 배경
- 무엇을 자동화하는가?
- Kubernetes의 구성요소
- Pod, kubelet, kube-apiserver, Node, Cluster 구조
- 마무리 정리

컨테이너란?
- 격리된 환경에서 프로세스를 실행하는 기술
- 리눅스 커널이 제공하는 namespce, cgroup 기능을 활용
- 파일 시스템, 네트워크, 리소스 등을 격리
- 다른 프로세스와 독립적으로 실행하는 환경 제공
왜 나왔나
하이퍼바이저는 커널이 포함된 완전한 OS가 포함되어 있어 무거움
이걸 Linux의 기본 가상화 기술을 통해 커널을 공유하는 가상화를 할 수 있게 만든 것
런타임(Runtime)이란?
프로그램이 실행될 때 필요한 환경, 기능, 동작을 제공하는 시스템 또는 계층
- 코드는 코드일 뿐
- 실행하려면 코드와 시스템 사이에서 도와주는 환경이 필요함
- 그게 바로 런타임 환경
언어 마다 각자의 런타임이 있고 컨테이너 역시 런타임이 존재한다.
그중 하나가 Docker
Docker는 컨테이너 런타임
- 이미지에서 컨테이너를 생성하고
- 컨테이너를 시작/중지/삭제하고
- 파일 시스템, 네트워크, 리소스 격리 등을 처리함
Kubernetes가 “컨테이너 하나 띄워줘”라고 하면,
Docker가 실제로 컨테이너를 실행함.
→ 이게 Docker를 런타임(Runtime)으로 사용한다는 의미
도커를 기반으로 한 번에 제어하기 위한 기술이 나옴 그게 쿠버네티스
컨테이너 기술의 표준화
이렇게 강력한 도구인 Docker가 나오면서 컨테이너 기반 생태계가 넓어졌고,
쿠버네티스가 등장하며 더더욱 확산되었다.
과거에는 도커를 표준 처럼 여겼지만 다양한 런타임이 등장하며 표준화의 요구가 생기기 시작함
그래서 나오게된게 CRI 와 OCI
CRI(Container Runtime Interface)
쿠버네티스가 다양한 컨테이너 런타임과 통신하기 위해 만든 표준 인터페이스(gRPC API )
왜 생겼을까?
초기에는 쿠버네티스가 Docker에만 의존해서 컨테이너 실행함
하지만 Docker 말고 다양한 런타임이 등장
여기서 문제 발생함
kubelet이 각 런타임마다 직접 통신 방식이 달라서 복잡/유연성 부족해짐
해결책으로 kublet이 컨테이너 런타임과 직접 통신하지 않고, CRI라는 공통 인터페이스를 통해 런타임과 통신하도록 구조를 변경함
이후 런타임들은 CRI를 지원하게 만들면 kubelet 변경없이 다양한 런타임을 사용할 수 있게 됨
CRI는 쿠버네티스와 컨테이너 런타임 간 통신 방식의 표준화 였다면
컨테이너의 포맷과 실행 방법 자체를 표준화하려는 시도가 OCI
쿠버네티스
| -> 이 부분을 표준화 시킨 것이 CRI
컨테이너 런타임
| -> 이 부분을 표준화 시킨 것이 OCI
컨테이너
문제가 더 있음
Docker는 CRI를 직접 지원하지 않아 중간에 dockershim이라는 어댑터가 필요했었음
→ 즉 도커를 전면적으로 수정하지 않고 CRI에 맞게 변경해주는 어댑터를 둔 것
Docker의 기능이 추가되거나 변경되면 이 dockershim도 맞춰서 변경을 해줘야하는 불편함이 있었음
하지만 ContainerD나 CRI-O는 CRI를 직접 구현한 것이라 기능이 추가되던 변경되던 CRI에 맞게 됨
쿠버네티스는 이렇게 불편한 Docker와 이별을 선언하고 CRI를 직접 지원하는 런타임만 지원하겠다 선언
이후 도커도 업계 표준이 되어가는 쿠버네티스에 퇴출되기 전에 CRI를 직접 지원하는 cri-dockerd를 만듬
하지만 간접계층이 많아 어디서 오류가 발생했는지 추적하기 힘들어 잘 사용 되진 않음
OCI(Opne Cloud Initiative)
왜 OCI가 나왔나
컨테이너 런타임 별로 이미지 저장 방식 달라 호환안됨
런타임마다 실행 방식 달라서 같은 CRI 요청에 다른 결과
그렇기에 CRI만 표준화했다고해서 다른 런타임끼리 교체가 불가능
Docker 독점 + 생태계 파편화
Docker가 컨테이너 이미지 포맷, 실행 방식, 저장 구조를 독자적으로 정의하고 있었음
생태계가 커지자, Docker가 단독으로 만든 이 방식을 모두가 따르는 것은 위험하다고 판단함
→ 컨테이너의 이미지 형식이나 실행 방식을 중립적으로 표준화해서 Docker 이외의 런타임도 호환될 수 있게 하자
Docker + CoreOS + RedHat + Google 등 여러 기업이 모여 표준화 함
표준화한 대상
- 이미지 형식
- 컨테이너 이미지 저장 방식(Layer구조, 메타데이터)
- 런타임 형식
- 컨테이너 실행 방식(cgroup, namespace, rootfs 등)
얻게 된 것
- 이미지 포맷이 같아 졌기에 다른 컨테이너 런타임이 만든 이미지가 호환이 됨
- 실행 방식이 같아져 런타임 교체해도 같은 요청에 같은 결과를 내줌
→ 결과적으로 런타임 교체가 아주 쉬워짐, 목적에 맞는 런타임 쏙쏙 바꿔낄 수 있음
도커는 이 표준화를 맞추기 위해 runc(최종 실행 담당)를 만들었고 ContainerD도 이걸 사용함
Docker와 ContainerD의 관계
ContainerD는 Docker에서 분리해 나온 컨테이너 런타임 관리 데몬이다.
실질적으로 컨테이너를 만들고 실행하고 관리하는 역할
다만 리눅스 커널에 시스템 콜 날려서 프로세스 만드는 건 runc
일단 ContainerD가 왜 분리되어 나왔는지 과거에는
docker run ubuntu
↓
Docker CLI
↓
Docker Engine
├─ 이미지 관리
├─ 컨테이너 빌드
├─ 네트워크, 볼륨 등
└─ 리눅스 API 직접 호출(LXC 이후엔 libcontainer)
→ 컨테이너 실행
도커 엔진이 직접 컨테이너를 실행하는 환경이었다.
이렇게 되면 단점들이 너무 많아 OCI 표준화 되었고 ContainerD와 runc가 분리된 것
이후 Docker에서의 컨테이너를 만드는 흐름을 보면
docker run ubuntu
↓
Docker CLI
↓
Docker Engine → REST API 호출
↓
containerd → 컨테이너 스펙 준비
↓
runc run → 컨테이너 프로세스 생성
여기서 쿠버네티스는 도커의 다양한 기능들은 자기가 지원할꺼니까 필요없었음
그렇기에 컨테이너를 실제로 조작하는 부분인 ContainerD와 runc만 가져다가 사용하기 시작함
이러한 표준화 덕분에 쿠버네티스가 현재 오케스트레이션의 표준처럼 여겨짐
하위 런타임을 마음대로 바꿔 낄 수 있게 됐으니까
Kubernetes
Kubernetes는 컨테이너 운영을 자동화하는 플랫폼이다.
쿠버네티스는 왜 나왔을까?
도커 자체로도 컨테이너를 만들고 없앨 수 있었다.
하지만 컨테이너가 점점 많아지면서 관리하기가 힘들어졌다.
더 많은 컨테이너를 관리하고 관찰할 수 있는 툴의 필요성이 생김
쿠버네티스는 뭘 할까
- 기존엔 사용자가 직접 Docker로 컨테이너 실행
- 쿠버네티스는 사용자의 요청을 대신 받아 Docker나 containerd 같은 런타임에게 명령
- 클러스터 전체의 컨테이너 실행을 자동화(= 오케스트레이션)
쿠버네티스 구성
Pod
- 쿠버네티스에서 가장 작은 배포 단위
- 하나 이상의 컨테이너가 같은 네트워크 공간을 공유하며 실행됨
- 일반적으로는 하나의 컨테이너만 포함됨
- 실제 애플리케이션이 실행되는 곳
Pod는 컨테이너를 담는 컨테이너 실행 단위
kube_apiserver
- 쿠버네티스의 중심 제어 API
- 사용자가 kubectl이나 자동화 도구로 내리는 요청을 받음
- 클러스터의 모든 요청의 진입점
- Pod 생성 요청도 여기서 처리됨
쿠버네티스의 중앙 통제탑(명령 수신, 상태보고, 인증 등 담당함)
kublet
- 각 노드에 설치되는 에이전트
- kube_apiserver로부터 받은 명령을 수행
- 실제로 컨테이너 런타임을 호출해 Pod를 실행함
- Pod 상태를 주기적으로 apiserver에 보고함
노드 안에서 실행을 책임지는 작업자
1. 사용자가 Pod 생성 요청
2. kube-apiserver가 요청 수신 및 인증/검증
3. kube-scheduler가 적절한 노드 선택
4. kube-apiserver가 node의 kubelet에 전달
5. kubelet이 container runtime을 호출해 Pod 실행
6. kubelet이 주기적으로 Pod 상태를 kube-apiserver에 보고
노드
쿠버네티스 클러스터에서 컨테이너(Pod)를 실제로 실행하는 물리 머신 혹은 가상 머신
즉 컨테이너가 실행될 서버
이 노드들과 쿠버네티스 컴포넌트(kube-apiserver, kubelet, etcd 등)가 모인 전체 시스템
쿠버네티스 클러스터 구성도
Control Plane(마스터 노드)
│ ├── kube-apiserver
│ ├── kube-scheduler
│ ├── kube-controller-manager
│ ├── etcd
│
├── Node 1 (Worker)
│ ├── kubelet
│ ├── container runtime (e.g., containerd)
│ ├── kube-proxy
│
├── Node 2 (Worker)
│ ├── ...
이렇게 노드들에게 명령할 마스터 노드와 실제 Pod이 실행될 노드들로 구성되어 있는게
쿠버네티스 클러스터다
사용자는 Worker node들을 만들어 클러스터에 등록해야함
마무리
컨테이너 기반 가상화의 끝판왕 쿠버네티스를 알기위해
컨테이너, 컨테이너 런타임, 도커, 표준화 까지 알아봤고
이후 진짜로 쿠버네티스를 배우기 시작할 것 같다.
뭔가 차곡차곡 쌓아지고 있는 기분이 들어 보람차다.