| 단계 | 가이드 | 주제 | 상태 |
|---|---|---|---|
| 1단계 | InfraDevGuide0001 | 인프라 개념 완전 정복 (서버·네트워크·스토리지) | ✅ 완료 |
| 2단계 | InfraDevGuide0002 | Linux 기초 완전 정복 | ✅ 완료 |
| 3단계 | InfraDevGuide0003 | 네트워크 기초 완전 정복 | ✅ 완료 |
| 4단계 | InfraDevGuide0004 | 클라우드 AWS 완전 정복 | ✅ 완료 |
| 5단계 | 👉 InfraDevGuide0005 | 🐳 Docker & 컨테이너 완전 정복 ← 현재 학습 중 | 🔥 학습 중 |
| 6단계 | InfraDevGuide0006 | CI/CD & 자동화 완전 정복 | ⏱ 예정 |
| 7단계 | InfraDevGuide0007 | 실전 프로젝트 & 취업 전략 | ⏱ 예정 |
| Ch | 챕터명 | 핵심 내용 | 난이도 |
|---|---|---|---|
| 01 | Docker란? — 컨테이너 혁명 | VM vs 컨테이너, Docker 설치, 핵심 개념 | ★☆☆☆☆ |
| 02 | Docker 이미지 완전 정복 | Dockerfile, 레이어, 빌드 최적화 | ★★☆☆☆ |
| 03 | 컨테이너 실행 & 관리 | run/stop/rm, 로그, exec, 포트 매핑 | ★★☆☆☆ |
| 04 | 볼륨(Volume) 데이터 영속성 | Named Volume, Bind Mount, DB 데이터 보존 | ★★☆☆☆ |
| 05 | 네트워크 완전 정복 | bridge/host/overlay, 컨테이너 간 통신 | ★★★☆☆ |
| 06 | Docker Compose 완전 정복 | 멀티 컨테이너, 환경변수, 의존성 | ★★★☆☆ |
| 07 | Dockerfile 고급 & 이미지 최적화 | Multi-stage, .dockerignore, 경량화 | ★★★☆☆ |
| 08 | Docker 보안 완전 정복 | 비root 실행, Secrets, 취약점 스캔 | ★★★★☆ |
| 09 | 레지스트리 & 이미지 관리 | Docker Hub, ECR, 이미지 태깅 전략 | ★★★☆☆ |
| 10 | 모니터링 & 트러블슈팅 | stats, 로그 수집, cAdvisor, 장애 대응 | ★★★★☆ |
| 11 | 쿠버네티스(K8s) 입문 | Docker→K8s, Pod/Deployment/Service | ★★★★☆ |
| 12 | 현업 면접 Q&A TOP 12 | Docker 면접 핵심 질문 & 완벽 답변 | ★★★★☆ |
🏠 쉬운 비유: Docker는 도시락통입니다. 밥+반찬+양념을 다 담아두면 집에서 먹든 회사에서 먹든 동일한 맛이 납니다. "내 PC에서는 되는데 서버에서 안 돼요" 문제가 사라집니다!
| 비교 항목 | 🖥️ 가상머신 | 🐳 Docker |
|---|---|---|
| OS 포함 여부 | 전체 OS 포함 (수GB) | Host OS 공유 (수MB) |
| 시작 속도 | 수 분 | 수 초 이내 |
| 이식성 | 낮음 | 매우 높음 (어디서나 동일) |
| 리소스 효율 | 낮음 | 높음 (경량) |
# Docker 공식 설치 스크립트 (가장 빠른 방법)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# 현재 사용자를 docker 그룹에 추가 (sudo 없이 사용)
sudo usermod -aG docker $USER
newgrp docker
# 설치 확인
docker --version # Docker version 26.x.x
docker compose version # Docker Compose version v2.x.x
docker run hello-world # 첫 컨테이너 실행 테스트
컨테이너의 설계도. 읽기 전용 레이어 스택. docker build로 생성. Docker Hub에서 공유.
이미지의 실행 인스턴스. 격리된 프로세스. docker run으로 생성. 여러 개 동시 실행 가능.
이미지 저장·배포 저장소. Docker Hub, AWS ECR, GitHub Container Registry.
# FROM: 베이스 이미지 지정 (항상 첫 번째!)
FROM node:20-alpine
# LABEL: 이미지 메타데이터 (관리 목적)
LABEL maintainer="devops@company.com" version="1.0.0"
# ENV: 환경변수 설정 (컨테이너 실행 중에도 유지)
ENV NODE_ENV=production PORT=3000 APP_DIR=/app
# WORKDIR: 작업 디렉토리 설정
WORKDIR $APP_DIR
# COPY: 캐시 최적화 - package.json 먼저 복사!
COPY package*.json ./
# RUN: 이미지 빌드 시 명령 실행
RUN npm ci --only=production && npm cache clean --force
# 소스코드 복사 (의존성 설치 후에!)
COPY . .
# EXPOSE: 포트 문서화
EXPOSE 3000
# HEALTHCHECK: 컨테이너 상태 확인
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -qO- http://localhost:3000/health || exit 1
# USER: 비root 사용자 실행 (보안 필수!)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# CMD: 컨테이너 시작 명령
CMD ["node", "server.js"]
# 레이어 구조 시각화
[ Layer 7 ] COPY . . ← 소스 변경 시 여기부터 재빌드
[ Layer 6 ] RUN npm ci ← package.json 변경 시 재빌드
[ Layer 5 ] COPY package*.json ./ ← 자주 안 변함 → 캐시 재사용!
[ Layer 4 ] ENV NODE_ENV=... ← 거의 안 변함
[ Layer 3 ] WORKDIR /app
[ Layer 2 ] RUN adduser ...
[ Layer 1 ] FROM node:20-alpine ← 항상 캐시 재사용
# 핵심 원칙: 자주 변하는 레이어는 아래에, 안 변하는 것은 위에!
# → 소스코드가 바뀌어도 npm install 캐시는 재사용 가능!
# 이미지 빌드
docker build -t my-app:1.0.0 .
docker build -t my-app:dev -f Dockerfile.dev .
docker build --no-cache -t my-app:latest . # 캐시 무시
# 이미지 관리
docker images
docker image prune -a # 미사용 이미지 정리
docker history my-app:1.0.0 # 레이어 히스토리
| 베이스 이미지 | 크기 | 특징 | 추천 용도 |
|---|---|---|---|
| ubuntu:22.04 | ~77MB | 풀 패키지, apt 지원 | 범용, 복잡한 앱 |
| alpine:3.19 🏆 | ~5MB | 초경량, apk 패키지 | 프로덕션 최적 🏅 |
| node:20-alpine | ~135MB | Node.js 포함 Alpine | Node.js 앱 |
| scratch | 0MB | 완전 빈 이미지 | Go 정적 바이너리 |
docker run -d # 백그라운드 실행
-it # 인터랙티브 TTY
--name my-container # 컨테이너 이름
-p 8080:80 # 포트 매핑 (호스트:컨테이너)
-v my-volume:/data # Named Volume 마운트
-v $(pwd):/app # Bind Mount
-e NODE_ENV=production # 환경변수
--env-file .env # .env 파일 환경변수
--network my-network # 네트워크 연결
--restart unless-stopped # 재시작 정책 (현업 권장)
--memory 512m # 메모리 제한
--cpus 0.5 # CPU 제한
--read-only # 파일시스템 읽기 전용 (보안)
--user 1001:1001 # 실행 사용자 (보안)
--rm # 종료 시 자동 삭제
--log-opt max-size=10m # 로그 최대 크기
--log-opt max-file=3 # 로그 파일 최대 수
nginx:alpine
# 컨테이너 목록
docker ps # 실행 중만
docker ps -a # 전체 (중지 포함)
# 로그 확인
docker logs my-container # 전체 로그
docker logs -f my-container # 실시간 로그 (tail -f)
docker logs --tail 100 my-container # 최근 100줄
docker logs --since 1h my-container # 1시간 이내
# 내부 접속 (현업 필수 디버깅!)
docker exec -it my-container /bin/sh # Alpine
docker exec -it my-container /bin/bash # Ubuntu
docker exec -it my-container env # 환경변수 확인
# 상세 정보 & 리소스
docker inspect my-container
docker stats # 실시간 리소스
docker cp my-container:/app/error.log ./ # 파일 복사
| 정책 | 동작 | 사용 상황 |
|---|---|---|
| no | 자동 재시작 안 함 | 개발/테스트 |
| always | 항상 재시작 | 중요 서비스 |
| unless-stopped 🏅 | 수동 중지 제외하고 항상 재시작 | 현업 권장 |
| on-failure[:3] | 비정상 종료 시만 (최대 3회) | 배치 작업 |
💡 왜 볼륨이 필요한가? 컨테이너를 삭제하면 내부 데이터도 사라집니다. DB 데이터, 업로드 파일을 영구 보존하려면 볼륨이 반드시 필요합니다.
| 종류 | 저장 위치 | 용도 | 추천 |
|---|---|---|---|
| Named Volume 🏅 | /var/lib/docker/volumes/ | DB 데이터, 앱 데이터 | ✅ 권장 |
| Bind Mount | 호스트 지정 경로 | 개발 시 소스코드 동기화 | 개발용 |
| tmpfs | RAM (재시작 시 삭제) | 임시 민감 데이터 | 특수 상황 |
# Named Volume - MySQL DB 데이터 보존
docker volume create mysql-data
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret -v mysql-data:/var/lib/mysql --restart unless-stopped mysql:8.0
docker volume inspect mysql-data # 볼륨 정보 확인
# Bind Mount - 개발 환경 소스코드 동기화
docker run -d --name dev-node -p 3000:3000 -v $(pwd):/app # 현재 디렉토리 동기화
-v /app/node_modules # node_modules 보호
node:20-alpine sh -c "npm install && npm run dev"
# 볼륨 백업
docker run --rm -v mysql-data:/backup-source -v $(pwd):/backup alpine tar czf /backup/mysql-$(date +%Y%m%d).tar.gz -C /backup-source .
# 볼륨 정리
docker volume prune # 미사용 볼륨 삭제
| 드라이버 | 격리 | 특징 | 사용 상황 |
|---|---|---|---|
| bridge 🏅 | 컨테이너 격리 | 기본값, 가상 LAN, 포트 매핑 필요 | 단일 호스트 (현업 기본) |
| host | 없음 | 호스트 네트워크 직접 사용 | 성능 중요한 네트워크 앱 |
| none | 완전 격리 | 네트워크 없음 | 보안 격리, 배치 작업 |
| overlay | 멀티 호스트 | 여러 서버 간 컨테이너 통신 | 분산 환경 (K8s) |
# 커스텀 브리지 네트워크를 써야 하는 이유:
# 1. 컨테이너 이름으로 DNS 자동 해석! (IP 직접 지정 불필요)
# 2. 네트워크 격리로 보안 향상
# 3. 기본 bridge는 컨테이너명 DNS 해석 불가!
docker network create app-network
# 컨테이너를 커스텀 네트워크에 연결
docker run -d --name backend --network app-network my-backend:1.0
docker run -d --name db --network app-network postgres:15
# ✨ 핵심: 같은 네트워크 = 이름으로 통신!
docker exec backend ping db # 이름으로 핑!
docker exec backend curl http://db:5432 # 이름으로 연결!
docker network ls
docker network inspect app-network
docker network connect app-network other-container
💡 Docker Compose란?
웹+DB+캐시+Nginx를 하나의 YAML 파일로 정의하고 docker compose up -d 한 번으로 전체 스택을 올리는 도구입니다!
version: "3.8"
services:
nginx:
image: nginx:alpine
ports: ["80:80","443:443"]
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
app: { condition: service_healthy }
restart: unless-stopped
networks: [frontend]
app:
build: { context: ., dockerfile: Dockerfile.prod }
expose: ["3000"]
environment:
NODE_ENV: production
DB_HOST: db # 서비스명 = DNS! IP 불필요
env_file: [.env]
depends_on:
db: { condition: service_healthy }
healthcheck:
test: ["CMD","wget","-qO-","http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
networks: [frontend, backend]
deploy:
resources:
limits: { cpus: '1.0', memory: 512M }
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: supersecret
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL","pg_isready -U appuser"]
interval: 10s
retries: 5
restart: unless-stopped
networks: [backend]
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes: [redis-data:/data]
restart: unless-stopped
networks: [backend]
volumes:
postgres-data:
redis-data:
networks:
frontend: { driver: bridge }
backend:
driver: bridge
internal: true # ← 외부 인터넷 차단! DB 보안의 핵심
docker compose up -d # 백그라운드 전체 시작
docker compose up -d --build # 이미지 재빌드 후 시작
docker compose down # 전체 중지 & 삭제
docker compose down -v # 볼륨까지 삭제 (초기화)
docker compose ps # 서비스 상태
docker compose logs -f # 전체 로그 실시간
docker compose logs -f app # 특정 서비스 로그
docker compose exec app sh # 컨테이너 접속
docker compose up -d --scale app=3 # 3개로 수평 확장
🏗️ Chapter 07. Dockerfile 고급 & Multi-stage Build
이미지 크기를 줄이고 빌드 속도를 높이는 프로덕션 필수 기술
🎯 Multi-stage Build란?
하나의 Dockerfile 안에서 여러 빌드 단계(stage)를 정의하여, 최종 이미지에는 실행에 필요한 파일만 포함시키는 기법입니다.
| 구분 | 일반 빌드 | Multi-stage 빌드 |
|---|---|---|
| 이미지 크기 | 크다 (빌드도구 포함) | 작다 (실행파일만) |
| 보안 | 소스코드/도구 노출 | 최소한의 파일만 |
| 대표 예시 | node:18 (1.1GB) | node:18-alpine (50MB) |
📝 Go 언어 Multi-stage 예시
# === Stage 1: 빌드 환경 ===
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
# === Stage 2: 실행 환경 (최소화) ===
FROM scratch # 완전히 빈 이미지
COPY --from=builder /app/main /main
EXPOSE 8080
ENTRYPOINT ["/main"]
# 결과: 빌드이미지 ~400MB → 실행이미지 ~10MB
📝 Node.js Multi-stage 예시
# === Stage 1: 의존성 설치 ===
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# === Stage 2: 빌드 ===
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# === Stage 3: 실행 환경 ===
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
⚡ Dockerfile 최적화 10가지 원칙
변경 빈도가 낮은 레이어(의존성)를 먼저, 자주 바뀌는 소스코드를 나중에 COPY
node_modules, .git, *.log, dist 등 불필요한 파일 빌드 컨텍스트 제외
ubuntu(72MB) → alpine(5MB). 단, musl libc 호환성 확인 필요
RUN apt-get update && apt-get install -y pkg && rm -rf /var/lib/apt/lists/*
FROM node:18.19.0-alpine3.19 (latest 금지 - 재현 불가 빌드 방지)
ADD는 URL 다운로드/압축해제 가능하나, 단순 복사는 항상 COPY 사용
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost/ || exit 1
RUN adduser -D appuser && chown -R appuser /app; USER appuser
ARG: 빌드 시점 변수(이미지에 저장 안됨), ENV: 런타임 환경변수
DOCKER_BUILDKIT=1 docker build . (병렬빌드, 캐시 마운트, 시크릿 마운트)
🔧 BuildKit 고급 기능
# syntax=docker/dockerfile:1
# 캐시 마운트 - npm 패키지를 빌드 간 캐시 유지
FROM node:18-alpine
RUN --mount=type=cache,target=/root/.npm npm ci
# 시크릿 마운트 - 이미지에 남기지 않고 빌드 시 접근
RUN --mount=type=secret,id=mytoken cat /run/secrets/mytoken | pip install --extra-index-url ...
# 빌드 시 사용:
# echo "mytoken=abc123" | docker build --secret id=mytoken,- .
# 바인드 마운트 - 소스를 COPY 없이 읽기 전용 마운트
RUN --mount=type=bind,source=.,target=/src ls /src
🔒 Chapter 08. Docker 보안 완전 정복
컨테이너 보안은 선택이 아닌 필수 - 현업에서 반드시 적용해야 하는 보안 실천법
⚠️ 컨테이너 보안 위협 TOP 5
| 위협 | 설명 | 대응 방법 |
|---|---|---|
| 취약한 베이스 이미지 | CVE 취약점 포함 이미지 사용 | Trivy/Snyk 스캔 자동화 |
| root 실행 | 컨테이너 탈출 시 호스트 권한 획득 | USER 비root 계정 지정 |
| 시크릿 노출 | 이미지 레이어에 패스워드/키 포함 | Docker Secrets / Vault |
| 과도한 권한 | 불필요한 Linux 캐퍼빌리티 | --cap-drop ALL --cap-add 최소 |
| 읽기/쓰기 파일시스템 | 공격자가 컨테이너 내 파일 변조 | --read-only 플래그 |
🛡️ 1. 비root 사용자 실행
# Dockerfile 내 비root 사용자 생성
FROM node:18-alpine
# 전용 그룹/사용자 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --only=production
# root 대신 appuser로 전환
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]
# 실행 시 확인
# docker run myapp whoami → appuser (UID 1001 등)
# docker run --user 1001:1001 myapp # UID 직접 지정
🔑 2. Docker Secrets (Swarm 모드)
# 시크릿 생성 (암호화된 Raft 스토어에 저장)
echo "super_secret_password" | docker secret create db_password -
# 서비스에 시크릿 마운트
docker service create --name myapp --secret db_password myapp:latest
# 컨테이너 내: /run/secrets/db_password 파일로 접근
# Docker Compose에서 시크릿 사용 (Compose v3.1+)
# compose.yml 예시:
# secrets:
# db_password:
# external: true
# services:
# app:
# secrets:
# - db_password
# environment:
# - DB_PASSWORD_FILE=/run/secrets/db_password
🔍 3. 이미지 취약점 스캔 (Trivy)
# Trivy 설치 (macOS)
brew install trivy
# 이미지 스캔
trivy image nginx:latest
trivy image --severity HIGH,CRITICAL myapp:latest
# 파일시스템 스캔
trivy fs /path/to/project
# CI/CD 파이프라인 통합 (GitHub Actions)
# - name: Scan image
# uses: aquasecurity/trivy-action@master
# with:
# image-ref: myapp:latest
# exit-code: '1' # 취약점 발견 시 빌드 실패
# severity: 'CRITICAL'
# Docker Scout (Docker 공식)
docker scout cves myapp:latest
docker scout recommendations myapp:latest
🔐 4. 런타임 보안 강화
# 읽기전용 파일시스템 (중요!)
docker run --read-only --tmpfs /tmp --tmpfs /run myapp:latest
# 리소스 제한 (DoS 방지)
docker run --memory="512m" --memory-swap="1g" --cpus="0.5" --pids-limit=100 myapp:latest
# 불필요한 캐퍼빌리티 제거
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE --security-opt no-new-privileges:true myapp:latest
# seccomp 프로파일 적용
docker run --security-opt seccomp=default.json myapp:latest
# AppArmor 프로파일
docker run --security-opt apparmor=docker-default myapp:latest
✅ Docker 보안 체크리스트
- ☐ 비root 사용자로 컨테이너 실행 (USER 지정)
- ☐ --read-only 파일시스템 적용
- ☐ 최신 베이스 이미지 사용 + 정기 스캔
- ☐ ENV에 패스워드/키 직접 입력 금지
- ☐ --cap-drop ALL 후 필요한 것만 추가
- ☐ 메모리/CPU 리소스 제한 설정
- ☐ Docker daemon TLS 인증 활성화
- ☐ 프라이빗 레지스트리 접근 제어
📦 Chapter 09. 컨테이너 레지스트리 & 이미지 관리 전략
이미지를 어디에, 어떻게 저장하고 배포할 것인가 - 현업 레지스트리 완전 가이드
🌐 주요 컨테이너 레지스트리 비교
| 레지스트리 | 특징 | 무료 플랜 | 추천 용도 |
|---|---|---|---|
| Docker Hub | 가장 대중적, 공식 이미지 제공 | 퍼블릭 무제한 | 오픈소스 프로젝트 |
| AWS ECR | IAM 연동, EKS/ECS 네이티브 통합 | 500MB/월 프리티어 | AWS 기반 서비스 |
| GCR / Artifact Registry | GKE 최적화, 자동 취약점 스캔 | 0.5GB/월 | GCP 기반 서비스 |
| GHCR (GitHub) | GitHub Actions와 완벽 통합 | 퍼블릭 무료 | CI/CD 파이프라인 |
| Harbor (self-hosted) | 온프레미스, RBAC, 복제 지원 | 무료 (오픈소스) | 기업 내부 레지스트리 |
🏷️ 이미지 태깅 전략 (Semantic Versioning)
# 버전 태그 (불변 - 프로덕션 필수)
docker tag myapp:local myregistry.io/team/myapp:1.2.3
docker tag myapp:local myregistry.io/team/myapp:1.2
docker tag myapp:local myregistry.io/team/myapp:1
# 환경별 태그
docker tag myapp:1.2.3 myregistry.io/team/myapp:1.2.3-prod
docker tag myapp:1.2.3 myregistry.io/team/myapp:1.2.3-staging
# Git 커밋 해시 태그 (CI/CD에서 자동화)
GIT_SHA=$(git rev-parse --short HEAD)
docker tag myapp:local myregistry.io/team/myapp:$GIT_SHA
# latest 태그는 dev 환경에서만, 프로덕션 사용 금지!
# 멀티 아키텍처 빌드 (x86 + ARM)
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry.io/team/myapp:1.2.3 --push .
🔧 AWS ECR 실전 사용법
# 1. ECR 레지스트리 인증 (12시간 유효)
ECR_URL=123456789012.dkr.ecr.ap-northeast-2.amazonaws.com
aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin $ECR_URL
# 2. 레포지토리 생성 (푸시 시 자동 스캔 활성화)
aws ecr create-repository \
--repository-name myapp \
--image-scanning-configuration scanOnPush=true
# 3. 이미지 빌드 및 푸시
docker build -t myapp:1.0.0 .
docker tag myapp:1.0.0 $ECR_URL/myapp:1.0.0
docker push $ECR_URL/myapp:1.0.0
# 4. 이미지 라이프사이클 정책
# 최신 10개만 유지, 30일 이상 된 태그 없는 이미지 자동 삭제
aws ecr put-lifecycle-policy --repository-name myapp \
--lifecycle-policy-text file://lifecycle-policy.json
🔄 GitHub Actions + GHCR 자동 배포
# .github/workflows/docker-publish.yml
name: Docker Build and Push
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: GITHUB_ACTOR
password: GITHUB_TOKEN_SECRET
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/myorg/myapp
tags: |
type=sha
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/myorg/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
📊 Chapter 10. 모니터링 & 트러블슈팅 완전 정복
컨테이너 운영의 핵심 - 장애를 사전에 감지하고 빠르게 해결하는 법
📊 컨테이너 모니터링 스택 (Full Observability)
| 도구 | 역할 | 수집 대상 |
|---|---|---|
| cAdvisor | 컨테이너 메트릭 수집기 | CPU, 메모리, 네트워크, 디스크I/O |
| Prometheus | 시계열 메트릭 저장소 | 모든 서비스 메트릭 스크래핑 |
| Grafana | 시각화 대시보드 | Prometheus 데이터 시각화 |
| Loki | 로그 수집/저장 | 컨테이너 stdout/stderr 로그 |
| Jaeger / Zipkin | 분산 추적 (Tracing) | 마이크로서비스 요청 흐름 추적 |
📋 Prometheus + Grafana + cAdvisor 스택 설치
# monitoring/compose.yml
services:
prometheus:
image: prom/prometheus:v2.49.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
ports: ["9090:9090"]
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=15d
grafana:
image: grafana/grafana:10.3.0
volumes:
- grafana-data:/var/lib/grafana
ports: ["3000:3000"]
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
depends_on: [prometheus]
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.47.0
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports: ["8080:8080"]
privileged: true
volumes:
prometheus-data:
grafana-data:
# prometheus.yml - 스크래핑 설정
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: cadvisor
static_configs:
- targets: ['cadvisor:8080']
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
- job_name: myapp
static_configs:
- targets: ['myapp:8080']
metrics_path: /metrics
# Grafana 대시보드 ID:
# 193 - Docker 컨테이너 모니터링
# 1860 - Node Exporter Full
# 11074 - cAdvisor exporter
🚨 실전 트러블슈팅 가이드
docker logs --tail 50 컨테이너명
docker inspect 컨테이너명 | grep -A 5 State
docker stats 컨테이너명
--memory 옵션으로 메모리 제한 설정
docker network inspect 네트워크명
컨테이너명으로 DNS 해석 확인
ls -la /var/lib/docker/volumes/
chown -R 1000:1000 /data
🔧 핵심 디버깅 명령어 모음
# === 컨테이너 상태 진단 ===
docker stats # 실시간 리소스 사용량
docker stats --no-stream # 1회 스냅샷
docker top 컨테이너명 # 컨테이너 내 프로세스
docker inspect 컨테이너명 # 모든 설정/상태 JSON
# === 로그 분석 ===
docker logs 컨테이너명 # 전체 로그
docker logs -f --tail 100 컨테이너명 # 마지막 100줄 + 실시간
docker logs --since 5m 컨테이너명 # 최근 5분 로그
docker logs 컨테이너명 2>&1 | grep ERROR # 에러만 필터
# === 컨테이너 내부 접속 및 진단 ===
docker exec -it 컨테이너명 sh # 쉘 접속
docker exec 컨테이너명 ps aux # 프로세스 목록
docker exec 컨테이너명 netstat -tlnp # 포트 확인
docker exec 컨테이너명 cat /etc/hosts # DNS 설정 확인
docker exec 컨테이너명 curl -sf http://localhost:8080/health
# === 이미지/컨테이너 분석 ===
docker history 이미지명 # 레이어 히스토리
docker diff 컨테이너명 # 파일 변경 사항
dive 이미지명 # 레이어별 파일 분석 (dive 설치 필요)
# === 시스템 정리 ===
docker system df # 디스크 사용량
docker system prune -af # 모든 미사용 리소스 삭제
docker image prune --filter "until=48h" # 48시간 이상 된 이미지
📋 헬스체크 & 자동 복구 설정
# Dockerfile HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# Docker Compose 헬스체크
services:
app:
image: myapp:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# depends_on에서 healthcheck 활용
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# 자동 재시작 정책
docker run --restart=on-failure:3 myapp:latest # 실패 시 최대 3번
docker run --restart=unless-stopped myapp:latest # 수동 중지 전까지 재시작
docker run --restart=always myapp:latest # 항상 재시작
⎈️ Chapter 11. 쿠버네티스(K8s) 입문 - Docker 다음 단계
Docker를 마스터했다면 이제 K8s로 - 컨테이너 오케스트레이션의 세계
❓ Docker만으로는 왜 부족한가?
| 문제 | Docker만 사용 시 | K8s 해결책 |
|---|---|---|
| 스케일링 | 수동으로 컨테이너 복제 | HPA - CPU/메모리 기반 자동 확장 |
| 자가 치유 | 죽은 컨테이너 수동 재시작 | 죽으면 자동으로 새 Pod 생성 |
| 무중단 배포 | 배포 시 다운타임 발생 | Rolling Update / Blue-Green 배포 |
| 로드밸런싱 | 외부 LB 수동 구성 | Service가 자동으로 LB 역할 |
| 멀티 호스트 | 단일 서버에 종속 | 여러 노드에 분산 배치 |
🏗️ K8s 핵심 아키텍처
┌─────────────────────────────────────────────────────┐
│ K8s 클러스터 │
│ ┌──────────────────────────────────────────────┐ │
│ │ Control Plane (마스터 노드) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │ │
│ │ │ API │ │ etcd │ │ Scheduler │ │ │
│ │ │ Server │ │ (데이터) │ │ (배치결정) │ │ │
│ │ └─────────┘ └──────────┘ └─────────────┘ │ │
│ │ ┌───────────────┐ │ │
│ │ │ Controller │ │ │
│ │ │ Manager │ │ │
│ │ └───────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Worker Node │ │ Worker Node │ ... │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ kubelet │ │ │ │ kubelet │ │ │
│ │ │ kube- │ │ │ │ kube- │ │ │
│ │ │ proxy │ │ │ │ proxy │ │ │
│ │ │┌─┐┌─┐┌─┐│ │ │ │┌─┐┌─┐ │ │ │
│ │ ││P││P││P││ │ │ ││P││P│ │ │ │
│ │ ││o││o││o││ │ │ ││o││o│ │ │ │
│ │ ││d││d││d││ │ │ ││d││d│ │ │ │
│ │ │└─┘└─┘└─┘│ │ │ │└─┘└─┘ │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
📋 K8s 핵심 오브젝트 (YAML 예시)
# 1. Deployment - 앱 배포 및 관리
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3 # 3개의 Pod 실행
selector:
matchLabels:
app: myapp
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 배포 중 추가로 생성할 최대 Pod 수
maxUnavailable: 0 # 배포 중 사용 불가 최소 Pod 수 (0 = 무중단)
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myregistry.io/myapp:1.2.3 # 반드시 버전 태그!
ports:
- containerPort: 8080
resources:
requests: # 최소 보장 리소스
memory: "128Mi"
cpu: "250m"
limits: # 최대 허용 리소스
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
# 2. Service - 로드밸런싱 및 서비스 디스커버리
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
selector:
app: myapp # 위 Deployment의 Pod 선택
ports:
- port: 80
targetPort: 8080
type: ClusterIP # 클러스터 내부만 접근
# type: LoadBalancer # 외부 접근 (클라우드 LB 자동 생성)
# type: NodePort # 노드 IP:포트로 외부 접근
---
# 3. ConfigMap - 설정 값 분리
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
APP_ENV: production
LOG_LEVEL: info
DB_HOST: postgres-svc
---
# 4. Secret - 민감 정보 저장 (base64 인코딩)
apiVersion: v1
kind: Secret
metadata:
name: myapp-secret
type: Opaque
data:
DB_PASSWORD: c3VwZXJzZWNyZXQ= # base64 인코딩 값
# echo -n "supersecret" | base64
⚡ kubectl 주요 명령어
# === 배포 관리 ===
kubectl apply -f deployment.yml # YAML 적용 (생성/수정)
kubectl delete -f deployment.yml # YAML 삭제
kubectl rollout status deployment/myapp # 배포 상태 확인
kubectl rollout undo deployment/myapp # 이전 버전으로 롤백
kubectl scale deployment myapp --replicas=5 # 수동 스케일링
# === 상태 조회 ===
kubectl get pods -o wide # Pod 상세 목록
kubectl get all # 모든 리소스
kubectl describe pod Pod이름 # Pod 상세 정보
kubectl logs Pod이름 -c 컨테이너명 -f # 로그 실시간 조회
# === 디버깅 ===
kubectl exec -it Pod이름 -- sh # Pod 내부 접속
kubectl port-forward svc/myapp-svc 8080:80 # 로컬 포트 포워딩
kubectl get events --sort-by=lastTimestamp # 이벤트 시간순 조회
# === 컨텍스트 관리 ===
kubectl config get-contexts # 사용 가능한 클러스터
kubectl config use-context 클러스터명 # 클러스터 전환
💡 K8s 학습 로드맵
Docker를 마스터한 후 K8s 학습 순서: minikube 로컬 설치 → Pod/Deployment/Service 기본 → ConfigMap/Secret → Ingress 컨트롤러 → Helm 패키지 매니저 → EKS/GKE/AKS 클라우드 관리형 K8s
추천 실습 환경: minikube, kind(Kubernetes in Docker), k3s (경량 K8s)
🎤 Chapter 12. 현업 면접 Q&A TOP 15
DevOps / 인프라 엔지니어 면접에서 실제로 나오는 Docker 질문과 모범 답안
Q1. VM과 컨테이너의 차이점을 설명해주세요.
A: VM은 하이퍼바이저 위에 Guest OS를 포함한 전체 운영체제를 가상화합니다. 부팅에 분~수십초가 걸리고 GB 단위 용량을 사용합니다. 반면 컨테이너는 호스트 OS의 커널을 공유하며 프로세스 수준으로 격리됩니다. 밀리초 단위로 시작하고 MB 단위로 가볍습니다. VM은 강한 격리가 필요한 경우(멀티 테넌트 클라우드), 컨테이너는 빠른 배포와 마이크로서비스에 적합합니다.
Q2. Docker 레이어(Layer) 구조와 Union File System을 설명해주세요.
A: Docker 이미지는 읽기 전용 레이어들의 스택입니다. 각 Dockerfile 명령어(RUN, COPY, ADD)는 새 레이어를 생성합니다. Union File System(OverlayFS)이 여러 레이어를 하나의 파일시스템으로 합쳐서 보여줍니다. 컨테이너 실행 시 최상단에 쓰기 가능한 얇은 레이어가 추가됩니다(Copy-on-Write). 이 구조 덕분에 동일 베이스 이미지를 여러 컨테이너가 공유하여 디스크를 절약합니다. 레이어 캐시 덕분에 빌드도 빠릅니다.
Q3. ENTRYPOINT와 CMD의 차이점은?
A: ENTRYPOINT는 컨테이너가 항상 실행할 기본 명령을 정의하며, docker run 시 재정의하기 어렵습니다. CMD는 ENTRYPOINT에 전달할 기본 인자를 정의하며, docker run 시 쉽게 재정의됩니다. 예를 들어 ENTRYPOINT ["python", "app.py"]를 설정하면 컨테이너는 항상 python app.py를 실행합니다. CMD ["--port", "8080"]을 추가하면 기본 포트를 지정하지만, docker run myapp --port 9090으로 변경 가능합니다. 실행 파일처럼 동작시키려면 ENTRYPOINT, 기본값만 설정하려면 CMD를 사용합니다.
Q4. Docker 네트워크 종류와 각 사용 사례를 설명해주세요.
A: bridge는 기본 네트워크로 같은 호스트 내 컨테이너들이 통신합니다. 사용자 정의 bridge 네트워크에서는 컨테이너명으로 DNS 해석이 됩니다. host는 컨테이너가 호스트 네트워크를 직접 사용합니다. 네트워크 오버헤드가 없어 고성능이지만 포트 충돌 위험이 있습니다. overlay는 여러 호스트에 걸친 컨테이너 통신에 사용됩니다. Docker Swarm과 K8s에서 주로 활용됩니다. none은 네트워크를 완전히 비활성화합니다. 보안이 중요한 배치 작업에 사용됩니다.
Q5. Docker 이미지를 최소화하는 방법들을 말해주세요.
A: 첫째, Multi-stage build를 사용합니다. 빌드 도구는 빌드 스테이지에서만 사용하고 최종 이미지에는 실행 파일만 포함합니다. 둘째, Alpine 기반 이미지를 사용합니다. ubuntu 72MB 대비 alpine은 5MB입니다. 셋째, RUN 명령어를 체이닝하여 레이어 수를 줄이고, 패키지 캐시를 삭제합니다. 넷째, .dockerignore로 빌드 컨텍스트에서 불필요한 파일을 제외합니다. 다섯째, 불필요한 패키지 설치를 피합니다. 여섯째, Go 언어의 경우 scratch 이미지를 기반으로 단일 바이너리를 복사합니다.
Q6. Named Volume과 Bind Mount의 차이점은?
A: Named Volume은 Docker가 관리하는 스토리지입니다. 경로를 지정할 필요 없이 이름만으로 관리하며, 이식성이 좋고 권한 문제가 적습니다. 프로덕션 데이터베이스 등 영속 데이터에 적합합니다. Bind Mount는 호스트의 특정 경로를 컨테이너에 마운트합니다. 호스트 파일시스템과 직접 연동되어 개발 환경에서 코드 핫리로드에 유용합니다. 그러나 호스트 경로에 종속되어 이식성이 낮고 권한 문제가 발생할 수 있습니다.
Q7. 컨테이너 보안을 강화하는 방법을 설명해주세요.
A: 첫째, 비root 사용자로 실행합니다(USER 지정). 둘째, --read-only 플래그로 읽기 전용 파일시스템을 사용합니다. 셋째, --cap-drop ALL로 모든 Linux 캐퍼빌리티를 제거하고 필요한 것만 추가합니다. 넷째, Trivy나 Docker Scout으로 취약점을 정기 스캔합니다. 다섯째, ENV나 ARG에 시크릿을 하드코딩하지 않고 Docker Secrets나 Vault를 사용합니다. 여섯째, 최신 베이스 이미지를 사용합니다. 일곱째, --pids-limit으로 프로세스 수를 제한합니다.
Q8. Docker Compose와 Docker Swarm, Kubernetes의 차이점은?
A: Docker Compose는 단일 호스트에서 여러 컨테이너로 구성된 애플리케이션을 정의하고 실행합니다. 개발/테스트 환경에 주로 사용됩니다. Docker Swarm은 여러 호스트에 걸친 Docker 네이티브 오케스트레이션입니다. 설정이 간단하지만 기능이 K8s보다 제한적입니다. Kubernetes는 가장 강력한 컨테이너 오케스트레이션 플랫폼입니다. 자동 스케일링, 자가 치유, 무중단 배포, 서비스 디스커버리, 시크릿 관리 등을 제공합니다. 복잡하지만 대규모 프로덕션 환경의 표준입니다.
Q9. 컨테이너가 계속 재시작될 때 어떻게 디버깅하나요?
A: 1) docker logs 컨테이너명으로 에러 로그를 확인합니다. 2) docker inspect 컨테이너명으로 종료 코드(ExitCode)와 재시작 횟수를 확인합니다. Exit code 1은 앱 에러, 137은 OOM Kill, 143은 SIGTERM입니다. 3) docker stats로 메모리 사용량이 한계에 가까운지 확인합니다. 4) 헬스체크 설정이 너무 엄격한지 확인합니다. 5) --restart 정책이 재시작을 계속 유발하는지 확인합니다. 6) 필요 시 --entrypoint sh로 진입해 직접 실행해봅니다.
Q10. Docker 이미지 태깅 전략을 어떻게 가져가시겠습니까?
A: 프로덕션에서는 latest 태그를 절대 사용하지 않습니다. Semantic Versioning(1.2.3)을 기본으로 사용하고, Git 커밋 SHA를 함께 태그합니다. 이를 통해 배포된 버전이 정확히 어떤 코드인지 추적 가능합니다. 환경별로 태그를 분리하여(1.2.3-prod, 1.2.3-staging) 환경 간 이미지를 명확히 구분합니다. CI/CD에서는 PR 머지 시 자동으로 빌드하고, 버전 태그를 자동 생성합니다. 오래된 이미지는 라이프사이클 정책으로 자동 정리합니다.
Q11~Q15. 고급 면접 질문
Q11. 컨테이너와 마이크로서비스의 관계를 설명해주세요.
A: 마이크로서비스는 각 서비스를 독립적으로 배포 가능한 작은 단위로 나누는 아키텍처입니다. 컨테이너는 이러한 마이크로서비스를 패키징하고 배포하기에 이상적인 기술입니다. 컨테이너의 격리, 이식성, 빠른 시작이 마이크로서비스의 독립 배포와 확장 요구사항과 완벽하게 맞습니다.
Q12. Docker를 사용한 CI/CD 파이프라인을 설계해주세요.
A: 코드 푸시 → GitHub Actions 트리거 → docker build (Multi-stage) → Trivy로 보안 스캔 → GHCR/ECR에 이미지 푸시 → 태그 기반 버전 관리 → 스테이징 환경 배포 (docker compose / K8s) → 자동화 테스트 → 프로덕션 배포 (Blue-Green 또는 Rolling) → Grafana로 배포 후 모니터링
Q13. 컨테이너에서 로그를 어떻게 관리하나요?
A: 12Factor App 원칙에 따라 컨테이너는 stdout/stderr에만 로그를 출력해야 합니다. Docker는 이를 캡처하여 로그 드라이버(json-file, fluentd, gelf 등)로 전달합니다. 프로덕션에서는 Fluentd나 Filebeat로 로그를 수집하여 ELK 스택(Elasticsearch + Logstash + Kibana)이나 Loki + Grafana에 저장하고 분석합니다.
Q14. Docker의 Copy-on-Write(CoW) 메커니즘을 설명해주세요.
A: 컨테이너가 이미지의 파일을 수정할 때 원본 레이어는 건드리지 않고, 수정할 파일만 컨테이너 레이어로 복사한 후 수정합니다. 이로 인해 같은 이미지에서 여러 컨테이너를 실행해도 각 컨테이너는 독립적인 파일 변경이 가능하고, 이미지 레이어는 공유되어 디스크를 절약합니다.
Q15. 컨테이너에서 상태(state)를 어떻게 관리하나요?
A: 컨테이너 자체는 Stateless(무상태)로 설계하는 것이 원칙입니다. 상태 데이터(DB, 파일 등)는 컨테이너 외부에 분리합니다. 방법으로는 1) Docker Volume을 사용하여 호스트에 영속화, 2) 외부 DB 서비스(RDS 등) 사용, 3) S3 같은 오브젝트 스토리지 사용, 4) Redis 같은 외부 캐시 서버 사용이 있습니다. 이렇게 해야 컨테이너를 언제든지 교체/삭제해도 데이터가 보존됩니다.
✅ Docker 현업 투입 체크리스트
이 모든 항목을 이해하고 실습했다면 현업에서 Docker를 사용할 준비가 되었습니다!
📚 기초 개념 (Ch01-03)
- ☐ VM vs 컨테이너 차이 설명 가능
- ☐ Docker 설치 및 Hello World 실행
- ☐ 이미지와 컨테이너의 관계 이해
- ☐ Dockerfile 작성 (FROM/RUN/COPY/CMD)
- ☐ docker build/run/ps/stop/rm 명령어
- ☐ 포트 바인딩 (-p) 이해 및 사용
- ☐ 환경변수 (-e) 주입 이해
- ☐ docker logs / exec 활용
💾 스토리지 & 네트워크 (Ch04-05)
- ☐ Named Volume으로 DB 데이터 영속화
- ☐ Bind Mount로 개발 핫리로드 구성
- ☐ docker volume ls/inspect/rm
- ☐ 사용자 정의 bridge 네트워크 생성
- ☐ 컨테이너명으로 DNS 통신 테스트
- ☐ 네트워크 격리 원리 이해
📋 Compose & 고급 빌드 (Ch06-07)
- ☐ docker-compose.yml로 웹+DB 스택 구성
- ☐ depends_on + healthcheck 설정
- ☐ docker compose up/down/logs
- ☐ Multi-stage build 적용 경험
- ☐ .dockerignore 설정
- ☐ Alpine 기반 이미지로 사이즈 최소화
- ☐ docker system prune으로 정리
🔒 보안 & 운영 (Ch08-10)
- ☐ 비root 사용자로 컨테이너 실행
- ☐ Trivy로 이미지 취약점 스캔
- ☐ 메모리/CPU 리소스 제한 설정
- ☐ HEALTHCHECK 설정 경험
- ☐ ECR 또는 GHCR에 이미지 푸시
- ☐ Prometheus + Grafana 모니터링 구축
- ☐ 컨테이너 트러블슈팅 경험 (OOM, 재시작 등)
🎉 Docker & 컨테이너 완전 정복!
이 포스팅을 통해
Docker의 A부터 Z까지 학습을 완료했습니다.
다음 단계로 쿠버네티스(K8s)와 CI/CD 파이프라인 자동화에 도전하세요!
🗺️ InfraDevGuide 시리즈 전체 로드맵
| 단계 | 제목 | 상태 |
|---|---|---|
| InfraDevGuide0001 | Linux 기초 완전 정복 | ✅ 완료 |
| InfraDevGuide0002 | 네트워킹 기초 | ✅ 완료 |
| InfraDevGuide0003 | Git & 버전 관리 | ✅ 완료 |
| InfraDevGuide0004 | Shell Script & 자동화 | ✅ 완료 |
| InfraDevGuide0005 | Docker & 컨테이너 완전 정복 ← 현재 | 📖 학습중 |
| InfraDevGuide0006 | CI/CD 파이프라인 | ⏳ 예정 |
| InfraDevGuide0007 | Kubernetes 완전 정복 | ⏳ 예정 |
📝 InfraDevGuide 시리즈 | 인프라 개발자를 위한 완전 정복 가이드
📅 2025 | Docker & 컨테이너 완전 정복 A-Z