Infra Architecture/Compute Platforms
Compute Platforms - 07. 컨테이너 런타임
한 커널을 나눠 쓰는 더 가벼운 가상화 — 컨테이너 런타임
2013년 3월, Docker가 샌프란시스코의 Python 컨퍼런스에서 첫 데모를 했다. "5초 만에 PostgreSQL을 띄우세요"라는 발표가 화면에 떴다. 발표자가 docker run postgres 한 줄을 치자, 수 초 만에 PostgreSQL이 실행됐다 — VM을 만들고 OS를 설치하고 패키지를 깔 필요 없이. 청중은 박수를 쳤지만, 많은 사람이 "이게 VM보다 왜 빠른 건가?"라는 질문을 품고 있었다. 답은 단순했다 — 컨테이너는 VM처럼 완전한 OS를 복제하지 않는다. 한 OS 커널을 여러 컨테이너가 나눠 쓰면서 격리만 한다. OS 부팅이 없으니 빠를 수밖에.
이 글은 컨테이너(container)와 컨테이너 런타임(container runtime)을 다룬다. 이전 글에서 본 가상화(VM)가 "한 서버를 여러 서버로 쪼개는" 기술이었다면, 컨테이너는 "한 OS 커널을 여러 프로세스가 공유하면서 격리하는" 더 가벼운 기술이다. foundations 01에서 "같은 아파트 안의 방" 비유로 맛보기로 소개했다 — VM이 "아파트 한 층을 층마다 쪼개 세입자에게 임대"라면, 컨테이너는 "같은 아파트(커널) 안의 방(컨테이너)을 벽(namespaces)으로 분리"하는 것이다. 벽만 있고 층을 통째로 나누지 않으니 가볍다.
컨테이너란 — 한 커널, 여러 격리된 프로세스
컨테이너의 정의는 — 하나의 운영체제 커널을 공유하면서, 다른 컨테이너와 격리된 실행 환경. VM과의 핵심 차이를 이해하려면 둘을 나란히 놓고 봐야 한다.
flowchart TD
subgraph VM["VM 구조"]
HW1["물리 하드웨어"] --> HP["하이퍼바이저"]
HP --> VM1["VM 1<br/>(커널 + 앱)"]
HP --> VM2["VM 2<br/>(커널 + 앱)"]
HP --> VM3["VM 3<br/>(커널 + 앱)"]
end
subgraph CT["컨테이너 구조"]
HW2["물리 하드웨어"] --> KRNL["공유 커널 (Linux)"]
KRNL --> C1["컨테이너 1<br/>(앱만)"]
KRNL --> C2["컨테이너 2<br/>(앱만)"]
KRNL --> C3["컨테이너 3<br/>(앱만)"]
end
VM은 각자 자체 커널을 갖는다 — VM마다 완전한 Linux(또는 Windows)가 올라간다. 그래서 무겁다. 컨테이너는 커널을 하나만 쓴다. 호스트의 Linux 커널을 공유하고, 그 위에서 각 컨테이너가 앱만 돌린다. 커널 부팅이 없으니 시작이 빠르고(수 초 vs VM의 수십 초), 메모리 오버헤드가 적다(커널 인스턴스가 하나뿐). 단점은 — 커널을 공유하니 격리 수준이 VM보다 약하다. 커널 버그(예: 커널 패닉)가 모든 컨테이너에 영향을 준다. VM이라면 한 VM의 커널 패닉은 다른 VM에 안 미친다.
| 특성 | VM | 컨테이너 |
|---|---|---|
| 커널 | 각자 자체 커널 | 호스트 커널 공유 |
| 시작 시간 | 수십 초 | 수 초 |
| 메모리 오버헤드 | 큼 (커널 N개) | 작음 (커널 1개) |
| 격리 수준 | 강함 (하이퍼바이저) | 약함 (커널 기능) |
| 다른 OS 실행 | 가능 (Linux 위에서 Windows) | 불가능 (Linux 컨테이너는 Linux만) |
| 보안 경계 | 강함 | 상대적으로 약함 |
마지막 행이 중요하다 — "Linux 컨테이너는 Linux만 실행한다"는 건, Linux 호스트 위에서 Windows 컨테이너를 못 돌린다는 뜻이다. VM은 하이퍼바이저가 CPU를 가상화하니 어느 OS든 돌아간다. 컨테이너는 커널 시스템 콜을 공유하니 — 같은 커널(Linux)의 앱만 돌린다. Windows 컨테이너(Windows 호스트 위에서 Windows 컨테이너)도 있지만, Linux 컨테이너와는 호환되지 않는다.
namespaces — 컨테이너를 격리하는 벽
컨테이너가 "같은 커널을 공유하면서 격리된다"는 건 — Linux 커널의 namespaces(네임스페이스) 기능 덕분이다. namespaces는 커널이 프로세스에게 "네가 보는 세계"를 다르게 만들어 주는 기능이다. 프로세스 A가 보는 프로세스 목록·네트워크 인터페이스·파일시스템 마운트가 프로세스 B와 다르게 설정된다.
비유하자면 — 한 회사(커널) 안의 부서(컨테이너). 각 부서는 자기 부서의 직원(프로세스)만 보고, 자기 부서의 회의실(네트워크 포트)만 쓰고, 자기 부서의 캐비닛(파일시스템)만 연다. 다른 부서가 뭘 하는지 모른다. 하지만 같은 건물(커널)의 전기·수도·난방(CPU 스케줄링·메모리 관리)은 공유한다.
Linux의 주요 namespaces는 7개다(2026년 기준):
| Namespace | 격리하는 것 | 비유 |
|---|---|---|
| PID | 프로세스 ID | 내 부서 직원 번호 (다른 부서 직원은 안 보임) |
| NET | 네트워크 (인터페이스·포트·라우팅) | 내 부서 전화선 (다른 부서 전화는 안 보임) |
| MNT | 파일시스템 마운트 | 내 부서 캐비닛 (다른 부서 파일은 안 보임) |
| IPC | 프로세스 간 통신 (공유 메모리·세마포어) | 내 부서 내선 전화 (다른 부서와 내선 연결 안 됨) |
| UTS | 호스트 이름·도메인 이름 | 내 부서 간판 (다른 이름) |
| USER | 사용자·그룹 ID | 내 부서 직급 체계 (root가 다를 수 있음) |
| CGROUP | cgroup 뷰 | 내 부서 예산 (다른 부서 예산은 안 보임) |
컨테이너가 실행될 때, 컨테이너 런타임이 이 namespaces를 새로 만들어 컨테이너 프로세스를 그 안에 넣는다. 컨테이너 안의 프로세스는 자기가 PID 1인 줄 알지만(실제로는 호스트에서는 PID 12345일 수 있음), 자기 네임스페이스 안에서는 PID 1로 보인다. 마치 사진관에 들어가면 거울에 비친 내 모습만 보이듯, 네임스페이스가 프로세스의 "세계"를 한정한다.
cgroups — 자원을 나눠주는 울타리
namespaces가 "보이는 것"을 격리한다면, cgroups(control groups) 는 "쓸 수 있는 자원"을 제한한다. 컨테이너가 CPU를 무제한 쓰면 다른 컨테이너가 굶주리고, 메모리를 무제한 쓰면 호스트가 OOM(Out of Memory)으로 죽는다. cgroups가 이걸 막는다.
cgroups는 프로세스 그룹에 대해 CPU 시간·메모리·디스크 I/O·네트워크 대역폭의 한계(limit) 를 설정한다. 예를 들어 — "컨테이너 A는 CPU 2코어까지만, 메모리 4GB까지만 쓸 수 있다"고 설정하면, 컨테이너 A가 그 한계를 넘으려 할 때 커널이 제지한다. 메모리 한계를 넘으면 OOM killer가 컨테이너의 프로세스를 죽이고, CPU 한계를 넘으면 스로틀(throttle)된다.
cgroups의 두 버전이 있다. cgroups v1은 각 자원(CPU·메모리·IO)마다 별도 계층을 만드는 방식이어서 관리가 복잡했다. cgroups v2(2020년대 표준)는 통합 계층(unified hierarchy)으로 단순화됐다. 현대 Linux 배포판(Ubuntu 22.04+·RHEL 9+)은 기본이 cgroups v2다.
# 컨테이너의 cgroup 제한 확인 (호스트에서)
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/cpu.max
# 출력 예: "200000 100000" = 200ms / 100ms = CPU 2코어 제한
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.max
# 출력 예: "4294967296" = 4GB 메모리 제한
OCI — 컨테이너의 표준
2015년, Docker가 컨테이너 생태계를 독점하면서 발생한 문제를 해결하기 위해 OCI(Open Container Initiative) 가 만들어졌다. OCI는 컨테이너 이미지 형식과 런타임의 표준을 정의한다. 표준이 있으면 — Docker 이미지를 만들어도 containerd·CRI-O·Podman 같은 다른 런타임에서 그대로 실행할 수 있다. 벤더 종속이 줄어든다.
OCI는 세 가지 사양(spec)을 정의한다:
- OCI Image Spec — 컨테이너 이미지의 형식. 레이어 구조(layered filesystem)·매니페스트·설정 파일의 표준. Docker 이미지(
.tar형태)가 이 표준을 따른다. - OCI Runtime Spec — 컨테이너 실행 환경의 표준. namespaces·cgroups·마운트를 어떻게 설정할지의 명세.
runc가 이 사양의 참조 구현이다. - OCI Distribution Spec — 컨테이너 이미지를 레지스트리에서 배포하는 방식의 표준. Docker Hub·GitHub Container Registry·Harbor가 이를 따른다.
OCI가 중요한 이유는 — 컨테이너 생태계가 다중 런타임 시대로 갔기 때문이다. Docker가 처음엔 유일했지만, 2017년 Kubernetes가 Docker를 버리고 containerd로 갈아타면서(CRI 도입), Docker의 독점이 깨졌다. 지금은 containerd(기본)·CRI-O(Red Hat)·Docker Engine(개발용)·Podman(데몬 없는 컨테이너)이 공존한다. OCI 표준 덕분에 이 런타임들이 같은 이미지를 같은 방식으로 실행할 수 있다.
컨테이너 런타임 — 컨테이너를 실제로 실행하는 소프트웨어
컨테이너 런타임(container runtime) 은 컨테이너 이미지를 받아서 실제로 컨테이너 프로세스를 실행하는 소프트웨어다. 세 가지 층으로 나뉜다.
flowchart TD
K8S["Kubernetes (kubelet)"] -->|CRI 인터페이스| HIGH["고수준 런타임<br/>(containerd, CRI-O)"]
HIGH -->|OCI Runtime Spec| LOW["저수준 런타임<br/>(runc, crun)"]
LOW -->|namespaces + cgroups| KERNEL["Linux 커널"]
저수준 런타임(low-level runtime) — namespaces·cgroups를 직접 설정해서 컨테이너 프로세스를 만드는 소프트웨어. runc가 사실상 표준이다. runc는 OCI Runtime Spec의 참조 구현이고, Docker가 만들어서 CNCF에 기증했다. runc가 하는 일은 — 이미지에서 파일시스템을 준비하고, namespaces를 만들고, cgroups를 설정하고, 컨테이너 프로세스를 exec() 하는 것. 단순하지만 핵심적인 역할. 다른 저수준 런타임으로 crun(C로 작성, Red Hat)이 있고, 성능이 runc보다 약간 좋다.
고수준 런타임(high-level runtime) — 이미지 다운로드·레이어 조립·스토리지 관리·네트워크 설정까지 담당. runc를 호출해서 실제 컨테이너를 실행한다. containerd가 가장 널리 쓰인다 — Docker에서 분리된 핵심 컴포넌트로, CNCF 졸업 프로젝트. Kubernetes의 기본 런타임이고, Docker Engine도 내부적으로 containerd를 쓴다. CRI-O는 Red Hat이 Kubernetes 전용으로 만든 경량 런타임(containerd의 Kubernetes 특화 버전). OpenShift(Red Hat Kubernetes)의 기본.
CRI(Container Runtime Interface) — Kubernetes가 컨테이너 런타임과 통신하는 표준 API. CRI가 있어서 Kubernetes는 containerd든 CRI-O든 Docker든 같은 인터페이스로 제어할 수 있다. 2017년에 Docker를 직접 호출하던 방식(dockershim)을 버리고 CRI로 전환한 것이 Kubernetes의 컨테이너 런타임 독립의 핵심 결정이었다. 더 자세한 내용은 Kubernetes 서브프로젝트에서 다룬다.
| 런타임 | 층 | 전형 환경 | 특징 |
|---|---|---|---|
| runc | 저수준 | 모든 컨테이너 (직접 사용 안 함) | OCI 표준 참조 구현 |
| crun | 저수준 | RHEL/Fedora 계열 | C 작성, runc보다 가벼움 |
| containerd | 고수준 | Kubernetes 기본, Docker 내부 | CNCF 졸업, 가장 널리 쓰임 |
| CRI-O | 고수준 | OpenShift(Red Hat) | Kubernetes 전용, 경량 |
| Docker Engine | 고수준 | 개발 환경 | containerd 위에 빌드 도구 추가 |
| Podman | 고수준 | RHEL, 데몬 없는 환경 | Docker 명령어 호환, daemon 없음 |
컨테이너 이미지 — 레이어 구조
컨테이너를 실행하려면 먼저 이미지(image) 가 있어야 한다. 이미지는 컨테이너의 파일시스템과 설정을 담은 패키지다. 핵심은 — 레이어(layer) 구조로 돼 있다는 것.
이미지는 여러 개의 읽기 전용 레이어가 쌓인 구조다. 베이스 레이어(Ubuntu 24.04), 그 위에 패키지 레이어(nginx), 그 위에 설정 레이어(nginx.conf), 그 위에 앱 레이어(웹 앱). 컨테이너가 실행되면 이 레이어들 위에 쓰기 가능 레이어(writable layer) 가 하나 더 올라간다. 컨테이너 안에서 파일을 변경하면, 변경 사항은 이 쓰기 가능 레이어에 기록되고 원본 레이어는 수정되지 않는다(copy-on-write).
레이어 구조의 장점은 — 공유와 재사용이다. 100개의 컨테이너가 모두 Ubuntu 24.04 베이스를 쓰면, Ubuntu 레이어는 디스크에 한 번만 저장된다. 100개의 컨테이너가 각자 500MB의 Ubuntu를 복사하지 않고, 같은 레이어를 공유한다. 디스크 절약이 크다. foundations 05에서 본 KSM(Kernel Same-page Merging)이 메모리 페이지를 합치는 것과 비슷한 원리지만, 이건 파일시스템 레벨에서 일어난다.
# 컨테이너 이미지의 레이어 예시
레이어 4 (쓰기 가능): 컨테이너 실행 중 변경 사항
레이어 3 (읽기 전용): 앱 코드 (COPY app/ /app/)
레이어 2 (읽기 전용): nginx 패키지 (RUN apt install nginx)
레이어 1 (읽기 전용): Ubuntu 24.04 베이스 (FROM ubuntu:24.04)
이미지는 Dockerfile(FROM, RUN, COPY 명령)로 정의하고, docker build 또는 podman build로 만든다. 이미지 빌드의 세부는 이미지 빌드와 Immutable Infrastructure 글에서 깊이 다룬다.
컨테이너 vs VM — 실제 선택
"컨테이너가 좋은가 VM이 좋은가"는 잘못된 질문다. 둘은 다른 용도다.
- VM이 유리한 경우: 다른 OS를 돌려야 할 때(Linux 위에서 Windows VM), 강력한 격리가 필요할 때(보안·컴플라이언스), 레거시 앱(특정 커널 버전 요구)을 돌려야 할 때.
- 컨테이너가 유리한 경우: 빠른 시작·확장이 필요할 때(마이크로서비스), 같은 OS(Linux)를 공유할 수 있을 때, CI/CD 파이프라인에서 이미지를 빌드·배포할 때.
현대 클라우드 네이티브 환경에서는 — 둘을 같이 쓴다. 물리 서버 위에 VM(또는 베어메탈), 그 위에 Kubernetes, 그 위에 수십~수백 개의 컨테이너. VM이 하드웨어 격리를, 컨테이너가 앱 수준 격리를 담당한다. AWS EKS·GCP GKE 같은 관리형 Kubernetes가 이 구조로 돌아간다.
실습 — 컨테이너 상태 직접 확인
1. 컨테이너 런타임 버전
# containerd 버전
containerd --version
# Docker Engine 버전
docker version --format '{{.Server.Version}}'
# Podman 버전
podman --version
확인할 것: containerd 1.7+ 또는 2.0+가 2026년 기준 현대적. Docker는 24.0+. Podman은 4.0+.
2. 실행 중인 컨테이너 목록
# Docker
docker ps
# 또는 containerd 직접
ctr --namespace k8s.io containers list
# Podman
podman ps
확인할 것: STATUS가 Up인 컨테이너. IMAGE 열에서 어떤 이미지로 실행됐는지.
# 예상 출력 (docker ps)
CONTAINER ID IMAGE STATUS PORTS NAMES
a1b2c3d4e5f6 nginx:latest Up 3 hours 0.0.0.0:80->80/tcp web-server
f7e8d9c0b1a2 postgres:16 Up 3 hours 5432/tcp db
3. 컨테이너의 namespaces 확인
# 컨테이너의 PID를 찾고, 그 PID의 namespaces 확인
PID=$(docker inspect --format '{{.State.Pid}}' web-server 2>/dev/null || echo "")
if [ -n "$PID" ]; then
ls -la /proc/$PID/ns/
fi
확인할 것: 각 namespace(net, pid, mnt, ipc, uts, user, cgroup)가 고유 번호를 갖는다. 호스트 프로세스의 namespace 번호와 다르면 — 격리되고 있다는 뜻.
# 예상 출력 (/proc/PID/ns/)
lrwxrwxrwx 1 root root 0 Jul 21 10:00 cgroup -> cgroup:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 21 10:00 ipc -> ipc:[4026531838]
lrwxrwxrwx 1 root root 0 Jul 21 10:00 mnt -> mnt:[4026531839]
lrwxrwxrwx 1 root root 0 Jul 21 10:00 net -> net:[4026531840]
lrwxrwxrwx 1 root root 0 Jul 21 10:00 pid -> pid:[4026531841]
lrwxrwxrwx 1 root root 0 Jul 21 10:00 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 21 10:00 uts -> uts:[4026531842]
4. 컨테이너의 cgroup 제한
# Docker로 컨테이너의 자원 제한 확인
docker stats --no-stream
# 또는 직접 cgroup 파일에서
PID=$(docker inspect --format '{{.State.Pid}}' web-server 2>/dev/null)
CGROUP=$(cat /proc/$PID/cgroup 2>/dev/null | head -1)
echo "Cgroup: $CGROUP"
확인할 것: docker stats의 CPUPERC·MEMUSAGE / LIMIT. 제한 없음(--memory·--cpus 안 준 경우)이면 LIMIT가 호스트 전체 자원.
# 예상 출력 (docker stats)
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O
a1b2c3d4e5f6 web-server 0.52% 45MiB / 7.6GiB 0.58% 12MB/8MB
f7e8d9c0b1a2 db 2.14% 234MiB / 1GiB 23.4% 5MB/45MB
5. 컨테이너 이미지 레이어 구조
# 이미지의 레이어 확인
docker image inspect nginx:latest --format '{{json .RootFS.Layers}}' | python3 -m json.tool
# 또는 dive 도구로 레이어별 파일 변화 확인
dive nginx:latest 2>/dev/null || echo "dive 미설치 (go install github.com/wagoodman/dive@latest)"
확인할 것: 레이어의 SHA256 해시 목록. 각 해시가 하나의 레이어(파일시스템 변경분)를 가리킨다.
미검증: 위 명령은 Docker(또는 Podman/containerd)가 설치된 환경에서 동작. WSL2·Linux VM에서 Docker를 설치하면 실습 가능.
dive는 별도 설치 필요.
판단 표 — 컨테이너 vs VM vs 베어메탈
| 기준 | 베어메탈 | VM | 컨테이너 |
|---|---|---|---|
| 시작 시간 | 수 분 (부팅) | 수십 초 | 수 초 |
| 자원 오버헤드 | 없음 | 중간 (하이퍼바이저) | 낮음 (커널 공유) |
| 격리 수준 | 물리적 (최강) | 강함 (하이퍼바이저) | 중간 (커널 기능) |
| 밀도 (서버당 워크로드) | 1 ~ 수 개 | 수십 개 | 수백 ~ 수천 개 |
| 다른 OS 실행 | 가능 | 가능 | 불가능 (동일 커널만) |
| 보안 경계 | 물리적 | 강함 | 약함 (커널 공유) |
| 전형 환경 | 성능 극대화, 규제 | 엔터프라이즈, 레거시 | 마이크로서비스, CI/CD |
컨테이너는 "가상화의 가벼운 버전"이 아니라 "다른 철학"이다
도입의 Docker 데모로 돌아가자. 2013년에 "5초 만에 PostgreSQL을 띄우세요"는 마술처럼 보였지만, 원리를 이해하면 — 커널을 공유하고 namespaces로 격리하고 cgroups로 제한하는 우아한 엔지니어링이었다. 컨테이너는 VM을 "더 가볍게 만든" 버전이 아니다. 격리의 철학이 다르다 — VM이 "완전한 컴퓨터를 복제"한다면, 컨테이너는 "한 커널을 나눠 쓰면서 벽으로 막는다". 더 가볍지만 더 약한 격리. 더 빠르지만 같은 OS만. 트레이드오프가 다른, 별개의 기술이다.
컨테이너를 이해하면 다른 질문에도 답이 보인다. "왜 Kubernetes가 인기인가" → 수천 개의 컨테이너를 관리하는 도구가 필요하니까. "왜 클라우드 네이티브가 컨테이너를 기본으로 하나" → 빠른 시작·확장이 마이크로서비스에 맞으니까. "왜 Docker가 Dockerfile로 이미지를 만드나" → foundations 06의 IaC 원칙("인프라를 코드로")이 컨테이너에 적용된 것이니까. Kubernetes의 세부(오케스트레이션)는 Kubernetes 서브프로젝트에서, 본 서브프로젝트는 컨테이너 런타임 자체(containerd·OCI·runc)를 다뤘다.
다음 글에서는 컨테이너(와 VM) 위에서 도는 서비스를 관리하는 층을 다룬다 — systemd. Linux의 PID 1, 서비스 유닛, 저널, 부팅 의존성 그래프가 어떻게 서버의 모든 서비스를 관리하는지. 컨테이너 런타임도 결국 systemd가 관리하는 서비스 중 하나로 돈다.
참고
- Docker Inc., "Docker: The Linux Container Engine", PyCon 2013 — Docker 첫 데모. 접근 2026-07-21
- Open Container Initiative, "OCI Image Format Specification" & "OCI Runtime Specification", Linux Foundation, 2015~ — 컨테이너 표준. 접근 2026-07-21. URL: https://opencontainers.org/
- Linux kernel documentation, "Namespaces" & "Cgroups v2", kernel.org — namespaces·cgroups 공식 문서. 접근 2026-07-21
- containerd Project, "containerd: An Industry-Standard Container Runtime", CNCF — containerd 문서. 접근 2026-07-21. URL: https://containerd.io/
- Kerrisk, M. "Namespaces in operation", LWN.net, 2013 — namespaces 7종 심층 연재. 접근 2026-07-21
- Bui, T. "Analysis of Docker Security", arXiv 1501.02967, 2015 — 컨테이너 보안 분석. 접근 2026-07-21
- Gregg, B.
2nd ed, Addison-Wesley, 2020, Ch.9 — 컨테이너 성능 분석. 접근 2026-07-21 - Burns, B. et al. <Kubernetes: Up and Running> 3rd ed, O'Reilly, 2022 — 컨테이너 오케스트레이션. 접근 2026-07-21
'Infra Architecture > Compute Platforms' 카테고리의 다른 글
| Compute Platforms - 09. 이미지 빌드와 Immutable Infrastructre (0) | 2026.07.23 |
|---|---|
| Compute Platforms - 08. 서비스 관리와 systemd (0) | 2026.07.23 |
| Compute Platforms - 06. HCI (Hyper-Converged Infrastructure) (0) | 2026.07.22 |
| Compute Platforms - 05. VM 라이프사이클 (1) | 2026.07.22 |
| Compute Platforms - 04. 가상화 기초와 하이퍼바이저 (1) | 2026.07.22 |