이미지를 빌드했는데 크기가 2GB이고, 내부에 어떤 패키지가 들어 있는지 정확히 모른다면 프로덕션에 올려도 괜찮을까요?

이미지 경량화가 중요한 이유

이미지 크기는 단순한 디스크 사용량 문제가 아닙니다.

  • 배포 속도: 이미지가 클수록 pull에 시간이 오래 걸립니다. 장애 시 롤백도 느려집니다.
  • 보안 공격 표면: 포함된 패키지가 많을수록 취약점이 존재할 확률이 높아집니다.
  • 네트워크 비용: 레지스트리에서 이미지를 pull할 때마다 대역폭을 소비합니다.
  • 콜드 스타트: Kubernetes에서 노드에 이미지가 없으면 처음 pull하는 시간이 Pod 시작 시간에 포함됩니다.

경량화 기법

1. 베이스 이미지 선택

DOCKERFILE
# 나쁜 예: 전체 OS 포함 (~900MB)
FROM node:20

# 보통: Debian slim (~200MB)
FROM node:20-slim

# 좋은 예: Alpine Linux (~140MB)
FROM node:20-alpine

# 더 좋은 예: distroless (~120MB)
FROM gcr.io/distroless/nodejs20-debian12

2. 멀티스테이지 빌드

빌드 도구는 빌드 스테이지에만 두고, 결과물만 런타임 스테이지로 복사합니다.

DOCKERFILE
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
CMD ["node", "dist/server.js"]

3. 패키지 설치 최적화

DOCKERFILE
# apt: 추천하지 않는 패키지 제외 + 캐시 정리
RUN apt-get update \
    && apt-get install -y --no-install-recommends curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# apk: 캐시 없이 설치
RUN apk add --no-cache curl

4. 불필요한 파일 제거

.dockerignore로 빌드 컨텍스트에서 제외합니다.

TEXT
# .dockerignore
.git
.github
node_modules
*.md
docs/
tests/
.env*
.vscode
.idea
coverage/

5. 레이어 합치기

DOCKERFILE
# 나쁜 예: 파일이 이전 레이어에 남아있음
RUN wget https://example.com/large-file.tar.gz
RUN tar xzf large-file.tar.gz
RUN rm large-file.tar.gz

# 좋은 예: 하나의 레이어에서 모두 처리
RUN wget https://example.com/large-file.tar.gz \
    && tar xzf large-file.tar.gz \
    && rm large-file.tar.gz

6. 이미지 크기 분석 도구

BASH
# docker history로 레이어별 크기 확인
docker history myapp:latest

# dive로 상세 분석 (추천)
dive myapp:latest

# 이미지 크기 비교
docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}"

dive는 각 레이어에 어떤 파일이 추가/변경/삭제되었는지 시각적으로 보여줍니다.

보안 스캐닝

왜 스캐닝이 필요한가

이미지에 포함된 OS 패키지, 언어 라이브러리에는 알려진 취약점(CVE)이 있을 수 있습니다. 빌드 시점에는 안전하더라도, 이후에 새로운 CVE가 발견될 수 있습니다.

Trivy

오픈소스 취약점 스캐너로, 가장 널리 사용됩니다.

BASH
# 설치 (macOS)
brew install trivy

# 이미지 스캔
trivy image myapp:latest

# 심각도 필터링 (HIGH, CRITICAL만)
trivy image --severity HIGH,CRITICAL myapp:latest

# JSON 출력
trivy image --format json --output result.json myapp:latest

# 취약점이 발견되면 실패 (CI에서 활용)
trivy image --exit-code 1 --severity CRITICAL myapp:latest

출력 예시:

PLAINTEXT
myapp:latest (alpine 3.19.1)
Total: 3 (HIGH: 2, CRITICAL: 1)

┌──────────────┬────────────────┬──────────┬────────┬──────────────────────┐
│   Library    │ Vulnerability  │ Severity │ Status │   Fixed Version      │
├──────────────┼────────────────┼──────────┼────────┼──────────────────────┤
│ libssl3      │ CVE-2024-1234  │ CRITICAL │ fixed  │ 3.1.4-r5            │
│ libcrypto3   │ CVE-2024-5678  │ HIGH     │ fixed  │ 3.1.4-r5            │
│ curl         │ CVE-2024-9999  │ HIGH     │ fixed  │ 8.5.0-r1            │
└──────────────┴────────────────┴──────────┴────────┴──────────────────────┘

Trivy의 다양한 스캔 대상

BASH
# 파일 시스템 스캔 (빌드 전에 소스 코드 검사)
trivy fs --scanners vuln,secret .

# Dockerfile 자체의 문제 점검
trivy config Dockerfile

# SBOM 생성
trivy image --format spdx-json --output sbom.json myapp:latest

Docker Scout

Docker Desktop에 내장된 보안 분석 도구입니다.

BASH
# Docker Desktop 최신 버전에서 사용 가능
docker scout cves myapp:latest

# 권고 사항 확인
docker scout recommendations myapp:latest

# SBOM 확인
docker scout sbom myapp:latest

# 베이스 이미지 업데이트 제안
docker scout compare --to myapp:previous myapp:latest

Snyk

BASH
# 설치 및 인증
npm install -g snyk
snyk auth

# 이미지 스캔
snyk container test myapp:latest

# 모니터링 (지속적으로 새 취약점 알림)
snyk container monitor myapp:latest

CI 파이프라인에 통합

YAML
# GitHub Actions 예시
name: Security Scan
on: push

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .

이어지는 단계에서 빌드, 테스트, 배포를 수행합니다.

YAML

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: myapp:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: 1

      - name: Upload scan results
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

SBOM (Software Bill of Materials)

SBOM은 이미지에 포함된 모든 소프트웨어 구성 요소의 목록입니다. 식품의 성분표와 같은 역할입니다.

SBOM이 필요한 이유

  • 새로운 CVE 발견 시, 해당 패키지를 포함한 이미지를 빠르게 식별
  • 라이선스 컴플라이언스 확인
  • 공급망 보안 (Supply Chain Security) 확보

SBOM 생성

BASH
# Trivy로 SPDX 형식 SBOM 생성
trivy image --format spdx-json -o sbom.spdx.json myapp:latest

# CycloneDX 형식
trivy image --format cyclonedx -o sbom.cdx.json myapp:latest

# Docker Scout으로 SBOM 확인
docker scout sbom myapp:latest

# Syft로 SBOM 생성 (Anchore 프로젝트)
syft myapp:latest -o spdx-json > sbom.json

이미지 서명

이미지가 신뢰할 수 있는 빌드 파이프라인에서 생성되었는지 검증할 수 있습니다.

BASH
# Cosign으로 이미지 서명 (Sigstore 프로젝트)
cosign sign myregistry.com/myapp:v1.0.0

# 서명 검증
cosign verify myregistry.com/myapp:v1.0.0

# 키 없는 서명 (keyless signing — OIDC 기반)
cosign sign --yes myregistry.com/myapp:v1.0.0

실전 체크리스트

빌드 전

  • .dockerignore 작성
  • 경량 베이스 이미지 선택 (alpine, slim, distroless)
  • 멀티스테이지 빌드 적용
  • 비루트 사용자(USER) 설정
  • 베이스 이미지 버전 고정

빌드 후

  • docker history로 레이어 크기 확인
  • dive로 불필요한 파일 확인
  • Trivy/Docker Scout으로 취약점 스캔
  • CRITICAL/HIGH 취약점 해결
  • SBOM 생성

CI/CD

  • 빌드 파이프라인에 스캐닝 통합
  • CRITICAL 취약점 발견 시 빌드 실패 설정
  • 정기적인 베이스 이미지 업데이트
  • SBOM 아카이빙

주의할 점

1. alpine 이미지가 musl libc를 사용해서 glibc 기반 바이너리가 동작하지 않는다

경량화를 위해 alpine으로 바꿨는데 애플리케이션이 not found 에러로 시작되지 않는 경우가 많습니다. alpine은 glibc 대신 musl libc를 사용하므로, glibc에 동적 링크된 바이너리는 실행 자체가 안 됩니다. Go는 CGO_ENABLED=0으로 정적 빌드하고, Python/Node는 네이티브 모듈 호환성을 테스트한 뒤 전환하세요.

2. distroless 이미지는 셸이 없어서 프로덕션 디버깅이 극도로 어렵다

distroless는 공격 표면을 최소화하지만, 컨테이너에 exec로 들어가서 네트워크나 파일 시스템을 확인할 수 없습니다. 장애 상황에서 원인 파악이 지연됩니다. Kubernetes 환경이면 kubectl debug로 ephemeral container를 붙이는 방법을 미리 익혀두세요.

3. 보안 스캔에서 CRITICAL 취약점이 베이스 이미지에 있으면 직접 고칠 수 없다

Trivy가 베이스 이미지(예: debian:bookworm)의 OpenSSL 취약점을 CRITICAL로 보고해도, 업스트림에서 패치된 이미지를 배포할 때까지 직접 수정이 불가능합니다. 이 경우 베이스 이미지를 최신 태그로 업데이트하거나, 패치된 다른 베이스 이미지로 전환하는 것이 유일한 방법입니다.

정리

  • 이미지 경량화는 배포 속도, 보안, 비용 모든 측면에서 중요합니다.
  • 경량 베이스 이미지 + 멀티스테이지 빌드가 가장 효과적인 경량화 조합입니다.
  • Trivy, Docker Scout, Snyk 같은 도구로 이미지에 포함된 취약점을 사전에 발견합니다.
  • SBOM을 생성하면 새로운 CVE 발견 시 영향받는 이미지를 빠르게 식별할 수 있습니다.
  • CI 파이프라인에 보안 스캐닝을 통합하여, 취약한 이미지가 프로덕션에 배포되는 것을 방지합니다.
댓글 로딩 중...