Skip to Content

4장. 5분 데모 — Docker가 바꾼 것은 격리가 아니라 포장

이미지는 59.6MB라고 말하는데 실제로 내려받는 건 25MB고, 같은 베이스로 이미지를 하나 더 만들면 새로 쌓이는 층은 2바이트입니다. Docker가 더한 건 격리가 아니라 이 포장이에요. 3장에서 본 커널 기능은 그대로 있었습니다.

{/* 삽화 자리: 창고에 이미 놓여 있던 물건들(선·계량기 = 커널 기능)은 그대로인데, 새로 생긴 것은 그 앞의 포장대와 라벨 프린터, 그리고 선반에 번호가 붙은 상자들. 작업자가 상자 하나를 집어 그대로 배송 카트에 올린다. 상자를 열어 내용물을 다시 만드는 사람이 아무도 없다. 프롬프트: docs/book/illustration-prompts-container.md */}


배경

3장이 남긴 질문은 이것이었습니다. 격리에 필요한 커널 기능은 2009년이면 다 있었고 그걸 묶은 LXC도 2008년에 나왔는데, 왜 “컨테이너”가 일상어가 된 건 2013년인가.

2013년 3월 15일, 산타클라라에서 열린 PyCon US에서 라이트닝 토크 하나가 있었습니다. 제목은 “The future of Linux Containers”, 발표자는 dotCloud의 Solomon Hykes였고 주어진 시간은 5분이었어요. 그 자리에서 공개된 물건이 Docker입니다. 같은 달 오픈소스로 풀렸고요(📄 문서 기반 — Docker ).

5분은 Docker가 고른 시간이 아닙니다. PyCon 라이트닝 토크가 원래 5분이에요.

재미있는 건 그 5분이 남았다는 겁니다. 3장에서 본 연표(mount 2002, UTS·IPC 2006, PID·cgroups 2008, net 2009)만 읊어도 5분이 가는데, 그걸 읊지 않았어요. 읊을 필요가 없었습니다. 격리는 자기가 만든 게 아니었으니까요. 초기 Docker는 LXC를 실행 드라이버로 그냥 가져다 썼습니다.

그럼 5분 동안 무엇을 보여준 걸까요.

스토리

명령 한 줄이었습니다.

같은 일을 하는 데 그전까지 무엇이 필요했는지 나란히 놓아보면 차이가 보입니다.

LXC (2008)Docker (2013)
무엇을 담을지 정한다템플릿을 고르고 rootfs를 만든다—
컨테이너를 정의한다설정 파일을 쓴다—
띄운다lxc-start—
안에 들어간다lxc-attach—
전부네 단계docker run nginx 한 줄

1장에서 chroot 안에 동적 로더까지 손수 복사하던 걸 기억하시죠. 그 일이 규모만 커진 채 남아 있었습니다.

PHK가 1999년 커밋에 뭐라고 썼는지 2장에서 봤습니다. jail 설정 스크립트는 없으니 달라고 하지 말라고요. 14년이 지나도 그 자리는 비어 있었습니다. 커널은 준비돼 있는데 사람이 손으로 채워야 하는 구간이 길었어요.

Docker가 메운 게 그 구간입니다.

그런데 왜 LXC는 못 했을까요. LXC에도 템플릿이 있었습니다. lxc-create -t ubuntu 하면 rootfs를 받아왔어요. 차이는 그다음입니다. 내가 만든 걸 남에게 그대로 건넬 방법이 없었습니다.

Docker는 그 자리에 공개 레지스트리를 놨습니다. 남이 올린 이미지를 내가 그대로 받고, 내가 만든 이미지를 남이 그대로 받아요. 템플릿은 만드는 법을 알려주지만, 이미지는 만들어진 것 자체를 건넵니다. 3장이 물었던 “5년 먼저 나온 도구를 어떻게 제쳤나”의 답이 여기 있습니다.

핵심

docker run 한 줄이 실제로 하는 일

이미지를 지우고 nginx를 처음부터 받아 띄워봤습니다.

내려받기 + 기동까지 : 2.7초 내려받은 양(압축) : 25.0 MB (8겹) 풀어놓은 크기 : 59.6 MB

✅ 실측 (Docker Engine 29.7.2 / Docker Desktop 4.87.0 / Apple M1 / macOS 26.6, 2026-09. docker rmi + docker image prune -af 뒤 측정. 시간은 네트워크에 따라 달라집니다. 전체 소스: docs/book/code/container/ch04/what-docker-added.sh)

그 사이에 일어난 일을 풀어 쓰면 이렇습니다. 레지스트리에 이미지를 요청하고, 레이어를 병렬로 내려받고, 압축을 풀어 쌓고, namespace와 cgroup을 걸고, 프로세스를 띄웁니다. 제가 친 건 이미지 이름 하나였고요.

두 숫자가 다른 것도 짚어둘 만합니다. 이미지가 “59.6MB”라고 말할 때 그건 풀어놓은 뒤 디스크를 차지하는 양이에요. 네트워크로 오가는 건 압축된 25MB입니다.

배포판은 다르고 커널은 하나입니다

같은 컨테이너 안팎에서 배포판 이름과 커널 버전을 같이 찍어봤습니다.

배포판 커널 컨테이너 안 Alpine Linux v3.24 7.0.12-linuxkit 호스트 Docker Desktop 7.0.12-linuxkit

✅ 실측 (같은 환경, 같은 소스. 여기서 호스트는 macOS가 아니라 Docker Desktop의 리눅스 VM입니다. 리눅스 서버에서는 Ubuntu 같은 진짜 배포판 이름이 나옵니다)

배포판 이름은 다른데 커널 문자열은 한 글자도 다르지 않습니다. 2장에서 Solaris 문서가 “zone은 자기 커널이 없다”며 제약으로 적어둔 그 사실이에요. 다만 이번엔 제약이 아니라 기능으로 보입니다. 내 앱이 기대하는 배포판을 통째로 들고 다니면서, 커널은 호스트 걸 빌려 쓰는 거죠.

이미지는 이름이 아니라 주소입니다

방금 받은 이미지를 다시 물어보면 이렇게 답합니다.

16개 플랫폼을 가리키는 목록입니다. 그중 둘: linux/amd64 sha256:7396be67b6f53012a5cf955fa9040619294c25ccacf11e22af5de1b572fc756e linux/arm64 sha256:03cceb152c777cf8f2ec2e7ccac7893ab9e2fb7262e7aa3caabc765bd33ad92c

✅ 실측 (같은 환경, 같은 소스)

nginx:alpine은 이름이고, sha256:...이 내용의 지문입니다. 내용이 한 바이트라도 다르면 지문이 달라져요.

이게 왜 중요하냐면, 배포에서 제일 자주 나는 사고가 “내 노트북에서는 되는데 서버에서는 안 된다”이기 때문입니다. 같은 이름의 패키지가 어제와 오늘 다른 내용일 때 생기는 일이죠. 지문으로 박아두면 어제와 오늘이 같아집니다.

다만 여기서 한 걸음 더 들어가야 합니다. 위 출력을 보세요. nginx:alpine 하나에 플랫폼별 이미지가 16개 들어 있고, 제 M1이 실제로 받은 건 arm64용입니다. 같은 태그를 x86 서버에 걸면 서버는 amd64용, 다른 바이트를 받아요.

그러니까 지문이 없애주는 건 “어제와 오늘이 다른” 문제지 “내 기계와 네 기계가 다른” 문제가 아닙니다. 후자는 그대로 남아요. 선은 벽이 아니라는 이 책의 문장이 여기에도 적용됩니다.

latest 태그가 왜 위험한지도 여기서 나옵니다. 이름은 그대로인데 가리키는 지문이 바뀌거든요. 그 이야기는 17장입니다.

60MB짜리 이미지 둘, 새로 쌓인 건 2바이트

같은 베이스에서 파일 하나씩만 다르게 넣은 이미지 두 개를 만들어봤습니다.

두 이미지 레이어 각 9개 중 8개가 같은 레이어 각 이미지가 말하는 크기 : 59.6 MB 두 번째에서 새로 쌓인 레이어 : 2B

✅ 실측 (같은 환경, 같은 소스)

두 이미지 모두 자기 크기를 60MB라고 말합니다. 그런데 두 번째를 만들 때 새로 생긴 층은 2바이트예요. 제가 넣은 b 한 글자와 줄바꿈, 딱 그만큼입니다.

엄밀히 말하면 디스크가 딱 2바이트 늘어나는 건 아닙니다. tar 헤더가 붙고 파일시스템 블록이 잡히니 실제로는 몇 KB예요. 요점은 정확한 바이트가 아니라 자릿수입니다. 60MB냐 몇 KB냐죠.

레이어 아홉 겹 중 여덟 겹을 그대로 공유하기 때문입니다. 이미지는 통짜 덩어리가 아니라 겹겹이 쌓인 층이고, 같은 층은 한 번만 저장됩니다. 그래서 같은 베이스를 쓰는 이미지가 100개여도 베이스는 한 벌만 있어요.

배포할 때도 같습니다. 앱 코드만 바뀌면 바뀐 층만 오가요.

언제 이게 안 통하는지도 말해둬야겠습니다. 베이스 이미지가 다르면 공유할 층이 하나도 없습니다. 팀마다 다른 베이스를 쓰면 이 이점이 통째로 날아가요. 그리고 층은 순서대로 쌓이기 때문에, 자주 바뀌는 걸 아래쪽에 두면 그 위의 층이 전부 다시 쌓입니다. 이 구조를 어떻게 만드는지는 8장, 순서를 어떻게 잡아야 하는지는 11장입니다.

커널 바깥에서 벌어진 일

정리하면 세 가지입니다. 전부 커널 바깥이에요.

커널이 이미 하던 것Docker가 더한 것
격리namespace (2002~2009)(가져다 씀)
자원 제한cgroups (2008)(가져다 씀)
무엇을 담을 것인가union 파일시스템 (8장)레이어를 주소로 묶는 이미지 포맷
어디서 가져올 것인가—레지스트리
어떻게 만들 것인가—Dockerfile

3장에서 cgroups를 만든 사람들이 자기 물건 이름을 내렸던 걸 떠올려 보세요. 자원을 재고 제한하는 장치 하나로는 컨테이너가 안 된다고요. 옳은 판단이었습니다. 빠져 있던 게 이 아래 세 줄이었어요.

여기까지가 배송 이야기입니다. 이미지는 정말 상자예요. 포장하고, 라벨 붙이고, 부칩니다. 그런데 docker run을 치는 순간 상자는 사라집니다. 남는 건 프로세스 하나고요.

빌린 것을 돌려주다

Docker는 이후 격리 쪽도 직접 만듭니다. 2014년 3월 0.9에서 기본 실행 드라이버를 Go로 쓴 자체 컴포넌트 libcontainer로 바꿨어요. LXC 드라이버는 옵션으로 2016년까지 남았고요(3장 참고).

그사이 다툼도 있었습니다. 2014년 12월 CoreOS가 rkt를 내놓으며 appc라는 별도 규격을 밀었어요. Docker 말고 다른 표준을 세우자는 쪽이었습니다(📄 문서 기반 — rkt ).

그 다툼이 반년 만에 정리됩니다. 2015년 6월 22일, Docker와 CoreOS를 비롯한 업계가 Linux Foundation 아래 Open Container Initiative를 세웁니다. 컨테이너 포맷과 런타임의 열린 표준을 만들자는 목적이었고, Docker는 자기 런타임 runC를 기증해 출발점으로 삼게 했습니다(📄 문서 기반 — OCI ).

OCI가 지금 관리하는 규격은 셋입니다. 런타임, 이미지, 그리고 배포 규격이요. 마지막 것은 레지스트리 API고, 이것도 Docker가 쓰던 걸 바탕으로 합니다. 싸워서 이긴 결과가 업계 규격이 된 셈이에요.

오늘 여러분이 쓰는 도구가 Docker가 아니어도 같은 이미지를 쓸 수 있는 이유가 이것입니다. 5장부터 나오는 이야기는 특정 제품 이야기가 아니에요.

정리

  • 2013년 3월 15일 PyCon US의 5분짜리 라이트닝 토크에서 Docker가 공개됐습니다. 5분으로 충분했던 건 설명할 격리 기술이 없었기 때문이에요. 초기 Docker는 LXC를 그대로 가져다 썼습니다.
  • Docker가 더한 건 커널 바깥의 세 가지입니다. 이미지 포맷, 레지스트리, Dockerfile. LXC에도 템플릿은 있었지만 만든 것을 남에게 그대로 건넬 방법이 없었어요.
  • 이미지는 내용의 지문(digest)으로 고정됩니다. 다만 태그 하나가 플랫폼별 이미지 16개를 가리키기도 해서, 지문은 “어제와 오늘”을 고정하지 “내 기계와 네 기계”를 고정하지는 않습니다.
  • 이미지는 레이어로 쌓이고 같은 층은 한 번만 저장됩니다. 실측에서 59.6MB짜리 두 이미지가 여덟 층을 공유했고, 두 번째에서 새로 쌓인 층은 2바이트였습니다. 내려받는 양도 59.6MB가 아니라 압축된 25MB고요.
  • 2014년 3월 0.9에서 기본 드라이버를 libcontainer로 바꿨고, 2014년 12월 CoreOS의 rkt·appc와 갈린 뒤 2015년 6월 22일 OCI로 합의했습니다. 지금 OCI 규격은 런타임·이미지·배포 셋입니다.

생각해볼 질문

1부가 여기서 끝납니다. chroot가 경로 하나를 속인 1979년에서 시작해, 감옥이 지어지고, 커널이 격리를 종류별로 쪼개고, 그 위에 포장이 씌워지기까지 34년을 봤습니다.

그런데 지금까지 한 이야기는 전부 바깥에서 본 것입니다. 누가 무엇을 언제 만들었는지요.

정작 내 컨테이너 안이 어떻게 생겼는지는 아직 안 봤습니다.

여는 글에서 제가 “컨테이너 안”이라는 말을 여러 번 썼고, 그때 질문 하나를 남겨뒀죠. 자바 프로세스는 정말 어딘가 안에 들어가 있는 걸까요.

호스트에서 프로세스 목록을 뽑아보면 됩니다. 이제 볼 차례예요.

▶ 5장. 컨테이너는 프로세스다 — VM과 무엇이 다른가

Last updated on