LabHub
배우기 러닝패스 코스

Docker Fundamentals

What Is an Image and What Is a Container

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

이미지는 읽기 전용 레이어를 쌓아 만든 파일 트리이고, 컨테이너는 그 위에 쓰기 레이어 하나를 얹은 뒤 실행 중인 평범한 리눅스 프로세스입니다.

Layer map: 읽기 전용 레이어를 쌓아 만든 파일 트리 · 평범한 리눅스 프로세스 · 사용자 공간의 파일 트리 · 복사한 뒤(copy-up)

왜 이게 필요했나

"컨테이너는 가벼운 VM"이라는 설명이 오래 돌아다녔지만, 이 문장은 거의 모든 실무 문제에서 틀린 예측을 내놓습니다. 호스트에서 ps 를 쳐 보면 컨테이너 안의 nginx 가 그대로 보입니다.

48213 root      nginx
48261 systemd+  nginx

같은 프로세스를 컨테이너 안에서 보면 1 nginx, 29 nginx 로 보입니다. 프로세스가 두 벌 있는 게 아니라 같은 프로세스를 다른 네임스페이스에서 본 것입니다. 커널 버전을 확인해 보면 더 분명해집니다. 호스트도, alpine 컨테이너도, ubuntu 컨테이너도 전부 같은 커널을 보고합니다. 이미지가 제공하는 것은 커널이 아니라 사용자 공간의 파일 트리뿐입니다.

어떻게 동작하나

레이어는 유니온 파일시스템(overlayfs)으로 겹쳐집니다.

overlayfs 의 레이어 구조 — 읽기 전용 이미지 레이어 위에 쓰기 레이어 한 장이 얹혀 merged 라는 파일 트리 하나로 보이고, 읽기는 위에서부터 내려가고, 쓰기는 아래 파일을 통째로 위로 복사하는 copy-up 이며, 삭제는 화이트아웃 표식만 남겨 아래 원본이 그대로 남는다

merged   <- 컨테이너가 보는 뷰
  ↑ upperdir  (쓰기 가능, 컨테이너 레이어)
  ↑ lowerdir  (읽기 전용, 이미지 레이어들이 겹겹이)

모든 레이어는 내용의 SHA-256 해시로 이름이 붙습니다. 내용이 같으면 이름도 같기 때문에 중복 저장이 없고, 해시만 비교하면 무결성이 검증됩니다. 이미지 ID 가 sha256:... 인 것도 같은 이유입니다.

레이어가 쌓이는 순간 무엇이 결정되나

Dockerfile 의 명령 하나가 레이어 하나입니다. 그리고 레이어는 지워지지 않습니다. 이 두 문장에서 실무의 함정 대부분이 나옵니다.

COPY secret.pem /tmp/secret.pem     ← 레이어 3에 파일이 들어간다
RUN ./setup.sh && rm /tmp/secret.pem  ← 레이어 4는 "지웠다" 는 기록일 뿐

최종 이미지에서 /tmp/secret.pem 은 안 보이지만, 레이어 3에는 그대로 있습니다. docker save 로 풀면 누구나 꺼냅니다. 비밀을 이미지에 넣었다가 지우는 것은 지운 것이 아닙니다. 빌드 시크릿(RUN --mount=type=secret)이나 멀티스테이지로 해결합니다.

같은 이유로 크기도 줄지 않습니다.

RUN apt-get update && apt-get install -y build-essential   # +400MB
RUN apt-get purge -y build-essential                       # 여전히 +400MB

RUN 안에서 받고 지워야 그 레이어의 최종 상태만 남습니다.

컨테이너를 지워도 남는 것

컨테이너를 지우면 쓰기 레이어가 사라집니다. 그래서 컨테이너 안에서 만든 파일은 docker rm 과 함께 없어집니다. 이것이 볼륨이 필요한 이유입니다.

저장 위치 컨테이너 삭제 후 쓰기 성능 언제 쓰나
쓰기 레이어 사라진다 느리다(copy-up) 임시 파일
익명 볼륨 남는다(이름이 없어 찾기 어렵다) 빠르다 실수로 생기는 것
이름 있는 볼륨 남는다 빠르다 DB 데이터
bind mount 호스트 파일 그대로 빠르다 개발 중 소스

"copy-up" 이 중요합니다. overlayfs 에서 아래 레이어의 파일을 수정하면 전체가 위 레이어로 복사됩니다. 1GB 짜리 파일의 한 바이트를 고치면 1GB 가 복사됩니다. 데이터베이스를 볼륨 없이 컨테이너 레이어에서 돌리면 안 되는 이유가 이것입니다.

이미지 이름과 다이제스트

태그는 움직입니다. nginx:1.25 는 어제와 오늘이 다른 이미지일 수 있습니다. latest 는 더합니다 — "최신" 이라는 뜻이 아니라 태그를 생략했을 때의 기본 이름 일 뿐입니다.

nginx:1.25                                    ← 움직인다
nginx@sha256:a1b2c3...                        ← 절대 안 움직인다

운영 배포는 다이제스트로 고정합니다. 그래야 "어제는 됐는데 오늘 안 된다" 를 "어제와 오늘이 같은 이미지인가" 로 먼저 확인할 수 있습니다.

현장에서 만나는 모습

같은 베이스를 쓰는 컨테이너 100개가 lowerdir 을 통째로 공유하기 때문에 컨테이너 생성이 빠르고 디스크가 절약됩니다. 반대로 copy-up 비용 때문에 데이터베이스 파일처럼 큰 쓰기는 반드시 볼륨으로 빼야 합니다.

또 하나. docker savedocker export 를 헷갈리면 하루가 날아갑니다. save 는 레이어·태그·히스토리를 포함한 이미지를 내보내고, export 는 컨테이너의 파일시스템 한 장만 내보냅니다. export 한 것을 import 하면 ENTRYPOINT 도 ENV 도 전부 사라진 채로 살아납니다.

다음 실습에서 할 것

이미지 목록을 직접 훑고, 컨테이너를 하나 띄워 로그를 확인하고, 태그가 새 이미지를 만들지 않는다는 것을 이미지 ID 로 증명합니다.