[ "한빛미디어 서평단 <나는리뷰어다> 활동을 위해서 책을 협찬 받아 작성된 서평입니다." ]
스즈키 료: 별명은 호게 상. 어쩌다 들어간 대학의 정보통신 계열 학과에서 프로그래밍을 접한 후 정보통신 분야에 빠져들었다. 2012년 모 전자 메이커 대기업에 취직해서 백엔드 엔지니어로서 ISP 서비스 개발에 종사했다. 2021년 미라이토디자인으로 이직, 현재는 Zenn(엔지니어 정보 공유 커뮤니티)에 투고하거나 회사 유튜브 채널에 진지한 동영상이나 그렇지 못한 동영상을 공개하고 있다(?)고 한다. (자료를 이 이상으로 찾지 못했다..)
🔥 책 예제 소스 // 구글 도서, 책 미리 보기 // 한빛 책 링크
『그림으로 배우는 도커』
는 총 7부로 구성되어 있으며, 1부부터 5부까지는 도커의 기본 개념과 명령어, Dockerfile 작성법까지 점진적으로 설명하고, 6부에서는 실무에 가까운 컨테이너 활용 및 복합 이미지 구축을 다루며, 7부에서는 실제 운영 환경에서 마주치는 디버깅, 트러블슈팅, 그리고 환경 구성 노하우까지 전반적으로 포괄하고 있다.
이 책의 가장 큰 장점은 단연 "그림" 이다!!(책 이름 값이 쩔어요~)
단순한 시각 보조 수준을 넘어, 도커의 내부 동작과 추상적 개념들을 직관적으로 이해하게 해주는 데 압도적으로 효과적이다. 도커를 처음 접했던 시절, 유튜브 영상 하나 와 여기저기 흩어진 문서들로 간신히 image 하나를 만들어보고 감탄했던 기억이 있다. 외장하드에 우분투를 넣고 들고 다니며 겨우 컨테이너 하나 띄워봤던 그 어설픈 시작과 비교하면, 이 책은 입문자에게 너무나 친절하고, 중급자에게는 뼈대가 정리되는 경험을 선사한다.
특히 이 책은 '당장 도커를 다뤄야 하는 사람' 에게 실질적인 도움이 되는 책 이다. 시스템의 깊은 구조나 리눅스 커널 수준에서 컨테이너의 작동 원리를 파고들기보다는, 도커를 어떻게 사용할 수 있는지를 실습 중심으로 풀어낸다. container
, image
, Dockerfile
, 그리고 docker compose
까지, "순차적" 으로 기능을 확장해 나가며 자연스럽게 독자가 불편함을 느끼고 그 불편함을 해결하는 흐름으로 구성되어 있다.
5부까지의 실습 은 각각의 도커 명령어를 하나하나 직접 실행하면서 기본기를 다지고, 이후 Dockerfile
을 중심으로 필요한 설정을 직접 추가해가며 컨테이너를 구성한다. 이 과정을 통해 단순 실행에서 끝나는 것이 아니라 '왜 이렇게 구성해야 하는가' 를 되짚게 되고, 최종적으로는 웹 메일함 프로젝트를 compose 파일 하나로 통합 실행하면서 도커를 실무에 도입할 수 있는 감각을 얻게 된다.
(출처: https://gngsn.tistory.com/129)
다만 이 책은 도커의 작동 원리 자체를 깊이 있게 이해하고자 하는 독자에게는 분명 아쉬움이 남는다. 예를 들어 리눅스 커널의 cgroup
과 namespace
가 어떻게 자원 격리와 프로세스 분리를 구현하는지, overlay filesystem
이 어떻게 이미지 레이어를 효율적으로 구성하고 병합하는지에 대한 구조적 설명은 거의 등장하지 않는다.
또한 컨테이너의 PID 네임스페이스와 유저 네임스페이스, 네트워크 브리징 방식 등 도커의 겉모습이 아닌 '안쪽 구조'를 궁금해하는 독자라면, 이 책의 실습 위주 구성은 다소 단조롭게 느껴질 수 있다. 실습 중심의 빠른 흐름은 입문자에게는 유익하지만, 개념과 구조를 충분히 곱씹으며 이해하고 싶은 독자에게는 설명이 생략되거나, 추상적인 레이어에서 멈춘다는 점이 아쉽다.
개념적 깊이보다는 실용적 완성도가 돋보이는 책이며, 현업 개발자라면 책장 한켠에 두고 필요할 때마다 꺼내보게 될, 그런 실전형 책인 것 같다.
(1장 ~ 4장)
가상화란, 소프트웨어로 하드웨어 자원을 추상화하여 마치 물리적인 머신처럼 보이도록 하는 기술이다. 이를 통해 하나의 물리 머신에서 여러 개의 가상 머신(VM)을 운용할 수 있고, 각각의 VM은 자체 OS 및 서비스를 운영하므로 충돌 없이 다중 서비스를 구축하는 데에 유리하다.
가상화의 방식은 크게 세 가지로 나뉜다.
컨테이너형은 리눅스 커널에 의존하기 때문에, 리눅스 기반이 아닌 환경에서는 리눅스 커널을 별도로 사용해야 한다. 또한 호스트 머신의 CPU 아키텍처(예: amd64, arm64)는 컨테이너의 실행에도 영향을 미친다. 예를 들어 애플 실리콘(macOS/arm64)에서는 해당 아키텍처를 지원하는 이미지를 사용해야 한다.
도커의 핵심 구성은 다음 세 가지이다.
이러한 구조 덕분에 CLI나 GUI(도커 데스크탑 등)는 결국 API를 호출하는 방식으로 동일한 기능을 수행한다.
CLI 클라이언트를 위한 명령어 모음세트가 결국 docker compose
(.yaml
) 과 같다고 이해하면 된다.
도커 허브(Docker Hub)는 도커 이미지의 저장소로, GitHub처럼 버전 관리가 가능하고, 동시에 패키지 매니저와 같은 역할도 수행한다. 사용자는 이를 통해 공식 이미지나 개인 이미지 저장 및 배포가 가능하다.
컨테이너 및 이미지 규격은 OCI(Open Container Initiative) 라는 비영리 단체에서 정의하고 있으며, 이는 도커 외에도 Podman 등 다양한 도구들이 이 표준을 따르게 해준다.
도커는 리눅스의 핵심 기능을 조합하여 컨테이너를 구현하고 있으며, 그 핵심은 다음 세 가지이다.
이로 인해 각 컨테이너는 독립된 PID 1(최상위 프로세스)을 가지며, 이는 호스트와 충돌하지 않는다. 컨테이너 내부에서 ps
명령어를 입력하면 자신의 PID 1만 보이는 것도 이 때문이다.
더 depth 있는 커널에 대한 정보는 https://amsekharkernel.blogspot.com/2016/11/what-is-linux-namespace-cgroups.html 와 Linux Kernel Documentation 의 https://docs.kernel.org/admin-guide/namespaces/index.html, https://docs.kernel.org/admin-guide/cgroup-v2.html 를 추천한다.
이미지는 컨테이너 실행에 필요한 실행 파일, 라이브러리, 설정 등을 여러 개의 레이어(layer) 로 구성한다. 각 레이어는 tar 아카이브 파일이며, 불변 속성을 갖는다. 여러 이미지가 공통 레이어를 공유할 수 있어 저장 공간 및 네트워크 사용을 최적화할 수 있다.
Dockerfile
은 이미지를 만들기 위한 설정 파일로, 새로운 레이어를 추가하는 역할을 한다.
초기에는 모든 명령어가 docker
접두사를 사용했지만, 명령어가 너무 많아지면서 v1.13 이후부터 docker container
, docker image
, docker network
등으로 분기되었다. 이로 인해 명령어 체계가 좀 더 직관적으로 구성되었다.
docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
특히 4.6장 명령어 치트 시트, 정리가 진짜 잘 되어 있다. sub 명령어 묶음과 container
의 status
에 따라서 flow chart 처럼 정리된 것을 보고 해당 장표 찢어서 어디 벽에 붙여 놓을까 했다. ㅎ
(5장 ~ 11장)
도커 컨테이너 내부에서 실행되는 첫 번째 프로세스는 Linux 시스템 상의 PID1에 해당하며, 이 프로세스가 종료되면 컨테이너 자체가 종료된다.
이는 컨테이너가 본질적으로 하나의 단일 프로세스를 중심으로 동작하는 경량 VM이라는 점을 극명하게 보여주는 설계다. 그래서 컨테이너 정지는 곧 PID1 프로세스의 종료를 의미하고, 그 이후 컨테이너는 stop
명령으로 정지시키거나 rm
명령으로 삭제하는 방식으로 관리된다. (필자는 stop 상태 그대로 둔 적이 딱히 없다고 한다! 물론 나도...)
앞으로 명령어의 상세한 내용은 도커레퍼런스 를 적극 참조하자!
docker container run ubuntu whoami
처럼 이미지를 기반으로 한 번만 실행하고 끝내는 방식도 가능하다.
(그림 설명이 진짜.. 너무 친절하다!!!)
이때 whoami
는 명령어 이후 인자는 모두 ARG...
로 처리된다. 실행 후 자동 삭제는 --rm
옵션으로 가능하다. (--name 과 --rm 이 조합이 좋음)
--name
으로 컨테이너 이름을 지정하면 관리가 쉬워진다. docker run --rm --name test ubuntu echo hello
같은 조합은 일회성 테스트에 유용하다.
--interactive --tty
, 줄여서 -it
--interactive
는 컨테이너의 표준 입력(stdin) 을 열어둔 상태로 유지하여, 사용자가 입력을 계속 보낼 수 있도록 하는 것--tty
는 가상 터미널(TTY) 을 할당한다. 즉, 컨테이너에서 실행되는 프로세스가 터미널인 것처럼 동작할 수 있게 하는 것이다!docker container run -it --rm python python3
형태로 Python REPL 을 바로 열 수 있다. bash 셸을 실행할 경우 docker run -it ubuntu bash
와 같이 활용이 가능하다.
docker container run --publish 8080:80 nginx
와 같이 --publish
또는 -p
옵션으로 호스트와 컨테이너 간의 포트 바인딩이 가능하다.MySQL
을 예로 들면 아래와 같이 "필수 환경 변수 값" 들이 있다.
docker container run \
--name db \
--rm \
--env MYSQL_ROOT_PASSWORD=secret \
--publish 3306:3306 \
mysql
대부분의 공식 이미지들은 필수 환경 변수들이 명시되어 있으며, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD 등 추가적인 설정도 가능하다.
도커 허브에 공식 이미지마다 환경 변수 문서가 잘 정리되어 있다!
--detach
또는 -d
옵션으로 실행하면 컨테이너는 백그라운드에서 실행되고, 사용자 셸은 즉시 반환된다. (사실 이거 detach 가 아니라 daemon 인 줄 알았다.. attach 가 있고, 이 행위와 반대의 detach 개념이라는 점..!) -> 정확하게는 "표준 입출력을 분리" 하는 옵션이다.
docker run -d ...
는 nginx, mysql 같이 지속적으로 동작해야 하는 서버성 컨테이너에 필수적인 실행 방식이다.
docker container logs [OPTIONS] CONTAINER
, --follow (-f)
옵션과 함께 사용하면 실시간 로그 확인이 가능하다.
docker container exec [OPTIONS] CONTAINER COMMAND [ARG...]
docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker container exec -it db bash
, exec
는 기존에 가동 중인 컨테이너의 내부에서 별도의 프로세스를 실행하는 방식이다.
이미 실행 중인 컨테이너에 접속해서 새로운 명령어를 실행할 수 있는 방법이 exec
이다. run
은 새로운 컨테이너를 만드는 것이고, exec
은 기존에 살아 있는 컨테이너에 명령을 추가로 집어넣는 것이다.
예를 들어 docker container exec -it db bash
와 같은 명령어는 마치 SSH 를 사용하는 것처럼 컨테이너 내부에 진입하는 효과를 준다. 하지만 이는 프로세스 단위의 실행일 뿐, 실제 SSH 가 아니며, VM 과는 다르게 컨테이너는 전체 운영체제를 제공하지 않기 때문에 혼동하면 안 된다! (이는 "호스팅형 가상화" 와 "컨테이너형 가상화" 차이를 알아야 한다는 것이다!)
(12장 ~ 15장)
도커 이미지는 여러 레이어(layer)로 구성되어 있으며, 최상단의 쓰기 가능 레이어(writable layer) 외에는 모두 읽기 전용(read-only layer)이다. 이미지를 구성하는 각 레이어는 설치나 설정 등 시스템 상태의 변화를 담고 있으며, 최종적으로 컨테이너를 만들 때 이 레이어들 위에 쓰기 가능한 레이어가 덧붙여진다.
메타데이터(metadata)
는 이미지 전체의 속성으로 환경 변수, 기본 실행 명령 등을 포함한다.[HOST[:PORT_NUMBER]/][NAMESPACE/]REPOSITORY[:TAG]
형태다.HOST
생략 시 기본값은 docker.io
NAMESPACE
생략 시 library
사용 (공식 이미지)TAG
생략 시 latest
가 기본값이나, 이는 이미지의 최신 상태가 예기치 않게 변경될 수 있으므로 주의가 필요하다.도커 이미지 관련 주요 명령어는 docker image --help
를 통해 확인 가능하며, --help
옵션을 적극적으로 사용하는게 많은 도움이 된다!
❯ docker image --help
Usage: docker image COMMAND
Manage images
Commands:
build Build an image from a Dockerfile
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Display detailed information on one or more images
load Load an image from a tar archive or STDIN
ls List images
prune Remove unused images
pull Download an image from a registry
push Upload an image to a registry
rm Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
Run 'docker image COMMAND --help' for more information on a command.
docker image ls [OPTIONS] [REPOSITORY[:TAG]]
: 호스트 머신에 존재하는 이미지 목록을 확인docker image pull [OPTIONS] NAME[:TAG|@DIGEST]
: 외부 레지스트리에서 이미지 다운로드, docker container run
명령어에서도 자동 수행된다!docker image inspect [OPTIONS] IMAGE [IMAGE...]
: 이미지의 상세 메타데이터를 JSON 형식으로 출력RepoTags
, Config.Env
, Config.Cmd
항목을 확인하면 유용하다!이미지 조작 명령어는 디버깅과 환경 구성 시의 핵심 도구이며, JSON으로 나오는 상세 정보를 통해 이미지 구성과 환경 변수, 실행 커맨드를 사전에 파악할 수 있다.
docker container run --name myubuntu --interactive --tty ubuntu:22.04 bash
apt update & apt install vim & which vi
-> 그냥 ubuntu 이미지에서 which vi 하면 존재하지 않는다!, 이 순서로 myubuntu 에 vim 을 설치하는 것!docker container commit [OPTIONS] CONTAINER [REPOSITORY:[TAG]]
을 통해 이미지를 새로 만들어 보자!❯ docker container commit myubuntu vi-ubuntu:commit
sha256:2204542f690c950e74aef2c8d2af737b1f9edb06d3edbb6eb1294a92e718ff62
❯ docker image ls vi-ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
vi-ubuntu commit 2204542f690c 10 seconds ago 185MB
❯ docker container run --rm vi-ubuntu:commit which vi
/usr/bin/vi
commit
은 컨테이너에서 이미지를 만들지만, 만들어진 이미지는 git 관리 또는 파일 저장소 업로드 불가능하다. 그래서 export
를 활용한다.
이러면 tar
아카이브 파일로 추출이 가능하며, 이후 tar
를 기반으로 image 처럼 읽어 올 수 있다! 그럼 두 명령어는 어떤 차이가 있을까?!
container 중심 말고 당연히 "IMAGE" 중심으로 tar
아카이브파일을 만들고 불러오는 것도 가능하다.
docker image save [OPTIONS] IMAGE [IMAGE...] (다수 이미지 지정 가능하다는 의미)
로 이미지를 tar 아카이브 파일로 작성 가능하며, load
명령어로 다시 이미지화 가능하다! 왜씀? 이미지 백업, 이관 & 이동에 활용!
(16장 ~ 19장)
위에서 살펴본 "tar" 파일 중심으로는 이미지 내용을 알 수 없다. 그렇다고 모르는 tar 를 합쳐서, 하나 하나의 layer 로 활용해서 쓰기에도 쉽지 않고 버전관리도 어렵다. 하지만 도커의 "이미지" 자체를 만들일은 굉장히 많다. 그때 도커파일(Dockerfile)을 사용해야 한다.
Dockerfile 은 컨테이너 환경을 코드로 명확하게 정의하고, 반복 가능한 이미지를 만들 수 있게 해주는 강력한 도구다. 기존엔 컨테이너를 만들고 설정하고 저장하는 흐름이 container run → exec → commit
으로 다소 수동적이었다면, 도커파일은 그 과정을 완전히 코드화할 수 있게 해준다.
특히 이미지를 tar로 만들면 내부 내용이 추상화되어 파악하기 어렵기 때문에, 어떤 이미지든 그 빌드 과정을 명시적으로 남길 수 있는 도커파일은 매우 중요하다.
명령어 | 설명 | 예시 |
---|---|---|
FROM | 베이스 이미지 지정 | FROM ubuntu:22.04 |
ARG | 빌드 시점 변수 | ARG VERSION=1.0 |
ENV | 환경 변수 설정 | ENV NODE_ENV=production |
LABEL | 이미지 메타데이터 | LABEL maintainer="you@example.com" |
WORKDIR | 작업 디렉토리 설정 | WORKDIR /app |
COPY | 로컬 파일 복사 | COPY . /app |
ADD | 파일 복사, URL 다운로드, 압축 해제 | ADD https://example.com/file.tar.gz /app/ |
RUN | 빌드 시 명령어 실행 | RUN apt-get update && apt-get install -y curl |
CMD | 기본 실행 명령어 | CMD ["npm", "start"] |
ENTRYPOINT | 컨테이너 실행 명령어 | ENTRYPOINT ["python3", "app.py"] |
EXPOSE | 수신 대기 포트 | EXPOSE 8080 |
VOLUME | 볼륨 마운트 디렉토리 | VOLUME ["/data"] |
USER | 실행 사용자/UID 설정 | USER appuser |
HEALTHCHECK | 상태 확인 명령어 | HEALTHCHECK CMD curl --fail http://localhost:8080 |
SHELL | 기본 셸 지정 | SHELL ["/bin/bash", "-c"] |
ONBUILD | 파생 이미지용 명령어 | ONBUILD COPY . /app/src |
이 도커파일 명령어들은 이미지가 만들어지는 과정을 '계층별로' 기록하는 역할을 한다. RUN
같은 명령은 실행 결과가 새로운 이미지 레이어로 저장되고, ENV
, LABEL
, EXPOSE
같은 명령은 이미지의 메타데이터로 저장된다. 도커 이미지가 결국 읽기 전용 레이어들의 조합이고, 도커파일은 그 조합의 '조리법'인 셈이다.
특히 FROM
은 도커 이미지의 연쇄 구조를 만들어낸다. 하나의 이미지가 다른 이미지의 기반이 되고, 그 위에 또 다른 이미지가 올라가는 구조인데, 이건 마치 Git
의 커밋 히스토리를 타고 올라가듯이 도커 이미지의 뿌리까지 따라갈 수 있다는 뜻이다. 도커 허브에서 이미지를 봤을 때, 어떤 베이스 이미지에서 파생되었는지 확인하는 것도 가능하다.
vi가 가능한 우분투 이미지
FROM ubuntu
RUN apt-get update && apt-get install -y vim
시간대 설정 및 로그 출력이 설정된 MySQL 이미지
FROM mysql:latest
ENV TZ=Asia/Seoul
COPY ./my.cnf /etc/mysql/conf.d/
RUN echo "설정된 시간대와 로그 출력을 위한 설정 포함"
간단한 파이썬 웹 서버 이미지
FROM python:3.11
COPY ./app.py /app/app.py
CMD ["python", "/app/app.py"]
5부에서는 도커를 단순한 실행 도구가 아니라, 서비스 환경을 제어하는 방법으로 확장하는 과정을 보여준다. 핵심은 두 가지다: 볼륨과 네트워크.
volume
기능을 통해 데이터를 유지할 수 있다. volume create
, --mount
옵션을 사용해 데이터를 보존하고, 컨테이너 재시작 시에도 동일한 상태를 유지하게 된다.--volume
과 --mount
의 커멘트 차이, 특히 볼륨 마운트와 바인드 마운트를 차이에 집중하는게 아주 좋았다. docker network create
, --network
옵션을 사용해 여러 컨테이너 간의 통신을 설정할 수 있다. 기본 브릿지 네트워크 외에 독립 네트워크를 정의하고, PHP 컨테이너와 MySQL 컨테이너가 서로 통신할 수 있게 연결하는 구조는 실제 서비스 구성에서 매우 자주 활용된다.전체적으로 실습 위주이며, 단순한 실행 단계를 넘어서, 컨테이너 간의 협업을 이해하게 해준다. 이 과정은 일종의 "도커로 시스템 아키텍처를 설계하는 감각"을 키우는 데 도움된다.
아래는 개인적인 사견 및 정리
각 컨테이너는 network namespace
안에 있고(서로 격리된 네트워크 공간) 컨테이너마다 veth pair (virtual ethernet pair
) 를 생성해서, 하나는 컨테이너 내부에, 다른 하나는 도커 브릿지 네트워크(docker0
) 에 연결한다. 그래서 컨테이너 내부 통신 흐름은 아래와 같은 느낌이다.
[컨테이너1] veth0 <--> [브릿지 docker0] <--> veth1 [컨테이너2]
볼륨은 VFS 구조 하에 컨테이너의 mount namespace를 활용해 호스트 디렉토리 또는 독립된 볼륨을 연결하며, 핵심은 mount() syscall을 통해 실제 경로가 연결된다는 점이다.
6부는 개발자가 자주 마주치는 스택 구성 (PHP, MySQL, Mailpit 등)을 도커파일 기반으로 직접 만들어보고, docker compose
로 통합하는 구조를 다룬다. 핵심은 다음과 같다.
COPY
, RUN
, ENV
등을 활용해 설정값과 초기 상태를 명확히 한다.compose.yaml
을 통해 의존성을 명시하고, 이름, 네트워크, 볼륨까지 정리한 하나의 선언적 환경 정의 파일로 완성된다.이 과정에서 중요한 포인트는 "각 컨테이너가 배타적으로 동작하는 게 아니라, 서로 보완하며 작동한다"는 개념이다. 웹 서버가 DB를 참조하고, 메일 서버가 로그를 수신하는 방식은 서비스 간의 연결성과 설정의 유연함을 도커로 어떻게 구성할 수 있는지를 체감할 수 있다.
특히 compose.yaml로 정리할 때의 구조화된 쾌감은 단순한 실습을 넘어선 실전 감각으로 이어진다.
7부는 도커를 현업에서 운영단에서 활용하는 시선을 제공한다.
.dockerignore
, 여러 compose.yaml 파일 결합 활용docker inspect
, logs
, exec
등을 통해 문제 상황을 좁혀가는 방식이 파트는 기술적인 팁 외에도, 운영 중 마주칠 수 있는 현실적인 문제들을 예방하고 해결하는 방법론이 담겨 있다. 도커를 쓰면서 언젠가 반드시 맞닥뜨릴 상황들 — 퍼포먼스 문제, 아키텍처 호환성, 계정/정책 변화 등 — 에 대한 대비가 된다.
7부는 실습보다도, 읽으면서 ‘현실 감각’을 얻는 장이다. 개인적으로 이 책에서 가장 유익했던 챕터이기도 하며, 도커를 단순히 실행 도구가 아닌 서비스 운영 플랫폼으로 인식하게 되는 계기였다.