컨테이너는 커널을 어떻게 나눠 쓰는가: Namespace·Cgroups·OverlayFS

Docker가 가상머신 없이도 프로세스를 격리하는 원리를 Linux Namespace·Cgroups·Union File System 세 축으로 정리하고, Docker 아키텍처와 오케스트레이션·보안 실무까지 다룬다.

2026-08-12 · 최초 발행 2026-01-02

가상머신은 하드웨어부터 가상화해 게스트 OS를 통째로 띄우기 때문에 GB 단위 크기에 부팅만 수 분씩 걸린다. 컨테이너는 이 무게를 걷어내고 호스트 커널을 공유한 채 프로세스, 파일 시스템, 네트워크만 격리해 MB 단위 크기와 수 초 시작 시간을 만든다. 서버 한 대에서 수백에서 수천 개의 컨테이너를 동시에 돌릴 수 있는 밀도 차이는 여기서 나온다.

컨테이너들컨테이너 런타임호스트 OS물리 하드웨어CPU, 메모리, 디스크Linux 커널Docker Enginecontainerd컨테이너 1(App + 의존성)컨테이너 2(App + 의존성)컨테이너 3(App + 의존성)

이 차이는 격리 수준의 트레이드오프이기도 하다. 컨테이너는 프로세스 격리에 머물러 오버헤드가 낮고 마이크로서비스·DevOps 워크플로에 적합하지만, 커널을 공유하는 만큼 보안 격리는 가상머신보다 낮다. 여러 OS를 동시에 써야 하거나 강한 격리가 필요하면 여전히 가상머신 쪽이 답이다.

특성 컨테이너 가상머신
격리 수준 프로세스 격리 하드웨어 가상화
OS 커널 호스트와 공유 각 VM마다 독립
크기 MB 단위 GB 단위
시작 시간 수 초 수 분
밀도 수백~수천 개 수십 개
오버헤드 낮음 높음
보안 격리 낮음 높음
용도 마이크로서비스, DevOps 다중 OS, 강한 격리

Linux Namespace: 프로세스가 보는 세상을 나눈다

이 격리를 만드는 핵심 기술이 Linux Namespace다. 각 Namespace는 프로세스에게 시스템 자원의 독립된 뷰를 제공해, 호스트나 다른 Namespace와 분리된 것처럼 보이게 한다. 컨테이너 하나는 보통 여러 Namespace를 조합해서 만들어진다.

PID Namespace는 프로세스 ID를 격리해 호스트에서는 PID 1000인 프로세스가 컨테이너 안에서는 PID 1로 보이게 한다. Network Namespace는 네트워크 인터페이스·라우팅 테이블·포트를 격리해 컨테이너마다 독립된 네트워크 스택을 갖게 하고, veth pair로 호스트와 연결한다. Mount Namespace는 파일 시스템 마운트 포인트를 격리해 컨테이너별로 독립된 루트 파일 시스템을 만들고, UTS Namespace는 호스트명·도메인명을 분리해 hostname 명령 결과가 컨테이너마다 다르게 나오게 한다. IPC Namespace는 공유 메모리·세마포어·메시지 큐 같은 프로세스 간 통신을 분리하며, User Namespace는 사용자·그룹 ID를 격리해 컨테이너 내부의 root가 호스트에서는 일반 사용자로 취급되게 함으로써 보안을 강화한다. Cgroup Namespace는 컨테이너가 자신의 cgroup 트리만 보게 제한한다.

Namespace 격리프로세스PIDNamespaceNetworkNamespaceMountNamespaceUTSNamespaceIPCNamespaceUserNamespace

Cgroups: 격리된 프로세스가 자원을 얼마나 쓸지 정한다

Namespace가 "무엇이 보이는가"를 결정한다면, Cgroups(Control Groups)는 "얼마나 쓸 수 있는가"를 결정한다. CPU, 메모리, 디스크 I/O, 네트워크 대역폭을 프로세스 그룹 단위로 제한하고 사용량을 모니터링하는 커널 기능이다. CPU Cgroup은 컨테이너 A에 CPU 50%, 컨테이너 B에 30%처럼 점유율을 나눌 수 있고, Memory Cgroup은 메모리 사용량을 제한해 OOM이 발생했을 때 해당 컨테이너만 종료되게 하며 스왑 사용도 제어한다. Block I/O Cgroup은 디스크 읽기·쓰기 속도와 IOPS를 제한하고, Network Cgroup은 대역폭과 트래픽 우선순위를 정한다.

docker run --cpus="1.5" --memory="512m" nginx
# CPU 1.5 코어, 메모리 512MB 제한

이미지가 쌓이는 방식: Union File System과 Copy-on-Write

컨테이너 이미지는 읽기 전용 레이어들의 스택이다. 각 레이어는 이전 레이어 대비 변경 사항만 담고 있어 여러 이미지가 같은 레이어를 공유하고 재사용할 수 있다. 컨테이너를 실행하면 이 스택 맨 위에 읽기-쓰기 레이어가 하나 생성돼 런타임 중 변경 사항을 담는다. 읽기 전용 레이어를 수정해야 할 때는 해당 파일을 읽기-쓰기 레이어로 복사한 뒤 수정하는 Copy-on-Write 방식을 쓰기 때문에 원본 레이어는 그대로 보존된다.

Docker 이미지컨테이너 실행컨테이너 레이어(읽기-쓰기)Layer 3: AppLayer 2: DependenciesLayer 1: OS LibrariesLayer 0: Base Image

이 레이어 병합을 실제로 구현하는 게 Union File System이다. Linux 커널 표준이자 Docker 기본 스토리지 드라이버인 OverlayFS는 lower·upper 두 디렉토리를 병합하는 방식으로 동작한다. 초기 Docker가 쓰던 AUFS는 여러 레이어를 지원했고, Btrfs·ZFS는 스냅샷과 CoW를 파일 시스템 수준에서 아예 내장하고 있다.

Docker 아키텍처: Client부터 runc까지

Docker는 이 세 기술 위에 사용자 친화적인 계층을 얹은 것이다. Docker Client는 docker 명령어로 사용자 인터페이스를 제공하고 Docker Daemon(dockerd)과 통신한다. Daemon은 백그라운드에서 이미지 빌드와 컨테이너 실행을 관리하며 Docker API를 노출하고, containerd가 OCI(Open Container Initiative) 표준을 따라 이미지 관리와 컨테이너 실행을 맡는다. 실제로 컨테이너 프로세스를 만들고 Namespace·Cgroups를 설정하는 건 더 낮은 층의 runc다. Docker Registry는 이미지 저장소로, Docker Hub 같은 공개 레지스트리와 사설 레지스트리에 push·pull 명령으로 이미지를 주고받는다.

REST APIpush/pullDocker Client(docker 명령어)Docker Daemon(dockerd)containerdruncDocker Registry(Docker Hub)컨테이너프로세스

이미지는 Dockerfile이라는 빌드 스크립트로 정의되고, 각 명령어가 새 레이어를 만든다.

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y nginx
COPY index.html /var/www/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

FROM이 베이스 이미지를, RUN이 패키지 설치 레이어를, COPY가 파일 추가 레이어를 만들고 EXPOSE·CMD는 메타데이터로 남는다. 이렇게 만들어진 이미지는 불변(Immutable)이라, 수정하려면 새 레이어를 쌓아 새 이미지를 만들어야 한다.

네트워킹: 기본은 Bridge, 필요하면 Host나 Overlay

컨테이너 네트워크는 기본값인 Bridge Network(docker0 가상 브리지)를 통해 컨테이너 간 통신이 가능하고 호스트에서 NAT로 외부와 연결된다. Host Network는 Namespace 격리를 포기하는 대신 호스트 네트워크를 직접 써서 최고 성능을 낸다. None Network는 아예 네트워크를 붙이지 않는 완전 격리 모드다. 다중 호스트에 걸친 컨테이너 통신은 Overlay Network가 맡으며, Swarm이나 Kubernetes 같은 오케스트레이션 환경에서 쓰인다. 포트는 호스트 포트를 컨테이너 포트로 포워딩하는 방식으로 노출한다.

docker run -p 8080:80 nginx
# 호스트 8080 → 컨테이너 80

오케스트레이션: 컨테이너가 많아지면 사람이 관리할 수 없다

컨테이너 수가 늘어나면 배포·스케일링·복구를 자동화하는 계층이 필요해진다. Kubernetes는 Pod(하나 이상의 컨테이너 그룹), Service(Pod에 대한 네트워크 엔드포인트), Deployment(Pod 배포·업데이트 관리), Namespace(클러스터 자원 격리) 같은 개념 위에서 로드 밸런싱, 자동 복구(Self-Healing), 롤링 업데이트를 제공한다. Docker Swarm은 Docker 네이티브 오케스트레이션으로 여러 Docker 호스트를 클러스터로 묶어 서비스 스케일링과 로드 밸런싱을 제공하되, Kubernetes보다 설정이 간단하고 경량이다.

보안: 커널을 공유한다는 것의 대가

컨테이너 보안 문제의 근원은 결국 커널 공유다. 호스트 커널의 취약점이 그대로 모든 컨테이너에 영향을 주고, 최악의 경우 컨테이너 탈출(Container Escape)로 이어질 수 있다. 이를 막기 위한 강화 기법으로 User Namespace(root 권한 격리), Seccomp(시스템 콜 필터링), AppArmor·SELinux(강제 접근 제어), Capabilities(최소 권한 원칙)를 쓴다. 이미지 자체도 위협 표면이라 신뢰할 수 있는 이미지만 쓰고, Trivy나 Clair 같은 도구로 스캔하며, 취약점 패치를 정기적으로 적용해야 한다.

격리 수준을 더 끌어올리고 싶을 때 선택할 수 있는 대안도 있다. gVisor는 Google이 만든 보안 컨테이너 런타임으로, 커널 시스템 콜을 사용자 공간에서 에뮬레이션해 공격 표면을 줄인다. Kata Containers는 경량 가상머신 기반으로 VM 수준 격리와 컨테이너의 경량성을 동시에 노린다.

실무에서 쓰이는 패턴

마이크로서비스 아키텍처에서는 각 서비스를 독립 컨테이너로 배포해 독립적인 확장·업데이트와 장애 격리를 얻는다. CI/CD 파이프라인에서는 개발·테스트·운영 환경을 동일하게 맞춰 빌드 재현성을 보장하는 데 컨테이너를 쓰며, GitLab CI·Jenkins·GitHub Actions가 이 방식을 기본으로 지원한다. 로컬 개발에서는 Docker Compose로 DB, Redis, Kafka 같은 여러 의존 서비스를 한 번에 띄운다.

version: "3"
services:
  web:
    image: nginx
    ports:
      - "8080:80"
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: secret

Namespace와 Cgroups가 격리와 자원 제한을 담당하고, Union File System이 효율적인 이미지 배포를 가능하게 하고, Docker가 이를 표준화된 워크플로로 묶어낸 것 — 이 세 층이 결합해야 비로소 "컨테이너"라는 실행 단위가 완성된다.

컨테이너DockerNamespaceCgroupsKubernetes