Objectives
-
What are Containers?
-
What is Docker?
-
Why do you need it?
-
What can it do?
-
Run Docker Containers
-
Create Docket Image
-
Networks in Docker
-
Docker Compose
-
Docker Concepts in Depth
-
Docker for Windows/Mac
-
Docker Swarm
-
Docker vs Kubernetes
Docker Overview
Why do you need Docker?

- Compatibility/Dependency
- 여러 기술 스택을 사용할 때 (e.g. Node.js, MongoDB, Redis, Ansible)
- 운영 체제와의 호환성 문제 + 라이브러리와 의존성 문제
- 어떤 서비스는 A 버전, 다른 서비스는 B 버전의 라이브러리와 의존성이 필요함
- 변경 사항이 생길 때 마다 컴포넌트와 인프라 간의 호환성을 확인해야 함..
- 이런 호환성 매트릭스 문제를 The Matrix from Hell 이라고도 부름
- Long setup time
- 새로운 개발자 합류 시 새로운 개발 환경 구성이 쉽지 않음
- Different Dev/Test/Prod environments
- 개발은 macOS 에서, 테스트는 클라우드 CentOS 에서, 운영은 AWS Linux 에서 한다면 개발 산출물이 구동되는 환경이 다르기 때문에 일관성을 유지하기 어렵다. 나무를 이장하는 과정을 생각해보자. 나무를 안전하게 이장하기 위해선 자라온 흙과 함께 이식되어야 하는데, 이는 컨테이너가 SW의 구동에 필요한 환경요소(bin/lib 등)을 함께 포함하여 다른 환경에서 구동되는 모습과 비슷하다.
What can it do?

- 이런 호환성 문제를 해결해줄 수 있고, 컴포넌트를 수정하거나 변경하면서도 다른 컴포넌트에 영향을 주지 않으면서 운영 체제를 수정할 수 있는 것이 바로 Docker
- Docker 가 있으면 컴포넌트를 실행할 때 각자의 의존성과 라이브러리를 갖춘 분리된 컨테이너를 활용해 같은 가상 머신과 운영 체제에서 환경과 컨테이너를 분리할 수 있었음
What are Containers?

- 컨테이너란 완전히 분리된 환경으로 각자의 프로세스, 서비스 그리고 네트워크 인터페이스, 마운트를 가상 머신처럼 가지고 있지만 운영 체제 커널은 동일함
- 컨테이너는 전에도 있던 개념 (e.g. LXC, LXD, LXCFS 등)
- Docker 는 그 중 LXC 컨테이너를 사용함
- 컨테이너 환경은 저수준이라 설정이 어렵지만 Docker 에서 제공하는 강력한 기능성의 고수준툴이 있으면 쉽게 작업할 수 있음
Containers are just Processes
컨테이너에 대해 가장 먼저 이해해야 할 점은, 운영체제 관점에서 보면 컨테이너는 호스트에서 직접 실행되는 다른 애플리케이션과 똑같은 프로세스라는 것이다. 이를 확인하기 위해 Linux 가상 머신을 만들고 Docker를 설치한다. (참고: 이 시리즈의 실습을 따라 하려면 Docker for Windows/macOS보다 표준 Linux VM 사용을 권장한다. 그쪽 도구들은 내부 동작을 가리는 복잡성을 더한다.)
먼저 VM에 nginx라는 이름의 활성 프로세스가 있는지 확인한다:
root@lima-default:~# ps -fC nginx
UID PID PPID C STIME TTY TIME CMD
아직 NGINX 웹 서버를 띄우지 않았으므로 빈 목록이 반환된다.
이제 Docker Hub의 nginx 이미지로 컨테이너를 실행한다:
root@lima-default:~# docker run -d nginx:1.23.1
Unable to find image 'nginx:1.23.1' locally
1.23.1: Pulling from library/nginx
6a8bb8f4c2d1: Pull complete
a5e2bdbd69c7: Pull complete
37b74cc0b42a: Pull complete
8d8f8963a6d4: Pull complete
71e4fd647850: Pull complete
df8e44b0463f: Pull complete
Digest: sha256:2f770d2fe27bc85f68fd7fe6a63900ef7076bc703022fe81b980377fe3d27b70
Status: Downloaded newer image for nginx:1.23.1
f36035e1c29f7aaf1065a51a4709b39d03fcd05ae8fa997c4f8139c33ae6bf1a
컨테이너가 올라오면 ps 명령을 다시 실행한다:
root@lima-default:~# ps -fC nginx
UID PID PPID C STIME TTY TIME CMD
root 7311 7287 0 23:06 ? 00:00:00 nginx: master process nginx -g daemon off;
syslog 7370 7311 0 23:06 ? 00:00:00 nginx: worker process
syslog 7371 7311 0 23:06 ? 00:00:00 nginx: worker process
syslog 7372 7311 0 23:06 ? 00:00:00 nginx: worker process
syslog 7373 7311 0 23:06 ? 00:00:00 nginx: worker process
이번에는 여러 NGINX 프로세스가 머신에서 실행 중인 것이 보인다. Linux 머신 입장에서는 누군가 방금 호스트에서 NGINX를 실행한 것과 다름없다.
root@lima-default:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f36035e1c29f nginx:1.23.1 "/docker-entrypoint.…" 36 seconds ago Up 35 seconds 80/tcp blissful_hypatia
“컨테이너는 프로세스다”라는 개념을 더 파고들면, 한 가지 의문이 생긴다. Docker 이미지로 시작한 NGINX 서버와, VM에 그냥 설치한 NGINX를 어떻게 구별할까? 방법은 여러 가지지만 가장 쉬운 첫 번째는 docker ps로 실행 중인 컨테이너를 확인하는 것이다.
root@lima-default:~# ps -ef --forest
UID PID PPID C STIME TTY TIME CMD
...
root 7311 7287 0 23:06 ? 00:00:00 \_ nginx: master process nginx -g daemon off;
syslog 7370 7311 0 23:06 ? 00:00:00 \_ nginx: worker process
syslog 7371 7311 0 23:06 ? 00:00:00 \_ nginx: worker process
syslog 7372 7311 0 23:06 ? 00:00:00 \_ nginx: worker process
syslog 7373 7311 0 23:06 ? 00:00:00 \_ nginx: worker process
또는 Linux 프로세스 도구로 웹 서버가 컨테이너로 실행 중인지 판단할 수도 있다. ps의 --forest 옵션(예: ps -ef --forest)을 쓰면 프로세스 계층 구조를 볼 수 있다. 이 경우 NGINX 프로세스들의 부모 프로세스는 containerd-shim-runc-v2다. 호스트에서 실행 중인 컨테이너마다 shim 프로세스가 하나씩 보인다. 이 shim 프로세스는 containerd의 일부이며 Docker가 컨테이너 프로세스를 관리하는 데 사용한다. shim 프로세스의 목적은 호스트에서 실행 중인 컨테이너를 모두 재시작하지 않고도 containerd나 Docker 데몬을 재시작할 수 있게 하는 것이다.
Interacting with a Container as a Process
이제 컨테이너가 그냥 프로세스라는 것을 안다. 그렇다면 이것이 컨테이너와 상호작용하는 방식 측면에서 무엇을 의미할까? 프로세스로서 다룰 수 있다는 점은 동작 문제를 디버깅할 때나, 실행 중인 컨테이너의 변경을 조사할 때(예: 포렌식 조사) 유용하다. 여기서 기억할 점이 몇 가지 있는데, 첫 번째는 /proc 파일시스템을 사용해 실행 중인 컨테이너에 대한 정보를 더 얻을 수 있다는 것이다.
Linux의 /proc 파일시스템은 가상(pseudo) 파일시스템이다. 실제 파일을 담고 있지 않으며, 대신 실행 중인 시스템에 대한 정보로 채워진다. Docker를 실행하는 호스트에서 적절한 권한만 있으면, /proc를 통해 호스트에서 실행 중인 어떤 컨테이너의 정보든 접근할 수 있다.
앞서 띄운 NGINX 컨테이너의 정보를 살펴보자. 테스트 시스템에서 nginx 프로세스 ID는 7311이다. /proc의 파일을 나열하면 호스트의 모든 프로세스마다 번호가 매겨진 디렉터리가 보이고, NGINX 프로세스도 그중 하나다. 각 디렉터리에는 해당 프로세스 정보가 담긴 여러 파일·디렉터리가 들어 있으므로, 2336 디렉터리로 들어가 컨테이너 프로세스에 대해 더 알아볼 수 있다.
root@lima-default:~# sudo ls /proc
1 113 15 1965 240 34 4 4304 4745 5091 6 69 7373 8 932 cpuinfo interrupts kpagecount net swaps vmallocinfo
10 1188 16 2 26 347 40 4331 4747 51 60 7 74 80 992 crypto iomem kpageflags pagetypeinfo sys vmstat
1018 12 1648 20 27 348 406 4339 4748 52 61 71 7406 81 acpi devices ioports latency_stats partitions sysrq-trigger zoneinfo
1030 13 1650 200 28 35 408 44 48 5250 62 72 7436 82 asound diskstats irq loadavg pressure sysvipc
1042 130 17 2016 29 36 41 4462 488 53 63 7287 7438 910 bootconfig driver kallsyms locks schedstat thread-self
1048 1373 176 21 3 37 4107 45 489 54 64 73 7440 912 buddyinfo dynamic_debug kcore mdstat scsi timer_list
1054 14 1777 22 30 376 4164 4653 49 56 65 7311 7441 913 bus execdomains key-users meminfo self tty
1073 143 18 23 32 38 42 4679 5 57 66 7370 761 921 cgroups fb keys misc slabinfo uptime
11 144 19 239 320 383 4286 47 50 58 67 7371 773 922 cmdline filesystems kmsg modules softirqs version
111 1490 1963 24 33 39 43 4717 5004 59 68 7372 78 930 consoles fs kpagecgroup mounts stat version_signature
파일 편집기나 프로세스 모니터 같은 도구가 제거되도록 하드닝(hardening)된 컨테이너를 다룰 때도 Linux 도구가 유용하다. 컨테이너 이미지 하드닝은 흔한 보안 권고지만, 그만큼 디버깅을 까다롭게 만든다. 호스트의 /proc 디렉터리에서 컨테이너의 루트 파일시스템에 접근하면 컨테이너 내부 파일을 편집할 수 있다. /proc/[PID]/root로 이동하면 해당 PID를 가진 컨테이너 프로세스의 디렉터리 목록을 볼 수 있다.
이 경우 sudo ls /proc/7311/root를 실행하면 다음과 같은 목록이 나온다.
root@lima-default:~# sudo ls /proc/7311/root
bin dev docker-entrypoint.sh home media opt root sbin sys usr
boot docker-entrypoint.d etc lib mnt proc run srv tmp var
이제 touch로 이 디렉터리에 파일을 추가하면, docker exec로 컨테이너의 파일을 나열해 그 파일이 추가됐음을 확인할 수 있다. 이 기법으로 호스트에서 컨테이너의 설정 파일을 편집하는 등의 작업을 할 수 있다.
root@lima-default:~# sudo touch /proc/7311/root/my_new_file
root@lima-default:~# docker exec blissful_hypatia ls /
bin
boot
dev
docker-entrypoint.d
docker-entrypoint.sh
etc
home
lib
media
mnt
my_new_file
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
컨테이너가 프로세스라는 데서 오는 또 다른 이점은, 컨테이너 도구 없이도 호스트 수준 도구로 그 프로세스를 종료할 수 있다는 것이다. Docker의 재시작 정책(restart policy) 같은 설정과 이상하게 얽힐 수 있어 일상적으로 쓸 만한 방법은 아니지만, 필요한 경우가 있을 수 있다.
예를 들어 sudo kill 7311 명령으로 NGINX 컨테이너를 종료한다. 그런 다음 docker ps로 컨테이너가 더 이상 존재하지 않음을 확인할 수 있다.
root@lima-default:~# sudo kill 7311
root@lima-default:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
컨테이너를 구현하는 핵심 커널 기술
namespace: 프로세스가 자신만의 파일시스템, 네트워크, PID, user, hostname 을 갖는 것처럼 보이게 격리 (mount, UTS, IPC, PID, network, user 등)cgroup: 프로세스별 CPU, 메모리, 디스크, 네트워크 등 리소스 사용량을 제한, 모니터링chroot/ overlayfs: 각 컨테이너가 독립된 파일시스템을 갖는 것처럼 보이게 함 (호스트 FS 일부를 격리하거나 여러 계층을 합침)capabilities/seccomp/ AppArmor, SELinux: 권한 제한, 보안 격리namespace와cgroup메커니즘 상세는 06. Docker Engine 참조
Operating System
- Ubuntu, Fedora, SUSE, CentOS 와 같은 운영 체제는 운영 체제 커널과 소프트웨어로 구성됨
- 운영 체제 커널은 기반 하드웨어와 상호 작용하는 역할을 함
- Linux 인 운영 체제 커널은 그대로지만 소프트웨어 때문에 운영 체제의 차이가 발생
- 이 소프트웨어에는 다양한 UI와 드라이버, 컴파일러, 파일 관리자, 개발자 툴 등이 있음
- 즉, 모든 운영 체제에는 동일한 Linux 커널이 있지만, 일부 커스텀 소프트웨어 때문에 서로 다른 운영 체제가 되는 것
Sharing the kernel?
- Docker 가 설치된 Ubuntu 운영 체제가 있으면
- Docker 는 같은 커널에 기초하기만 하면 어떤 운영 체제라도 실행할 수 있음
- 이 경우 Linux 커널이 되고 기반 운영 체제가 Ubuntu 라면
- Docker 는 Debian, Fedora, SUSE, CentOS 등의 OS에 기반한 컨테이너를 실행할 수 있음
- Docker 는 모든 운영 체제와 호환되는 Docker 호스트의 기반 커널을 활용하는 것
- Windows 같은 경우 이와 같은 커널을 공유하지 않음
- 즉, Linux 가 설치된 Docker 호스트에서는 Windows 기반 컨테이너를 실행할 수 없음
- 따라서 Windows 서버에 Docker 를 설치해야 함
- Windows 에 Docker 를 설치하고 Linux 기반 컨테이너를 실행할 수 있지 않음?
- Windows 에서 Linux 컨테이너를 실행하는 것이 아니라 내부적으로 Linux 가상 머신에서 Linux 컨테이너를 실행하는 것
- 즉, Windows 위에 Linux 가상 머신에서 Linux 컨테이너를 실행하는 것
- 다른 커널을 실행할 수 없으면 뭐가 좋냐?
- 하이퍼바이저와 달리 Docker 는 같은 하드웨어 상에서 다른 운영 체제를 가상화하는 것이 아니라
- 애플리케이션을 패키지화하고 컨테이너화해서 원하는 만큼 실행할 수 있도록 배포하는 것이기 때문
Conatianers vs Virtual Machines

- 가상 머신의 경우 각 가상 머신에 각자의 운영 체제와 의존성, 그리고 애플리케이션이 설치됨
- 이러한 상층 구조는 기반 리소스를 많이 사용하는데 이는 여러 개의 가상 운영 체제와 커널이 실행되기 때문
- 또한 가상 머신은 무거워서 용량을 많이 차지하고 주로 그 단위가 GB에 달하는데 Docker 컨테이너는 가볍고 MB 단위의 용량만을 차지하므로 몇 초 만에 Docker 컨테이너를 부트할 수 있는데 가상 머신은 운영 체제 전체를 부트해야하기 때문에 보다 오래 걸림
- 또 한가지 중요한 점은 Docker 가 완전히 분리되지 못한다는 점. 이는 컨테이너 간 공유하는 커널과 같은 리소스가 많기 때문
- 반대로 가상 머신은 기반 운영 체제나 커널에 의존하지 않아 완전히 분리되어 서로 다른 운영 체제의 다른 애플리케이션을 실행할 수 있음
- 즉, 같은 하이퍼바이저에서 Linux 또는 Windows 기반 앱을 실행할 수 있음
- 컨테이너 또는 가상 머신만을 사용해야 한다는 뜻이 아닙니다 컨테이너와 가상 머신은 함께 사용해야 합니다
- 수천 개의 Docker 호스트에서 수천 가지의 앱을 실행하고자 할 때는 대개 가상 Docker 호스트에 기반해 컨테이너들이 구성하여 두 가지 기법을 모두 활용할 수 있습니다
- 즉, 가상화의 이점을 살려 필요할 때마다 Docker 호스트를 프로비저닝할 수 있고 한편으로는 Docker의 이점을 살려 필요에 따라 애플리케이션을 프로비저닝하고 확장할 수 있게 됩니다
- 하지만 이 경우에는 그 정도로 많은 가상 머신이 필요하지 않습니다
- 여기는 각 애플리케이션당 가상 머신을 프로비저닝한 경우이고 대신 수천 개의 컨테이너에 가상 머신을 수백 개 프로비저닝할 수도 있겠죠
How is it done?
- 많은 기관에서 자기 제품을 컨테이너화해서 Docker Hub 또는 Docker Store 라는 퍼블릭 Docker 보관소에 공개함
- 이곳에서 운영 체제, 데이터베이스, 서비스, 툴 등의 이미지를 찾을 수 있고 호스트에 Docker 를 설치한 뒤 docker run 명령어에 이미지 이름만 붙이면 애플리케이션을 쉽게 실행할 수 있음
Container vs Image
- 이미지는 VM 템플릿과 같은 패키지 또는 템플릿으로 컨테이너를 만드는 데 사용되며
- 컨테이너는 서로 분리된 이미지 인스턴스가 실행되는 곳으로 각자의 환경과 프로세스들을 지니고 있음
- 이미 도커화된 제품의 이미지가 많고 만약 찾는 이미지가 없다면 이미지를 만들어 공유할 수 있음
Docker in DevOps
- Docker 이전엔 개발팀이 애플리케이션을 개발하고 운영팀에게 지시사항을 전달하여 호스트 설정과 필요 설치 사항 그리고 의존성 구성등을 알려줬음
- Docker 이후엔 개발팀과 운영팀이 함께 이런 지침들을 Docker 파일로 관리할 수 있음
- Docker 파일을 사용하면 애플리케이션 이미지를 생성할 수 있어 Docker 가 설치된 호스트라면 어디서든 실행할 수 있고 어떤 환경에서도 똑같이 작동함
- 운영팀은 이미지로 애플리케이션을 배포만 하면 되는 것
Docker on Mac
Docker Toolbox
- Mac 에서 Docker 를 쓰려면 원래 이게 필요했음
- Mac 의 VirtualBox 로 만들어진 Linux VM 의 Docker
- Mac OS 에서 Linux Container 를 실행시킬 뿐
- Docker Toolbox 에는 Oracle VirtualBox, Docker Engine, Docker Machine, Docker Compose, Kitematic 이 포함됨
- Docker Toolbox 실행 파일을 다운로드 및 설치하면 VirtualBox 가 설치되고 내부에서 Docker 를 실행하는 Boot to Docker 라는 경량화된 VM 을 배포함
Docker Desktop for Mac
- Oracle VirtualBox 대신 HyperKit 가상화 기술을 사용함