컨테이너와 도커
컨테이너가 등장하기 이전
- 가상화가 나오기 전
- 개발(Local)과 운영(Production) 서버의 환경 불일치
- 개발을 진행한 Local 환경과 Production 서버 환경이 다른경우
-> OS가 다르기 때문에 라이브러리 등을 설치할 때 다르게 진행해야 함
- Local 환경과 Production 서버가 같은 OS를 사용해도, 서버에선 올바르게 작동하지 않을 수 있음(환경변수, Permission 등)
- 한 대의 서버는 하나의 용도로만 사용됨
- 하나의 서버에 하나의 OS, 프로그램을 운영(남는 서버 공간은 그대로 방치됨)
- 안정적이지만 비효율적임
- 하이퍼바이저기반의 VM 가상화의 등장
- 가상화: 하나의 물리머신상에서 복수의 시스템을 동시운영하는 것
- 하이퍼바이저는 물리 리소스를 분할하여 독립적인 가상환경(VM)의 서버를 이용가능하게 함
- 하이퍼바이저: 호스트 컴퓨터에서 다수의 운영 체제를 동시에 실행하기 위한 논리적 플랫폼을 말한다. 가상화 머신 모니터 또는 가상화 머신 매니저라고도 부른다(위키백과)
- VM(Virtual Machine): 호스트 머신이라고 하는 실제 물리적인 컴퓨터 위에, OS를 포함한 가상화 소프트웨어를 두는 방식
- ex) 호스트 머신은 Window인데, Window(Host OS)에서 Linux(Guest OS)를 실행
- GCP의 Compute Engine 또는 AWS EC2가 이런 개념을 활용함
- 클라우드 회사에서 미리 만든 Image를 바탕으로 computing 서비스를 통해 사용자에게 동일한 컴퓨팅 환경을 제공
그러나 OS 위에 OS를 하나 더 실행시키는 점에서 VM은 리소스를 많이 잡아먹는다는 단점이 있었음(무거움)
VM은 각각이 하나의 완전한 컴퓨터이며 항상 GuestOS를 설치해야 함
컨테이너와 도커
컨테이너
VM의 무거움을 크게 덜어주면서, 가상화를 좀 더 경량화된 프로세스의 개념으로 만든 기술
(응용 소프트웨어를 캡슐화하는 것. (모듈화 시키는 것))
- 호스트 OS 위에 컨테이너 엔진(ex. Docker Engine)을 설치하고 애플리케이션 작동에 필요한 바이너리, 라이브러리 등을 하나로 모아 각자가 별도의 서버인 것처럼 사용하는 환경
- 애플리케이션의 코드, 종속성, 그리고 실행환경을 하나의 패키지로 구성
- 장점
- 민첩성
- 애플리케이션을 빠르게 빌드하고 배포할 수 있음
- 이식성
- OS 간 혹은 클라우드 간 이식이 가능
- 개발 환경부터 프로덕션 환경까지 일관된 형식을 사용할 수 있음
- 확장성
- 같은 Host에서 쉽게 많은 컨테이너(애플리케이션)을 사용할 수 있음
- 신속한 스케일링을 지원(컨테이너는 애플리케이션 단위로 스케일링이 가능)
도커
컨테이너 기술을 사용하여 application 실행환경 구축 및 운용 하기위한 오픈 소스 플랫폼
- 도커가 설치되어 있다면 어디서든 컨테이너를 실행할 수 있음
- 컨테이너(서버환경을 한번에 소프트웨어화 시킴)로 서버를 배포하므로 모든 서비스들의 배포 과정이 동일해짐
- 컨테이너 이미지를 직접 생성하고 공유할 수 있음
- 원격 저장소: Container Registry
- Docker Hub
- GCR
- ECR
- Docker Image: 컨테이너를 실행할 때 사용할 수 있는 템플릿
- OS, 설정을 포함한 실행환경을 담고 있음
- Linux, Window, Mac 어디에서나 동일하게 실행할 수 있음(단, window와 Mac 위에서는 추가적인 레이어(Linux) 구성이 필요)
- ex. PC방에선 어느 자리에 앉아도 모든 컴퓨터가 같은 환경을 갖고 있음
- Docker Container: Docker Image를 활용해 실행된 인스턴스
os 레벨 가상화
- 도커는 OS 레벨 가상화임
- OS 레벨 가상화: 운영체제의 커널이 하나의 사용자 공간 인스턴스가 아닌, 여러 개의 격리된 사용자 공간 인스턴스를 갖출 수 있도록 하는 서버 가상화 방식
- 즉, 도커는 호스트 OS의 커널을 공유함
- 도커는 리눅스 환경(커널)에서만 실행할 수 있음
- 따라서 윈도우나 맥 Host에서 도커 컨테이너를 사용하고 싶다면, VM 등과 같은 추가적인 레이어를 구성해야 함
리눅스 커널이란?
- 리눅스 커널: 하드웨어의 자원을 관리하고 추상화하여 프로세스에게 할당하고 관리하는 역할을 수행
- 리눅스 배포판의 구성을 살펴보면, 리눅스 커널과 다양한 컴포넌트(서비스 데몬, 애플리케이션, 데스크톱 소프트웨어 등)로 구성됨
컨테이너 생태계 : CNCF(Cloud Native Computing Foundation)란?
- Cloud Native Computing Foundation은 컨테이너 기술을 발전시키고 기술 산업이 발전할 수 있도록 지원하기 위해 2015년에 설립된 Linux Foundation 프로젝트임.
(Cloud Native : 클라우드의 이점을 최대로 활용할 수 있도록 애플리케이션을 구축하고 실행하는 방식)
- CNCF의 대표 프로젝트들
- 쿠버네티스
- HELM
- containerd
- Prometheus
- ...
도커의 구조
컨테이너 표준 - OCI, CRI 그리고 CRI-O
- 컨테이너에 대한 관심이 증가하면서 주요 IT 벤더와 클라우드 공급자들은 컨테이너 기반의 솔루션을 발표했고 관련 스타트업 또한 급증해 컨테이너 생태계를 넓혀오고 있었음.
- 하지만 포맷과 런타임에 대한 특정한 규격이 없다 보니 컨테이너의 미래가 불안정하게 됨.
- 이러한 문제를 해결하기 위해 2015년 6월 도커, 코어OS, AWS, 구글, 마이크로소프트, IBM 등 주요 플랫폼 벤더들은 애플리케이션 이식성(Portability)의 관점에서 컨테이너 포맷과 런타임에 대한 개방형 업계 표준을 만들기 위해 OCI(Open Container Initiative)를 구성하게 됨
- 이후 2016년 12월에는 쿠버네티스(Kubernetes)의 컨테이너 런타임을 만들기 위한 CRI(Container Runtime Interface)가 등장하게 됨
OCI(Open Container Initiative)
- 배경
- 컨테이너 포맷과 런타임에 대한 개방형 업계 표준의 필요
- OCI : 컨테이너 포멧과 런타임에 대한 업계 표준
- 현재 대부분의 컨테이너 런타임들은 이 형식을 따르고 있음
- OCI 런타임 : 컨테이너 실행에 필요한 저수준 컨테이너 런타임
- 역할: 리눅스 커널에 있는 주요 작업들을 관리(컨테이너를 실제 실행하는 역할)
- ex) runc는 oci 표준에 따라 컨테이너를 생성 및 실행하기 위한 CLI 도구임
CRI(Container Runtime Interface)
- 배경
- 초기 쿠버네티스는 컨테이너를 실행하기 위해 도커를 사용하였는데, 해당 코드는 쿠버네티스 클러스터 워커 노드의 에이전트인 Kublet 소스 내부에 통합되어 있었음
- 이러한 방식은 개발자들로 하여금 Kublet에 대한 깊은 이해를 필요하게 하였고, 이는 유지보수 등에 오버헤드를 발생시킴
- 이를 해결하고자 쿠버네티스는 CRI를 만들어 명확하게 정의된 추상화 계층을 제공함.
- CRI : 쿠버네티스에서 다양한 컨테이너 런타임을 사용할 수 있도록 하는 플러그인 인터페이스
- CRI 런타임 : 컨테이너 실행에 필요한 고수준 컨테이너 런타임
- 역할 : 컨테이너 라이프사이클, 이미지 등을 관리
- ex) containerd, CRI-O, Docker Engine, …
CRI-O
- 배경: 도커의 문제점
- 클라이언트-서버 문제점
- 도커는 클라이언트(Docker CLI) - 서버(Docker Demon) 애플리케이션으로 구성되어 있음
- 이중 서버는 이미지 빌드 관리, 공유, 실행, 인스턴스 관리 등 너무 많은 관리를 담당하고 있으며 모든 컨테이너를 자식 프로세스로 소유하고 있음
- 이로 인해 무거울 뿐 아니라 어디선가 장애가 발생하면 모든 자식 프로세스에 영향을 끼치게 됨
- 보안 관련 문제
- 모든 도커 명령은 루트 권한을 가진 사용자에 의해서만 실행할 수 있기 때문에 보안 문제가 발생할 수 있음
- 클라이언트 - 서버 모델을 사용하기 때문에 관리자가 보안 이벤트를 로깅할 수 있는 audit 보안 기능을 사용할 수 없음
- CRI-O : 경량화된 컨테이너의 실행을 목적으로 레드햇이 개발한 Kubernetes 용 Open Container Initiative (OCI) 컨테이너 런타임
- CRI-O 런타임은 컨테이너 ‘실행’을 목적으로 경량화했기 떄문에 도커가 제공하는 컨테이너 생성 및 이미지 빌드와 같은 기능은 제공하지 않으나, 다음과 같은 도구들로 해결할 수 있다.
- PODMAN : 컨테이너 엔진의 외부에서 Pod 와 컨테이너 이미지 관리 (run, stop, start, ps, attach, exec 등)
- BuildAH : 이미지 빌드 & 푸시
- Skepo : 이미지 서버
도커의 구조
dockerd (Docker Daemon)
HTTP 기반 REST API를 제공하여, Docker Client가 Container를 제어할 수 있게 하는 역할을 수행하는 서버 프로세스
- docker 명령을 받고, containerd를 구동
- Docker Image Build
- Container Network 설정
- Container Log 기록
- docker-proxy 구동
- docker-proxy는 Node 외부에서 Container안으로 Packet을 Forwarding 해주는 Proxy Server 역할을 수행
containerd
- OCI 스펙을 준수하는 dockerd 요청에 따라서 config.json(Container Config) 파일을 생성하고, containerd-shim, runc를 이용하여 container를 생성(직접 생성 x)하는 Daemon 역할을 수행
- continerd는 container-shim들의 부모 프로세스
- cgroup, namespace 등을
- Node에 Container Image가 존재하지 않는다면, 이미지를 Pull하는 역할을 수행
containerd-shim
- containerd는 containerd-shim을 통해 runC를 호출하고 runC는 container를 생성하고 종료함
- 이후 containerd-shim 프로세스를 subreaper로 만들어서, 1. 생성된 container의 stdin/out/err 담당 & 2. Init Process의 Exit Code를 담당하는 Process가 containerd-shim 이 되도록 함.
- Subreaper Process : 자신의 모든 하위 Process 중에서 고아 Process가 발생하면, Node Init Process(→ Linux default 설정)가 아니라 자기 자신이 고아 Process를 거두며 새로운 부모 Process가 된다는 의미 → Container Init Process의 부모 Process가 containerd-shim이기 때문에 Container Init Process가 종료될 경우 containerd-shim은 SIGCHLD Signal을 받게 되고, Container Init Process의 ExitCode를 얻을 수 있게 되는 것임
runC
- containerd가 생성한 config.json(Container Config) 파일을 통해 Container를 실제로 생성하는 역할을 수행
- config.json은 OCI 스펙을 기반으로 작성됨
- runC는 Container를 생성한 뒤 바로 종료됨
- runC는 containerd-shim으로부터 실행되며, runC의 stdin/out/err는 runC를 실행한 process의 stdin/out/err를 그대로 이용함.
- 따라서 runC의 stdin/out/err는 container-shim과 동일함
Reference
컨테이너와 도커
도커의 구조