Post

Docker

Docker

목차

Part 1: Docker 기초

  1. Docker 개요
  2. Docker 이미지
  3. Dockerfile 작성

Part 2: Docker 컨테이너

  1. Docker 컨테이너 관리
  2. Docker 네트워킹
  3. Docker 볼륨과 스토리지

Part 3: Docker 고급

  1. Docker Compose
  2. Docker 레지스트리
  3. Docker 보안
  4. Docker 모니터링 및 로깅

Part 1: Docker 기초

1. Docker 개요

1.1 Docker란?

Docker의 정의

Docker는 컨테이너 기반 애플리케이션 개발, 배포, 실행 플랫폼이다.

Docker의 주요 가치

  • Build once, run anywhere: 한 번 빌드하면 어디서나 실행
  • Isolation: 애플리케이션 간 격리
  • Portability: 환경 독립적 실행
  • Lightweight: VM보다 가벼운 리소스 사용
  • Fast: 빠른 시작과 배포

1.2 Docker 아키텍처

Docker 전체 아키텍처

Docker는 여러 컴포넌트가 계층적으로 구성된 클라이언트-서버 아키텍처다:

flowchart TB
    subgraph client["Docker Client"]
        cli["docker CLI<br/>docker run, build, ps ..."]
    end

    subgraph dockerd["Docker Daemon (dockerd)"]
        api["API 서버"]
        img["이미지 관리<br/>pull, push, build"]
        net["네트워크 관리<br/>bridge, overlay, host"]
        vol["볼륨 관리<br/>local, nfs, etc"]
    end

    subgraph containerd_box["containerd"]
        lifecycle["컨테이너 생명주기 관리<br/>create, start, stop"]
        imgpull["이미지 pull/push"]
        snapshot["스냅샷 관리"]
        netconn["네트워크 연결"]
    end

    subgraph shim["containerd-shim (프로세스별)"]
        parent["runc의 부모 프로세스"]
        stdio["STDIO, 시그널 전달"]
        cleanup["컨테이너 종료 후 정리"]
    end

    subgraph runc["runc (컨테이너별)"]
        ns["Namespace 생성<br/>PID, Net, Mount, UTS"]
        cgroup["Cgroup 설정<br/>CPU, Memory 제한"]
        rootfs["Rootfs 마운트<br/>OverlayFS"]
        proc["컨테이너 프로세스 실행<br/>PID 1"]
    end

    container[("Container<br/>Process")]

    client -->|"REST API / Unix Socket<br/>/var/run/docker.sock"| dockerd
    dockerd -->|"gRPC"| containerd_box
    containerd_box -->|"Shim API"| shim
    shim -->|"exec"| runc
    runc --> container

Docker 실행 흐름 (docker run의 내부 동작)

사용자가 docker run nginx를 실행할 때:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
1. Docker Client
   └─> docker run nginx 명령 파싱
   └─> REST API 요청 생성: POST /containers/create
   └─> Unix socket으로 dockerd에 전송

2. Docker Daemon (dockerd)
   └─> 요청 수신 및 검증
   └─> 이미지 존재 확인
        ├─ 없으면: Registry에서 pull (docker.io/library/nginx:latest)
        └─ 있으면: 다음 단계
   └─> 네트워크 할당 (기본: bridge)
   └─> 볼륨 준비
   └─> gRPC로 containerd에 컨테이너 생성 요청

3. containerd
   └─> 이미지 스냅샷 준비 (OverlayFS 레이어 설정)
   └─> 컨테이너 번들 생성 (config.json, rootfs)
   └─> containerd-shim 프로세스 생성
   └─> shim에게 runc 실행 지시

4. containerd-shim
   └─> runc 실행: runc create <container-id>
   └─> runc이 종료되어도 컨테이너 유지 (daemonless 컨테이너)
   └─> STDIO 스트림 관리
   └─> 종료 시그널 전달

5. runc
   └─> Namespace 생성 (clone() 시스템 콜)
        ├─ PID Namespace: 독립 프로세스 트리
        ├─ Network Namespace: 독립 네트워크 스택
        ├─ Mount Namespace: 독립 파일시스템
        ├─ UTS Namespace: 독립 호스트명
        ├─ IPC Namespace: 독립 IPC
        ├─ User Namespace: UID/GID 매핑 (선택)
        └─ Cgroup Namespace: Cgroup 격리
   └─> Cgroup 설정 (/sys/fs/cgroup/...)
        ├─ CPU: cpu.cfs_quota_us, cpu.cfs_period_us
        ├─ Memory: memory.limit_in_bytes
        └─ Block I/O: blkio.throttle.read_bps_device
   └─> Rootfs 마운트 (OverlayFS)
        ├─ LowerDir: 이미지 레이어 (읽기 전용)
        ├─ UpperDir: 컨테이너 쓰기 레이어
        └─> MergedDir: 통합 파일시스템
   └─> Capabilities 설정 (CAP_NET_BIND_SERVICE 등)
   └─> Seccomp 필터 적용 (시스템 콜 제한)
   └─> pivot_root() 또는 chroot()
   └─> execve(): nginx 프로세스 실행 (PID 1)

6. 컨테이너 프로세스
   └─> nginx 실행 (컨테이너 내부 PID 1)
   └─> 호스트에서는 다른 PID (예: 12345)

각 컴포넌트의 역할 상세

1. Docker Client (docker CLI)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 역할:
- 사용자 명령을 REST API 요청으로 변환
- dockerd와 Unix socket 또는 TCP로 통신
- 원격 dockerd 접근 가능 (DOCKER_HOST 환경변수)

# 예시:
docker run nginx
  ↓
POST /v1.41/containers/create
{
  "Image": "nginx",
  "HostConfig": { "NetworkMode": "bridge" }
}

2. Docker Daemon (dockerd)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 역할:
- Docker API 서버
- 고수준 컨테이너 관리 (이미지, 네트워크, 볼륨)
- Registry와 통신 (이미지 pull/push)
- containerd에 저수준 작업 위임

# 책임:
- Dockerfile 빌드 (buildkit 사용)
- Docker Compose 지원
- Docker Swarm 오케스트레이션 (선택)
- 플러그인 시스템 (네트워크, 볼륨 드라이버)

# 설정 파일:
/etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

3. containerd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 역할:
- 업계 표준 컨테이너 런타임
- Docker 외에도 Kubernetes에서 직접 사용 가능
- 이미지 관리 (pull, push, 스냅샷)
- 컨테이너 생명주기 (create, start, stop, delete)

# CRI 플러그인:
- Kubernetes가 containerd를 직접 호출 가능
- dockerd 없이 Kubernetes 실행 가능 (경량화)

# 명령어:
ctr images pull docker.io/library/nginx:latest
ctr run --rm -t docker.io/library/nginx:latest nginx-test

# 데이터 위치:
/var/lib/containerd/
├── io.containerd.content.v1.content/  # 이미지 레이어 (blob)
├── io.containerd.snapshotter.v1.overlayfs/  # 스냅샷 (레이어 관리)
└── io.containerd.metadata.v1.bolt/  # 메타데이터 (bolt DB)

4. containerd-shim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 역할:
- runc와 containerd 사이의 중간 프로세스
- Daemonless 컨테이너: dockerd/containerd 재시작 시에도 컨테이너 유지
- runc는 컨테이너 시작 후 종료되지만 shim은 계속 실행

# 책임:
1. STDIO 스트림 관리 (docker logs)
2. 종료 시그널 전달 (docker stop → SIGTERM)
3. 종료 상태 보고 (exit code)
4. TTY 관리 (docker exec -it)

# 프로세스 트리:
dockerd (PID 1000)
  └─> containerd (PID 1100)
      └─> containerd-shim (PID 1200, 컨테이너 A)
      │    └─> nginx (PID 1201, 컨테이너 A 내부 PID 1)
      └─> containerd-shim (PID 1300, 컨테이너 B)
           └─> redis (PID 1301, 컨테이너 B 내부 PID 1)

# containerd 재시작해도 shim과 nginx는 계속 실행됨!

5. runc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 역할:
- OCI Runtime Specification 구현
- 실제 리눅스 커널 기능 호출 (Namespace, Cgroup)
- 컨테이너 프로세스 실행 후 자신은 종료

# 동작 단계:
runc create <container-id>  # Namespace/Cgroup 설정
runc start <container-id>   # 컨테이너 프로세스 시작
runc delete <container-id>  # 정리

# OCI 번들 구조:
/var/run/docker/containerd/<container-id>/
├── config.json  # OCI 설정 (Namespace, Cgroup, Capabilities)
└── rootfs/      # 컨테이너 루트 파일시스템 (OverlayFS 마운트)

# config.json 예시:
{
  "ociVersion": "1.0.0",
  "process": {
    "args": ["nginx", "-g", "daemon off;"],
    "cwd": "/",
    "env": ["PATH=/usr/local/sbin:/usr/local/bin:..."]
  },
  "root": {
    "path": "rootfs",
    "readonly": true  # 이미지는 읽기 전용, UpperDir로 쓰기
  },
  "linux": {
    "namespaces": [
      {"type": "pid"},
      {"type": "network"},
      {"type": "mount"},
      {"type": "uts"}
    ],
    "resources": {
      "memory": {"limit": 536870912},  # 512MB
      "cpu": {"quota": 50000, "period": 100000}  # 50% CPU
    },
    "capabilities": [
      "CAP_CHOWN",
      "CAP_NET_BIND_SERVICE"
    ]
  }
}

Docker vs containerd vs runc 비교

컴포넌트레벨역할사용자
dockerd고수준이미지 빌드, 네트워크, 볼륨, ComposeDocker CLI
containerd중간컨테이너 생명주기, 이미지 관리dockerd, Kubernetes (CRI)
runc저수준Namespace/Cgroup 생성, 프로세스 실행containerd (via shim)

Kubernetes에서 Docker 제거 (Dockershim Deprecation)

1
2
3
4
5
6
7
8
9
10
[이전] Kubernetes + Docker:
kubelet → dockerd → containerd → runc

[현재] Kubernetes + containerd (직접):
kubelet → containerd (CRI 플러그인) → runc

이유:
- dockerd는 Kubernetes에 불필요한 기능 포함 (빌드, Compose 등)
- containerd가 CRI를 직접 구현하여 중간 단계 제거
- 더 빠르고 경량화

Docker Objects (리소스)

Images (이미지):

  • 읽기 전용 템플릿
  • 레이어드 파일시스템 (OverlayFS)
  • Dockerfile로 빌드
  • Registry에 저장 (Docker Hub, ECR, GCR 등)

Containers (컨테이너):

  • 이미지의 실행 가능한 인스턴스
  • 읽기/쓰기 레이어 추가 (UpperDir)
  • 독립된 Namespace와 Cgroup
  • Stateless (기본), Volume으로 상태 유지

Networks (네트워크):

  • bridge: 기본, 단일 호스트
  • host: 호스트 네트워크 직접 사용
  • overlay: 다중 호스트 (Swarm)
  • macvlan: MAC 주소 할당

Volumes (볼륨):

  • 영구 데이터 저장
  • 컨테이너 삭제 후에도 유지
  • 드라이버: local, nfs, cifs, etc.

Docker Registry (이미지 저장소)

1
2
3
4
5
6
7
8
9
10
Docker Hub (공개):
  - docker pull nginx
  - docker push myuser/myapp:v1.0

프라이빗 레지스트리:
  - AWS ECR: <account-id>.dkr.ecr.<region>.amazonaws.com/myapp
  - GCR: gcr.io/<project-id>/myapp
  - Harbor: harbor.example.com/myproject/myapp
  - Docker Registry (오픈소스):
      docker run -d -p 5000:5000 registry:2

면접 질문 예시

Q1: “docker run 명령이 실행되는 전체 흐름을 설명하라”

A:

  1. Docker Client가 명령을 REST API로 변환 → dockerd
  2. dockerd가 이미지 확인 (없으면 pull), 네트워크/볼륨 준비 → containerd에 gRPC 요청
  3. containerd가 이미지 스냅샷 준비, containerd-shim 생성
  4. shim이 runc 실행 지시
  5. runc가 Namespace/Cgroup 생성, OverlayFS 마운트, 컨테이너 프로세스 실행 (PID 1)
  6. runc 종료, shim이 컨테이너 관리 유지

Q2: “containerd-shim의 역할은 무엇인가?”

A:

  • runc와 containerd 사이의 중간 프로세스
  • Daemonless 컨테이너: dockerd/containerd 재시작 시에도 컨테이너 유지
  • STDIO 관리 (docker logs), 시그널 전달 (docker stop), 종료 상태 보고
  • runc는 컨테이너 시작 후 종료되지만 shim은 계속 실행되어 컨테이너의 부모 프로세스 역할

Q3: “Kubernetes가 Docker를 제거한 이유는?”

A:

  • dockerd는 Kubernetes에 불필요한 기능 포함 (빌드, Compose, Swarm 등)
  • containerd가 CRI 플러그인으로 Kubernetes와 직접 통신 가능
  • kubelet → dockerd → containerd → runc 대신, kubelet → containerd → runc로 단순화
  • 더 빠르고 경량화, 메모리 사용량 감소

1.3 Docker 설치

Linux (Ubuntu)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 오래된 버전 제거
sudo apt-get remove docker docker-engine docker.io containerd runc

# 필수 패키지 설치
sudo apt-get update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

# Docker 공식 GPG 키 추가
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Repository 추가
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Docker 설치
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 설치 확인
sudo docker run hello-world

일반 사용자 권한 부여

1
2
3
4
5
6
7
8
9
# docker 그룹에 사용자 추가
sudo usermod -aG docker $USER

# 로그아웃 후 재로그인
# 또는 현재 세션에 적용
newgrp docker

# 권한 확인
docker run hello-world

1.4 Docker 기본 명령어

이미지 관련

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 이미지 검색
docker search nginx

# 이미지 다운로드
docker pull nginx
docker pull nginx:1.25

# 이미지 목록
docker images
docker image ls

# 이미지 삭제
docker rmi nginx
docker image rm nginx

# 사용하지 않는 이미지 정리
docker image prune

컨테이너 관련

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 컨테이너 실행
docker run nginx
docker run -d nginx  # 백그라운드
docker run -it ubuntu bash  # 대화형

# 실행 중인 컨테이너 목록
docker ps
docker container ls

# 모든 컨테이너 (중지된 것 포함)
docker ps -a
docker container ls -a

# 컨테이너 중지
docker stop <container>

# 컨테이너 시작
docker start <container>

# 컨테이너 재시작
docker restart <container>

# 컨테이너 삭제
docker rm <container>

# 강제 삭제 (실행 중이어도)
docker rm -f <container>

시스템 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 디스크 사용량
docker system df

# 전체 정리 (사용하지 않는 모든 리소스)
docker system prune

# 모든 리소스 강제 정리 (볼륨 포함)
docker system prune -a --volumes

# 로그 확인
docker logs <container>
docker logs -f <container>  # 실시간

# 리소스 사용량 모니터링
docker stats
docker stats <container>

2. Docker 이미지

2.1 이미지 개념

레이어 기반 아키텍처

Docker 이미지는 여러 읽기 전용 레이어로 구성된다:

1
2
3
4
5
6
7
8
9
10
11
Image: nginx:latest
    ↓
Layer 5: nginx 설정 파일
    ↓
Layer 4: nginx 설치
    ↓
Layer 3: 패키지 업데이트
    ↓
Layer 2: 베이스 파일시스템
    ↓
Layer 1: 커널 부트 파일시스템

OverlayFS (Union Mount)

  • 여러 레이어를 단일 디렉토리 구조로 제공
  • 읽기 전용 베이스 레이어와 쓰기 가능한 컨테이너 레이어 분리
  • 효율적인 메모리 사용

이미지 레이어 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 이미지 히스토리
docker history nginx:latest

# 각 레이어의 크기와 생성 명령 확인
docker history --no-trunc nginx:latest

# 이미지 inspect
docker inspect nginx:latest

# JSON 형식으로 모든 메타데이터 출력
# - 레이어 정보
# - 환경 변수
# - 포트 설정
# - 볼륨 마운트 포인트
# - 엔트리포인트/CMD

2.2 이미지 레지스트리

Docker Hub

공식 공개 레지스트리:

1
2
3
4
5
6
7
8
# Docker Hub에서 pull (기본)
docker pull nginx

# 명시적 지정
docker pull docker.io/library/nginx

# 사용자 이미지
docker pull username/myimage

프라이빗 레지스트리

1
2
3
4
5
6
7
8
9
# 로그인
docker login myregistry.com
# Username, Password 입력

# 프라이빗 레지스트리에서 pull
docker pull myregistry.com/myapp:latest

# 로그아웃
docker logout myregistry.com

Harbor, GitLab Registry 등

엔터프라이즈 레지스트리:

  • 접근 제어
  • 이미지 스캐닝
  • 복제 및 백업
  • 취약점 관리

2.3 이미지 태그

태그의 개념

태그는 이미지의 버전을 식별하는 레이블이다.

태그 형식

1
2
3
4
5
6
7
[registry/][namespace/]repository[:tag]

예시:
nginx:latest
nginx:1.25
docker.io/library/nginx:1.25-alpine
myregistry.com/myapp:v1.0.0

태그 규칙

1
2
3
4
5
6
7
8
9
10
11
12
# 태그 없이 pull하면 latest
docker pull nginx
# = docker pull nginx:latest

# 특정 버전
docker pull nginx:1.25

# Alpine 기반 (경량)
docker pull nginx:alpine

# 특정 OS/아키텍처
docker pull nginx:1.25-alpine-arm64

이미지 태깅 및 공유

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 로컬 이미지에 태그 추가
docker tag nginx:latest myregistry.com/nginx:v1.0

# 여러 태그 가능
docker tag nginx:latest nginx:production
docker tag nginx:latest nginx:stable

# Docker Hub에 푸시
docker login
docker tag myapp:latest username/myapp:latest
docker push username/myapp:latest

# 프라이빗 레지스트리에 푸시
docker login myregistry.com
docker tag myapp:latest myregistry.com/myapp:latest
docker push myregistry.com/myapp:latest

이미지 저장/로드

1
2
3
4
5
6
7
8
9
10
11
12
# 이미지를 tar 파일로 저장
docker save -o myapp.tar myapp:latest

# tar 파일에서 이미지 로드
docker load -i myapp.tar

# 여러 이미지 저장
docker save -o images.tar nginx:latest alpine:latest

# 압축
docker save myapp:latest | gzip > myapp.tar.gz
gunzip -c myapp.tar.gz | docker load

이미지 익스포트/임포트 (컨테이너)

1
2
3
4
5
6
7
8
9
10
11
12
# 실행 중인 컨테이너를 이미지로
docker commit <container> myapp:snapshot

# 컨테이너를 tar로 익스포트
docker export <container> -o container.tar

# tar에서 이미지 생성
docker import container.tar myapp:imported

# 차이점:
# - save/load: 이미지 레이어 보존
# - export/import: 단일 레이어로 압축

2.4 이미지 빌드

docker build 기본

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 현재 디렉토리의 Dockerfile 사용
docker build .

# 태그 지정
docker build -t myapp:latest .

# 특정 Dockerfile 지정
docker build -f Dockerfile.prod -t myapp:prod .

# 빌드 컨텍스트 제외 (.dockerignore)
# .dockerignore 파일 작성:
# node_modules
# .git
# *.log

빌드 캐시

Docker는 레이어를 캐시하여 빌드 속도를 향상시킨다:

1
2
3
4
5
# 캐시 무시하고 빌드
docker build --no-cache -t myapp:latest .

# 특정 레이어부터 다시 빌드
# (Dockerfile 수정 시 자동)

멀티 스테이지 빌드

빌드 단계를 분리하여 최종 이미지 크기 축소:

1
2
3
4
5
6
7
8
9
10
11
12
# 빌드 스테이지
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# 실행 스테이지
FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]

# 최종 이미지는 alpine만 포함 (작음)

BuildKit

차세대 빌드 엔진:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BuildKit 활성화
export DOCKER_BUILDKIT=1

# 또는 /etc/docker/daemon.json
{
  "features": {
    "buildkit": true
  }
}

# 장점:
# - 병렬 빌드
# - 빌드 캐시 개선
# - 비밀 정보 안전 처리
# - 더 나은 성능

3. Dockerfile 작성

3.1 Dockerfile 기본 구조

Dockerfile이란?

Dockerfile은 Docker 이미지를 빌드하는 명령어 스크립트이다.

기본 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 베이스 이미지
FROM node:18-alpine

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 복사
COPY package*.json ./

# 의존성 설치
RUN npm install --production

# 애플리케이션 코드 복사
COPY . .

# 포트 노출
EXPOSE 3000

# 실행 명령
CMD ["node", "server.js"]

3.2 Dockerfile 주요 명령어

FROM - 베이스 이미지

1
2
3
4
5
6
7
8
9
10
11
# 공식 이미지
FROM ubuntu:22.04

# 경량 이미지
FROM alpine:3.18

# 멀티 스테이지의 특정 스테이지
FROM node:18 AS builder

# Scratch (빈 이미지)
FROM scratch

WORKDIR - 작업 디렉토리

1
2
3
4
5
6
7
8
9
10
# 작업 디렉토리 설정
WORKDIR /app

# 이후 명령은 모두 /app에서 실행
# 디렉토리가 없으면 자동 생성

# 중첩 가능
WORKDIR /app
WORKDIR src
# 현재 위치: /app/src

COPY vs ADD

1
2
3
4
5
6
7
8
9
10
11
12
13
# COPY: 단순 파일/디렉토리 복사
COPY package.json /app/
COPY src/ /app/src/

# ADD: 복사 + 추가 기능
ADD https://example.com/file.tar.gz /app/
# URL에서 다운로드

ADD archive.tar.gz /app/
# 자동 압축 해제

# 권장: 일반적으로 COPY 사용
# ADD는 특별한 경우에만

RUN - 명령 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Shell 형식
RUN apt-get update && apt-get install -y nginx

# Exec 형식
RUN ["apt-get", "update"]

# 여러 명령을 하나로 (레이어 최소화)
RUN apt-get update && \
    apt-get install -y \
      nginx \
      curl && \
    rm -rf /var/lib/apt/lists/*

# 각 RUN은 새 레이어 생성
# 최적화: 관련 명령을 하나의 RUN으로

ENV - 환경 변수

1
2
3
4
5
6
7
8
9
10
# 환경 변수 설정
ENV NODE_ENV=production
ENV PORT=3000

# 여러 변수 한번에
ENV NODE_ENV=production \
    PORT=3000 \
    LOG_LEVEL=info

# 빌드 시 및 런타임에 모두 사용

ARG - 빌드 인수

1
2
3
4
5
6
7
8
9
10
# 빌드 시에만 사용하는 변수
ARG VERSION=latest
ARG BUILD_DATE

# 사용
FROM node:${VERSION}
LABEL build-date=${BUILD_DATE}

# 빌드 시 전달
# docker build --build-arg VERSION=18 .

EXPOSE - 포트 노출

1
2
3
4
5
6
7
# 포트 노출 (문서화 목적)
EXPOSE 80
EXPOSE 443/tcp
EXPOSE 53/udp

# 실제 포트 매핑은 실행 시:
# docker run -p 8080:80 myapp

CMD vs ENTRYPOINT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# CMD: 기본 명령 (덮어쓰기 가능)
CMD ["nginx", "-g", "daemon off;"]

# 실행 시 덮어쓰기:
# docker run myapp echo "hello"

# ENTRYPOINT: 항상 실행되는 명령
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 실행 시:
# docker run myapp -c /custom/nginx.conf
# = nginx -c /custom/nginx.conf

# 조합:
# ENTRYPOINT: 실행 파일
# CMD: 기본 인수

VOLUME - 볼륨 마운트 포인트

1
2
3
4
5
# 볼륨 마운트 포인트 선언
VOLUME /data
VOLUME ["/var/log", "/var/db"]

# 컨테이너 실행 시 익명 볼륨 자동 생성

USER - 실행 사용자

1
2
3
4
5
6
7
8
9
# 사용자 생성
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup

# 사용자 전환
USER appuser

# 이후 명령은 appuser로 실행
# 보안 모범 사례: root 사용 지양

LABEL - 메타데이터

1
2
3
4
5
6
7
8
9
# 메타데이터 추가
LABEL version="1.0"
LABEL description="My Application"
LABEL maintainer="devops@example.com"

# 여러 레이블
LABEL version="1.0" \
      description="My Application" \
      maintainer="devops@example.com"

3.3 Dockerfile 최적화

레이어 최소화

1
2
3
4
5
6
7
8
9
10
11
12
13
# 나쁜 예: 레이어가 많음
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get install -y curl
RUN apt-get clean

# 좋은 예: 하나의 레이어
RUN apt-get update && \
    apt-get install -y \
      nginx \
      curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

캐시 활용

1
2
3
4
5
6
7
8
9
10
# 나쁜 예: 코드 변경 시 의존성도 재설치
COPY . /app
RUN npm install

# 좋은 예: 의존성 파일만 먼저 복사
COPY package*.json /app/
RUN npm install
COPY . /app

# package.json 변경 시에만 npm install 재실행

베이스 이미지 선택

1
2
3
4
5
6
7
8
9
10
11
12
# 크기 비교:
# node:18 (약 1GB)
FROM node:18

# node:18-slim (약 200MB)
FROM node:18-slim

# node:18-alpine (약 170MB)
FROM node:18-alpine

# Distroless (최소, 보안 강화)
FROM gcr.io/distroless/nodejs18

불필요한 파일 제외 (.dockerignore)

1
2
3
4
5
6
7
8
9
10
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.DS_Store
*.md
tests/
docs/

3.4 Dockerfile 보안 모범 사례

최소 권한 원칙

1
2
3
4
5
6
7
8
# 나쁜 예: root로 실행
FROM nginx

# 좋은 예: 일반 사용자
FROM nginx
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup
USER appuser

민감 정보 제외

1
2
3
4
5
6
7
# 나쁜 예: 비밀키 포함
ENV API_KEY=secret123

# 좋은 예: 런타임에 주입
# docker run -e API_KEY=secret123 myapp

# 또는 Docker Secrets 사용

최신 패치

1
2
3
4
5
6
7
# 베이스 이미지 업데이트
FROM alpine:3.18

# 시스템 패키지 업데이트
RUN apk update && \
    apk upgrade && \
    rm -rf /var/cache/apk/*

취약점 스캔

1
2
3
4
5
# Trivy로 이미지 스캔
trivy image myapp:latest

# Docker Scout (Docker Desktop)
docker scout cves myapp:latest

Part 2: Docker 컨테이너

4. Docker 컨테이너 관리

4.1 컨테이너 생명주기

컨테이너 상태

  • Created: 생성됨, 아직 시작 안 함
  • Running: 실행 중
  • Paused: 일시 정지
  • Stopped: 중지됨
  • Deleted: 삭제됨

생명주기 명령

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 생성 (시작 안 함)
docker create nginx

# 생성 + 시작
docker run nginx

# 시작
docker start <container>

# 중지 (SIGTERM, 10초 후 SIGKILL)
docker stop <container>

# 강제 중지 (SIGKILL)
docker kill <container>

# 재시작
docker restart <container>

# 일시 정지
docker pause <container>

# 재개
docker unpause <container>

# 삭제
docker rm <container>

# 강제 삭제
docker rm -f <container>

4.2 docker run 옵션

기본 실행

1
2
3
4
5
6
7
8
9
10
11
# 포그라운드 실행
docker run nginx

# 백그라운드 실행 (-d, --detach)
docker run -d nginx

# 컨테이너 이름 지정
docker run --name mynginx nginx

# 실행 후 자동 삭제 (--rm)
docker run --rm nginx

대화형 모드

1
2
3
4
5
6
7
8
9
10
11
# 대화형 모드 (-it)
docker run -it ubuntu bash

# -i: STDIN 유지
# -t: TTY 할당

# 실행 중인 컨테이너에 접속
docker exec -it mynginx bash

# 특정 사용자로 실행
docker exec -it -u root mynginx bash

포트 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 단일 포트
docker run -p 8080:80 nginx

# 여러 포트
docker run -p 8080:80 -p 4443:443 nginx

# 호스트 IP 지정
docker run -p 127.0.0.1:8080:80 nginx

# UDP 포트
docker run -p 53:53/udp dns-server

# 호스트 포트 자동 할당
docker run -p 80 nginx

환경 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
# 단일 환경 변수
docker run -e NODE_ENV=production myapp

# 여러 환경 변수
docker run -e NODE_ENV=production -e PORT=3000 myapp

# 파일에서 읽기
docker run --env-file .env myapp

# .env 파일 형식:
# NODE_ENV=production
# PORT=3000
# DB_HOST=db.example.com

볼륨 마운트

1
2
3
4
5
6
7
8
9
10
11
# Named 볼륨
docker run -v myvolume:/data nginx

# Bind mount
docker run -v /host/path:/container/path nginx

# 읽기 전용
docker run -v myvolume:/data:ro nginx

# tmpfs (메모리)
docker run --tmpfs /tmp nginx

네트워크

1
2
3
4
5
6
7
8
9
10
11
# 특정 네트워크
docker run --network mynetwork nginx

# 호스트 네트워크
docker run --network host nginx

# 네트워크 없음
docker run --network none nginx

# 네트워크 별칭
docker run --network mynetwork --network-alias db postgres

리소스 제한

1
2
3
4
5
6
7
8
9
10
11
12
# 메모리 제한
docker run -m 512m nginx
docker run --memory=512m nginx

# CPU 제한
docker run --cpus=1.5 nginx

# CPU 공유
docker run --cpu-shares=512 nginx

# 블록 I/O
docker run --device-write-bps=/dev/sda:10mb nginx

재시작 정책

1
2
3
4
5
6
7
8
9
10
11
# 항상 재시작
docker run --restart=always nginx

# 실패 시에만 재시작
docker run --restart=on-failure nginx

# 최대 재시작 횟수
docker run --restart=on-failure:3 nginx

# 명시적으로 중지하지 않으면 재시작
docker run --restart=unless-stopped nginx

4.3 컨테이너 모니터링

로그 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 로그 출력
docker logs <container>

# 실시간 로그 (tail -f)
docker logs -f <container>

# 마지막 N줄
docker logs --tail 100 <container>

# 타임스탬프 포함
docker logs -t <container>

# 특정 시간 이후
docker logs --since 2023-01-01T00:00:00 <container>
docker logs --since 1h <container>

리소스 사용량

1
2
3
4
5
6
7
8
9
10
11
# 실시간 통계
docker stats

# 특정 컨테이너
docker stats <container>

# 스트리밍 없이 한 번만
docker stats --no-stream

# 출력:
# CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O

프로세스 확인

1
2
3
4
5
6
7
8
# 컨테이너 내 프로세스
docker top <container>

# ps aux 형식
docker top <container> aux

# 실행 중인 프로세스 확인
docker exec <container> ps aux

파일 시스템 변경사항

1
2
3
4
5
6
7
# 이미지 대비 변경된 파일
docker diff <container>

# 출력:
# A: 추가된 파일
# C: 변경된 파일
# D: 삭제된 파일

이벤트 모니터링

1
2
3
4
5
6
7
8
9
# 실시간 Docker 이벤트
docker events

# 특정 컨테이너
docker events --filter container=<container>

# 특정 이벤트 타입
docker events --filter event=start
docker events --filter event=stop

4.4 컨테이너 디버깅

실행 중인 컨테이너 접속

1
2
3
4
5
6
7
8
9
# Bash 실행
docker exec -it <container> bash

# sh 실행 (Alpine 등)
docker exec -it <container> sh

# 특정 명령 실행
docker exec <container> cat /etc/hosts
docker exec <container> env

컨테이너 inspect

1
2
3
4
5
6
7
# 전체 정보 (JSON)
docker inspect <container>

# 특정 필드 추출
docker inspect --format='{{.State.Status}}' <container>
docker inspect --format='{{.NetworkSettings.IPAddress}}' <container>
docker inspect --format='{{json .Config.Env}}' <container> | jq

파일 복사

1
2
3
4
5
6
7
8
# 컨테이너 → 호스트
docker cp <container>:/path/to/file ./local/path

# 호스트 → 컨테이너
docker cp ./local/file <container>:/path/to/

# 디렉토리 복사
docker cp <container>:/app ./app-backup

네트워크 디버깅

1
2
3
4
5
6
7
8
9
10
11
# 네트워크 정보 확인
docker exec <container> ip addr
docker exec <container> ip route

# 연결 테스트
docker exec <container> ping google.com
docker exec <container> curl http://api.example.com

# DNS 확인
docker exec <container> nslookup google.com
docker exec <container> cat /etc/resolv.conf

헬스체크

1
2
3
# Dockerfile에서 헬스체크 정의
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost/ || exit 1
1
2
3
4
5
6
7
8
9
# 실행 시 헬스체크 추가
docker run --health-cmd='curl -f http://localhost/ || exit 1' \
           --health-interval=30s \
           --health-timeout=3s \
           --health-retries=3 \
           nginx

# 헬스 상태 확인
docker inspect --format='{{.State.Health.Status}}' <container>

4.5 컨테이너 정리

중지된 컨테이너 삭제

1
2
3
4
5
6
7
8
# 모든 중지된 컨테이너 삭제
docker container prune

# 확인 없이 삭제
docker container prune -f

# 특정 레이블 필터
docker container prune --filter "label=env=test"

자동 삭제 (–rm)

1
2
3
4
5
# 종료 시 자동 삭제
docker run --rm nginx

# 임시 작업에 유용
docker run --rm -it ubuntu bash

대량 정리

1
2
3
4
5
# 실행 중이지 않은 모든 컨테이너 삭제
docker ps -aq -f status=exited | xargs docker rm

# 특정 이미지의 모든 컨테이너 삭제
docker ps -a --filter ancestor=nginx -q | xargs docker rm -f

5. Docker 네트워킹

5.1 Docker 네트워크 드라이버

Bridge 네트워크 (기본)

Docker의 기본 네트워크 모드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 기본 브리지 네트워크
docker network inspect bridge

# 특징:
# - 네트워크: 172.17.0.0/16
# - 게이트웨이: 172.17.0.1 (docker0 브리지)
# - 컨테이너 간 IP로만 통신 (이름 해석 안 됨)

# 사용자 정의 브리지 생성
docker network create --driver bridge mybridge

# 서브넷 지정
docker network create --driver bridge \
  --subnet=192.168.100.0/24 \
  --gateway=192.168.100.1 \
  mybridge

# 장점:
# - 자동 DNS 해석 (컨테이너 이름으로 통신)
# - 네트워크 격리
# - 동적 연결/해제 가능

사용자 정의 브리지 vs 기본 브리지

1
2
3
4
5
6
7
8
9
10
# 기본 브리지
docker run -d --name web1 nginx
docker run -d --name web2 nginx
docker exec web1 ping web2  # 실패 (IP로만 가능)

# 사용자 정의 브리지
docker network create mynet
docker run -d --name web1 --network mynet nginx
docker run -d --name web2 --network mynet nginx
docker exec web1 ping web2  # 성공 (DNS 해석됨)

Host 네트워크

컨테이너가 호스트의 네트워크 스택을 직접 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Host 네트워크로 실행
docker run --network host nginx

# 특징:
# - 최고 성능 (네트워크 오버헤드 없음)
# - 포트 매핑 불필요 (컨테이너 포트 = 호스트 포트)
# - 네트워크 격리 없음
# - 호스트의 모든 네트워크 인터페이스 접근

# 주의:
# - 여러 컨테이너가 같은 포트 사용 불가
# - 보안 위험 (호스트 네트워크 노출)
# - Linux에서만 완전 지원 (Mac/Windows는 제한적)

None 네트워크

네트워크를 완전히 비활성화한다.

1
2
3
4
5
6
7
8
9
10
11
12
# None 네트워크로 실행
docker run --network none alpine

# 특징:
# - lo (루프백)만 존재
# - 외부 통신 완전 차단
# - 최대 격리

# 용도:
# - 네트워크가 필요 없는 배치 작업
# - 보안이 매우 중요한 작업
# - 커스텀 네트워크 설정

Overlay 네트워크

여러 Docker 호스트에 걸친 네트워크를 구성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Docker Swarm 초기화 필요
docker swarm init

# Overlay 네트워크 생성
docker network create \
  --driver overlay \
  --subnet 10.0.9.0/24 \
  myoverlay

# 서비스 배포
docker service create \
  --name web \
  --network myoverlay \
  --replicas 3 \
  nginx

# 특징:
# - 멀티 호스트 통신
# - 자동 서비스 디스커버리
# - 내장 로드 밸런싱
# - 암호화 가능 (--opt encrypted)

# Kubernetes에서는 CNI 플러그인 사용

Macvlan 네트워크

컨테이너에 고유한 MAC 주소를 할당한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Macvlan 네트워크 생성
docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  mymacvlan

# 컨테이너 실행
docker run --network mymacvlan \
  --ip=192.168.1.100 \
  nginx

# 특징:
# - 컨테이너가 물리 네트워크에 직접 연결된 것처럼 보임
# - 각 컨테이너가 고유 MAC 주소 가짐
# - 라우터/스위치가 컨테이너를 별도 호스트로 인식

# 용도:
# - 레거시 애플리케이션 (MAC 주소 기반 라이센싱)
# - 네트워크 모니터링 도구
# - DHCP 서버

# 주의:
# - 프로미스큐어스 모드 필요할 수 있음
# - 클라우드 환경에서는 지원 안 될 수 있음

5.2 네트워크 관리

네트워크 목록 및 정보

1
2
3
4
5
6
7
8
9
10
11
# 네트워크 목록
docker network ls

# 네트워크 상세 정보
docker network inspect bridge

# 특정 컨테이너의 네트워크 정보
docker inspect <container> --format '{{json .NetworkSettings.Networks}}' | jq

# 네트워크에 연결된 컨테이너 목록
docker network inspect mynet --format '{{range .Containers}}{{.Name}} {{end}}'

네트워크 생성 옵션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 기본 생성
docker network create mynet

# 드라이버 지정
docker network create --driver bridge mynet

# 서브넷 및 게이트웨이
docker network create \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  mynet

# IP 범위 지정
docker network create \
  --subnet 172.20.0.0/16 \
  --ip-range 172.20.240.0/20 \
  mynet

# IPv6 활성화
docker network create \
  --ipv6 \
  --subnet 2001:db8::/64 \
  mynet

# Internal 네트워크 (외부 통신 차단)
docker network create --internal mynet

# 레이블
docker network create \
  --label environment=production \
  mynet

컨테이너 네트워크 연결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 생성 시 네트워크 지정
docker run --network mynet nginx

# 실행 중인 컨테이너에 네트워크 추가
docker network connect mynet <container>

# IP 주소 지정
docker network connect --ip 172.20.0.10 mynet <container>

# 네트워크 별칭
docker network connect --alias db mynet postgres

# 네트워크 연결 해제
docker network disconnect mynet <container>

# 강제 연결 해제
docker network disconnect -f mynet <container>

네트워크 삭제

1
2
3
4
5
6
7
8
# 네트워크 삭제
docker network rm mynet

# 사용하지 않는 네트워크 정리
docker network prune

# 확인 없이 정리
docker network prune -f

5.3 포트 매핑 상세

포트 매핑 방식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 기본 매핑 (모든 인터페이스)
docker run -p 8080:80 nginx
# 0.0.0.0:8080 → 컨테이너:80

# 특정 IP에만 바인딩
docker run -p 127.0.0.1:8080:80 nginx
# localhost에서만 접근 가능

# 특정 IP 범위
docker run -p 192.168.1.10:8080:80 nginx

# UDP 포트
docker run -p 53:53/udp dns-server

# TCP와 UDP 모두
docker run -p 8080:80/tcp -p 53:53/udp myapp

# 포트 범위
docker run -p 8000-8010:8000-8010 myapp

# 호스트 포트 자동 할당
docker run -P nginx
# Dockerfile의 EXPOSE 포트를 랜덤 포트로 매핑

포트 확인

1
2
3
4
5
6
7
8
# 컨테이너의 포트 매핑 확인
docker port <container>

# 특정 포트 확인
docker port <container> 80

# 모든 매핑 정보
docker inspect <container> --format '' | jq

포트 매핑과 iptables

1
2
3
4
5
6
7
8
# Docker가 생성한 iptables 규칙 확인
sudo iptables -t nat -L -n

# DOCKER 체인 확인
sudo iptables -t nat -L DOCKER -n

# 예시 규칙:
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80

5.4 컨테이너 간 통신

같은 네트워크 내 통신

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 네트워크 생성
docker network create myapp-net

# 데이터베이스 컨테이너
docker run -d \
  --name db \
  --network myapp-net \
  -e POSTGRES_PASSWORD=secret \
  postgres

# 애플리케이션 컨테이너
docker run -d \
  --name web \
  --network myapp-net \
  -e DATABASE_URL=postgresql://postgres:secret@db:5432/mydb \
  myapp

# web에서 db로 접근
docker exec web ping db  # 성공
docker exec web psql -h db -U postgres  # 성공

다중 네트워크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 프론트엔드 네트워크
docker network create frontend

# 백엔드 네트워크
docker network create backend

# 웹 서버 (프론트엔드만)
docker run -d --name web --network frontend nginx

# API 서버 (양쪽 모두)
docker run -d --name api --network frontend myapi
docker network connect backend api

# 데이터베이스 (백엔드만)
docker run -d --name db --network backend postgres

# 결과:
# web → api (가능)
# api → db (가능)
# web → db (불가능, 격리됨)

네트워크 별칭

1
2
3
4
5
6
7
8
9
10
11
12
# 여러 별칭으로 참조
docker run -d \
  --network mynet \
  --network-alias db \
  --network-alias database \
  --network-alias postgres \
  postgres

# 모든 별칭으로 접근 가능
docker exec web ping db
docker exec web ping database
docker exec web ping postgres

서비스 디스커버리

1
2
3
4
5
6
7
8
# 같은 서비스의 여러 인스턴스
docker run -d --name web1 --network mynet --network-alias web nginx
docker run -d --name web2 --network mynet --network-alias web nginx
docker run -d --name web3 --network mynet --network-alias web nginx

# 라운드 로빈 DNS
# "web"을 조회하면 세 인스턴스의 IP가 번갈아 반환됨
docker exec client nslookup web

5.5 Docker DNS

내장 DNS 서버

Docker는 127.0.0.11에 내장 DNS 서버를 제공한다.

1
2
3
4
5
6
7
8
9
10
# 컨테이너 내 DNS 설정 확인
docker exec web cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0

# Docker DNS 서버가 다음을 해석:
# 1. 컨테이너 이름 → IP
# 2. 네트워크 별칭 → IP
# 3. 서비스 이름 → 여러 IP (라운드 로빈)
# 4. 외부 도메인 → 호스트 DNS로 전달

커스텀 DNS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 컨테이너별 DNS 서버 지정
docker run --dns 8.8.8.8 nginx

# 여러 DNS 서버
docker run --dns 8.8.8.8 --dns 8.8.4.4 nginx

# DNS 검색 도메인
docker run --dns-search example.com nginx
# ping server → server.example.com 시도

# DNS 옵션
docker run --dns-option ndots:2 nginx

# /etc/hosts 추가
docker run --add-host myhost:192.168.1.100 nginx

전역 DNS 설정

1
2
3
4
5
6
7
8
9
# /etc/docker/daemon.json
{
  "dns": ["8.8.8.8", "8.8.4.4"],
  "dns-search": ["example.com"],
  "dns-opts": ["ndots:2"]
}

# Docker 재시작
sudo systemctl restart docker

5.6 네트워크 트러블슈팅

연결 문제 진단

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 컨테이너 IP 확인
docker inspect <container> --format '{{.NetworkSettings.IPAddress}}'

# 네트워크 인터페이스 확인
docker exec <container> ip addr

# 라우팅 테이블
docker exec <container> ip route

# 연결 테스트
docker exec <container> ping google.com
docker exec <container> ping 8.8.8.8

# 포트 열림 확인
docker exec <container> nc -zv db 5432

# DNS 확인
docker exec <container> nslookup google.com
docker exec <container> dig google.com

네트워크 디버깅 컨테이너

1
2
3
4
5
6
7
8
9
10
# nicolaka/netshoot 사용 (추천)
docker run -it --rm --network container:<container> nicolaka/netshoot

# 사용 가능한 도구:
# - tcpdump: 패킷 캡처
# - curl, wget: HTTP 테스트
# - nmap: 포트 스캔
# - iperf: 대역폭 테스트
# - traceroute: 경로 추적
# - mtr: 네트워크 진단

패킷 캡처

1
2
3
4
5
6
7
8
9
10
11
12
13
# 컨테이너 내부에서
docker exec <container> tcpdump -i eth0 -w /tmp/capture.pcap

# 호스트의 veth에서
# veth 인터페이스 찾기
docker exec <container> cat /sys/class/net/eth0/iflink
ip link | grep <번호>

# 캡처
sudo tcpdump -i <veth> -w capture.pcap

# Wireshark로 분석
wireshark capture.pcap

일반적인 문제와 해결

문제: 컨테이너가 외부와 통신 안 됨

1
2
3
4
5
6
7
8
9
10
11
12
# 1. IP 포워딩 확인
cat /proc/sys/net/ipv4/ip_forward
# 1이어야 함

# 2. NAT 규칙 확인
sudo iptables -t nat -L POSTROUTING -n

# 3. 방화벽 확인
sudo iptables -L -n

# 4. DNS 확인
docker exec <container> cat /etc/resolv.conf

문제: 컨테이너 간 통신 안 됨

1
2
3
4
5
6
7
8
9
# 1. 같은 네트워크인지 확인
docker network inspect <network>

# 2. ICC(Inter-Container Communication) 활성화 확인
docker network inspect <network> | grep "com.docker.network.bridge.enable_icc"
# true여야 함

# 3. 방화벽 규칙 확인
sudo iptables -L DOCKER-ISOLATION -n

문제: DNS 해석 실패

1
2
3
4
5
6
7
8
9
10
11
12
# 1. Docker DNS 작동 확인
docker exec <container> nslookup google.com 127.0.0.11

# 2. 호스트 DNS 확인
cat /etc/resolv.conf

# 3. 컨테이너 DNS 설정 확인
docker exec <container> cat /etc/resolv.conf

# 4. 네트워크 재생성
docker network rm <network>
docker network create <network>

6. Docker 볼륨과 스토리지

6.1 스토리지 옵션 비교

Docker는 데이터 영속성을 위해 세 가지 마운트 타입을 제공한다.

볼륨 (Volumes) - 권장

Docker가 관리하는 영구 저장소:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 볼륨 생성
docker volume create myvolume

# 볼륨 위치
ls /var/lib/docker/volumes/myvolume/_data

# 장점:
# - Docker가 완전히 관리
# - 백업/복원 용이
# - 플랫폼 독립적
# - 여러 컨테이너가 안전하게 공유 가능
# - 볼륨 드라이버로 원격 스토리지 지원

# 단점:
# - 호스트에서 직접 접근하기 어려움

Bind Mount

호스트 디렉토리를 직접 마운트:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Bind mount
docker run -v /host/path:/container/path nginx

# 장점:
# - 호스트 파일시스템에 직접 접근
# - 개발 시 편리 (코드 변경 즉시 반영)
# - 호스트의 모든 경로 사용 가능

# 단점:
# - 호스트 경로 의존성
# - 플랫폼 종속적
# - 보안 위험 (호스트 파일시스템 노출)
# - Docker가 관리하지 않음

tmpfs Mount

메모리에만 존재하는 임시 저장소:

1
2
3
4
5
6
7
8
9
10
11
12
# tmpfs mount
docker run --tmpfs /app/cache nginx

# 장점:
# - 매우 빠름 (메모리 속도)
# - 민감한 정보 임시 저장 (컨테이너 종료 시 자동 삭제)
# - 디스크 I/O 없음

# 단점:
# - 메모리 사용
# - 영구적이지 않음
# - Linux에서만 사용 가능

비교표

특성VolumeBind Mounttmpfs
Docker 관리OXO
백업 용이성OXX
성능좋음좋음매우 좋음
플랫폼 독립OXX
영구성OOX
개발 편의성중간높음낮음

6.2 볼륨 관리

볼륨 생성 및 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 볼륨 생성
docker volume create myvolume

# 드라이버 지정
docker volume create --driver local myvolume

# 옵션 지정
docker volume create \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/dir \
  nfs-volume

# 레이블
docker volume create --label env=prod myvolume

# 볼륨 목록
docker volume ls

# 볼륨 상세 정보
docker volume inspect myvolume

# 볼륨 사용 중인 컨테이너 확인
docker ps --filter volume=myvolume

볼륨 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 명명된 볼륨 마운트
docker run -v myvolume:/data nginx

# 익명 볼륨 (자동 생성)
docker run -v /data nginx

# 읽기 전용
docker run -v myvolume:/data:ro nginx

# --mount 사용 (더 명시적)
docker run \
  --mount type=volume,source=myvolume,target=/data \
  nginx

# 읽기 전용 (--mount)
docker run \
  --mount type=volume,source=myvolume,target=/data,readonly \
  nginx

# 볼륨 드라이버 옵션
docker run \
  --mount type=volume,source=myvolume,target=/data,volume-driver=local \
  nginx

볼륨 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 볼륨 삭제
docker volume rm myvolume

# 여러 볼륨 삭제
docker volume rm vol1 vol2 vol3

# 사용하지 않는 볼륨 정리
docker volume prune

# 확인 없이 정리
docker volume prune -f

# 특정 레이블 필터
docker volume prune --filter "label=env=test"

6.3 Bind Mount 상세

Bind Mount 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 기본 bind mount
docker run -v /host/path:/container/path nginx

# 절대 경로 필요
docker run -v $(pwd)/app:/app nginx

# 읽기 전용
docker run -v /host/path:/container/path:ro nginx

# --mount 사용 (권장)
docker run \
  --mount type=bind,source=/host/path,target=/container/path \
  nginx

# 읽기 전용 (--mount)
docker run \
  --mount type=bind,source=/host/path,target=/container/path,readonly \
  nginx

개발 환경에서 활용

1
2
3
4
5
6
7
8
9
10
# 소스 코드 동기화
docker run -d \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/package.json:/app/package.json \
  -p 3000:3000 \
  node:18 \
  npm run dev

# 코드 변경 시 자동 반영 (nodemon, webpack-dev-server 등)
# 컨테이너 재시작 불필요

권한 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 문제: 컨테이너 내부 사용자와 호스트 사용자 UID 불일치
# 호스트: UID 1000
# 컨테이너: UID 33 (www-data)

# 해결 1: 컨테이너 사용자를 호스트 UID와 맞춤
docker run --user $(id -u):$(id -g) myapp

# 해결 2: Dockerfile에서 사용자 생성
RUN addgroup -g 1000 appgroup && \
    adduser -u 1000 -G appgroup -D appuser
USER appuser

# 해결 3: chown으로 소유권 변경 (엔트리포인트 스크립트)
chown -R appuser:appgroup /data

보안 주의사항

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 위험: 호스트 루트 마운트
docker run -v /:/host nginx  # 위험

# 위험: 민감한 디렉토리 마운트
docker run -v /etc:/host-etc nginx  # 위험

# 위험: Docker 소켓 마운트
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp
# Docker API 완전 접근 = 호스트 제어 가능

# 안전한 사용:
# - 필요한 최소 경로만 마운트
# - 가능하면 읽기 전용
# - 민감한 디렉토리 피하기

6.4 tmpfs Mount

tmpfs 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# tmpfs mount
docker run --tmpfs /tmp nginx

# 크기 제한
docker run --tmpfs /tmp:size=100M nginx

# --mount 사용
docker run \
  --mount type=tmpfs,target=/tmp,tmpfs-size=100M \
  nginx

# 여러 tmpfs
docker run \
  --tmpfs /tmp \
  --tmpfs /var/cache \
  nginx

사용 사례

1
2
3
4
5
6
7
8
9
10
11
# 1. 캐시 디렉토리
docker run --tmpfs /app/cache myapp

# 2. 임시 파일 처리
docker run --tmpfs /tmp myapp

# 3. 민감한 정보 (비밀번호, 토큰 등)
docker run --tmpfs /secrets myapp

# 4. 세션 데이터
docker run --tmpfs /var/lib/sessions myapp

6.5 볼륨 드라이버

로컬 드라이버 옵션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# NFS 볼륨
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/dir \
  nfs-volume

# CIFS/SMB 볼륨
docker volume create \
  --driver local \
  --opt type=cifs \
  --opt o=username=user,password=pass,addr=192.168.1.100 \
  --opt device=//192.168.1.100/share \
  cifs-volume

# tmpfs 볼륨
docker volume create \
  --driver local \
  --opt type=tmpfs \
  --opt device=tmpfs \
  --opt o=size=100m \
  tmpfs-volume

서드파티 볼륨 드라이버

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# REX-Ray (클라우드 스토리지)
docker plugin install rexray/ebs

docker volume create \
  --driver rexray/ebs \
  --opt size=10 \
  ebs-volume

# Convoy (스냅샷 지원)
docker volume create \
  --driver convoy \
  convoy-volume

# Flocker (컨테이너 이동 시 데이터 함께 이동)
docker volume create \
  --driver flocker \
  flocker-volume

6.6 데이터 백업 및 복원

볼륨 백업

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 방법 1: tar로 압축
docker run --rm \
  -v myvolume:/data \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/myvolume-backup.tar.gz /data

# 방법 2: 컨테이너를 통한 백업
docker run --rm \
  -v myvolume:/source:ro \
  -v $(pwd):/backup \
  alpine \
  sh -c "cd /source && tar czf /backup/backup.tar.gz ."

# 방법 3: 직접 복사
sudo cp -r /var/lib/docker/volumes/myvolume/_data ./backup

볼륨 복원

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# tar에서 복원
docker run --rm \
  -v myvolume:/data \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/myvolume-backup.tar.gz -C /

# 새 볼륨에 복원
docker volume create myvolume-restored

docker run --rm \
  -v myvolume-restored:/data \
  -v $(pwd):/backup \
  alpine \
  tar xzf /backup/backup.tar.gz -C /data

데이터베이스 백업

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# PostgreSQL 백업
docker exec postgres pg_dump -U postgres mydb > backup.sql

# 볼륨을 통한 백업
docker run --rm \
  -v postgres-data:/var/lib/postgresql/data \
  -v $(pwd):/backup \
  postgres \
  pg_dump -U postgres -f /backup/mydb.sql mydb

# MySQL 백업
docker exec mysql mysqldump -u root -p mydb > backup.sql

# MongoDB 백업
docker exec mongo mongodump --out /backup

Part 3: Docker 고급

7. Docker Compose

7.1 Docker Compose란?

Docker Compose의 목적

Docker Compose는 여러 컨테이너 애플리케이션을 정의하고 실행하는 도구이다.

주요 기능

  • 멀티 컨테이너 애플리케이션 정의 (YAML 파일)
  • 한 번의 명령으로 전체 스택 시작/중지
  • 환경별 설정 관리 (개발, 테스트, 프로덕션)
  • 서비스 스케일링
  • 의존성 관리

설치

1
2
3
4
5
6
7
8
9
# Docker Desktop은 Compose 포함

# Linux에서 별도 설치
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# 버전 확인
docker-compose --version

7.2 docker-compose.yml 기본

기본 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: '3.8'

services:
  web:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    networks:
      - frontend

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  db-data:

버전

1
2
3
4
5
6
7
# Compose 파일 버전 (3.8 권장)
version: '3.8'

# 버전별 주요 차이:
# - 3.x: Docker Swarm 지원
# - 2.x: 로컬 개발 중심
# - 1.x: 레거시 (사용 안 함)

7.3 서비스 정의

이미지 지정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  # 이미지 사용
  web:
    image: nginx:latest

  # Dockerfile 빌드
  app:
    build: .

  # Dockerfile 경로 지정
  app:
    build:
      context: ./app
      dockerfile: Dockerfile.dev

  # 빌드 인수
  app:
    build:
      context: .
      args:
        - VERSION=1.0
        - BUILD_DATE=2024-01-01

포트 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
services:
  web:
    ports:
      # 호스트:컨테이너
      - "8080:80"

      # IP 지정
      - "127.0.0.1:8080:80"

      # 여러 포트
      - "8080:80"
      - "4443:443"

      # UDP
      - "53:53/udp"

      # 포트 범위
      - "8000-8010:8000-8010"

환경 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
services:
  web:
    environment:
      # 직접 지정
      NODE_ENV: production
      PORT: 3000

        # 배열 형식
        - NODE_ENV=production
        - PORT=3000

    # 파일에서 로드
    env_file:
      - .env
      - .env.prod

  db:
    environment:
      # 호스트 환경 변수 참조
      DB_PASSWORD: ${DB_PASSWORD}

볼륨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
services:
  web:
    volumes:
      # Named volume
      - data:/app/data

      # Bind mount
      - ./app:/app
      - ./config:/etc/nginx

      # 읽기 전용
      - ./config:/etc/nginx:ro

      # tmpfs
      - type: tmpfs
        target: /tmp

volumes:
  data:
    driver: local

네트워크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      backend:
        # IP 주소 지정
        ipv4_address: 172.20.0.10

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

의존성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
  web:
    depends_on:
      - db
      - redis

  db:
    image: postgres

  redis:
    image: redis

# depends_on은 시작 순서만 제어
# 서비스가 준비될 때까지 기다리지 않음
# 준비 여부는 healthcheck나 wait-for-it 스크립트 사용

헬스체크

1
2
3
4
5
6
7
8
services:
  web:
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost" ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

재시작 정책

1
2
3
4
5
6
7
services:
  web:
    restart: always
    # no: 재시작 안 함 (기본값)
    # always: 항상 재시작
    # on-failure: 실패 시에만
    # unless-stopped: 명시적 중지 전까지

7.4 Docker Compose 명령어

기본 명령

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 서비스 시작 (빌드 포함)
docker-compose up

# 백그라운드 실행
docker-compose up -d

# 빌드만
docker-compose build

# 강제 재빌드
docker-compose up --build

# 특정 서비스만
docker-compose up web

# 스케일링
docker-compose up -d --scale web=3

서비스 관리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 서비스 중지
docker-compose stop

# 특정 서비스 중지
docker-compose stop web

# 서비스 시작 (이미 생성된 컨테이너)
docker-compose start

# 서비스 재시작
docker-compose restart

# 서비스 일시 정지
docker-compose pause

# 재개
docker-compose unpause

정리

1
2
3
4
5
6
7
8
9
10
11
# 컨테이너 중지 및 삭제
docker-compose down

# 볼륨도 삭제
docker-compose down -v

# 이미지도 삭제
docker-compose down --rmi all

# 고아 컨테이너 제거
docker-compose down --remove-orphans

로그 및 모니터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 로그 확인
docker-compose logs

# 실시간 로그
docker-compose logs -f

# 특정 서비스
docker-compose logs -f web

# 마지막 N줄
docker-compose logs --tail=100

# 타임스탬프 포함
docker-compose logs -t

# 실행 중인 서비스 확인
docker-compose ps

# 프로세스 확인
docker-compose top

명령 실행

1
2
3
4
5
6
7
8
# 서비스에서 명령 실행
docker-compose exec web bash

# 새 컨테이너에서 일회성 명령
docker-compose run web python manage.py migrate

# 컨테이너 생성 없이 실행
docker-compose run --rm web python script.py

설정 검증

1
2
3
4
5
6
7
8
# 설정 파일 검증
docker-compose config

# 환경 변수 치환 후 출력
docker-compose config

# 특정 파일 지정
docker-compose -f docker-compose.prod.yml config

7.5 실전 예시

웹 애플리케이션 스택

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
version: '3.8'

services:
  # Nginx 웹 서버
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static-files:/static
    depends_on:
      - web
    networks:
      - frontend

  # Django 웹 애플리케이션
  web:
    build: ./app
    command: gunicorn myapp.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./app:/app
      - static-files:/app/static
    environment:
      - DEBUG=False
      - DATABASE_URL=postgresql://postgres:secret@db:5432/mydb
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    networks:
      - frontend
      - backend

  # PostgreSQL 데이터베이스
  db:
    image: postgres:15
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=secret
    networks:
      - backend

  # Redis 캐시
  redis:
    image: redis:alpine
    volumes:
      - redis-data:/data
    networks:
      - backend

  # Celery Worker
  celery:
    build: ./app
    command: celery -A myapp worker -l info
    volumes:
      - ./app:/app
    environment:
      - DATABASE_URL=postgresql://postgres:secret@db:5432/mydb
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  postgres-data:
  redis-data:
  static-files:

개발 환경 vs 프로덕션

1
2
3
4
5
6
7
8
9
10
11
# docker-compose.yml (베이스)
version: '3.8'

services:
  web:
    build: .
    environment:
      - DATABASE_URL=postgresql://db/mydb

  db:
    image: postgres:15
1
2
3
4
5
6
7
8
9
10
11
# docker-compose.override.yml (개발 - 자동 로드)
version: '3.8'

services:
  web:
    volumes:
      - ./app:/app
    environment:
      - DEBUG=True
    ports:
      - "8000:8000"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# docker-compose.prod.yml (프로덕션)
version: '3.8'

services:
  web:
    image: myregistry.com/myapp:latest
    environment:
      - DEBUG=False
    restart: always

  db:
    volumes:
      - db-data:/var/lib/postgresql/data
    restart: always

volumes:
  db-data:
1
2
3
4
5
# 개발
docker-compose up  # .yml + .override.yml 자동 병합

# 프로덕션
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

8. Docker 레지스트리

8.1 Docker Hub

Docker Hub 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 로그인
docker login
# Username:
# Password:

# 이미지 태그
docker tag myapp:latest username/myapp:latest
docker tag myapp:latest username/myapp:v1.0

# 푸시
docker push username/myapp:latest
docker push username/myapp:v1.0

# 풀
docker pull username/myapp:latest

# 로그아웃
docker logout

자동 빌드 (Automated Builds)

Docker Hub는 GitHub/Bitbucket과 연동하여 자동 빌드를 지원한다:

  1. Docker Hub에서 저장소 생성
  2. GitHub 저장소 연결
  3. 빌드 규칙 설정
  4. Git 푸시 시 자동 빌드

프라이빗 저장소

1
2
3
4
5
# 프라이빗 저장소 풀 (로그인 필요)
docker pull username/private-repo:latest

# 무료 플랜: 1개 프라이빗 저장소
# 유료 플랜: 무제한

8.2 프라이빗 레지스트리 구축

Registry 컨테이너 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
# 기본 레지스트리
docker run -d \
  -p 5000:5000 \
  --name registry \
  -v registry-data:/var/lib/registry \
  registry:2

# 이미지 푸시
docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest

# 이미지 풀
docker pull localhost:5000/myapp:latest

TLS/SSL 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 인증서 준비
mkdir -p certs
# certs/domain.crt, certs/domain.key

# TLS로 레지스트리 실행
docker run -d \
  -p 443:443 \
  --name registry \
  -v registry-data:/var/lib/registry \
  -v $(pwd)/certs:/certs \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  registry:2

Basic 인증

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# htpasswd 파일 생성
mkdir auth
docker run --rm --entrypoint htpasswd \
  httpd:2 -Bbn username password > auth/htpasswd

# 인증 활성화
docker run -d \
  -p 5000:5000 \
  --name registry \
  -v registry-data:/var/lib/registry \
  -v $(pwd)/auth:/auth \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

# 로그인
docker login localhost:5000

8.3 Harbor

Harbor란?

Harbor는 엔터프라이즈급 컨테이너 레지스트리이다.

주요 기능

  • 웹 UI
  • RBAC (역할 기반 접근 제어)
  • 이미지 스캐닝 (Trivy, Clair)
  • 이미지 서명
  • 복제 (다중 레지스트리)
  • Helm 차트 저장소
  • 감사 로그

설치 (Docker Compose)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Harbor 다운로드
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz
tar xzvf harbor-offline-installer-v2.9.0.tgz
cd harbor

# 설정
cp harbor.yml.tmpl harbor.yml
vi harbor.yml
# hostname 수정
# 인증서 경로 설정 (선택)

# 설치
sudo ./install.sh

# 접속
# https://your-harbor-domain
# admin / Harbor12345 (기본)

Harbor 사용

1
2
3
4
5
6
7
8
9
10
11
12
# 로그인
docker login harbor.example.com

# 프로젝트 생성 (UI에서)
# library, myapp 등

# 이미지 푸시
docker tag myapp:latest harbor.example.com/library/myapp:latest
docker push harbor.example.com/library/myapp:latest

# 이미지 풀
docker pull harbor.example.com/library/myapp:latest

9. Docker 보안

9.1 이미지 보안

취약점 스캔

1
2
3
4
5
6
7
8
# Trivy로 이미지 스캔
trivy image myapp:latest

# Docker Scout (Docker Desktop)
docker scout cves myapp:latest

# 취약점 레포트 생성
trivy image --format json myapp:latest > report.json

신뢰할 수 있는 이미지 사용

  • 공식 이미지 사용 (library/)
  • 검증된 게시자 확인
  • 최신 버전 유지
  • 정기적 업데이트

9.2 컨테이너 보안

최소 권한 원칙

1
2
3
4
5
6
7
8
# 나쁜 예: root로 실행
FROM nginx

# 좋은 예: 일반 사용자
FROM nginx
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup
USER appuser

Read-only 파일시스템

1
2
3
4
5
6
7
8
# 읽기 전용 루트 파일시스템
docker run --read-only nginx

# 쓰기 가능한 부분만 tmpfs 마운트
docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  nginx

환경 변수로 민감 정보 전달

1
2
3
4
5
6
7
8
# 좋은 예
docker run -e API_KEY=${API_KEY} myapp

# 또는 Docker Secrets 사용 (Swarm)
docker secret create api_key api_key.txt
docker service create \
  --secret api_key \
  myapp

9.3 런타임 보안

권한 드롭 (CAP DROP/ADD)

1
2
3
4
5
6
7
8
9
10
# 불필요한 권한 제거
docker run --cap-drop=ALL nginx

# 필요한 권한만 추가
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

# 일반적인 권한:
# - NET_BIND_SERVICE: 1024 미만 포트 바인딩
# - SYS_CHROOT: chroot 사용
# - SETUID/SETGID: UID/GID 변경

Seccomp 프로파일

1
2
3
4
5
# 기본 seccomp 프로파일 사용
docker run --security-opt seccomp=unconfined nginx

# 커스텀 seccomp 프로파일
docker run --security-opt seccomp=/path/to/profile.json nginx

AppArmor 프로필

1
2
3
4
5
# AppArmor 프로필 사용
docker run --security-opt apparmor=docker-default nginx

# 커스텀 프로필
docker run --security-opt apparmor=/path/to/profile nginx

9.4 네트워크 보안

Internal 네트워크

1
2
3
4
5
# 외부 통신 차단
docker network create --internal internal-net

# 내부 통신만 가능
docker run --network internal-net nginx

네트워크 정책 (Kubernetes)

1
2
3
4
5
6
7
8
9
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
spec:
  podSelector: { }
  policyTypes:
    - Ingress
    - Egress

10. Docker 모니터링 및 로깅

10.1 컨테이너 로그

docker logs 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 로그 확인
docker logs <container>

# 실시간 로그 (tail -f)
docker logs -f <container>

# 마지막 N줄
docker logs --tail 100 <container>

# 타임스탐프 포함
docker logs -t <container>

# 특정 시간 이후
docker logs --since 1h <container>
docker logs --since 2024-01-01T10:00:00 <container>

로그 드라이버

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# json-file (기본)
docker run --log-driver json-file myapp

# syslog
docker run --log-driver syslog \
  --log-opt syslog-address=udp://127.0.0.1:514 \
  myapp

# journald
docker run --log-driver journald myapp

# Splunk
docker run --log-driver splunk \
  --log-opt splunk-token=<token> \
  --log-opt splunk-url=https://splunk.example.com:8088 \
  myapp

# AWS CloudWatch
docker run --log-driver awslogs \
  --log-opt awslogs-group=/docker/myapp \
  --log-opt awslogs-region=us-east-1 \
  myapp

로그 로테이션

1
2
3
4
5
6
# json-file 드라이버 로그 로테이션
docker run \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp

10.2 컨테이너 모니터링

docker stats

1
2
3
4
5
6
7
8
9
10
11
# 실시간 통계
docker stats

# 특정 컨테이너
docker stats myapp

# 스트리밍 없이 한 번만
docker stats --no-stream

# JSON 형식
docker stats --format='table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}'

cAdvisor (Google)

1
2
3
4
5
6
7
8
9
10
11
# cAdvisor 컨테이너 실행
docker run -d \
  --name=cadvisor \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --publish=8080:8080 \
  gcr.io/cadvisor/cadvisor:latest

# http://localhost:8080에서 접근

Prometheus + Grafana

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# docker-compose.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus

10.3 헬스 체크

Dockerfile에서 헬스체크

1
2
3
4
5
6
7
8
9
10
# 기본 헬스체크
HEALTHCHECK CMD curl -f http://localhost/ || exit 1

# 옵션 포함
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
  CMD curl -f http://localhost/ || exit 1

# 스크립트 사용
COPY healthcheck.sh /
HEALTHCHECK CMD /healthcheck.sh

실행 시 헬스체크

1
2
3
4
5
6
7
8
# 헬스체크 옵션
docker run \
  --health-cmd='curl -f http://localhost/' \
  --health-interval=30s \
  --health-timeout=10s \
  --health-retries=3 \
  --health-start-period=40s \
  myapp

헬스 상태 확인

1
2
3
4
5
# 헬스체크 상태 조회
docker inspect --format='{{.State.Health.Status}}' <container>

# 상세 정보
docker inspect --format='{{json .State.Health}}' <container> | jq

11. Docker 문제 해결

11.1 자주 발생하는 문제

컨테이너가 실행되지 않음

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 로그 확인
docker logs <container>

# 2. 컨테이너 상태 확인
docker ps -a
docker inspect <container>

# 3. 리소스 부족 확인
docker stats

# 4. 포트 충돌 확인
docker port <container>

포트 이미 사용 중

1
2
3
4
5
6
7
8
# Linux/Mac
lsof -i :8080

# Windows
netstat -ano | findstr :8080

# 포트를 사용 중인 프로세스 종료
kill -9 <pid>

디스크 공간 부족

1
2
3
4
5
6
7
# 디스크 사용량 확인
docker system df

# 정리
docker system prune
docker system prune -a
docker volume prune

11.2 디버깅 기법

컨테이너 접속

1
2
3
4
5
6
7
# 실행 중인 컨테이너
docker exec -it <container> bash

# 중지된 컨테이너 디버깅
docker run -it --rm \
  --volumes-from <stopped_container> \
  ubuntu bash

로그 분석

1
2
3
4
5
6
7
8
# 에러 로그만 필터링
docker logs <container> 2>&1 | grep ERROR

# 특정 시간대 로그
docker logs --since 1h <container>

# 로그 내보내기
docker logs <container> > container.log 2>&1

네트워크 디버깅

1
2
3
4
5
6
7
# nicolaka/netshoot 사용
docker run -it --rm \
  --network container:<container> \
  nicolaka/netshoot

# 패킷 캡처
docker exec <container> tcpdump -i eth0 -w /tmp/capture.pcap

11.3 성능 최적화

이미지 크기 최소화

1
2
3
4
5
6
7
8
9
# Multi-stage 빌드 사용
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

FROM node:18-alpine
COPY --from=builder /app/dist /app/dist
CMD ["node", "dist/index.js"]

레이어 캐싱 활용

1
2
3
4
# 변화 적은 것부터 복사
COPY package*.json ./
RUN npm install
COPY . .

리소스 제한

1
2
3
4
5
# 메모리 제한으로 안정성 향상
docker run -m 512m --memory-swap 1g myapp

# CPU 제한으로 이웃 노이즈 방지
docker run --cpus=1.5 --cpuset-cpus=0,1 myapp

기술 면접 대비

Q1: Docker와 VM의 차이점은?

원문 (Docker 공식 문서 - What is a Container?): Containers are an abstraction at the app layer that packages code and dependencies together. Multiple containers can run on the same machine and share the OS kernel with other containers, each running as isolated processes in user space.

번역: 컨테이너는 코드와 의존성을 함께 패키징하는 앱 계층의 추상화이다. 여러 컨테이너가 동일한 머신에서 실행되며 OS 커널을 다른 컨테이너와 공유하고, 각각 사용자 공간에서 격리된 프로세스로 실행된다.

A:

항목VMDocker Container
가상화 레벨하드웨어OS (커널 공유)
부팅 시간분 단위초 단위
메모리 사용GB 단위MB 단위
이미지 크기GB 단위MB~GB
격리 수준완전 격리프로세스 격리
하이퍼바이저필요불필요

Q2: Docker 이미지 레이어 구조의 장점은?

원문 (Docker 공식 문서 - Image Layers): A Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only.

번역: Docker 이미지는 일련의 레이어로 구성된다. 각 레이어는 이미지의 Dockerfile에 있는 명령어를 나타낸다. 마지막 레이어를 제외한 모든 레이어는 읽기 전용이다.

A:

  1. 디스크 공간 절약: 공통 베이스 이미지 레이어 공유
  2. 빌드 속도 향상: 변경된 레이어만 재빌드 (캐싱)
  3. 네트워크 효율: 변경된 레이어만 전송
  4. Copy-on-Write: 컨테이너 쓰기 레이어만 추가

Q3: docker run 명령의 전체 실행 흐름을 설명하라

A:

  1. Docker Client → REST API 요청을 dockerd에 전송
  2. dockerd → 이미지 확인 (없으면 pull), 네트워크/볼륨 준비 → containerd에 gRPC 요청
  3. containerd → 이미지 스냅샷 준비, containerd-shim 생성
  4. containerd-shim → runc 실행 지시
  5. runc → Namespace/Cgroup 생성, OverlayFS 마운트, 컨테이너 프로세스 실행
  6. runc 종료 → shim이 컨테이너의 부모 프로세스로 유지

Q4: containerd-shim의 역할은?

원문 (containerd 문서 - containerd-shim): The shim allows for daemonless containers. It basically sits as the parent of the container’s process to facilitate a few things including keeping the STDIO and other fds open.

번역: shim은 데몬리스 컨테이너를 가능하게 한다. 기본적으로 컨테이너 프로세스의 부모로서 STDIO와 다른 파일 디스크립터를 열어두는 것을 포함한 몇 가지 기능을 수행한다.

A:

  • Daemonless 컨테이너: dockerd/containerd 재시작 시에도 컨테이너 유지
  • STDIO 스트림 관리 (docker logs)
  • 종료 시그널 전달 (docker stop → SIGTERM)
  • 종료 상태(exit code) 보고

Q5: Dockerfile에서 COPY와 ADD의 차이는?

A:

기능COPYADD
로컬 파일 복사OO
URL 다운로드XO
tar 자동 압축해제XO (로컬 tar만)
권장O (명시적)X (예측 어려움)

Best Practice: 명시적인 COPY 사용 권장, URL은 RUN curl/wget 사용

Q6: Multi-stage Build의 장점은?

원문 (Docker 공식 문서 - Multi-stage builds): With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build.

번역: 멀티 스테이지 빌드에서는 Dockerfile에서 여러 FROM 문을 사용한다. 각 FROM 명령어는 다른 베이스를 사용할 수 있으며, 각각이 빌드의 새로운 단계를 시작한다.

A:

  1. 이미지 크기 최소화: 빌드 도구 없이 실행 파일만 포함
  2. 보안 강화: 빌드 시크릿이 최종 이미지에 포함되지 않음
  3. 단일 Dockerfile: 빌드와 런타임 환경을 하나의 파일로 관리
1
2
3
4
5
6
7
# 예시
FROM golang:1.21 AS builder
RUN go build -o app

FROM alpine:3.18
COPY --from=builder /app/app /app
CMD ["/app"]

Q7: Docker 네트워크 모드의 차이점은?

A:

모드특징사용 사례
bridge기본, NAT 사용단일 호스트 컨테이너 통신
host호스트 네트워크 직접 사용성능이 중요한 경우
none네트워크 없음보안이 중요한 배치 작업
overlay다중 호스트 네트워크Docker Swarm, 클러스터
macvlan물리 네트워크에 직접 연결레거시 앱, VLAN 필요 시

Q8: Docker 보안 Best Practice는?

A:

  1. 비root 사용자: USER 지시어로 비root 실행
  2. 읽기 전용 rootfs: --read-only 플래그
  3. Capabilities 제한: --cap-drop=ALL --cap-add=필요한것만
  4. 리소스 제한: --memory, --cpus 설정
  5. 신뢰할 수 있는 이미지: 공식 이미지, 이미지 스캔
  6. 시크릿 관리: 환경변수 대신 Docker secrets 사용
  7. 네트워크 격리: 필요한 컨테이너만 통신

참고 자료

공식 문서

런타임 관련

추가 학습

다음 단계

This post is licensed under CC BY 4.0 by the author.