이 문서의 과거 버전 (r1)을 보고 있습니다. 수정일: 2026.02.14 21:25
컨테이너 엔진은 컨테이너를 생성, 실행, 관리하는 핵심 소프트웨어 컴포넌트이다. 이는 애플리케이션과 그 종속성을 격리된 환경으로 패키징하고 배포하는 컨테이너화 기술의 기반을 제공한다. 컨테이너 엔진은 리눅스 커널의 네임스페이스와 cgroups 같은 기능을 추상화하여 사용자에게 표준화된 인터페이스를 제공함으로써, 개발자와 시스템 관리자가 복잡한 시스템 수준의 세부 사항을 직접 다루지 않고도 컨테이너를 효율적으로 운영할 수 있게 한다.
주요 기능으로는 컨테이너 이미지를 다운로드하고 관리하는 것, 이미지로부터 컨테이너를 생성하고 실행하는 것, 컨테이너의 생명주기(시작, 중지, 삭제)를 제어하는 것, 그리고 컨테이너에 대한 네트워킹과 스토리지 자원을 구성하는 것이 포함된다. 대표적인 예로 Docker 엔진이 있으며, 이는 초기 컨테이너 기술의 대중화에 결정적인 역할을 했다. 이후 생태계가 성장하면서 containerd, Podman, CRI-O와 같은 다양한 구현체가 등장했다.
컨테이너 엔진의 등장은 애플리케이션 개발과 배포 패러다임을 크게 변화시켰다. "어디서나 동일하게 실행된다"는 컨테이너의 특성은 개발 환경과 프로덕션 환경 간의 불일치 문제를 해결하고, 마이크로서비스 아키텍처의 폭넓은 채택을 가능하게 하는 핵심 동인이 되었다. 이는 현대 클라우드 네이티브 컴퓨팅의 근간을 이루는 기술 중 하나이다.
컨테이너 엔진은 컨테이너를 실행하고 관리하기 위한 핵심 소프트웨어 플랫폼이다. 이는 운영 체제의 커널 기능을 활용하여 애플리케이션과 그 종속성을 격리된 환경에서 실행할 수 있게 한다. 컨테이너 엔진의 주요 목표는 개발, 테스트, 배포의 일관성을 보장하면서 애플리케이션의 이식성과 효율성을 극대화하는 것이다.
컨테이너 엔진의 아키텍처는 몇 가지 핵심 구성 요소로 나뉜다. 가장 기본적인 층위는 컨테이너 런타임이다. 이는 컨테이너의 생명주기, 즉 생성, 시작, 중지, 삭제를 직접 담당하는 저수준 구성 요소이다. runc나 crun과 같은 런타임은 실제로 리눅스 커널의 네임스페이스와 cgroups 기능을 호출하여 컨테이너를 생성한다. 런타임 위에는 이미지 관리자가 위치한다. 이 구성 요소는 컨테이너 이미지를 레지스트리에서 풀(pull)하거나, 로컬에서 빌드하고, 저장하며, 이미지의 레이어를 관리하는 역할을 한다.
네트워크 및 스토리지 격리와 관리는 각각 전용 드라이버를 통해 이루어진다. 네트워크 드라이버는 브리지 네트워크, 호스트 네트워크, 오버레이 네트워크 등 다양한 네트워킹 모델을 구현하여 컨테이너 간 또는 외부와의 통신을 가능하게 한다. 반면, 스토리지 드라이버는 오버레이FS나 aufs와 같은 유니온 파일 시스템을 관리하여 이미지 레이어를 효율적으로 쌓고, 컨테이너의 쓰기 가능 레이어를 처리한다. 또한 볼륨과 바인드 마운트를 통해 데이터 지속성을 제공한다.
사용자와의 상호작용은 API 및 CLI 인터페이스를 통해 이루어진다. REST API는 다른 도구나 오케스트레이션 플랫폼(예: 쿠버네티스)이 컨테이너 엔진과 통신할 수 있는 표준화된 경로를 제공한다. 한편, docker나 podman과 같은 명령줄 인터페이스는 사용자가 직접 컨테이너를 실행하고 관리할 수 있는 편리한 명령어 세트를 노출한다. 이 인터페이스들은 내부적으로 하위 구성 요소들(런타임, 이미지 관리자 등)의 기능을 조율하고 사용자 요청을 실행한다.
컨테이너 런타임은 컨테이너의 실행 환경을 생성하고 관리하는 저수준 소프트웨어 컴포넌트이다. 이는 컨테이너 엔진의 핵심 구성 요소로, 실제로 리눅스 커널의 네임스페이스와 cgroups 같은 격리 기능을 활용하여 컨테이너 프로세스를 실행하고 생명주기를 제어한다. 컨테이너 런타임은 OCI의 런타임 스펙을 준수하는 경우가 많으며, runc가 가장 대표적인 구현체이다.
컨테이너 런타임은 크게 하위 런타임과 상위 런타임으로 구분된다. 하위 런타임은 컨테이너의 실제 실행과 생명주기 관리를 직접 담당한다. 반면, 상위 런타임은 이미지 전송, 네트워킹, 스토리지 관리 등 더 높은 수준의 기능을 제공한다. 예를 들어, containerd는 하위 런타임인 runc를 호출하여 컨테이너를 실행하는 상위 런타임에 해당한다.
주요 컨테이너 런타임의 역할과 특징은 다음과 같다.
런타임 이름 | 유형 | 주요 특징 |
|---|---|---|
하위 런타임 | OCI 표준의 참조 구현체. 컨테이너를 직접 생성하고 실행함. | |
상위 런타임 | 도커 엔진에서 분리된 산업 표준 런타임. 이미지 관리, 스토리지, 네트워킹을 제공함. | |
상위 런타임 | 쿠버네티스의 Container Runtime Interface에 최적화된 경량 런타임. | |
하이퍼바이저 기반 런타임 | 가상머신 수준의 강력한 격리를 제공하며 OCI 호환성을 유지함. |
컨테이너 엔진은 사용자의 명령을 받아 상위 런타임을 호출하고, 상위 런타임은 최종적으로 하위 런타임을 통해 커널 기능을 이용해 컨테이너를 실행한다. 이 계층적 구조는 모듈성과 유연성을 보장한다.
이미지 관리자는 컨테이너 엔진이 컨테이너 이미지를 다운로드, 저장, 검색 및 관리하는 역할을 담당하는 핵심 구성 요소이다. 이 컴포넌트는 도커 허브나 프라이빗 레지스트리와 같은 외부 컨테이너 레지스트리와 상호작용하여 이미지를 가져오고(pull), 로컬 이미지 저장소를 구성하며, 이미지의 레이어를 효율적으로 관리한다. 또한 이미지에 태그를 부여하거나 삭제하는 작업도 수행한다.
이미지 관리자의 핵심 기능은 이미지 레이어의 캐싱과 공유 메커니즘이다. 하나의 이미지는 여러 개의 읽기 전용 레이어로 구성되며, 이미지 관리자는 이러한 레이어를 로컬 저장소에 독립적으로 저장한다. 서로 다른 이미지가 동일한 기반 레이어를 공유할 경우, 이미지 관리자는 디스크 공간을 절약하기 위해 해당 레이어를 재사용한다. 이 과정은 이미지 다운로드 속도를 높이고 저장소 사용량을 최적화한다.
이미지 관리자는 일반적으로 다음과 같은 작업 흐름을 처리한다.
작업 | 설명 |
|---|---|
Pull | 지정된 레지스트리에서 이미지와 그 모든 레이어를 다운로드하여 로컬에 저장한다. |
Inspect | 로컬에 저장된 이미지의 메타데이터(생성 기록, 환경 변수, 명령어 등)를 조회한다. |
Tag | 로컬 이미지에 새로운 참조 이름(태그)을 부여한다. |
Remove | 로컬 저장소에서 사용되지 않는 이미지나 레이어를 삭제한다. |
Prune | 사용되지 않는 모든 이미지 레이어를 정리하여 디스크 공간을 회수한다. |
이미지 관리자는 컨테이너 런타임과 긴밀하게 협력한다. 런타임이 새 컨테이너를 생성할 때, 이미지 관리자는 해당 이미지의 최상위 레이어를 제공하고, 런타임은 그 위에 쓰기 가능한 컨테이너 레이어를 추가한다. 이 구조는 이미지의 불변성과 컨테이너의 가변성을 동시에 보장한다.
컨테이너 엔진은 컨테이너의 네트워크 연결과 스토리지 관리를 추상화하기 위해 드라이버 아키텍처를 채용한다. 이 드라이버들은 플러그인 형태로 구현되어 있으며, 사용자는 애플리케이션 요구사항에 맞는 적절한 드라이버를 선택하거나 개발자가 새로운 드라이버를 만들어 시스템을 확장할 수 있다. 이 구조는 핵심 엔진의 복잡성을 줄이면서도 다양한 인프라 환경과 사용 사례를 유연하게 지원하는 데 기여한다.
네트워크 드라이버는 컨테이너가 네트워크에 연결되고 서로 통신하는 방식을 결정한다. 가장 일반적인 드라이버로는 bridge, host, none이 있다. bridge 드라이버는 기본값으로, 엔진이 호스트 내부에 가상의 브리지 네트워크를 생성하고 각 컨테이너에 가상 네트워크 인터페이스를 할당한다. host 드라이버는 컨테이너가 호스트의 네트워크 스택을 직접 사용하게 하여 격리를 제거하지만 성능상 이점을 제공한다. none 드라이버는 컨테이너에 네트워크 인터페이스를 할당하지 않는다. 또한 overlay 드라이버는 여러 도커 데몬 호스트에 걸쳐 분산된 컨테이너 간 네트워킹을 가능하게 하며, macvlan 드라이버는 컨테이너에 물리적 네트워크의 MAC 주소를 부여한다.
스토리지 드라이버는 컨테이너의 쓰기 가능한 레이어와 이미지 레이어를 호스트의 파일 시스템에 어떻게 저장하고 관리할지 담당한다. 이 드라이버는 유니온 파일 시스템 기술을 활용하여 여러 레이어를 단일 통합 뷰로 제공한다. 선택 가능한 드라이버는 호스트 운영체제의 커널이 지원하는 파일 시스템에 따라 달라진다.
드라이버 | 주로 사용되는 환경 | 주요 특징 |
|---|---|---|
| 최신 리눅스 커널 | 성능과 안정성이 뛰어나 현대적 리눅스의 기본 권장 드라이버이다. |
| 우분투 등 이전 리눅스 | 초기 도커에서 널리 사용되었지만 커널 메인라인에 포함되지 않았다. |
| RHEL/CentOS 7의 이전 커널 | 직접 루프백 장치 또는 블록 스토리지를 사용한다. |
| 각각의 파일 시스템 환경 | |
| 윈도우 | 윈도우 컨테이너를 위한 마이크로소프트의 네이티브 드라이버이다. |
스토리지 드라이버는 컨테이너의 생명주기 동안 발생하는 모든 파일 생성, 수정, 삭제 작업을 관리하며, 컨테이너 삭제 시 해당 쓰기 가능 레이어도 함께 제거한다. 데이터의 영구적 보존을 위해서는 볼륨이나 바인드 마운트를 별도로 사용해야 한다.
컨테이너 엔진은 REST API와 명령줄 인터페이스(CLI)를 통해 외부에서 제어되고 상호작용한다. 이 인터페이스들은 사용자와 다른 시스템 구성 요소가 엔진의 모든 기능을 활용할 수 있는 통로 역할을 한다.
주요 API는 일반적으로 HTTP 기반의 RESTful API로 제공된다. 이 API는 컨테이너와 이미지의 생명주기 관리, 네트워크 및 스토리지 리소스 구성, 시스템 정보 조회 등 모든 핵심 작업을 프로그래밍 방식으로 수행할 수 있게 한다. 쿠버네티스와 같은 오케스트레이션 도구는 이 API를 통해 컨테이너 엔진과 통신하여 클러스터를 관리한다. API 서버는 보안을 위해 TLS 암호화와 인증 메커니즘을 지원한다.
사용자에게 가장 친숙한 인터페이스는 CLI 도구이다. docker나 podman 같은 CLI 명령어는 사용자가 터미널에서 직관적으로 컨테이너를 실행하고 관리할 수 있게 한다. CLI는 내부적으로 대부분 API 호출로 변환되어 엔진에 전달된다. 주요 CLI 명령어 카테고리는 다음과 같다.
카테고리 | 주요 명령어 예시 | 역할 |
|---|---|---|
컨테이너 관리 |
| 컨테이너의 실행, 중지, 조회, 로그 확인 |
이미지 관리 |
| 이미지 다운로드, 생성, 목록 조회, 삭제 |
시스템 정보 |
| 엔진 및 호스트 시스템 정보 확인 |
네트워크 관리 |
| 사용자 정의 네트워크 생성 및 관리 |
볼륨 관리 |
| 데이터 볼륨 생성 및 관리 |
이러한 이중 인터페이스 구조 덕분에 컨테이너 엔진은 자동화 스크립트와 수동 운영 모두에 효과적으로 사용될 수 있다. CLI는 인간 사용자를 위한 편의성을, API는 다른 소프트웨어 시스템과의 통합을 위한 유연성을 제공한다.
컨테이너 엔진은 사용자의 명령을 받아 컨테이너의 전체 생명주기를 관리한다. 이 과정은 컨테이너 런타임과 이미지 관리자 등 핵심 구성 요소들의 협업을 통해 이루어진다. 생명주기 관리는 크게 생성, 실행, 관리, 중지, 삭제의 단계로 구분된다.
컨테이너 생성 및 시작 과정은 컨테이너 이미지를 기반으로 한다. 사용자가 docker run 또는 podman run과 같은 명령을 실행하면, 엔진은 먼저 지정된 이미지를 로컬에서 찾거나 원격 레지스트리에서 가져온다. 이후 이미지의 최상위 레이어 위에 새로운 쓰기 가능 레이어(컨테이너 레이어)를 생성하고, 네트워크 인터페이스와 IP 주소를 할당하며, 네임스페이스와 cgroups를 설정하여 격리된 실행 환경을 준비한다. 마지막으로 이미지에 정의된 진입점 또는 명령을 실행하여 컨테이너 프로세스를 시작한다.
실행 중인 컨테이너 관리에는 상태 모니터링, 로그 수집, 실행 명령 변경, 파일 시스템 복사 등의 작업이 포함된다. 엔진은 실행 중인 모든 컨테이너의 상태(실행 중, 일시 중지, 재시작 등)를 추적한다. 사용자는 CLI 명령어를 통해 실행 중인 컨테이너 내부에 접속하거나, 로그를 실시간으로 확인하거나, CPU/메모리 사용량을 모니터링할 수 있다. 또한 exec 명령을 통해 실행 중인 컨테이너 내부에서 새로운 프로세스를 시작할 수도 있다.
컨테이너 중지 및 삭제는 생명주기의 마지막 단계이다. 중지(stop) 명령은 먼저 컨테이너 내의 주 프로세스에 종료 신호를 보내고, 지정된 시간 내에 프로세스가 종료되지 않으면 강제 종료 신호를 보낸다. 프로세스가 종료되면 컨테이너는 'Exited' 상태가 된다. 삭제(rm) 명령은 'Exited' 상태의 컨테이너를 제거하며, 이때 컨테이너 실행 중 생성된 쓰기 가능 레이어와 관련된 네트워크, 로그 등의 모든 리소스가 정리된다. 기본적으로 실행 중인 컨테이너는 강제 옵션 없이는 삭제할 수 없다.
생명주기 단계 | 주요 동작 | 관련 CLI 명령어 예시 (Docker 기준) |
|---|---|---|
생성 & 시작 | 이미지 풀, 파일 시스템 준비, 네트워크/격리 설정, 프로세스 실행 |
|
실행 중 관리 | 상태 조회, 로그 확인, 내부 접속, 자원 모니터링 |
|
중지 | 주 프로세스에 종료 신호 전송, 컨테이너 정지 |
|
삭제 | 컨테이너와 관련된 모든 리소스 제거 |
|
컨테이너 생성 및 시작 과정은 컨테이너 엔진이 컨테이너 이미지를 기반으로 격리된 프로세스 환경을 만들어내는 일련의 단계를 말한다. 이 과정은 주로 컨테이너 런타임에 의해 수행되며, API나 CLI를 통해 사용자의 명령을 받아 시작된다.
먼저, 엔진은 사용자가 지정한 이미지 이름을 확인하고, 로컬에 해당 이미지가 존재하는지 검사한다. 이미지가 로컬에 없다면, 미리 구성된 레지스트리 (예: Docker Hub)에서 이미지를 다운로드(풀(pull))한다. 로컬에 저장된 이미지는 여러 이미지 레이어로 구성되어 있으며, 엔진은 이 레이어들을 읽어들여 루트 파일 시스템을 준비한다. 이후, 엔진은 리눅스 커널의 네임스페이스와 cgroups를 활용하여 격리된 실행 환경을 생성한다. 이 단계에서 네트워크 스택, 프로세스 ID, 사용자 ID, 마운트 포인트 등이 호스트 시스템으로부터 분리된다.
단계 | 주요 작업 | 담당 구성 요소 |
|---|---|---|
1. 명령 수신 및 이미지 확인 | 사용자로부터 | |
2. 컨테이너 파일 시스템 생성 | 이미지 레이어들을 결합하여 컨테이너의 읽기-쓰기 가능한 최상위 레이어를 생성한다. | |
3. 격리 환경 구성 | 네임스페이스(프로세스, 네트워크 등)와 cgroups(CPU, 메모리 제한)를 설정한다. | |
4. 네트워크 인터페이스 연결 | 지정된 네트워크 드라이버(예: 브리지)에 따라 컨테이너에 가상 네트워크 인터페이스를 할당하고 IP 주소를 부여한다. | |
5. 실행 명령어 수행 | 이미지에 정의된 CMD 또는 ENTRYPOINT 명령을 지정된 격리 환경 내에서 실행하여 컨테이너 프로세스를 시작한다. |
마지막으로, 엔진은 이미지 메타데이터에 정의된 기본 실행 명령(CMD 또는 ENTRYPOINT)을 새로 생성된 격리된 환경 내에서 실행한다. 이때 필요에 따라 환경 변수를 주입하거나, 포트 매핑을 설정하며, 볼륨이나 바인드 마운트를 연결한다. 모든 준비가 완료되면 컨테이너는 백그라운드 데몬이 아닌, 포그라운드에서 지정된 명령을 실행하는 프로세스로 시작된다. 이 프로세스가 종료되면 컨테이너의 생명주기도 끝나게 된다.
실행 중인 컨테이너 관리는 컨테이너 엔진이 컨테이너의 생명주기 동안 상태를 모니터링하고, 사용자 요청에 따라 상호작용하며, 자원 사용을 추적하는 일련의 작업을 포함한다. 엔진은 컨테이너 프로세스의 상태를 지속적으로 확인하여 실행 중, 일시 중지, 종료됨 등의 상태를 유지한다. 사용자는 CLI 도구나 API를 통해 실행 중인 컨테이너 목록을 조회하거나, 특정 컨테이너의 상세 정보(예: IP 주소, 바인딩된 포트, 자원 사용량)를 확인할 수 있다. 또한, 컨테이너 내부에서 실행 중인 프로세스 목록을 살펴보거나 실시간 로그 출력을 스트리밍받는 것도 일반적인 관리 작업이다.
자원 모니터링과 제한은 중요한 관리 기능이다. cgroups를 통해 설정된 CPU, 메모리, 디스크 I/O 등의 제한은 컨테이너 실행 중에도 동적으로 조정될 수 있는 경우가 있다. 엔진은 이러한 자원 사용량 메트릭스를 수집하여 사용자에게 제공하며, 과도한 자원 소비로 인한 호스트 시스템의 불안정을 방지한다. 실행 중인 컨테이너에 대한 변경 작업도 가능한데, 예를 들어 컨테이너에 새로운 환경 변수를 설정하거나, 실행 명령어를 변경하지 않고도 연결된 스토리지 볼륨을 추가할 수 있다.
실행 중인 컨테이너와의 상호작용은 여러 형태로 이루어진다. 가장 일반적인 방법은 exec 명령을 사용하여 컨테이너 내부에 새로운 터미널 세션을 생성하고 명령어를 실행하는 것이다. 또한, 컨테이너의 프로세스를 일시 중지시키거나(suspend), 일시 중지된 프로세스를 재개(resume)할 수 있다. 네트워크 관점에서는 실행 중인 컨테이너를 다른 사용자 정의 네트워크에 연결하거나, 기존 네트워크 연결을 해제하는 작업이 가능하다.
관리 작업 | 주요 명령어/기능 (예시) | 설명 |
|---|---|---|
상태 및 목록 조회 |
| 실행 중인 컨테이너 목록, 상세 정보, 실시간 자원 사용량 표시 |
상호작용 |
| 컨테이너 내부에서 명령 실행, 기존 프로세스에 연결, 로그 출력 확인 |
자원 제한 변경 |
| 실행 중인 컨테이너의 CPU, 메모리 제한 등 조정 |
네트워크 관리 |
| 컨테이너를 네트워크에 연결하거나 연결 해제 |
이러한 관리 기능들은 컨테이너 런타임과 엔진의 다른 구성 요소들이 협력하여 제공한다. 사용자의 관리 명령은 API를 통해 엔진에 전달되고, 엔진은 런타임을 호출하여 실제 리눅스 커널 수준의 작업을 수행한다. 이를 통해 다수의 컨테이너를 효율적으로 운영하고 문제 발생 시 신속하게 대응할 수 있는 기반을 마련한다.
컨테이너를 중지하는 주요 명령은 stop과 kill이다. stop 명령은 SIGTERM 시그널을 먼저 보내 정상 종료를 유도한 후, 지정된 타임아웃 시간이 지나도 프로세스가 종료되지 않으면 SIGKILL 시그널을 강제로 보낸다. 반면 kill 명령은 기본적으로 SIGKILL 시그널을 즉시 전송하여 컨테이너 내의 주 프로세스를 강제로 종료시킨다. 중지된 컨테이너는 파일 시스템과 상태를 그대로 유지하며, start 명령을 통해 다시 실행할 수 있다.
컨테이너 삭제는 rm 명령으로 수행된다. 이 명령은 실행 중인 컨테이너에는 적용되지 않으므로, 먼저 컨테이너를 중지하거나 -f (force) 옵션을 사용해야 한다. 삭제 프로세스는 컨테이너가 사용하던 리눅스 네임스페이스와 cgroups를 해제하고, 컨테이너 레이어에 해당하는 쓰기 가능한 스토리지 계층을 제거한다. 그러나 컨테이너를 생성하는 데 사용된 읽기 전용 이미지 레이어는 삭제되지 않고 시스템에 남아 다른 컨테이너에서 계속 재사용된다.
사용하지 않는 컨테이너와 리소스를 일괄 정리하는 명령도 존재한다. container prune 명령은 중지된 모든 컨테이너를 한 번에 삭제한다. 더 포괄적인 system prune 명령은 중지된 컨테이너뿐만 아니라 사용하지 않는 도커 이미지, 네트워크, 빌드 캐시까지 정리하여 디스크 공간을 회수한다. 이 과정은 시스템 정리와 자원 관리에 유용하지만, 필요한 이미지나 데이터가 삭제될 수 있으므로 주의가 필요하다.
명령어 | 주요 기능 | 참고 사항 |
|---|---|---|
| 컨테이너 정상 중지 | SIGTERM 후 SIGKILL을 전송 |
| 컨테이너 강제 중지 | 기본 시그널은 SIGKILL |
| 컨테이너 삭제 | 실행 중인 컨테이너는 |
| 모든 중지된 컨테이너 삭제 | 일괄 정리 |
| 사용하지 않는 모든 도커 객체 삭제 | 이미지, 컨테이너, 네트워크, 캐시 포함 |
컨테이너 이미지는 애플리케이션과 그 실행 환경을 패키징한 읽기 전용 템플릿이다. 이 이미지는 여러 개의 레이어로 구성되며, 각 레이어는 파일 시스템의 변경 사항을 나타낸다. 예를 들어, 베이스 운영체제 레이어 위에 패키지 설치, 소스 코드 추가, 환경 변수 설정 등의 레이어가 순차적으로 쌓인다. 이러한 유니온 파일 시스템 덕분에 여러 이미지가 공통의 베이스 레이어를 공유할 수 있어 저장 공간을 효율적으로 사용하고 이미지 전송 속도를 높인다.
이미지 빌드는 Dockerfile과 같은 선언적 파일에 정의된 명령어들을 순차적으로 실행하여 이루어진다. 각 명령어는 새로운 레이어를 생성한다. 빌드 과정에서 캐시 메커니즘은 이전에 빌드된 레이어를 재사용하여 빌드 시간을 단축시킨다. 완성된 이미지는 OCI 이미지 형식 표준에 따라 특정 구조로 저장되며, 일반적으로 tar 아카이브 형태로 압축되어 관리된다.
레이어 유형 | 설명 | 예시 |
|---|---|---|
베이스 레이어 | 운영체제의 핵심 파일 시스템을 제공한다. |
|
애플리케이션 레이어 | 런타임, 라이브러리, 패키지를 설치한다. |
|
구성 레이어 | 환경 변수, 작업 디렉토리, 사용자 등을 설정한다. |
|
실행 레이어 | 컨테이너 실행 시 동작할 명령어나 애플리케이션 파일을 추가한다. |
|
이미지는 중앙 저장소인 레지스트리에 배포되고 공유된다. 가장 대표적인 레지스트리는 Docker Hub이다. 사용자는 docker pull 명령어로 레지스트리에서 이미지를 가져오고, docker push 명령어로 자신의 이미지를 업로드한다. 레이어 기반 구조 덕분에 이미지를 다운로드할 때 이미 로컬에 존재하는 레이어는 다시 받지 않아도 되어 네트워크 대역폭을 절약한다.
컨테이너 이미지는 여러 개의 읽기 전용 레이어로 구성된 집합체이다. 각 레이어는 파일 시스템의 변경 사항을 나타내는 차분(diff)으로, 하위 레이어들 위에 순차적으로 쌓여 최종적인 컨테이너의 파일 시스템 뷰를 구성한다. 예를 들어, 우분투 기본 이미지 레이어 위에 APT 패키지 관리자로 Nginx를 설치하는 레이어, 그 위에 웹 애플리케이션 소스 코드를 복사하는 레이어가 추가될 수 있다. 이렇게 레이어를 쌓아 올리는 방식은 유니온 파일 시스템 기술을 기반으로 한다.
레이어의 작동 원리는 Copy-on-Write 전략에 기반한다. 컨테이너가 시작되면 모든 읽기 전용 이미지 레이어는 스택처럼 결합되어 단일 통합 파일 시스템을 제공한다. 컨테이너 내에서 파일을 수정해야 할 경우, 해당 파일이 속한 레이어는 읽기 전용이므로, 컨테이너 엔진은 최상위에 새로운 쓰기 가능 레이어(컨테이너 레이어)를 생성하고, 수정될 파일을 이 레이어로 복사한 후 변경 작업을 수행한다. 이는 기본 이미지 레이어의 무결성을 보장하면서도 컨테이너마다 독립적인 변경 사항을 가질 수 있게 한다.
레이어 유형 | 설명 | 특징 |
|---|---|---|
베이스 레이어 | 운영체제의 루트 파일 시스템 (예: Alpine, Ubuntu) | 이미지 스택의 최하단에 위치하는 기초 레이어이다. |
중간 레이어 | 패키지 설치, 파일 추가/삭제 등 빌드 명령어(Dockerfile의 각 줄)마다 생성된다. | 읽기 전용이며, 여러 이미지가 공유할 수 있어 저장 공간을 절약한다. |
컨테이너 레이어 | 실행 중인 컨테이너의 쓰기 가능 레이어 ("thin R/W layer") | 컨테이너 생명주기 동안의 모든 변경 사항이 저장되며, 컨테이너 삭제 시 함께 사라진다. |
이러한 레이어 기반 구조는 효율성과 재사용성을 극대화한다. 동일한 베이스 이미지를 사용하는 여러 애플리케이션은 디스크 상에서 그 베이스 레이어를 공유한다. 또한 이미지를 빌드할 때, Dockerfile의 각 명령어는 새로운 레이어를 생성하므로, 빌드 과정 중 특정 레이어까지의 결과는 캐시되어 동일한 빌드를 반복할 때 시간을 단축시킨다. 그러나 불필요하게 많은 레이어는 이미지 크기를 증가시킬 수 있으므로, 빌드 명령어를 통합하는 등의 최적화가 필요하다.
이미지 빌드는 일반적으로 Dockerfile이라는 텍스트 파일에 정의된 명령어 집합을 기반으로 수행된다. Dockerfile의 각 명령어(예: FROM, RUN, COPY, CMD)는 하나의 이미지 레이어를 생성한다. 이 빌드 과정은 유니온 파일 시스템을 통해 구현되며, 각 레이어는 이전 레이어에 대한 변경사항(파일 추가, 수정, 삭제)의 집합으로 구성된다. 빌드가 완료되면 이러한 레이어 스택이 하나의 읽기 전용 컨테이너 이미지를 형성한다.
이미지 저장 방식의 핵심은 레이어의 재사용과 공유에 있다. 서로 다른 이미지가 동일한 기본 레이어(예: 동일한 ubuntu:22.04 베이스 이미지)를 사용하면, 시스템 디스크에는 해당 레이어의 단일 사본만 저장된다. 이는 저장 공간을 절약하고 이미지 풀(pull) 속도를 높이는 데 기여한다. 로컬 시스템에서 이미지는 /var/lib/docker/(또는 해당 스토리지 드라이버 디렉토리) 하위에 레이어별로 분리되어 저장된다.
저장 구성 요소 | 설명 |
|---|---|
이미지 매니페스트 | 이미지를 구성하는 레이어들의 다이제스트(해시) 목록과 메타데이터를 포함하는 JSON 파일이다. |
레이어 디렉토리 | 각 레이어의 실제 파일 시스템 변경 내용이 압축된 tar 아카이브( |
이미지 구성 파일 | 이미지의 실행 환경 변수, 기본 명령어(CMD), 진입점(ENTRYPOINT) 등의 설정 정보를 담고 있다. |
최종적으로 빌드된 이미지는 고유한 다이제스트 (SHA256 해시)로 식별되며, 레지스트리에 푸시(push)될 때는 모든 레이어와 메타데이터 파일이 업로드된다. 레지스트리에서 이미지를 풀(pull)하면 필요한 레이어만 다운로드하여 로컬 저장소에 계층적으로 구성한다.
도커 허브나 쿠버네티스용 컨테이너 레지스트리와 같은 레지스트리는 컨테이너 이미지를 저장하고 배포하는 중앙 저장소 역할을 한다. 레지스트리는 클라이언트-서버 모델로 작동하며, 컨테이너 엔진은 API를 통해 이미지를 푸시(push)하거나 풀(pull)한다. 공개 레지스트리 외에도 AWS ECR, Google Container Registry, Azure Container Registry 같은 클라우드 제공 서비스나 Harbor, Quay 같은 사설 레지스트리 솔루션을 사용할 수 있다.
이미지 배포 과정은 일반적으로 빌드, 태그 지정, 푸시의 단계를 거친다. 개발자는 로컬에서 이미지를 빌드한 후 docker push 같은 명령어를 사용해 특정 레지스트리 주소와 이미지 이름, 태그를 지정하여 업로드한다. 풀 과정에서는 엔진이 지정된 레지스트리에서 이미지 매니페스트와 관련 이미지 레이어를 다운로드하여 로컬에 캐시한다. 이 구조는 이미지 레이어의 재사용성을 극대화하여 동일한 베이스 레이어를 공유하는 이미지의 배포 효율을 높인다.
레지스트리 유형 | 주요 예시 | 특징 |
|---|---|---|
공개 레지스트리 | 공개 이미지 무료 호스팅, 커뮤니티 활성화 | |
클라우드 서비스 | 클라우드 인프라와 긴밀 통합, 관리형 서비스 | |
사설/온프레미스 | 기업 내부 보안 요구 충족, 완전한 제어 가능 |
이미지 태그는 버전 관리의 핵심 수단이다. latest, v1.2.3, alpine 같은 태그를 통해 동일한 이미지의 서로 다른 버전이나 변종을 식별한다. 레지스트리는 다이제스트(digest)라는 고유한 SHA-256 해시값을 통해 이미지의 정확한 버전을 보장하여 무결성을 검증한다. 이는 특정 빌드를 안정적으로 배포하고 롤백하는 데 필수적이다.
컨테이너 엔진은 리눅스 커널이 제공하는 네임스페이스와 컨트롤 그룹(cgroups)이라는 두 가지 핵심 기술을 활용하여 프로세스의 격리와 자원 관리를 구현한다. 이 기술들은 컨테이너가 호스트 시스템 및 다른 컨테이너로부터 독립된 환경을 가지면서도 정의된 자원 한도 내에서 실행될 수 있도록 한다.
리눅스 네임스페이스의 종류와 역할
네임스페이스는 프로세스가 볼 수 있는 시스템 리소스의 범위를 제한하는 가상화 기술이다. 각 네임스페이스는 고유한 격리된 뷰를 제공하며, 주요 종류와 역할은 다음과 같다.
네임스페이스 | 격리 대상 | 설명 |
|---|---|---|
PID | 프로세스 ID | 컨테이너 내부는 자체 PID 1 프로세스를 가지며, 호스트의 프로세스 목록을 보지 못한다. |
Network | 네트워크 스택 | 컨테이너는 독자적인 네트워크 인터페이스, IP 주소, 라우팅 테이블, 포트 공간을 가진다. |
Mount | 파일 시스템 마운트 포인트 | 컨테이너는 호스트의 파일 시스템 레이아웃과 분리된 자신만의 마운트 포인트 집합을 가진다. |
UTS | 호스트명과 도메인명 | 컨테이너는 호스트 시스템과 독립적인 호스트 이름을 설정할 수 있다. |
IPC | 프로세스 간 통신(IPC) 자원 | 공유 메모리나 메시지 큐와 같은 IPC 자원이 컨테이너 간에 공유되지 않도록 한다. |
User | 사용자 및 그룹 ID | 컨테이너 내부에서 사용자 ID와 그룹 ID를 호스트의 ID와 별도로 재매핑할 수 있다[1]. |
cgroups를 통한 자원 격리 및 제한
컨트롤 그룹(cgroups)은 프로세스 그룹이 사용할 수 있는 시스템 자원의 양을 제한, 격리, 측정하는 커널 기능이다. 네임스페이스가 "무엇을 볼 수 있는가"를 격리한다면, cgroups는 "얼마나 사용할 수 있는가"를 관리한다. 주요 제어 가능한 자원 유형은 다음과 같다.
CPU: 컨테이너가 사용할 수 있는 CPU 시간의 비율이나 특정 CPU 코어를 할당한다.
메모리: 컨테이너가 사용할 수 있는 최대 메모리 및 스왑 공간을 설정한다. 한도를 초과하면 프로세스가 종료될 수 있다.
블록 I/O(디스크 읽기/쓰기): 디스크 장치에 대한 읽기/쓰기 속도나 대역폭을 제한한다.
네트워크 I/O: 네트워크 인터페이스를 통한 대역폭을 제어한다(일부 드라이버를 통해).
컨테이너 엔진은 컨테이너를 생성할 때 이 두 기술을 결합하여 사용한다. 먼저 cgroups를 구성하여 자원 한계를 설정한 다음, 네임스페이스를 생성하거나 조인하여 격리된 환경을 만든다. 이 조합을 통해 컨테이너는 마치 독립된 서버처럼 동작하지만, 실제로는 호스트 커널을 공유하는 경량의 프로세스 집합으로 실행된다.
리눅스 커널의 네임스페이스는 프로세스에 대한 시스템 리소스의 격리된 뷰를 제공하는 기능이다. 컨테이너 엔진은 이 기능을 활용하여 각 컨테이너가 마치 독립적인 시스템인 것처럼 느끼도록 만든다. 각 네임스페이스는 특정한 종류의 시스템 리소스를 분리하며, 한 네임스페이스 내의 변경 사항은 다른 네임스페이스에 영향을 미치지 않는다.
주요 네임스페이스의 종류와 그 역할은 다음과 같다.
네임스페이스 | 격리 대상 | 설명 |
|---|---|---|
PID | 프로세스 ID | 네임스페이스 내부에서는 독자적인 프로세스 ID 체계를 가진다. 컨테이너 내부의 첫 번째 프로세스는 PID 1로 보인다. |
Network | 네트워크 스택 | 독립적인 네트워크 인터페이스, IP 주소, 라우팅 테이블, 포트 번호 공간을 제공한다. |
Mount | 파일 시스템 마운트 포인트 | 컨테이너는 호스트의 파일 시스템 마운트 목록을 볼 수 없으며, 자신만의 마운트 포인트를 가질 수 있다. |
UTS | 호스트명과 도메인명 | 컨테이너가 자신의 호스트 이름과 NIS 도메인 이름을 설정할 수 있게 한다. |
IPC | 프로세스 간 통신 | System V IPC 객체나 POSIX 메시지 큐와 같은 자원을 컨테이너별로 격리한다. |
User | 사용자 및 그룹 ID | 컨테이너 내부에서 사용자와 그룹 ID를 호스트와 다른 값으로 재매핑할 수 있다. 컨테이너 내부의 루트 사용자는 호스트에서는 일반 권한의 사용자일 수 있다. |
Cgroup | 컨트롤 그룹 | cgroup 계층 구조를 격리하여, 컨테이너가 자신만의 cgroup 계층 구조를 보도록 한다. |
Time | 시스템 시간 | 시스템 클럭에 대한 오프셋을 설정할 수 있어, 컨테이너별로 다른 시스템 시간을 가질 수 있다[2]. |
컨테이너 엔진은 컨테이너를 생성할 때 이 네임스페이스들을 조합하여 사용한다. 예를 들어, docker run 명령은 기본적으로 새로운 PID, Network, Mount, UTS, IPC 네임스페이스를 생성한다. User 네임스페이스는 보안 강화를 위해 선택적으로 사용되며, 이를 통해 권한 상승 공격의 위험을 줄일 수 있다. 이러한 격리 메커니즘은 가상 머신의 하드웨어 수준 격리보다 가볍고 빠르게 동작하는 컨테이너의 특성을 가능하게 하는 핵심 기반이다.
cgroups(컨트롤 그룹)는 리눅스 커널의 기능으로, 프로세스 집합의 시스템 자원 사용을 제한, 격리, 계측하는 메커니즘을 제공한다. 컨테이너 엔진은 이 기능을 활용하여 각 컨테이너가 사용할 수 있는 CPU, 메모리, 디스크 I/O, 네트워크 대역폭 등의 자원을 격리하고 제한한다. 이를 통해 단일 호스트에서 실행되는 여러 컨테이너가 자원을 공정하게 나누어 사용하거나, 특정 컨테이너가 시스템 전체에 영향을 미치는 것을 방지할 수 있다.
cgroups는 여러 하위 시스템(컨트롤러)으로 구성되며, 각 하위 시스템은 특정 유형의 자원을 관리한다. 주요 하위 시스템과 그 역할은 다음과 같다.
하위 시스템 | 관리하는 자원 |
|---|---|
| CPU 시간 할당량 및 사용량 계측 |
| 메모리 사용량 및 제한, 스왑 공간 |
| 블록 장치(디스크)의 입출력 제한 |
| 장치 파일에 대한 접근 제어 |
| cgroup 내 프로세스의 일시 중지/재개 |
| 네트워크 패킷에 태그를 붙여 트래픽 제어 |
컨테이너 엔진은 컨테이너를 시작할 때, 해당 컨테이너의 모든 프로세스를 위한 cgroup을 생성하고 이 그룹에 자원 제한을 설정한다. 예를 들어, 메모리 하위 시스템을 통해 컨테이너가 사용할 수 있는 최대 메모리 양을 512MB로 제한할 수 있다. 컨테이너가 이 제한을 초과하여 메모리를 할당하려고 하면, 커널은 해당 프로세스를 종료시킨다. CPU 제한의 경우, 상대적인 가중치를 부여하거나 특정 CPU 코어에 프로세스를 고정시키는 방식으로 제어한다.
cgroups를 통한 자원 격리는 리눅스 네임스페이스가 제공하는 프로세스, 네트워크, 파일 시스템 등의 격리와 함께 작동하여 완전한 컨테이너 환경을 구성한다. 네임스페이스가 "무엇을 볼 수 있는가"를 격리한다면, cgroups는 "얼마나 사용할 수 있는가"를 제한한다. 이 조합은 호스트 시스템의 안정성을 보장하고, 다중 테넌트 환경에서의 자원 보장과 공정한 분배를 가능하게 하는 컨테이너 기술의 핵심 기반이 된다.
컨테이너 엔진은 컨테이너에 다양한 네트워크 연결 방식을 제공하기 위해 여러 네트워크 드라이버를 지원한다. 가장 일반적으로 사용되는 기본 드라이버로는 브리지 네트워크, 호스트 네트워크, 그리고 없음 네트워크가 있다. 브리지 네트워크는 기본 설정으로, 엔진이 자동으로 생성하는 가상의 브리지 인터페이스에 컨테이너를 연결한다. 이 경우 컨테이너는 가상의 사설 네트워크에 속하게 되며, 네트워크 주소 변환을 통해 외부 네트워크와 통신한다. 호스트 네트워크 드라이버를 사용하면 컨테이너가 호스트 시스템의 네트워크 스택을 직접 공유하여 네트워크 격리가 사라지지만 성능상 이점을 얻을 수 있다. 없음 네트워크 드라이버는 컨테이너에 어떠한 네트워크 인터페이스도 할당하지 않는다.
컨테이너 간 통신은 동일한 사용자 정의 네트워크에 연결된 경우 컨테이너 이름을 호스트명으로 사용하여 직접 가능하다. 브리지 네트워크를 사용할 경우, 기본적으로는 외부 네트워크로의 나가는 연결만 가능하며, 들어오는 연결을 허용하려면 호스트의 특정 포트를 컨테이너 포트에 명시적으로 매핑해야 한다[3]. 복잡한 다중 호스트 환경을 구성하기 위해 오버레이 네트워크를 사용할 수 있다. 이 드라이버는 여러 도커 데몬 호스트에 걸쳐 분산된 컨테이너들이 마치 같은 가상 네트워크에 있는 것처럼 통신할 수 있게 한다.
사용자는 특정 요구사항에 맞게 사용자 정의 네트워크를 생성할 수 있다. 사용자 정의 브리지 네트워크는 기본 브리지 네트워크보다 향상된 기능을 제공하며, DNS 기반 서비스 디스커버리를 통해 컨테이너 간 자동 이름 해석이 가능하다. 네트워크 드라이버의 선택은 애플리케이션의 격리 요구사항, 성능, 그리고 운영 환경에 따라 결정된다.
네트워크 드라이버 | 설명 | 주요 사용 사례 |
|---|---|---|
bridge | 가상 브리지를 통해 컨테이너를 연결하는 기본 네트워크. 컨테이너는 사설 네트워크를 가지며 NAT를 통해 외부와 통신한다. | 단일 호스트에서 실행되는 독립적인 컨테이너들. |
host | 컨테이너가 호스트의 네트워크 네임스페이스를 직접 사용한다. 네트워크 격리가 없다. | 네트워크 성능이 극도로 중요하고 격리가 필요 없는 경우. |
none | 컨테이너에 네트워크 인터페이스를 할당하지 않는다. | 네트워크 접근이 전혀 필요 없는 특수한 컨테이너. |
overlay | 여러 호스트에 걸친 컨테이너들이 통신할 수 있게 하는 가상 네트워크. | |
macvlan | 컨테이너에 물리적 네트워크의 MAC 주소를 부여하여 마치 물리적 장치처럼 보이게 한다. | 컨테이너가 물리 네트워크상의 특정 IP를 가져야 하는 레거시 애플리케이션. |
컨테이너 엔진은 컨테이너에 네트워크 연결을 제공하기 위해 여러 가지 기본 네트워크 드라이버를 제공한다. 가장 일반적으로 사용되는 드라이버는 브리지 네트워크, 호스트 네트워크, 그리고 없음 네트워크이다. 각 드라이버는 서로 다른 수준의 격리와 연결성을 제공하여 다양한 사용 사례에 맞게 선택할 수 있다.
브리지 네트워크는 기본적으로 생성되는 네트워크 모드이다. 이 모드에서는 엔진이 자체적으로 소프트웨어 네트워크 브리지를 생성하고, 각 컨테이너는 이 브리지에 연결된 가상 네트워크 인터페이스를 할당받는다. 컨테이너는 동일한 브리지 네트워크에 연결된 경우 서로 통신할 수 있으며, 호스트의 물리적 네트워크를 통해 외부 네트워크와도 통신이 가능하다. 이때 네트워크 주소 변환이 사용되어 컨테이너의 내부 IP가 호스트의 IP로 변환된다. 브리지 네트워크는 컨테이너 간 기본적인 격리를 유지하면서 네트워크 연결을 제공하는 표준 방식이다.
호스트 네트워크 드라이버를 사용하면 컨테이너가 호스트 머신의 네트워크 네임스페이스를 공유한다. 이는 컨테이너가 호스트의 네트워크 인터페이스와 IP 주소를 직접 사용한다는 의미이다. 결과적으로 네트워크 격리가 전혀 이루어지지 않으며, 컨테이너의 네트워크 성능은 브리지 모드에 비해 오버헤드가 적어 더 우수하다. 그러나 포트 충돌이 발생할 수 있고 보안 격리 측면에서 취약할 수 있다. 이 모드는 네트워크 성능이 극단적으로 중요한 경우나 단일 애플리케이션을 격리 없이 실행할 때 주로 사용된다.
없음 네트워크 드라이버는 컨테이너에 어떠한 네트워크 스택도 제공하지 않는다. 컨테이너 내부에는 루프백 인터페이스만 존재하며, 외부 네트워크나 다른 컨테이너와의 통신이 완전히 차단된다. 이 모드는 네트워크 접근이 전혀 필요 없는 배치 작업 컨테이너나, 보안상 외부 연결을 철저히 차단해야 하는 특수한 경우에 사용된다. 사용자는 나중에 사용자 정의 네트워크를 컨테이너에 연결할 수도 있다.
드라이버 유형 | 네트워크 격리 수준 | 성능 | 주요 사용 사례 |
|---|---|---|---|
브리지 | 컨테이너 별 가상 네트워크 | 표준 (NAT 오버헤드 있음) | 일반적인 애플리케이션 실행, 개발 환경 |
호스트 | 격리 없음 (호스트 네트워크 공유) | 우수 (오버헤드 최소화) | 고성능 네트워킹이 필요한 애플리케이션 |
없음 | 완전 차단 | 해당 없음 | 네트워크 접근 불필요한 작업, 보안 격리 테스트 |
컨테이너 간 통신은 일반적으로 동일한 사용자 정의 네트워크에 속해 있을 때 가장 간단하게 이루어진다. 엔진이 자동으로 해당 네트워크에 대한 DNS 서비스를 제공하여, 컨테이너 이름을 IP 주소로 자동 변환해주기 때문이다. 예를 들어, web 컨테이너는 app 컨테이너를 app이라는 호스트명으로 직접 핑하거나 연결할 수 있다. 이는 기본 브리지 네트워크에서는 지원되지 않는 기능이다.
컨테이너가 외부 네트워크(인터넷)에 연결하는 과정은 NAT(네트워크 주소 변환)를 통해 처리된다. 컨테이너 엔진은 호스트 머신에 가상의 브리지 인터페이스(예: docker0)를 생성하고, 컨테이너에 사설 IP 대역(예: 172.17.0.0/16)을 할당한다. 컨테이너에서 외부로 나가는 패킷은 호스트의 공인 IP로 SNAT(Source NAT)되어 전송되며, 들어오는 응답 패킷은 다시 해당 컨테이너로 라우팅된다.
외부에서 실행 중인 컨테이너의 서비스에 접근하려면 포트 매핑이 필요하다. -p 8080:80과 같은 옵션을 사용하면, 호스트의 8080 포트로 들어오는 트래픽을 컨테이너의 80 포트로 전달한다[4]. 이를 통해 호스트의 IP 주소와 매핑된 포트를 통해 컨테이너 애플리케이션에 접근할 수 있다.
통신 유형 | 설명 | 주요 메커니즘 |
|---|---|---|
동일 네트워크 내 컨테이너 간 | 컨테이너 이름으로 직접 통신 가능 | |
컨테이너 → 외부(인터넷) | 외부 네트워크 접근 | 호스트를 통한 NAT(네트워크 주소 변환) |
외부 → 컨테이너 | 컨테이너 서비스 노출 | 포트 퍼블리싱(포트 매핑) |
사용자는 도커나 포드맨과 같은 컨테이너 엔진에서 기본으로 제공하는 네트워크 모드 외에, 특정 요구사항을 충족하기 위해 사용자 정의 네트워크를 생성할 수 있다. 사용자 정의 네트워크는 기본 브리지 네트워크보다 향상된 기능을 제공하며, DNS 기반의 자동 서비스 디스커버리를 지원한다. 이를 통해 컨테이너는 IP 주소가 아닌 컨테이너 이름으로 서로를 찾아 통신할 수 있다.
오버레이 네트워크는 여러 도커 데몬 호스트에 걸쳐 분산된 컨테이너들이 마치 같은 가상 네트워크에 존재하는 것처럼 통신할 수 있게 해주는 네트워크 드라이버이다. 이는 도커 스웜 모드나 쿠버네티스와 같은 컨테이너 오케스트레이션 시스템에서 필수적이다. 오버레이 네트워크는 각 호스트의 물리적 네트워크 위에 가상의 네트워크 레이어를 생성하여 데이터 패킷을 캡슐화하고 라우팅한다.
사용자 정의 네트워크와 오버레이 네트워크의 주요 특성은 다음과 같이 비교할 수 있다.
특성 | 사용자 정의 네트워크 (예: 사용자 정의 브리지) | 오버레이 네트워크 |
|---|---|---|
적용 범위 | 단일 도커 호스트 내부 | 여러 도커 호스트에 걸친 클러스터 |
서비스 디스커버리 | 내장 DNS 지원 (컨테이너 이름/IP) | 내장 DNS 지원 (서비스/태스크 이름) |
연결성 | 동일 네트워크 내 컨테이너 간 자동 연결 | 다른 호스트의 컨테이너 간 가상 연결 |
주요 사용 사례 | 복잡한 단일 호스트 애플리케이션 스택 | 분산형 마이크로서비스, 컨테이너 오케스트레이션 |
오버레이 네트워크를 구성하려면 키-값 저장소(예: etcd 또는 콘술)를 통한 분산 네트워크 상태 관리와 호스트 간의 보안 통신 채널(암호화된 터널) 설정이 선행되어야 한다. 데이터 평면 트래픽은 VXLAN과 같은 캡슐화 프로토콜을 사용하여 전송된다.
컨테이너 엔진의 스토리지 관리는 컨테이너 내부의 데이터를 생성, 저장, 유지하는 방식을 다룬다. 컨테이너는 기본적으로 이미지 레이어 위에 생성된 쓰기 가능한 얇은 레이어에서 동작하며, 이 레이어의 모든 변경 사항은 컨테이너가 삭제되면 함께 사라진다. 따라서 데이터를 지속적으로 보존하기 위해 볼륨과 바인드 마운트와 같은 메커니즘을 제공한다. 볼륨은 컨테이너 엔진에 의해 완전히 관리되는 디렉터리로, 컨테이너의 생명주기와 독립적으로 존재한다. 바인드 마운트는 호스트 머신의 파일 시스템 경로를 컨테이너에 직접 연결하는 방식이다.
방식 | 설명 | 주요 사용 사례 |
|---|---|---|
볼륨 (Volume) | 도커 엔진에 의해 생성/관리되는 디렉터리. 컨테이너와 독립적. | 데이터 지속성, 컨테이너 간 데이터 공유. |
바인드 마운트 (Bind Mount) | 호스트의 특정 경로를 컨테이너에 직접 마운트. | 개발 환경(소스 코드 마운트), 호스트 시스템 파일 접근. |
tmpfs 마운트 | 호스트의 메모리에만 존재하는 임시 파일 시스템. | 비밀번호, 세션 데이터 등 일시적이고 민감한 정보. |
스토리지 드라이버는 컨테이너의 쓰기 가능 레이어가 호스트의 파일 시스템에 데이터를 저장하고 관리하는 방식을 결정한다. overlay2, aufs, devicemapper 등이 대표적이며, 호스트의 운영체제와 요구사항에 따라 선택된다. 이 드라이버는 이미지 레이어를 효율적으로 쌓고, 여러 컨테이너가 동일한 기본 이미지 레이어를 공유할 수 있게 하여 디스크 공간을 절약한다. 그러나 애플리케이션 데이터의 장기적 보존은 스토리지 드라이버가 아닌 볼륨을 통해 이루어지는 것이 일반적이다.
tmpfs 마운트는 디스크가 아닌 호스트 시스템의 메모리(RAM)에 파일 시스템을 생성한다. 이 방식으로 마운트된 데이터는 컨테이너가 중지되거나 재시작되면 휘발된다. 이는 처리 속도가 매우 빠르며 디스크 I/O 부하를 줄일 수 있지만, 호스트 메모리 용량에 제한을 받는다. 따라서 보안이 요구되는 임시 데이터나 성능이 중요한 일시적 파일을 저장하는 데 적합하다.
컨테이너 엔진에서 데이터를 지속적으로 저장하거나 호스트 시스템과 파일을 공유하기 위해 사용하는 주요 메커니즘은 볼륨과 바인드 마운트이다. 이 두 방식은 컨테이너의 생명주기와 독립적으로 데이터를 관리할 수 있게 해주며, 사용 목적과 관리 방식에 차이가 있다.
볼륨은 컨테이너 엔진에 의해 완전히 관리되는 디렉터리이다. 볼륨은 호스트 파일 시스템의 특정 경로(일반적으로 /var/lib/docker/volumes/ 내부)에 생성되지만, 사용자는 주로 엔진의 API나 CLI 명령어를 통해 이를 관리한다. 볼륨의 주요 장점은 데이터가 컨테이너로부터 완전히 분리된다는 점이다. 컨테이너를 삭제해도 볼륨과 그 안의 데이터는 유지되며, 여러 컨테이너가 동일한 볼륨을 동시에 마운트하여 데이터를 공유할 수 있다. 이는 데이터베이스의 데이터 디렉터리나 애플리케이션의 공유 설정 파일을 관리하는 데 적합하다.
반면, 바인드 마운트는 호스트 시스템의 기존 파일이나 디렉터리를 컨테이너 내부의 경로에 직접 연결하는 방식이다. 바인드 마운트는 호스트의 파일 시스템 구조에 의존하며, 절대 경로를 사용하여 마운트 지점을 지정한다. 이 방식은 호스트의 설정 파일(/etc 디렉터리 내 파일 등)을 컨테이너에 제공하거나, 개발 중인 소스 코드 디렉터리를 컨테이너의 작업 디렉터리에 마운트하여 변경 사항을 실시간으로 반영하는 데 주로 사용된다. 바인드 마운트는 볼륨보다 더 많은 제어권을 제공하지만, 호스트의 파일 시스템 구조를 컨테이너에 노출시키므로 보안에 더 주의를 기울여야 한다.
두 방식을 비교하면 다음과 같은 차이점이 명확해진다.
특징 | 볼륨 | 바인드 마운트 |
|---|---|---|
관리 주체 | 컨테이너 엔진 | 사용자 (호스트 파일 시스템) |
저장 위치 | 엔진 관리 영역 (e.g., | 호스트의 임의 경로 |
이식성 | 높음 (엔진 명령어로 관리 및 백업 용이) | 낮음 (호스트 경로에 의존) |
주요 사용 사례 | 데이터 지속성, 컨테이너 간 데이터 공유 | 개발 환경(소스 코드 마운트), 호스트 설정 공유 |
성능 | 일반적으로 리눅스 환경에서 비슷함 |
일반적으로 데이터 백업, 마이그레이션, 여러 컨테이너 간 안전한 공유가 필요할 때는 볼륨을 사용하는 것이 권장된다. 호스트와의 긴밀한 통합이나 개발 편의성이 더 중요할 경우 바인드 마운트를 선택한다. 많은 컨테이너 엔진은 docker run -v 또는 docker run --mount 명령어를 통해 두 가지 방식을 모두 지원한다[5].
스토리지 드라이버는 컨테이너의 쓰기 가능 계층을 관리하고, 이미지 레이어를 호스트 파일 시스템에 저장하는 방식을 결정하는 소프트웨어 컴포넌트이다. 컨테이너 엔진은 overlay2, aufs, devicemapper, btrfs, zfs 등 다양한 스토리지 드라이버를 지원하며, 호스트의 리눅스 커널과 파일 시스템에 따라 적합한 드라이버가 선택된다. 이 드라이버는 유니온 파일 시스템 기술을 활용하여 여러 읽기 전용 이미지 레이어를 단일 통합 뷰로 겹쳐 쌓고, 그 위에 컨테이너별 쓰기 가능 레이어를 생성한다. 이를 통해 동일한 기본 이미지를 공유하는 여러 컨테이너가 디스크 공간을 효율적으로 사용할 수 있다.
컨테이너 내부에서 생성되거나 수정된 모든 데이터는 기본적으로 이 쓰기 가능 레이어에 저장된다. 그러나 이 레이어는 컨테이너의 생명주기와 연결되어 있어, 컨테이너가 삭제되면 해당 레이어와 함께 내부 데이터도 영구적으로 사라진다. 이는 데이터 지속성을 요구하는 애플리케이션(예: 데이터베이스, 사용자 업로드 파일을 저장하는 웹 애플리케이션)에 중요한 문제이다. 데이터 지속성을 보장하기 위해 컨테이너 엔진은 호스트 파일 시스템의 특정 경로를 컨테이너 내부에 마운트하는 메커니즘을 제공한다.
주요 데이터 지속성 방법은 다음과 같다.
방법 | 설명 | 데이터 생존 범위 |
|---|---|---|
도커 엔진에 의해 관리되는 호스트 파일 시스템 내의 디렉토리( | 컨테이너 간 공유 및 재사용 가능, 컨테이너 삭제 후에도 보존 | |
호스트 시스템의 임의의 파일이나 디렉토리를 컨테이너에 직접 마운트한다. 볼륨보다 더 직접적인 제어가 가능하다. | 호스트 파일 시스템 경로에 의존, 컨테이너 삭제 후에도 보존 | |
| 데이터를 호스트의 메모리에만 저장한다. 디스크에 쓰지 않아 성능이 빠르지만, 호스트나 컨테이너 재시작 시 데이터가 사라진다. | 컨테이너 생명주기 내에서만 보존, 휘발성 |
따라서 스토리지 드라이버는 이미지 레이어의 효율적 관리를 담당하는 반면, 데이터 지속성은 주로 볼륨이나 바인드 마운트를 통해 컨테이너의 쓰기 가능 레이어 외부에 데이터를 저장함으로써 달성된다. 이는 애플리케이션 데이터와 컨테이너 인프라를 분리하는 핵심 설계 원칙이다.
tmpfs 마운트는 컨테이너의 파일 시스템에 tmpfs라는 임시 파일 시스템을 마운트하는 방식이다. 이는 RAM이나 스왑 공간을 활용하여 메모리 기반의 파일 시스템을 제공한다. tmpfs 마운트를 통해 생성된 모든 파일과 디렉터리는 호스트 시스템의 물리적 메모리에 저장되며, 컨테이너가 중지되거나 호스트 시스템이 재부팅되면 해당 데이터는 영구적으로 사라진다. 따라서 이 방식은 데이터 지속성이 필요 없는 임시 작업에 주로 사용된다.
주요 사용 사례로는 컨테이너 내 애플리케이션의 임시 파일 처리, 세션 데이터 저장, 또는 빈번한 읽기/쓰기 작업이 발생하는 캐시 디렉터리 마운트 등이 있다. Docker에서는 docker run 명령어에 --tmpfs 옵션을 사용하거나, 도커 컴포즈 파일 및 컨테이너 실행 시 --mount 옵션에 type=tmpfs를 지정하여 구현할 수 있다.
tmpfs 마운트의 주요 특성은 다음과 같이 정리할 수 있다.
특성 | 설명 |
|---|---|
저장 위치 | 호스트 시스템의 메모리(RAM/스왑) |
데이터 지속성 | 컨테이너 생명주기 또는 호스트 재부팅 시 소멸 (비영구적) |
성능 | 디스크 I/O보다 매우 빠른 읽기/쓰기 속도 |
주요 용도 | 임시 파일, 캐시, 세션 데이터, 스크래치 공간 |
자원 제한 |
|
이 방식은 디스크 기반 스토리지 드라이버나 볼륨을 사용할 때 발생할 수 있는 I/O 병목 현상을 줄이고 성능을 극대화할 수 있다. 그러나 메모리는 제한된 자원이므로, size 옵션 등을 통해 적절한 용량 제한을 설정하지 않으면 호스트 시스템의 메모리 부족을 초래할 수 있다는 점에 유의해야 한다.
컨테이너 엔진의 보안 구조는 리눅스 커널이 제공하는 격리 메커니즘을 기반으로 구축된다. 핵심은 리눅스 네임스페이스와 cgroups를 활용하여 프로세스, 네트워크, 파일 시스템, 사용자 ID 등의 자원을 격리하는 것이다. 예를 들어, 프로세스 네임스페이스는 컨테이너 내부에서 실행되는 프로세스가 호스트의 다른 프로세스를 보지 못하도록 차단한다. 파일 시스템 격리는 유니온 파일 시스템과 마운트 네임스페이스를 통해 구현되며, 컨테이너는 자신에게 할당된 이미지 레이어와 볼륨만 접근할 수 있다. 그러나 이러한 격리는 완벽한 가상화와 동일하지 않으며, 커널을 호스트와 공유한다는 점이 근본적인 차이이다.
권한 관리 측면에서, 루트 권한으로 컨테이너를 실행하는 것은 호스트 시스템에 대한 잠재적 위험을 초래한다. 이를 완화하기 위해 사용자 네임스페이스 매핑 기능을 사용할 수 있다. 이 기능은 컨테이너 내부의 루트 사용자를 호스트 시스템의 비특권 사용자로 매핑하여, 컨테이너가 탈출하더라도 호스트에서 제한된 권한만 갖도록 한다. 또한, Capabilities 시스템을 통해 컨테이너 프로세스가 필요 이상의 커널 권한을 갖지 않도록 세분화된 권한을 제거할 수 있다. 예를 들어, 네트워크 소켓을 바인딩하거나 시스템 시간을 변경하는 권한만을 제한적으로 부여한다.
컨테이너 보안의 일반적인 취약점과 모범 사례는 다음과 같이 정리할 수 있다.
취약점 영역 | 주요 위협 | 모범 사례 |
|---|---|---|
이미지 | 악성 코드가 포함된 베이스 이미지, 취약한 소프트웨어 버전 | 공식/신뢰할 수 있는 레지스트리 사용, 정기적 취약점 스캔, 최소한의 베이스 이미지(예: 알파인 리눅스) 사용 |
런타임 구성 | 불필요한 권한 부여, 민감한 호스트 디렉토리 마운트 |
|
네트워크 | 불필요한 포트 노출, 비암호화 통신 | 필요한 포트만 노출, 컨테이너 간 통신을 위한 사용자 정의 네트워크 사용, 네트워크 정책으로 트래픽 제어 |
시크릿 관리 | 구성 파일에 하드코딩된 패스워드, API 키 | 컨테이너 엔진이 제공하는 시크릿 관리 도구나 외부 키 관리 시스템 사용 |
마지막으로, 컨테이너의 생명주기 관리도 보안에 중요하다. 사용하지 않는 컨테이너는 중지하고, 오래된 또는 취약한 이미지는 정기적으로 삭제하여 공격 표면을 최소화해야 한다. 또한, 호스트 운영체제와 컨테이너 엔진 자체를 지속적으로 패치하는 것이 가장 기본적이면서도 효과적인 보안 조치이다.
컨테이너 격리 메커니즘은 호스트 시스템과 다른 컨테이너로부터 프로세스와 자원을 분리하는 기술이다. 이 격리는 리눅스 커널이 제공하는 기본 기능들을 조합하여 구현된다. 핵심은 리눅스 네임스페이스와 cgroups이며, 네임스페이스는 프로세스가 볼 수 있는 시스템 자원의 범위(예: 네트워크 스택, 파일 시스템 계층, 사용자 ID)를 제한함으로써 논리적 격리를 제공한다. cgroups는 CPU, 메모리, 디스크 I/O와 같은 물리적 자원의 사용량을 제한하고 격리한다. 이 두 기술의 결합은 각 컨테이너가 독립된 환경을 갖는 것처럼 보이게 만든다.
격리의 주요 수준은 프로세스 격리, 파일 시스템 격리, 네트워크 격리로 나눌 수 있다. 프로세스 격리는 PID 네임스페이스를 통해 이루어지며, 컨테이너 내부의 프로세스는 호스트나 다른 컨테이너의 프로세스를 보지 못한다. 파일 시스템 격리는 마운트 네임스페이스와 유니온 파일 시스템을 통해 구현되어, 컨테이너마다 독자적인 파일 시스템 뷰와 루트 디렉토리를 갖게 된다. 네트워크 격리는 네트워크 네임스페이스가 담당하여, 각 컨테이너는 가상 네트워크 인터페이스, 라우팅 테이블, 포트 공간을 별도로 가진다.
격리 유형 | 관련 리눅스 네임스페이스 | 주요 기능 |
|---|---|---|
프로세스 격리 | PID 네임스페이스 | 컨테이너 내부에서 독립적인 프로세스 ID 공간을 제공한다. |
파일 시스템 격리 | Mount 네임스페이스 | 컨테이너 전용의 파일 시스템 계층 구조와 마운트 포인트를 생성한다. |
네트워크 격리 | Network 네임스페이스 | 가상 네트워크 인터페이스, iptables 규칙, 라우팅 테이블을 격리한다. |
사용자 격리 | User 네임스페이스 | 컨테이너 내부와 호스트 간 사용자 및 그룹 ID를 재매핑한다. |
이러한 격리 메커니즘은 완전한 가상 머신 수준의 강력한 보안을 제공하지는 않는다. 모든 컨테이너는 동일한 호스트 커널을 공유하기 때문이다. 커널 취약점이 존재할 경우 격리가 뚫릴 수 있는 가능성이 있다[6]. 따라서 컨테이너 격리는 애플리케이션 수준의 충돌 방지와 자원 관리에 더 적합하며, 높은 수준의 보안이 요구되는 환경에서는 가상 머신과의 결합이나 시빌 컨테이너와 같은 추가 보안 강화 기술이 필요하다.
컨테이너 엔진의 보안 구조에서 권한 관리는 컨테이너 내부 프로세스가 호스트 시스템에 미칠 수 있는 영향을 제한하는 핵심 요소이다. 기본적으로 컨테이너 내부의 루트 사용자(UID 0)는 호스트 시스템의 루트 권한과 동일하지 않지만, 여전히 컨테이너 내부에서 높은 권한을 갖는다. 이로 인해 발생할 수 있는 잠재적 위험을 완화하기 위해, 컨테이너 엔진은 실행 시 --user 플래그를 통해 특정 사용자나 그룹으로 컨테이너를 실행할 수 있다. 또한, 리눅스 커널의 capabilities 메커니즘을 활용하여 루트 사용자에게 부여되는 광범위한 권한을 세분화된 권한 집합으로 제한할 수 있다. 예를 들어, 컨테이너 프로세스에서 네트워크 소켓을 바인딩하는 권한은 필요하지만, 시스템 관리 권한은 제거하는 방식이다.
사용자 네임스페이스(user namespace)는 컨테이너 격리를 강화하는 중요한 메커니즘이다. 이는 컨테이너 내부에서 사용되는 사용자와 그룹 식별자(UID/GID)를 호스트 시스템의 다른 UID/GID에 매핑한다. 결과적으로, 컨테이너 내부에서 루트(UID 0)로 실행되는 프로세스가 호스트 시스템에서는 권한이 없는 일반 사용자(예: UID 100000)로 매핑된다. 이는 컨테이너가 호스트의 파일 시스템이나 프로세스에 대한 권한 상승 공격을 성공시키기 어렵게 만든다. 사용자 네임스페이스를 활성화하면 보안 격리 수준이 크게 향상되지만, 볼륨 마운트 시 파일 권한 문제나 특정 애플리케이션의 호환성 문제를 초래할 수 있다.
권한 관리와 관련된 주요 모범 사례는 다음과 같다.
실천 항목 | 설명 |
|---|---|
비루트 사용자 실행 | 컨테이너 이미지에 특수 권한이 필요하지 않다면, Dockerfile의 |
불필요한 capabilities 제거 |
|
읽기 전용 루트 파일 시스템 |
|
사용자 네임스페이스 활성화 | 호스트 시스템 수준이나 데몬 설정에서 사용자 네임스페이스 지원을 활성화하고 매핑을 구성한다. |
권한 모드(privileged) 회피 | 특별한 경우가 아니면 컨테이너를 |
이러한 조치들은 컨테이너 탈출(container escape)과 같은 보안 사고의 가능성을 줄이고, 다중 테넌시 환경에서의 안전한 운영을 보장하는 데 기여한다.
컨테이너는 호스트 운영체제 커널을 공유하기 때문에 완전한 가상 머신만큼 견고한 격리를 제공하지 않는다. 주요 보안 취약점으로는 권한 상승 공격이 있다. 권한이 없는 사용자로 실행되도록 설계된 컨테이너 내부에서 루트 권한을 획득한 공격자가 호스트 시스템의 커널 취약점을 악용할 수 있다[7]. 또한, 취약점이 포함된 베이스 이미지 레이어를 사용하거나 불필요한 권한이 설정된 상태로 컨테이너를 실행하면 공격 표면이 넓어질 수 있다.
이러한 취약점을 완화하기 위한 핵심 모범 사례는 최소 권한의 원칙을 따르는 것이다. 컨테이너 애플리케이션은 가능한 한 루트 권한이 아닌 일반 사용자로 실행되어야 한다. Docker 엔진이나 Podman에서는 --user 플래그를 사용하거나 Dockerfile 내에서 USER 지시어를 정의하여 이를 설정할 수 있다. 또한, 불필요한 Linux Capabilities를 제거하고, 읽기 전용 루트 파일 시스템으로 컨테이너를 실행하며, 필요한 포트만 노출하는 것이 중요하다.
취약점 범주 | 설명 | 주요 모범 사례 |
|---|---|---|
이미지 보안 | 취약한 소프트웨어가 포함된 이미지 사용 | 정기적으로 이미지를 스캔하여 CVE 취약점을 점검하고, 신뢰할 수 있는 레지스트리에서 최소한의 베이스 이미지를 사용한다. |
런타임 구성 | 과도한 권한이나 불필요한 시스템 호출 허용 | Seccomp 프로필을 적용하고, 필요 없는 Linux Capabilities를 제거하며, AppArmor 또는 SELinux 정책을 사용한다. |
네트워크 격리 | 불필요한 네트워크 노출로 인한 공격 경로 확대 | 기본 브리지 네트워크 대신 사용자 정의 네트워크를 활용하고, 컨테이너 간 필요한 통신만 허용하는 네트워크 정책을 적용한다. |
자원 관리 | 자원 고갈을 통한 서비스 거부 공격 | cgroups를 통해 CPU, 메모리, 디스크 I/O 등의 사용량을 제한하여 한 컨테이너의 문제가 호스트나 다른 컨테이너로 전파되는 것을 방지한다. |
마지막으로, 컨테이너의 생명주기 관리도 보안에 중요하다. 사용하지 않는 컨테이너는 중지하고 삭제하며, 이미지 레이어와 같은 불필요한 아티팩트를 정리해야 한다. 또한, 시크릿 관리를 위해 평문으로 된 비밀번호나 API 키를 이미지나 환경 변수에 하드코딩하지 않고, 컨테이너 오케스트레이션 플랫폼이 제공하는 전용 시크릿 관리 도구를 사용하는 것이 필수적이다.
엔진 | 주요 특징 | 기본 런타임 | 오케스트레이션 연동 |
|---|---|---|---|
초기 대중화를 주도한 통합형 엔진으로, Docker CLI와 Docker 데몬으로 구성된다. 사용자 친화적인 도구 생태계를 갖추었다. | 초기에는 자체 runc 기반 런타임을 사용했으나, 현재는 containerd를 데몬 내에서 활용한다. | 쿠버네티스의 Docker shim을 통해 연동되었으나, 현재는 containerd나 CRI-O로의 전환을 권장한다. | |
Docker 엔진에서 분리된 산업 표준 컨테이너 런타임이다. 핵심 컨테이너 생명주기 관리 기능에 집중한 경량화된 데몬이다. | 쿠버네티스의 [[Container Runtime Interface | ||
데몬리스(Daemonless) 아키텍처를 채택한 도구로, 루트 권한 없이도 컨테이너를 실행할 수 있다. Docker CLI와 대부분 호환되는 명령어를 제공한다. | Podman 자체로 파드 개념을 지원하며, 쿠버네티스용 YAML 생성 기능을 갖추고 있다. 서비스 형태로 제공되는 Podman API도 존재한다. | ||
쿠버네티스의 [[Container Runtime Interface | CRI]]를 위해 특화되어 설계된 경량 런타임이다. 쿠버네티스 노드의 표준 런타임으로 사용된다. | runc, crun, kata-runtime 등 다양한 OCI 호환 런타임을 플러그인 방식으로 지원한다. |
Docker 엔진은 개발자 경험과 포괄적인 생태계로 인해 여전히 널리 사용된다. 반면, containerd는 쿠버네티스와 같은 프로덕션 오케스트레이션 환경에서 표준 런타임으로 자리 잡았다. 이는 보다 모듈화되고 효율적인 아키텍처를 제공하기 때문이다.
Podman은 보안과 데몬리스 운영을 중시하는 환경에서 주목받으며, Docker 엔진의 대안으로 성장했다. CRI-O는 쿠버네티스 네이티브 환경에 최적화되어 불필요한 기능을 제거함으로써 안정성과 성능을 추구한다. 이러한 엔진들은 모두 OCI 표준을 준수하여 호환성을 유지한다.
Docker 엔진은 Docker, Inc.가 개발한 초기이자 가장 널리 알려진 컨테이너 엔진이다. 이 엔진은 클라이언트-서버 아키텍처를 채택하고 있으며, 사용자가 Docker CLI를 통해 명령을 내리면 백그라운드에서 실행 중인 Docker 데몬이 이를 처리한다. 데몬은 컨테이너 이미지 관리, 컨테이너 실행 및 생명주기 관리, 네트워킹, 스토리지 볼륨 관리 등 모든 핵심 작업을 담당한다.
Docker 엔진의 초기 버전은 단일 모놀리식 바이너리로, 컨테이너 런타임, 이미지 빌드, 네트워킹 기능을 모두 포함했다. 그러나 이후 아키텍처는 점진적으로 분리되어 발전했다. 특히 containerd가 핵심 컨테이너 런타임으로 분리되어 Docker 엔진에 통합되었으며, runc를 하위 런타임으로 사용한다. 이 분리는 모듈화와 유연성을 높이는 데 기여했다.
주요 구성 요소와 특징은 다음과 같다.
구성 요소 | 역할 |
|---|---|
Docker 데몬 (dockerd) | 컨테이너 이미지와 인스턴스를 관리하는 지속적인 백그라운드 프로세스이다. |
Docker 클라이언트 (docker) | 사용자가 데몬과 상호작용하기 위해 사용하는 명령줄 인터페이스(CLI)이다. |
Docker 레지스트리 | Docker Hub와 같은 공개 또는 사설 이미지 레지스트리와 통신하여 이미지를 풀(pull)하거나 푸시(push)한다. |
containerd | 이미지 전송 및 저장, 컨테이너 실행 및 감독, 스토리지, 네트워크 어댑터 관리 등의 핵심 런타임 기능을 제공한다. |
runc | OCI (Open Container Initiative) 표준을 준수하는 저수준 컨테이너 런타임으로, 실제로 컨테이너 프로세스를 생성하고 실행한다. |
Docker 엔진은 사용자 친화적인 CLI와 강력한 생태계로 인해 컨테이너 기술의 대중화를 주도했다. Dockerfile을 통한 간편한 이미지 빌드, 다양한 네트워크 드라이버 지원, Docker Compose를 이용한 다중 컨테이너 애플리케이션 정의 기능이 대표적이다. 그러나 데몬이 루트 권한으로 실행되어야 한다는 점과 단일 데몬이 시스템 전체의 단일 실패 지점이 될 수 있다는 점은 비판의 대상이 되기도 했다. 이러한 제약은 루트리스(rootless) 모드의 도입과 같은 후속 개선과 함께, Podman과 같은 대안 엔진의 등장을 촉진하는 요인이 되었다.
containerd는 컨테이너 생명주기 관리를 위한 핵심 런타임이다. Docker 엔진에서 분리되어 발전한 이 엔진은 OCI 표준을 준수하며, 쿠버네티스의 컨테이너 런타임 인터페이스를 비롯한 다양한 상위 레벨 시스템을 위한 안정적인 기반을 제공한다. gRPC 기반의 API를 노출하여 컨테이너의 이미지 전송, 컨테이너 실행, 스토리지, 네트워크 인터페이스 연결 등의 저수준 작업을 처리한다.
containerd의 아키텍처는 모듈식으로 설계되어 있다. 핵심 컴포넌트는 컨테이너와 이미지의 메타데이터를 관리하는 '코어 런타임'이다. 실제 컨테이너 실행은 runc와 같은 OCI 호환 런타임을 통해 이루어진다. 이 분리된 구조는 containerd가 다양한 런타임 구현을 지원할 수 있게 하며, 보안과 안정성을 높인다. 또한, 스냅샷터를 통해 이미지 레이어와 컨테이너의 쓰기 가능 계층을 효율적으로 관리한다.
주요 기능은 다음과 같다.
기능 | 설명 |
|---|---|
이미지 관리 | |
컨테이너 실행 | runc를 호출하여 컨테이너를 생성하고 시작하며, 생명주기(일시정지, 재개, 종료)를 관리한다. |
스토리지 | 컨테이너의 쓰기 가능 계층과 이미지 레이어를 위한 스냅샷 시스템을 제공한다. |
네트워크 | 컨테이너에 네트워크 네임스페이스를 생성하고, CNI 플러그인을 통해 네트워크 인터페이스를 연결한다. |
containerd는 Docker 엔진의 내부 런타임으로 시작했으나, 현재는 독립적인 프로젝트로 클라우드 네이티브 컴퓨팅 재단에 기여된다. 이는 쿠버네티스와 같은 오케스트레이션 플랫폼에서 선호되는 경량화된 런타임으로, Docker 데몬의 전체 기능 세트보다 더 간결하고 효율적인 대안이 되었다.
Podman은 레드햇이 주도하여 개발한 오픈소스 컨테이너 엔진이다. Docker와 유사한 명령어 체계를 제공하지만, 데몬리스(daemonless) 아키텍처를 채택한 것이 가장 큰 특징이다. 즉, 중앙 데몬 프로세스 없이 직접 리눅스 커널의 컨테이너 기능(네임스페이스와 cgroups)을 호출하여 컨테이너를 실행한다. 이로 인해 시스템 자원 소모가 적고, 루트 권한 없이도 컨테이너를 관리할 수 있는 루트리스(rootless) 모드 실행이 핵심 장점으로 부각된다.
Podman은 Docker CLI와의 호환성을 중시하여 docker 명령어를 podman으로 대체해도 대부분 동작하도록 설계되었다. 그러나 내부 구조는 근본적으로 다르다. Podman은 systemd와의 통합을 강화하여 컨테이너를 systemd 서비스 유닛으로 관리할 수 있으며, Pod 개념을 네이티브로 지원한다. 이는 단일 컨테이너가 아닌, 여러 컨테이너를 하나의 애플리케이션 그룹으로 묶어 관리하는 쿠버네티스의 Pod 모델을 로컬 환경에서도 구현할 수 있게 해준다.
주요 구성 요소와 특징은 다음과 같이 정리할 수 있다.
구성 요소 / 특징 | 설명 |
|---|---|
아키텍처 | 데몬리스 아키텍처. 중앙 데몬 프로세스 없음. |
보안 모델 | 루트리스 컨테이너 실행을 완전히 지원. 사용자 네임스페이스 활용. |
CLI 호환성 | Docker CLI와 높은 수준의 명령어 호환성을 제공. |
Pod 지원 |
|
이미지 형식 | OCI (Open Container Initiative) 표준 이미지 형식(Docker 이미지와 호환)을 사용. |
통합 | systemd와의 깊은 통합으로 컨테이너를 시스템 서비스로 관리 가능. |
Podman은 containerd나 CRI-O와 같은 하위 수준의 컨테이너 런타임을 필요로 하지 않는 독립적인 엔진이다. 하지만 필요에 따라 이러한 런타임을 백엔드로 사용하도록 구성할 수도 있다. 데몬이 없기 때문에 권한 상승 공격 표면이 줄어들어 보안성이 강화되며, 특히 공유 환경이나 엄격한 보안 정책이 요구되는 엔터프라이즈 환경에서 각광받고 있다.
CRI-O는 쿠버네티스의 컨테이너 런타임 인터페이스를 구현한 경량화된 컨테이너 엔진이다. 쿠버네티스의 kubelet이 컨테이너를 생성하고 관리하기 위해 필요한 런타임을 제공하는 데 특화되어 설계되었다. 도커 엔진과 같은 범용 컨테이너 엔진에 비해 불필요한 기능을 제거하여 보안성을 높이고 오버헤드를 줄이는 것을 목표로 한다.
CRI-O는 OCI 호환 런타임을 사용하여 컨테이너를 실행한다. 일반적으로 기본 런타임으로 runc를 사용하며, 필요에 따라 crun이나 kata-containers와 같은 다른 OCI 호환 런타임으로 교체할 수 있다. 이미지 관리를 위해 containers/image 라이브러리를, 네트워크 관리를 위해 CNI 플러그인을 사용한다. 이러한 모듈식 설계는 각 구성 요소를 독립적으로 업데이트하거나 교체할 수 있는 유연성을 제공한다.
CRI-O의 주요 구성 요소와 역할은 다음과 같다.
구성 요소 | 역할 |
|---|---|
conmon | 컨테이너 모니터 프로세스. 컨테이너의 로그를 관리하고 터미널을 다루며, 컨테이너 프로세스가 종료되었을 때 정리 작업을 수행한다. |
oci-runtime | OCI 사양을 준수하는 런타임(예: runc). 실제로 컨테이너 프로세스를 생성하고 실행한다. |
storage | 컨테이너 이미지와 레이어를 관리한다. 주로 containers/storage 라이브러리를 사용한다. |
networking | 컨테이너 네트워크 인터페이스를 구성한다. CNI 플러그인을 통해 네트워크를 설정한다. |
CRI-O는 쿠버네티스 생태계에 최적화되어 있어, 쿠버네티스 포드의 생명주기 관리에 필요한 모든 CRI 작업을 지원한다. 이는 쿠버네티스 클러스터에서 도커 의존성을 제거하고자 하는 요구에서 발전했으며, 레드햇 오픈시프트와 같은 주요 쿠버네티스 배포판에서 기본 컨테이너 런타임으로 채택되고 있다.