Docker 이미지 크기는 Kubernetes Pod 시작 속도에 얼마나 영향을 줄까? 동일한 Go 앱을 Alpine, Debian Slim, Distroless로 빌드해 cold와 warm 시작 시간을 총 60회 측정했다.
Docker 이미지는 작을수록 좋다는 말을 자주 듣는다. 전송할 데이터와 저장 공간이 줄고, Kubernetes가 이미지를 내려받는 시간도 짧아지기 때문이다. 그렇다면 이미지가 10배 작아지면 Pod도 10배 빨리 준비될까?
테스트에는 동일한 Go HTTP 서버와 바이너리를 사용했다. 런타임 이미지만 Alpine 3.24, Debian 13 trixie-slim, Distroless static-debian13으로 변경해 비교했다.
먼저 결론부터 정리하면 다음과 같다.
이 실험에서 이미지 크기는 3.10MB부터 32.43MB까지 10.5배 차이 났지만, cold Pod Ready 중앙값은 1.43~1.47초로 비슷했다. 같은 호스트의 로컬 레지스트리에서는 이미지 pull이 28~53ms 수준이었고, Pod sandbox와 readiness probe 같은 고정 비용이 전체 시간을 지배했다.
이 결과가 “이미지 크기는 중요하지 않다”는 뜻은 아니다. 오히려 이미지 크기가 어느 조건에서 중요한지를 분리해서 봐야 한다는 뜻이다.

Docker 이미지 크기가 중요한 이유
컨테이너 이미지가 작아지면 일반적으로 다음 비용이 줄어든다.
- Registry에서 Node로 전송하는 데이터
- Node 디스크 사용량과 이미지 정리 부담
- 새 Node에서 발생하는 cold image pull 시간
- 불필요한 패키지와 실행 파일이 만드는 공격 표면
AWS도 큰 컨테이너 이미지가 cold start를 지연시켜 Pod 시작 시간에 영향을 줄 수 있다고 설명하며, 작은 베이스 이미지와 ECR VPC Endpoint, pull-through cache 등을 권장한다. 다만 네트워크 대역폭과 Registry 위치도 함께 보라고 명시한다. (AWS EKS Best Practices)
즉 “작은 이미지 = 빠른 Pod”라는 방향은 맞지만, Pod가 Ready가 될 때까지의 전체 시간이 이미지 다운로드만으로 구성되지는 않는다.
Kubernetes Pod가 시작되는 과정
Pod 생성부터 트래픽을 받을 수 있는 상태까지는 대략 다음 단계를 거친다.
PodScheduled: Scheduler가 Pod를 실행할 Node를 결정한다.PodReadyToStartContainers: Container Runtime과 CNI가 Pod sandbox 및 네트워크 준비를 마친다.- Image Pull: Node에 이미지가 없으면 Registry에서 레이어를 받는다.
- Container Start: 컨테이너 프로세스를 시작한다.
ContainersReady: readiness probe를 포함해 모든 컨테이너가 준비된다.Ready: Service가 트래픽을 보낼 수 있는 Pod가 된다.
Kubernetes 문서도 PodReadyToStartContainers가 True가 된 뒤 kubelet이 이미지를 받고 컨테이너를 생성할 수 있다고 설명한다. Running만 확인하면 실제 서비스 준비 시점과 다를 수 있으므로 이번 실험은 Ready를 종료 지점으로 사용했다. (Kubernetes Pod Conditions, Pod Lifecycle)
이미지 크기와 Image Pull의 관계
Image Pull 시간은 단순히 이미지 크기 하나로 결정되지 않는다.
Image Pull 시간
≈ Registry 응답 지연
+ 레이어 전송 시간
+ 레이어 수에 따른 요청 오버헤드
+ 압축 해제와 디스크 쓰기
+ 인증 및 네트워크 경로 비용
+ 이미 존재하는 레이어의 캐시 효과
이번 실험에서도 가장 작은 Distroless의 pull 중앙값이 가장 짧지 않았다. Distroless 이미지는 14개 레이어, Alpine과 Debian Slim은 각각 2개 레이어였다. 로컬 네트워크에서는 전송량 차이가 작아지고 레이어별 처리 같은 고정 비용이 상대적으로 크게 보였을 가능성이 있다. 이 부분은 관측 결과에 기반한 추정이며, 레이어 수만의 인과 효과를 따로 검증한 실험은 아니다.
Alpine vs Debian Slim vs Distroless
세 이미지는 용도가 다르다.
| 이미지 | 이 실험의 태그 | 특징 | 운영 시 고려사항 |
|---|---|---|---|
| Alpine | alpine:3.24 |
BusyBox와 musl 기반의 작은 배포판 | shell과 패키지 도구가 있어 진단이 비교적 쉽지만 glibc 전제를 확인해야 한다 |
| Debian Slim | debian:trixie-slim |
Debian 13의 불필요한 구성요소를 줄인 이미지 | 세 이미지 중 가장 크지만 호환성과 운영 편의가 좋다 |
| Distroless | static-debian13:nonroot |
앱 실행에 필요한 최소 파일만 포함 | shell과 패키지 관리자가 없어 공격 표면이 작지만 현장 디버깅 방식이 달라진다 |
Go 앱은 CGO_ENABLED=0으로 정적 빌드했기 때문에 세 이미지에서 동일한 바이너리를 사용할 수 있다. Distroless 공식 문서도 libc가 필요 없는 정적 Go 프로그램에는 static 이미지를 사용할 수 있다고 설명한다. (Distroless static image documentation)
Warm Start와 Cold Start의 차이
Kubernetes에서 imagePullPolicy: IfNotPresent를 사용하면 Node에 이미지가 있을 때 pull을 건너뛴다. 공식 문서의 정의도 “로컬에 이미지가 없을 때만 pull한다”이다. (Kubernetes Images)
따라서 두 상황을 분리했다.
- Cold: 각 Pod 생성 직전에 실험 이미지 세 개를 Node에서 모두 제거했다.
- Warm: 실험 시작 전에 이미지 세 개를 미리 받은 뒤 캐시를 유지했다.
검증 결과 cold 30회에는 모두 Pulling과 Successfully pulled 이벤트가 있었고, warm 30회에는 Pulling 이벤트가 없었으며 모두 already present on machine으로 기록됐다.
직접 테스트해보자
동일 조건
측정일은 2026년 9월 29일이며 환경은 다음과 같다.
| 항목 | 조건 |
|---|---|
| Host | Apple M1 Mac mini, 8-core CPU, 16GB RAM, arm64 |
| 가상화 | Colima 0.10.3, VZ |
| Kubernetes Node | 4 vCPU, 4GiB RAM, 15GiB disk |
| Kubernetes | K3s v1.35.0+k3s1 |
| kubectl | v1.35.0 |
| Container Runtime | Docker Server 29.5.2 |
| Registry | 별도 Colima VM의 로컬 Registry, 동일 호스트 가상 네트워크 |
| 반복 횟수 | 이미지별 cold 10회 + warm 10회, 총 60회 |
| Pod 순서 | 반복마다 Alpine → Debian → Distroless 순서를 회전 |
| Pull 정책 | IfNotPresent |
| Readiness probe | GET /health, periodSeconds: 1 |
Registry와 Kubernetes Node를 별도 VM으로 분리해 실제 pull 경로를 만들었지만, 두 VM은 같은 Mac 안의 가상 네트워크에 있다. 따라서 이 결과는 인터넷이나 ECR의 지연을 재현하지 않는다. 또한 cold 실행마다 컨테이너 이미지 캐시는 제거했지만 Registry VM과 Host OS의 페이지 캐시까지 비우지는 않았다.
동일한 Go 앱
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
})
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
빌더는 한 번만 정의하고 같은 /server 바이너리를 세 runtime target에 복사했다.
FROM golang:1.26.8-alpine3.24 AS builder
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
go build -trimpath -ldflags="-s -w" -o /server ./main.go
FROM alpine:3.24 AS alpine
COPY --from=builder /server /server
USER 65532:65532
ENTRYPOINT ["/server"]
FROM debian:trixie-slim AS debian-slim
COPY --from=builder /server /server
USER 65532:65532
ENTRYPOINT ["/server"]
FROM gcr.io/distroless/static-debian13:nonroot AS distroless
COPY --from=builder /server /server
ENTRYPOINT ["/server"]
세 이미지에서 꺼낸 바이너리는 모두 5,374,114 bytes였고 SHA-256도 68c3d7de...758aa9로 동일했다. 앱 코드, 빌드 옵션, CPU 아키텍처, 사용자 ID, 리소스 제한, probe는 모두 같고 runtime base image만 달랐다.
측정 방법
Kubernetes의 condition timestamp는 이 환경에서 1초 단위로 기록돼 20~100ms 수준의 차이를 비교하기에는 거칠었다. 그래서 두 값을 함께 수집했다.
- Pod의
creationTimestamp와 condition의lastTransitionTime kubectl apply직전부터kubectl wait --for=condition=Ready완료까지의 monotonic clock- Kubelet의
Pulled이벤트 메시지에 기록된 image pull 시간
본문의 Pod Ready 시간은 두 번째 값을 사용한다. 측정 클라이언트 비용도 포함되지만 모든 비교군에 동일하게 적용했고, 밀리초 단위 차이를 보존할 수 있다.

테스트 결과
1. 실제 이미지 크기
| 이미지 | Registry 레이어 합계 | 상대 크기 | 레이어 수 |
|---|---|---|---|
| Distroless | 3.09MB | 1.00x | 14 |
| Alpine | 6.42MB | 2.08x | 2 |
| Debian Slim | 32.43MB | 10.49x | 2 |
Distroless가 가장 작았고 Debian Slim은 Distroless의 약 10.5배였다. 세 이미지 모두 같은 앱 바이너리를 포함하므로 차이는 runtime base image에서 나온다.
2. Cold image pull 시간
| 이미지 | 중앙값 | 평균 | p95 | 최소~최대 |
|---|---|---|---|---|
| Alpine | 31.5ms | 49.1ms | 130.3ms | 23~187ms |
| Debian Slim | 28.0ms | 88.7ms | 365.1ms | 22~636ms |
| Distroless | 53.0ms | 69.9ms | 150.7ms | 44~224ms |
첫 pull에서 Debian Slim이 636ms까지 걸렸지만 이후 실행은 대부분 수십 ms였다. 동일 호스트 로컬 Registry와 캐시된 Registry 저장소라는 조건 때문에 32MB 전송도 매우 빨랐다. 그래서 이 값으로 ECR이나 외부 Registry의 pull 시간을 예측하면 안 된다.
3. Pod 생성부터 Ready까지
| 이미지 | Cold 중앙값 | Cold p95 | Warm 중앙값 | Warm p95 |
|---|---|---|---|---|
| Alpine | 1,432ms | 1,487ms | 1,530ms | 1,575ms |
| Debian Slim | 1,453ms | 1,503ms | 1,500ms | 1,574ms |
| Distroless | 1,468ms | 1,554ms | 1,525ms | 1,610ms |

세 이미지의 cold 중앙값 차이는 최대 약 37ms였다. 이미지 크기는 최대 10.5배 차이 났지만 전체 Pod Ready 시간은 2.6% 범위 안에 있었다.
또한 이번 표에서는 warm 중앙값이 cold보다 47~98ms 느리게 나왔다. 캐시가 시작을 느리게 만들었다는 뜻은 아니다. 차이가 readiness probe의 1초 주기와 실행 순서에 따른 sandbox 준비 변동보다 작고, 표본도 각 10회뿐이다. 이 조건에서는 warm의 이점을 구분할 만큼 image pull 비용이 크지 않았다고 해석하는 편이 타당하다.
이미지가 작다고 항상 빠른 것은 아니다
이번 실험이 보여준 핵심은 세 가지다.
첫째, 전체 Pod 시작 시간에서 image pull이 차지하는 비율을 봐야 한다. 수십 ms짜리 pull을 20ms 줄여도 1초 probe 주기가 지배하는 환경에서는 사용자 체감이 거의 달라지지 않는다.
둘째, 이미지 크기와 레이어 구조는 별개의 변수다. 가장 작은 Distroless는 14개 레이어였고 pull 중앙값은 53ms로 가장 길었다. 작은 파일 하나를 받는 상황과 작은 레이어 여러 개를 처리하는 상황은 같지 않다.
셋째, 새 Node에서는 상황이 달라질 수 있다. 이 실험에는 EC2 Node Provisioning, ECR 인증, NAT Gateway 또는 VPC Endpoint, 가용 영역 간 네트워크, 디스크 초기화가 없다. 실제 EKS scale-out에서는 다음 시간이 합쳐진다.
Node Provisioning
+ Node Ready
+ Pod Scheduling
+ Network / Storage 준비
+ Image Pull
+ Container Start
+ Application 초기화
+ Readiness Probe
AWS가 소개하는 Kubernetes upstream Pod startup SLO도 이미 준비된 Worker Node를 전제로 하며 image pull과 init container 시간은 제외한다. 따라서 EKS 오토스케일링의 전체 대기 시간과 같은 숫자로 보면 안 된다. (Amazon EKS Kubernetes Upstream SLOs)
그래서 어떤 Base Image를 선택해야 할까?
이 실험만으로 하나를 무조건 고를 수는 없다.
- Distroless: 정적 바이너리를 배포하고 shell 없는 운영 방식, non-root 실행, 별도 디버그 컨테이너를 받아들일 수 있을 때 적합하다.
- Alpine: 작은 이미지와 기본 shell 도구가 모두 필요하고, musl 호환성을 검증할 수 있을 때 유용하다.
- Debian Slim: glibc 호환성, 익숙한 진단 도구, 운영 편의를 우선할 때 현실적인 선택이다.
Pod 시작 속도만 놓고 보면 세 이미지의 차이는 이번 로컬 조건에서 사실상 작았다. 하지만 Distroless와 Alpine은 저장·전송량과 공격 표면 측면에서 여전히 가치가 있다. 반대로 단지 “몇 MB 더 작다”는 이유로 디버깅과 호환성 비용을 무시해서도 안 된다.
내 결론은 다음과 같다.
Base Image는 크기 순위로 고르는 것이 아니라, cold pull이 실제 병목인지 측정한 뒤 호환성·보안·운영성을 함께 보고 선택해야 한다.
EKS에서는 무엇을 추가로 측정해야 할까?
후속 실험에서는 같은 세 이미지를 ECR에 올리고 새 Node가 필요한 scale-out을 만들어 다음 구간을 분리할 예정이다.
- Autoscaler 또는 Karpenter가 Node를 요청한 시각
- EC2 생성부터 Node
Ready까지 PodScheduledPodReadyToStartContainersPulling과Pulled이벤트ContainersReady와Ready- 캐시된 기존 Node와 새 Node의 차이
그때는 이미지 크기의 영향이 이번 로컬 실험보다 커질 가능성이 있다. 다만 결과는 ECR 위치, EC2 네트워크 성능, Node 디스크, 이미지 레이어 재사용 여부에 따라 달라질 것이다.
자주 묻는 질문
Docker 이미지가 작으면 Kubernetes Pod가 무조건 빨리 시작하나?
아니다. Image Pull이 병목인 cold start에서는 유리하지만, 이미지가 캐시됐거나 sandbox 준비와 애플리케이션 초기화가 더 오래 걸리면 전체 Ready 시간 차이는 작아질 수 있다.
Alpine과 Distroless 중 무엇이 더 빠른가?
이번 조건에서는 cold Ready 중앙값이 Alpine 1,432ms, Distroless 1,468ms로 거의 비슷했다. 이 정도 차이로 일반적인 우열을 결론 내릴 수 없다. 호환성, shell 필요 여부, 보안 정책을 함께 봐야 한다.
imagePullPolicy: Always면 매번 전체 이미지를 다시 받나?
항상 Registry에 이미지 참조를 확인하지만, Container Runtime은 이미 존재하는 레이어를 재사용할 수 있다. 완전한 cold pull을 확인하려면 정책 이름만 보지 말고 Node의 이미지 상태와 Kubelet 이벤트를 함께 확인해야 한다.
로컬 K3s 결과를 EKS에 그대로 적용할 수 있나?
적용할 수 없다. 이 실험은 같은 호스트의 로컬 Registry를 사용했으며 새 EC2 Node 생성 시간도 포함하지 않았다. EKS에서는 ECR 네트워크 경로와 Node Provisioning을 포함해 다시 측정해야 한다.
측정 원본에는 60회 실행의 개별 시간, Kubernetes 이벤트, 이미지 digest와 동일 바이너리 SHA-256을 함께 보관했다. 평균만으로 결론을 만들지 않기 위해 본문에는 중앙값과 p95를 함께 제시했다.
'IT > Cloud' 카테고리의 다른 글
| 지능형 에이전트에서 에이전틱 AI로 (0) | 2026.09.26 |
|---|---|
| [Datadog] Prometheus Cardinality가 비용이 되는 순간 (0) | 2026.07.01 |
| [Prometheus] Label 하나가 시스템을 느리게 만든다 - 2. Cardinality (0) | 2026.06.21 |
| [Prometheus] Label 하나가 시스템을 느리게 만든다 - 1. Metric, Label, TimeSeries (0) | 2026.06.21 |
| [Prometheus] 왜 다들 프로메테우스를 사용할까 (0) | 2026.05.31 |