컨테이너 이미지와 시작 시간
2026-02-12 작성2026-09-17 수정5분 읽기
문서 도구
컨테이너 이미지 최적화와 시작 시간
컨테이너 이미지 크기와 구조는 Pod 시작 시간에 직접적인 영향을 미칩니다.
멀티스테이지 빌드
멀티스테이지 빌드를 사용하여 최종 이미지 크기를 최소화합니다.
Go 애플리케이션
# 빌드 스테이지
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-s -w" -o main .
# 실행 스테이지 (scratch: 5MB 이하)
FROM scratch
COPY /app/main /main
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 65534:65534
ENTRYPOINT ["/main"]
결과:
- 빌드 이미지: 300MB+
- 최종 이미지: 5-10MB
- 시작 시간: 1초 미만
Node.js 애플리케이션
# 빌드 스테이지
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# 실행 스테이지
FROM node:20-alpine
# 보안: non-root 사용자
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# 프로덕션 의존성만 복사
COPY /app/node_modules ./node_modules
COPY . .
USER nodejs
EXPOSE 8080
CMD ["node", "server.js"]
최적화 팁:
npm ci사용 (npm install보다 빠르고 안정적)--only=production으로 devDependencies 제외- 레이어 캐싱 활용 (COPY package*.json 먼저)
Java/Spring Boot 애플리케이션
# 빌드 스테이지
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
# 실행 스테이지
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
WORKDIR /app
COPY /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xms512m", "-Xmx1g", "-jar", "app.jar"]
이미지 프리풀 전략
EKS에서 이미지 프리풀(pre-pull)을 활용하여 Pod 시작 시간을 단축합니다.
Karpenter 이미지 프리풀
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
userData: |
#!/bin/bash
# 자주 사용하는 이미지 프리풀
docker pull myapp/backend:v2.1.0
docker pull myapp/frontend:v1.5.3
docker pull redis:7-alpine
docker pull postgres:16-alpine
DaemonSet으로 이미지 프리풀
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: image-prepuller
namespace: kube-system
spec:
selector:
matchLabels:
app: image-prepuller
template:
metadata:
labels:
app: image-prepuller
spec:
initContainers:
# 프리풀할 이미지마다 init container 추가
- name: prepull-backend
image: myapp/backend:v2.1.0
command: ["sh", "-c", "echo 'Image pulled'"]
- name: prepull-frontend
image: myapp/frontend:v1.5.3
command: ["sh", "-c", "echo 'Image pulled'"]
containers:
- name: pause
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: 1m
memory: 1Mi
distroless와 scratch 이미지
Google의 distroless 이미지는 애플리케이션 실행에 필요한 최소한의 파일만 포함합니다.
distroless 예시
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o main .
# distroless base
FROM gcr.io/distroless/static-debian12
COPY /app/main /main
USER 65534:65534
ENTRYPOINT ["/main"]
distroless 장점:
- 최소 공격 표면 (쉘, 패키지 매니저 없음)
- 작은 이미지 크기
- CVE 취약점 감소
scratch vs distroless:
| 이미지 | 크기 | 포함 사항 | 적합한 경우 |
|---|---|---|---|
| scratch | 0MB | 빈 파일시스템 | 완전 정적 바이너리 (Go, Rust) |
| distroless/static | ~2MB | CA certificates, tzdata | 정적 바이너리 + TLS/타임존 필요 |
| distroless/base | ~20MB | glibc, libssl | 동적 링크 바이너리 |
시작 시간 벤치마크
다양한 이미지 전략의 시작 시간 비교 (EKS 1.30, m6i.xlarge):
| 애플리케이션 | 베이스 이미지 | 이미지 크기 | Pull 시간 | 시작 시간 | 총 시간 |
|---|---|---|---|---|---|
| Go API | ubuntu:22.04 | 150MB | 8초 | 0.5초 | 8.5초 |
| Go API | alpine:3.19 | 15MB | 2초 | 0.5초 | 2.5초 |
| Go API | distroless/static | 5MB | 1초 | 0.5초 | 1.5초 |
| Go API | scratch | 3MB | 0.8초 | 0.5초 | 1.3초 |
| Node.js API | node:20 | 350MB | 15초 | 2초 | 17초 |
| Node.js API | node:20-alpine | 120MB | 6초 | 2초 | 8초 |
| Spring Boot | eclipse-temurin:21 | 450MB | 20초 | 15초 | 35초 |
| Spring Boot | eclipse-temurin:21-jre-alpine | 180MB | 10초 | 15초 | 25초 |
| Python Flask | python:3.12 | 400MB | 18초 | 3초 | 21초 |
| Python Flask | python:3.12-slim | 130MB | 7초 | 3초 | 10초 |
| Python Flask | python:3.12-alpine | 50MB | 3초 | 3초 | 6초 |
최적화 권장사항:
- 멀티스테이지 빌드 사용 → 50-90% 크기 감소
- alpine 또는 distroless 선택 → Pull 시간 50-80% 단축
- 이미지 캐싱 활성화 → 재배포 시 Pull 시간 거의 0
- Startup Probe 설정 → 느린 시작 앱 보호