이미지 경량화와 보안 스캐닝 — 프로덕션에 올리기 전에 확인할 것들
이미지를 빌드했는데 크기가 2GB이고, 내부에 어떤 패키지가 들어 있는지 정확히 모른다면 프로덕션에 올려도 괜찮을까요?
이미지 경량화가 중요한 이유
이미지 크기는 단순한 디스크 사용량 문제가 아닙니다.
- 배포 속도: 이미지가 클수록 pull에 시간이 오래 걸립니다. 장애 시 롤백도 느려집니다.
- 보안 공격 표면: 포함된 패키지가 많을수록 취약점이 존재할 확률이 높아집니다.
- 네트워크 비용: 레지스트리에서 이미지를 pull할 때마다 대역폭을 소비합니다.
- 콜드 스타트: Kubernetes에서 노드에 이미지가 없으면 처음 pull하는 시간이 Pod 시작 시간에 포함됩니다.
경량화 기법
1. 베이스 이미지 선택
# 나쁜 예: 전체 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. 멀티스테이지 빌드
빌드 도구는 빌드 스테이지에만 두고, 결과물만 런타임 스테이지로 복사합니다.
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. 패키지 설치 최적화
# 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로 빌드 컨텍스트에서 제외합니다.
# .dockerignore
.git
.github
node_modules
*.md
docs/
tests/
.env*
.vscode
.idea
coverage/
5. 레이어 합치기
# 나쁜 예: 파일이 이전 레이어에 남아있음
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. 이미지 크기 분석 도구
# docker history로 레이어별 크기 확인
docker history myapp:latest
# dive로 상세 분석 (추천)
dive myapp:latest
# 이미지 크기 비교
docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}"
dive는 각 레이어에 어떤 파일이 추가/변경/삭제되었는지 시각적으로 보여줍니다.
보안 스캐닝
왜 스캐닝이 필요한가
이미지에 포함된 OS 패키지, 언어 라이브러리에는 알려진 취약점(CVE)이 있을 수 있습니다. 빌드 시점에는 안전하더라도, 이후에 새로운 CVE가 발견될 수 있습니다.
Trivy
오픈소스 취약점 스캐너로, 가장 널리 사용됩니다.
# 설치 (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
출력 예시:
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의 다양한 스캔 대상
# 파일 시스템 스캔 (빌드 전에 소스 코드 검사)
trivy fs --scanners vuln,secret .
# Dockerfile 자체의 문제 점검
trivy config Dockerfile
# SBOM 생성
trivy image --format spdx-json --output sbom.json myapp:latest
Docker Scout
Docker Desktop에 내장된 보안 분석 도구입니다.
# 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
# 설치 및 인증
npm install -g snyk
snyk auth
# 이미지 스캔
snyk container test myapp:latest
# 모니터링 (지속적으로 새 취약점 알림)
snyk container monitor myapp:latest
CI 파이프라인에 통합
# 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 }} .
- 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 생성
# 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
이미지 서명
이미지가 신뢰할 수 있는 빌드 파이프라인에서 생성되었는지 검증할 수 있습니다.
# 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 아카이빙
정리
- 이미지 경량화는 배포 속도, 보안, 비용 모든 측면에서 중요합니다.
- 경량 베이스 이미지 + 멀티스테이지 빌드가 가장 효과적인 경량화 조합입니다.
- Trivy, Docker Scout, Snyk 같은 도구로 이미지에 포함된 취약점을 사전에 발견합니다.
- SBOM을 생성하면 새로운 CVE 발견 시 영향받는 이미지를 빠르게 식별할 수 있습니다.
- CI 파이프라인에 보안 스캐닝을 통합하여, 취약한 이미지가 프로덕션에 배포되는 것을 방지합니다.
댓글 로딩 중...