이전 글에서 namespace와 cgroup에 대해 알아보았다.
- namespace: 격리
- cgroup: 제한
그런데 이 둘만으로 Docker에서 사용되는 Container처럼 구현할 수는 없다.
컨테이너는 화물 컨테이너처럼 환경을 통째로 가져오는 기술이다. 따라서 실행 환경 설치를 하지 않고 바로 실행할 수 있다.
내가 처음 Docker에서 Container에 대해 설명을 들었을 때 화물 컨테이너라는 비유를 들었던 게 기억이 난다.
namespace / cgroup은 프로세스를 가두고... 제한하는 기술이다.
그렇다면 환경을 통째로 가져온다 이건 어떻게 구현할 수 있을까?
Union FS
포토샵 레이어를 떠올려보자.
[레이어 3] 텍스트 "Hello"
[레이어 2] 빨간 원
[레이어 1] 배경 이미지
----------------------
-> 합쳐서 보면 하나의 그림
각 레이어는 독립적인데, 위에서 내려다보면 하나의 이미지처럼 보인다.
Union FS는 이미지가 아닌, 파일 시스템 레이어를 겹치는 것이다.
[레이어 3] 내 앱 코드 (/app/server.js)
[레이어 2] Node.js 설치됨 (/usr/bin/node)
[레이어 1] Ubuntu 기본 파일들 (/bin, /lib, /etc...)
─────────────────────────────────────
→ 합쳐서 보면 하나의 root filesystem (/)
즉, 호스트에서 컨테이너를 만들고, 해당 컨테이너 안에서 ls/를 치면, 이 세 레이어가 합쳐진 결과를 보는 것이다.
Image Layer vs Container Layer
Docker에서는 레이어가 두 종류로 나뉜다.
| 구분 | 설명 | 권한 |
| Image Layer | docker pull로 받아온 레이어들 | 읽기 전용 |
| Container Layer | 컨테이너 실행 시 생성 | 읽기/쓰기 가능 |
마치 아래의 형태와 같다.

여기서 핵심은 Image Layer는 공유된다는 것이다.
예를 들어 nginx 이미지로 컨테이너 10개를 띄워도, Image Layer는 딱 한 개만 있으면 된다.
- 각 컨테이너는 자기만의 얇은 Container Layer를 가진다.
Copy-on-Write (CoW)
그럼 컨테이너에서 파일을 수정하면 어떻게 될까?
- 다시 한번 강조하지만 Image Layer는 읽기 전용이다.
이때 Copy-on-Write 전략이 사용된다.
- 컨테이너가 파일 수정을 요청한다
- 해당 파일이 Image Layer에 있으면 → Container Layer로 복사
- Container Layer에서 수정 진행
- Image Layer의 원본은 그대로 둔다.
[ 수정 전 ]
Container Layer: (비어있음)
Image Layer: /etc/config.txt (원본)
[ /etc/config.txt 수정 후 ]
Container Layer: /etc/config.txt (수정본) ← 여기서 읽힘
Image Layer: /etc/config.txt (원본) ← 그대로 보존
이 방식 덕분에 아래와 같은 이점을 얻을 수 있다.
- Image Layer를 여러 컨테이너가 안전하게 공유할 수 있다.
- 컨테이너 삭제 시 Container Layer만 날리면 된다.
- 디스크 공간을 절약할 수 있다.
직접 해보기
Docker가 사용하는 UnionFS 구현체 중 하나가 OverlayFS다. 직접 만들어보자.
docker run -it --privileged --name overlay-test ubuntu:22.04 /bin/bash
디렉토리 구조 만들기
# tmpfs로 작업 공간 만들기 (Docker 컨테이너 안에서는 이렇게 해야 되더라고요)
mkdir -p /overlay-test
mount -t tmpfs tmpfs /overlay-test
# 디렉토리 구조 생성
mkdir -p /overlay-test/{lower,upper,work,merged}
각 디렉토리의 역할
- lower: 읽기 전용 레이어 (Image Layer 역할)
- upper: 쓰기 가능 레이어 (Container Layer 역할)
- work: OverlayFS 내부 작업용 (신경 X)
- merged: 합쳐진 결과물 (컨테이너가 보는 세상)
lower에 파일 만들기
- 마치 nginx Image를 받은 상황이라고 해보자.
echo "나는 원본 파일이야" > /overlay-test/lower/original.txt
echo "나는 설정 파일이야" > /overlay-test/lower/config.txt
OverlayFS 마운트
- 읽기 전용 원본 폴더(lower)와 수정 사항이 저장될 폴더(upper)를 겹쳐서 하나의 폴더(merged)처럼 쓸 수 있도록 OverlayFS로 마운트하는 명령어다.
mount -t overlay overlay \
-o lowerdir=/overlay-test/lower,upperdir=/overlay-test/upper,workdir=/overlay-test/work \
/overlay-test/merged
결과 확인
ls /overlay-test/merged/
현재 upper는 비어있다.
ls /overlay-test/upper/echo "수정됐다!" >> /overlay-test/merged/original.txt
그럼 어떻게 될까
cat /overlay-test/merged/original.txt
# 나는 원본 파일이야
# 수정됐다!
cat /overlay-test/upper/original.txt
# 나는 원본 파일이야
# 수정됐다!
cat /overlay-test/lower/original.txt
# 나는 원본 파일이야
수정하는 순간 lower → upper로 복사가 일어난다.
새 파일 생성
- 새 파일은 바로 upper에 생긴다.
echo "나는 새로 만든 파일이야" > /overlay-test/merged/new-file.txt
ls /overlay-test/lower/
# config.txt original.txt
ls /overlay-test/upper/
# new-file.txt original.txt
파일 삭제와 Whiteout
- lower에 있는 config.txt를 삭제해보자.
rm /overlay-test/merged/config.txt

upper에 c------------ 라는 특이한 파일이 생긴다. 이건 Whiteout 파일이다.
- OverlayFS는 lower를 절대 수정하지 않는다.
- 대신 upper에 '이 파일은 삭제된 거다.' 라는 표시를 남긴다.
- merged에서 볼 때 whiteout이 있으면 해당 파일을 숨긴다.
컨테이너 vs VM

VM에는 각각 전체 OS를 이루므로 커널이 내부에 포함이 되지만
컨테이너에는 커널이 없다.
UnionFS로 만든 파일 시스템에는 /bin, /lib, /etc 같은 유저 공간 파일들만 있다.
커널은 호스트 것을 그대로 사용한다.
컨테이너 안에서 uname -a를 치면 호스트 커널 정보가 나온다.
Linux를 사용하고 있다면 Window 컨테이너는 사용할 수 없다.
컨테이너는 호스트 커널을 공유하기 때문에, 그 커널이 이해하는 시스템 콜만 쓸 수 있다.
docker run을 한다면?
docker run nginx를 한다고 가정해보자. 내부에서 일어나는 일은 아래와 같다.
1. 이미지 준비
Docker Engine: "nginx 이미지 있나?"
↓
로컬에 없으면 → Docker Hub에서 pull
↓
[ Layer 3: nginx 설정 파일 ]
[ Layer 2: nginx 바이너리 ]
[ Layer 1: Debian 기본 파일들]
2. Union FS로 파일 시스템 구성
┌──────────────────────────┐
│ Container Layer (R/W) │ ← 새로 생성 (쓰기용)
├──────────────────────────┤
│ Image Layers (Read Only) │ ← 합쳐서 하나의 / 로 보인다
└──────────────────────────┘
이게 컨테이너의 root filesystem이 됨
3. namespace 생성 요청
clone(CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS | ...)
↓
PID namespace → nginx가 PID 1번
NET namespace → 컨테이너만의 IP
MNT namespace → 위에서 만든 filesystem만 보임
UTS namespace → 컨테이너만의 hostname
4. cgroup 설정
/sys/fs/cgroup/docker/{container-id}/
↓
memory.max = 제한값
cpu.max = 제한값
pids.max = 제한값
5. 프로세스 실행
- nginx 프로세스 시작
- nginx 입장
- 난 PID 1이다(namespace)
- / 에는 nginx 관련 파일들만 있다(Union FS + MNT ns)
- 내 IP는 172.17.0.2다(NET namespace)
- 메모리는 512MB까지만 쓸 수 있다(cgroup)
- 호스트 입장
- nginx 지금 돌고 있는 컨테이너는 그냥 PID 12190인 프로세스일 뿐이다.
- nginx 입장
정리
컨테이너 = 특별히 설정된 프로세스
리눅스 커널 입장에서 컨테이너는 그냥 조금 특별하게 설정된 프로세스일 뿐.
세 가지 기술이 합쳐져서
- Union FS가 "환경을 통째로 가져온다"는 느낌을 만들고
- namespace가 프로세스를 격리된 세상에 가두고
- cgroup이 리소스 사용량을 제한한다
Docker가 이 과정들을 docker run {이미지 이름} 한 줄로 해결해준다.
'Infra > Docker' 카테고리의 다른 글
| Docker Data & Volumes (0) | 2025.12.12 |
|---|---|
| Docker Image & Container 명령어 정리 (0) | 2025.12.12 |
| Container의 근반이 되는 기술에 대해 알아보자(2) cgroup (0) | 2025.12.11 |
| Container의 근반이 되는 기술에 대해 알아보자(1) namespace (0) | 2025.12.11 |
| 도커(Docker)로 PHP / Laravel 개발 환경 구축하기 (0) | 2025.12.08 |