Theme:

docker run을 실행하면 Docker 데몬이 뭔가를 하고 컨테이너가 실행됩니다. 그런데 내부를 들여다보면 dockerd, containerd, runc라는 세 개의 프로세스가 관여합니다. 이들은 각각 무슨 일을 하는 걸까요?

Docker의 계층 구조

PLAINTEXT
┌── 사용자 ──────────────────────────────────────────┐
│  docker CLI                                        │
│  docker compose                                    │
└──────────┬─────────────────────────────────────────┘
           │ REST API (unix socket)

┌── Docker 데몬 (dockerd) ──────────────────────────┐
│  • 이미지 빌드 (BuildKit)                          │
│  • 네트워크 관리                                    │
│  • 볼륨 관리                                       │
│  • API 서버                                        │
└──────────┬─────────────────────────────────────────┘
           │ gRPC

┌── containerd ─────────────────────────────────────┐
│  • 이미지 pull/push                                │
│  • 컨테이너 생명주기 관리                           │
│  • 스냅샷(스토리지) 관리                            │
│  • 태스크 관리                                     │
└──────────┬─────────────────────────────────────────┘
           │ OCI Runtime Spec

┌── runc ───────────────────────────────────────────┐
│  • namespace 생성                                  │
│  • cgroups 설정                                    │
│  • 프로세스 실행                                    │
│  • 실행 후 즉시 종료 (상주하지 않음)                 │
└───────────────────────────────────────────────────┘

각 컴포넌트의 역할

Docker CLI

사용자가 직접 상호작용하는 명령줄 도구입니다.

BASH
# CLI → dockerd로 REST API 요청
docker run nginx
# 실제로는 POST /containers/create + POST /containers/{id}/start
BASH
# Docker 소켓 직접 호출 (CLI 없이)
curl --unix-socket /var/run/docker.sock \
    http://localhost/v1.44/containers/json | jq

dockerd (Docker Daemon)

Docker의 메인 데몬입니다. REST API를 제공하고, 빌드, 네트워크, 볼륨 등 고수준 기능을 관리합니다.

BASH
# dockerd 프로세스 확인
ps aux | grep dockerd
# root  1234  /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

# 설정 파일
cat /etc/docker/daemon.json

핵심: dockerd는 컨테이너를 직접 실행하지 않습니다. containerd에 위임합니다.

containerd

CNCF 졸업 프로젝트로, Docker뿐만 아니라 Kubernetes에서도 직접 사용하는 고수준 컨테이너 런타임입니다.

BASH
# containerd 프로세스
ps aux | grep containerd
# root  2345  /usr/bin/containerd

# containerd 직접 제어 (ctr 명령어)
ctr images ls
ctr containers ls
ctr tasks ls

containerd가 담당하는 것:

  • 이미지 pull/push (레지스트리 통신)
  • 이미지를 스냅샷으로 변환 (OverlayFS 레이어 관리)
  • 컨테이너 생성/삭제/시작/중지
  • 컨테이너 이벤트 모니터링
  • runc 같은 저수준 런타임 호출

runc

OCI Runtime Spec을 구현한 저수준 컨테이너 런타임입니다.

BASH
# runc로 직접 컨테이너 실행 (개념 이해용)
# 1. OCI 번들 준비 (rootfs + config.json)
mkdir -p bundle/rootfs
docker export $(docker create busybox) | tar xf - -C bundle/rootfs
runc spec --bundle bundle

# 2. config.json 편집 (namespace, cgroups 설정)
# 3. 실행
cd bundle
runc run my-container

핵심 특징:

  • namespace와 cgroups를 직접 설정
  • 프로세스를 실행한 후 runc 자체는 종료됨 (상주하지 않음)
  • containerd-shim이 컨테이너 프로세스의 부모 역할을 이어받음

containerd-shim

runc가 종료된 후 컨테이너 프로세스의 부모 역할을 합니다.

PLAINTEXT
containerd → runc(실행 후 종료) → containerd-shim(부모 역할) → 컨테이너 프로세스

shim의 역할:

  • 컨테이너의 stdin/stdout 처리
  • 컨테이너 종료 코드 보고
  • containerd가 재시작되어도 컨테이너 유지
BASH
# shim 프로세스 확인
ps aux | grep containerd-shim
# root  3456  /usr/bin/containerd-shim-runc-v2 -namespace moby -id abc123...

OCI (Open Container Initiative) 표준

배경

Docker가 컨테이너 시장을 주도하면서, 벤더 종속을 방지하기 위해 2015년에 OCI가 설립되었습니다.

세 가지 표준

1. Runtime Spec — 컨테이너 실행 방법

JSON
// config.json (OCI 런타임 번들의 핵심 파일)
{
    "ociVersion": "1.0.2",
    "process": {
        "terminal": false,
        "user": { "uid": 0, "gid": 0 },
        "args": ["nginx", "-g", "daemon off;"],
        "env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"],
        "cwd": "/"
    },
    "root": {
        "path": "rootfs",
        "readonly": false
    },
    "linux": {
        "namespaces": [
            { "type": "pid" },
            { "type": "network" },
            { "type": "mount" },
            { "type": "ipc" },
            { "type": "uts" }
        ],
        "resources": {
            "memory": { "limit": 536870912 },
            "cpu": { "shares": 1024 }
        }
    }
}

2. Image Spec — 이미지 형식

이미지 매니페스트, 설정, 레이어의 형식을 정의합니다. Docker 이미지와 OCI 이미지는 호환됩니다.

3. Distribution Spec — 이미지 배포

레지스트리 API를 표준화합니다. Docker Hub, GHCR, ECR 등이 이 표준을 따릅니다.

대안 런타임

Podman — Docker 호환 데몬리스 런타임

BASH
# Docker CLI와 거의 동일한 명령어
podman run -d --name web nginx
podman ps
podman images

특징:

  • 데몬 없음: 각 명령이 독립적으로 실행. 데몬이 죽어도 컨테이너에 영향 없음
  • Rootless 기본: 일반 사용자 권한으로 컨테이너 실행
  • Systemd 통합: 컨테이너를 systemd 유닛으로 관리 가능
  • Pod 지원: Kubernetes Pod 개념을 로컬에서 시뮬레이션
BASH
# Docker에서 Podman으로 전환 (별칭 설정)
alias docker=podman

# Kubernetes YAML 생성
podman generate kube myapp > myapp.yaml

CRI-O — Kubernetes 전용 런타임

PLAINTEXT
Kubernetes (kubelet)

       │ CRI (Container Runtime Interface)

   ┌── CRI-O ──┐
   │            │
   │  runc      │
   └────────────┘
  • Kubernetes CRI만 구현 (Docker API는 지원하지 않음)
  • 경량화된 설계
  • Red Hat/OpenShift의 기본 런타임

gVisor — 샌드박스 런타임

PLAINTEXT
일반 컨테이너:
컨테이너 → 시스템콜 → 호스트 커널

gVisor 컨테이너:
컨테이너 → 시스템콜 → gVisor(사용자 공간 커널) → 제한된 호스트 시스템콜
BASH
# gVisor(runsc) 설치 후
docker run --runtime=runsc nginx
  • Go로 작성된 사용자 공간 커널
  • 약 200개의 시스템 콜을 재구현
  • 호스트 커널 노출을 최소화하여 보안 강화
  • 성능 오버헤드가 있음 (시스템 콜 집약적 워크로드)
  • GCP의 Cloud Run에서 기본 사용

Kata Containers — 경량 VM 런타임

PLAINTEXT
Kata Containers:
컨테이너 → 경량 VM(QEMU/Firecracker) → 전용 커널 → 호스트 커널
  • 각 컨테이너가 경량 가상 머신 안에서 실행
  • 진정한 커널 격리 (하드웨어 가상화)
  • 보안이 매우 높지만 시작 시간과 리소스 오버헤드 존재

런타임 비교

런타임격리 수준시작 시간리소스 오버헤드호환성
runcnamespace/cgroups매우 빠름낮음표준
gVisor사용자 공간 커널빠름보통대부분 호환
Kata경량 VM보통높음대부분 호환

Kubernetes에서의 컨테이너 런타임

PLAINTEXT
┌── Kubernetes ──────────────────────────────────┐
│  kubelet                                        │
│    │                                            │
│    │ CRI (Container Runtime Interface)          │
│    ├─→ containerd → runc                        │
│    ├─→ CRI-O → runc                             │
│    └─→ containerd → gVisor (runsc)              │
└─────────────────────────────────────────────────┘

Kubernetes 1.24부터 dockershim이 제거되었습니다. 이제 containerd나 CRI-O를 직접 사용합니다.

BASH
# Kubernetes 노드의 컨테이너 런타임 확인
kubectl get nodes -o wide
# NAME     STATUS   ROLES    VERSION   CONTAINER-RUNTIME
# node-1   Ready    <none>   v1.29.0   containerd://1.7.13

프로세스 관계 확인

BASH
# Docker로 실행된 컨테이너의 프로세스 트리
pstree -p $(pgrep dockerd)
# dockerd(1234)
#   └── (containerd는 별도 프로세스)

pstree -p $(pgrep -x containerd)
# containerd(2345)
#   ├── containerd-shim(3456)
#   │     └── nginx(4567)  ← 컨테이너 프로세스
#   └── containerd-shim(3457)
#         └── postgres(4568)

정리

  • Docker는 dockerd → containerd → runc 계층 구조로 동작합니다.
  • dockerd는 API, 빌드, 네트워크 등 고수준 기능을, containerd는 이미지와 컨테이너 생명주기를, runc는 namespace/cgroups 설정과 프로세스 실행을 담당합니다.
  • OCI 표준이 이미지 형식과 런타임을 표준화하여, 다양한 도구가 상호 운용 가능합니다.
  • Podman은 데몬 없이 동작하는 Docker 호환 도구이고, CRI-O는 Kubernetes 전용 경량 런타임입니다.
  • gVisorKata Containers는 보안이 중요한 환경에서 추가 격리를 제공합니다.
  • 이 계층 구조를 이해하면 "Docker가 없어도 컨테이너를 실행할 수 있다"는 말이 왜 사실인지 알게 됩니다.
댓글 로딩 중...