📂 목차
- 가상머신과 컨테이너
- Layer 구조와 Caching 개념
- Docker 아키텍처
- Dockerfile 베스트 프랙티스
- 컨테이너 볼륨
- Docker Storage 구조
- Docker Logging 방식
📚 본문
가상머신과 컨테이너
가상머신
VM 이라고도 불리며, 하이퍼바이저를 통해 물리 서버 위에 여러 독립된 운영체제를 구동하는 방식이다. 따라서 Linux VM, Window VM 등등은 운영체제가 각각 필요하게 된다.
┌──────────────────────────────────────────┐
│ App A │ App B │ App C │
├────────────────┼─────────────┼───────────┤
│ Guest OS 1 │ Guest OS 2 │ Guest OS 3│
├────────────────┴─────────────┴───────────┤
│ Hypervisor (VMware, VirtualBox) │
├──────────────────────────────────────────┤
│ Host Operating System │
├──────────────────────────────────────────┤
│ Physical Infrastructure │
└──────────────────────────────────────────┘장점
- 커널까지 완전히 분리되어 있어 컨테이너보다 격리가 강함
- 일반 서버 처럼 다루면 되므로 레거시 시스템 운영에 적합하다
- 하이퍼바이저 기능을 활용하여 CPU/메모리 분리 배정이 가능하다
- 서로 다른 OS 를 병렬로 실행 가능하다.
단점
- 게스트 OS 까지 포함하기 때문에 컨테이너 대비 CPU/RAM 오버헤드가 크다
- VM 이미지가 GB 단위로 크고 배포/확장이 부담이 간다.
- OS 전체가 올라가야 해서 올리는데 오래 걸린다.
컨테이너
컨테이너는 호스트 OS의 커널을 공유하면서, 애플리케이션 실행에 필요한 프로세스와 라이브러리를 격리해 독립된 실행 환경을 제공하는 방식이다.
┌─────────────────────────────────────────┐
│ App A │ App B │ App C │
├────────────┼─────────────┼──────────────┤
│ Libs/Bins │ Libs/Bins │ Libs/Bins │
├────────────┴─────────────┴──────────────┤
│ Docker Engine (Container Runtime) │
├─────────────────────────────────────────┤
│ Host Operating System (Kernel) │
├─────────────────────────────────────────┤
│ Physical Infrastructure │
└─────────────────────────────────────────┘위를 보면, 호스트 커널을 공유하는 것을 볼 수 있고, 이는 Linux Namespaces, cgroups 기반으로 커널을 공유하며 격리를 수행하게 된다. 그 아래의 Docker Engine 이 및 기타 컨테이너 런타임이 프로세스 격리와 자원 관리를 담당한다.
장점
- 게스트 OS 가 없어 이미지가 작고 배포가 빠르다.
- 커널 공유 구조로 메모리와 CPU 오버헤드가 적다.
- 빌드, 테스트, 배포 속도가 매우 빠르다
- 하나의 서버에 많은 컨테이너를 올리기 쉽다.
단점
- 동일한 커널 계열에서만 실행 가능
- 커널을 공유하므로 취약점이 있을 시 영향 범위 증가
- 커널 기능이 필요한 경우 VM 이 필요할 수도 있다.
도커는 이런 컨테이너 아키텍처를 따라서 만들어졌다.
Layer 구조와 Caching 개념
도커 이미지는 여러 개의 레이어로 구성된다. Dockerfile 의 각 명령어가 한 레이어를 생성하며, 레이어 캐싱을 통해 이전과 동일한 명령어 / 컨텍스트면 다시 빌드하지 않고 재사용할 수 있다. 위에서 아래로 평가되기 때문에 자주 변경되는 명령은 아래로, 변경이 적은 명령은 위로 배치하는 것이 효율적이다.
캐싱이 깨지는 케이스
- COPY 로 가져오는 소스가 변경됨
- RUN 명령어 내부 내용이 변경됨
- 순서 변경
Docker 아키텍처
┌─────────────┐ REST API ┌──────────────┐
│ Docker │ ←───────────────→ │ Docker │
│ Client │ │ Daemon │
│ (docker) │ │ (dockerd) │
└─────────────┘ └──────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Images │ │Container│ │ Network │
└─────────┘ └─────────┘ └─────────┘
│
┌────▼────┐
│Registry │
│(Hub/ECR)│
└─────────┘우리는 도커 틀라이언트(도커 앱, 도커 CLI 등등)를 통해 dockerd 와 통신을 하고 직접적인 작업은 전부 dockerd 가 맡게 된다. dockerd 와 클라이언트 사이에서는 rest api 통신을 통해 주고 받게 된다(json 보냄).
- Docker Client: Docker CLI, Docker Desktop, Code Docker 확장 등이 모두 클라이언트에 속한다. 단지 명령어 전달만 수행한다.
Docker CLI는 REST API로 Docker 데몬과 통신
Unix 소켓(/var/run/docker.sock) 또는 TCP 사용
원격 Docker 호스트에도 연결 가능:
docker -H tcp://192.168.1.100:2375 ps
-
Docker Daemon: 컨테이너 관리, 이미지 관리, 네트워크 관리, 볼륨 관리, 로컬 이미지 저장, Registry 와 통신하여 push/pull 수행을 담당한다.
-
Containers: 이미지로부터 실행되는 실제 프로세스이며 네임스페이스와 cgroups 을 통해 독립된 실행 환경을 제공한다.
-
Network: 컨테이너간 통신 구조(bridge, host, overlay 등)를 통해 컨테이너끼리 혹은 외부와 연결될 수 있도록 관리한다.
-
Registry: 이미지 저장소이며 기본적으로 우리는 Image 를 Docker Hub 에서 가져오지만 이 외에도 AWS ECR, Github Registry 등등 사설 CR(Container Registry) 에게서도 얻을 수 있다.
-
Public Registry 사용 시 주의사항:
신뢰할 수 있는 이미지만 사용 (Official Images 우선)
이미지 스캔 도구로 취약점 검사 (Trivy, Clair)
최신 버전 유지 및 정기적 업데이트
docker hub 에서 image 는 검색하여 사용하자.
Dockerfile 베스트 프랙티스
- 불필요 레이어 최소화(RUN 명령어들은 && 로 묶기)
- 멀티 스테이지 빌드로 최종 이미지 크기 최소화
- 특정 태그 고정(latest 보다 버전 명시)
- 캐시 최적화를 고려한 명령 순서 구성
.dockerignore파일 적극 활용- root 실행 지양 -> USER 로 앱 실행
- 가능한 슬림 이미지 사용(
alpine,distroless,slim) - 빌드 아티팩트(
node_modules, …)를 직접 COPY 하지 말고 Docker 내부에서 빌드
컨테이너 볼륨
컨테이너는 기본적으로 휘발성이기 때문에, 컨테이너를 지우면 내부 파일도 같이 삭제된다. 따라서 DB 데이터를 보존한다거나 로그를 저장한다거나 업로드 파일을 유지하거나 여러 컨테이너 간 데이터 공유 등등은 볼륨이 없이는 불가능한 작업이다.
이를 해결하기 위해 Docker Volume 이라는 기능이 있다. 컨테이너 외부에 존재하면서 컨테이너와 연결되는 독립된 영구 저장소이다.
- 컨테이너 삭제해도 데이터 유지
- 여러 컨테이너가 같은 공간 공유 가능
- OS 의 커널 기능을 이용해 빠르고 안전하게 마운트
Named Volume
도커가 관리하는 따로 분리된 저장 공간이다.
docker volume create mydata
docker run -v mydata:/var/lib/mysql ...실제 호스트 물리 저장소 내의 /var/lib/mysql 에 존재하며, 도커가 알아서 관리를 하고, 별칭을 통하여 접근할 수 있다. 가장 많이 쓰이는 방식이다.
Bind Mount
호스트의 특정 경로를 그대로 컨테이너 내부에 연결한다.
여기서는 별칭이 없어 Named Volume 과는 차이가 있다.
docker run -v /host/path:/container/path- 개발에서 주로 사용
- 호스트 파일 시스템 구조에 의존
하지만 운영환경에서는 관리가 어려워 잘 안씀
Docker Storage 구조
Docker 의 저장 구조는 크게 3가지 레이어로 나뉜다.
- 이미지 레이어
- 컨테이너 레이어
- 볼륨
3가지가 다 합쳐 컨테이너의 파일 시스템을 형성한다.
이미지 레이어
docker pull 하면 도커는 여러 개의 레이어로 분할된 파일 시스템을 저장한다.
ubuntu:latest
├─ layer1 (기본 리눅스 파일)
├─ layer2 (패키지 설치)
└─ layer3 (환경설정)특징은 불변이라는 것이다.
컨테이너 레이어
컨테이너를 docker run 할 때 생성되는 레이어이며, Dockerfile 에 기록된 지시문들 하나하나와도 같다.
이미지 레이어 (RO)
+
컨테이너 레이어 (RW)볼륨
외부에 있는 스토리지이다. 설명은 생략한다.
Docker Logging 방식
[컨테이너 내부]
│ stdout / stderr
▼
[containerd / dockerd]
│ (로그 드라이버로 전달)
▼
[Log Driver] ← docker engine 설정으로 선택
├─ json-file (기본)
├─ local
├─ journald
├─ syslog
├─ fluentd
├─ awslogs
└─ gelf …도커는 컨테이너 내부에서 출력되는 stdout, stderr를 기반으로 로그를 처리하게 된다. docker 는 단순히 컨테이너의 출력 스트림을 수집한 뒤, 지정된 로그 드라이버에게 던져주는 구조가 된다.
기본 로그 방식: json-file
대부분의 Linux Docker 환경에서는 기본적으로 json-file 로그 드라이버가 사용된다.
- 컨테이너마다 로그 파일이 생성됨
- 저장 위치:
/var/lib/docker/containers/<container-id>/<container-id>-json.log
이 파일은 로그가 쌓일수록 디스크를 많이 차지하기 때문에 운영에서는
logrotate또는 다른 로그 드라이버 사용을 고려함
local
도커가 자체적으로 로그를 더 효율적으로 관리하는 방식이다.
- 바이너리 형태로 압축 및 저장
- 메타데이터 구분 쉬움
- json-file 보다 빠르고 공간 효율 좋음
- Docker Desktop 에서는 default 로 쓰는 경우도 있다
운영환경에서는 json-file 대신 local 을 추천하는 경우가 많다.
journald
시스템 데몬 로그(systemd-journald)에게 직접 전달되는 방식이며 로그가 OS의 journalctl 명령에서 확인이 가능하다. 이는 리눅스에서만 사용할 수 있다.
syslog
컨테이너 로그를 로컬 또는 원격 syslog 서버로 바로 전송한다.
실무에서는 다음과 같은 환경에서 사용하게 된다:
- On-premise 서버 클러스터
- 기존 syslog 인프라가 있었을 때
- 중앙화된 보안 로그 관리 필요
Fluentd / GELF / AWS Logs / GCP Logs
컨테이너 로그 -> 외부 로그 수집 시스템으로 바로 전송
- Fluentd -> Elasticsearch, Loki, Splunk, Kafka
- GELF -> Graylog, logstash
- awslogs -> CloudWatch
- gcp logging -> Google Cloud Logging
Kubernetes 는 stdout -> Fluentd -> Elastic/Loki 로 들어가는 구조다.
Docker Logging 동작 원리 Docker Engine은 다음을 한다:
- 컨테이너 프로세스의 stdout/stderr 파이프 FD를 잡는다
- containerd-shim이 그 스트림을 읽는다
- Docker Engine이 읽고 지정된 로그 드라이버에게 전달한다
- 따라서 컨테이너가 죽어도 로그는 이미 수집되어 저장됨