어떤 패키지가 Docker 이미지의 용량을 늘리고 있나요?
Source: Dev.to
위 링크에 포함된 글의 본문을 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다.
layer‑blame: 이미지 레이어에 대한 git blame
layer‑blame은 Docker 이미지의 모든 바이트를 해당 바이트를 도입한 패키지에 할당합니다.
예시: Alpine
docker save alpine:3.20 -o alpine.tar
layer-blame alpine.tar
Image total: 8.4 MB across 1 layers · package attribution: 100%
Layer 0 8.4 MB ADD alpine-minirootfs-3.20.10-aarch64.tar.gz /
4.8 MB libcrypto3 pkg ← largest line highlighted
911.7 KB libssl3 pkg
906.0 KB busybox pkg
706.5 KB musl pkg
327.1 KB apk-tools pkg
이제 레이어의 모든 바이트에 소유자가 지정됩니다; libcrypto3가 8.4 MB 중 4.8 MB를 차지합니다.
다른 도구와의 비교
| 도구 | 표시 내용 |
|---|---|
docker history | 레이어 크기만 표시하고 내용은 표시하지 않음 |
dive | 레이어를 파일 단위로 인터랙티브하게 탐색 |
| layer‑blame | 모든 바이트에 대한 패키지 수준 할당을 제공 (비인터랙티브 보고서) |
dive는 탐색에 좋고, layer‑blame은 할당에 좋습니다. 두 도구를 함께 사용하세요.
예시: python:3.12‑slim
docker save python:3.12-slim -o py.tar
layer-blame --top 5 py.tar
Image total: 137.7 MB across 4 layers · package attribution: 69%
Layer 0 95.8 MB # debian.sh --arch 'arm64' out/ 'trixie' ...
22.5 MB libc6 pkg
9.1 MB coreutils pkg
7.4 MB perl-base pkg
7.3 MB libssl3t64 pkg
6.6 MB util-linux pkg
Layer 2 38.2 MB RUN /bin/sh -c set -eux; savedAptMark="$(apt-mark showmanual)"; …
6.3 MB /usr/local/lib/libpython3.12.so.1.0 file
1.8 MB /usr/local/lib/python3.12/ensurepip/_bundled/pip-...whl file
1.1 MB /usr/local/lib/python3.12/lib-dynload/unicodedata...so file
…
Layer 2에서는 가장 큰 기여도가 파일로 보고됩니다. 이는 이미지가 소스에서 Python을 빌드했기 때문에 해당 바이트가 어떤 dpkg 패키지에도 속하지 않기 때문입니다. “package attribution: 69%” 헤더는 이미지의 69 %가 패키지에 깔끔하게 매핑된다는 것을 알려 주며, 나머지는 더 자세히 살펴볼 필요가 있음을 의미합니다.
사용법
docker save -o image.tar
layer-blame [flags] image.tar
Docker 데몬이 필요 없습니다 – layer‑blame은 저장된 tarball(또는 일반 OCI 레이아웃)을 직접 파싱하므로 CI에 친화적입니다.
플래그
| 플래그 | 기본값 | 설명 |
|---|---|---|
--top N | 5 | 각 레이어별 상위 N명의 기여자를 표시합니다 |
--no-color | false | ANSI 색상을 비활성화합니다 (NO_COLOR와 비‑TTY 출력도 존중) |
--version | false | 버전, 커밋, 빌드 날짜를 출력합니다 |
작동 방식 (다섯 단계의 결정적 절차)
-
Load the tarball / OCI layout via
go-containerregistry, normalizing Docker‑save, BuildKit, and containerd/OCI formats.
→go-containerregistry를 통해 tarball / OCI 레이아웃을 로드하고, Docker‑save, BuildKit, containerd/OCI 포맷을 정규화합니다. -
Walk each layer’s filesystem diff, recording every added file and its size. Whiteout (deletion) markers are ignored.
→ 각 레이어의 파일시스템 차이를 순회하면서 추가된 모든 파일과 그 크기를 기록합니다. Whiteout(삭제) 마커는 무시됩니다. -
Build a file→package index from the image’s own package databases:
→ 이미지 자체의 패키지 데이터베이스에서 파일→패키지 인덱스를 구축합니다:- Alpine –
/lib/apk/db/installed - Debian/Ubuntu –
/var/lib/dpkg/info/*.list
- Alpine –
-
Group added bytes by owning package. Files with no owner are reported individually, ensuring large unattributed artifacts surface by name.
→ 소유 패키지별로 추가된 바이트를 그룹화합니다. 소유자가 없는 파일은 개별적으로 보고되어, 이름으로 큰 무소속 아티팩트가 드러나도록 합니다. -
Map each layer back to the Dockerfile instruction (
created_byfrom the image config) and print the table.
→ 이미지 설정의created_by정보를 사용해 각 레이어를 Dockerfile 명령으로 매핑하고 표를 출력합니다.
The novel part is step 4 – the JOIN between added bytes and the package database. Everything else is plumbing.
→ 새로운 부분은 4단계, 즉 추가된 바이트와 패키지 데이터베이스 간의 JOIN입니다. 나머지는 모두 파이프라인에 해당합니다.
제한 사항 및 주의점
- Scratch / distroless 이미지는 패키지 데이터베이스가 없으므로, 귀속이 파일 수준으로 되돌아갑니다.
- 멀티‑스테이지
COPY --from파일은 원본 패키지 정보를 잃어버리며, 대상 레이어에서 귀속되지 않은 것으로 표시됩니다. - 현재는 apk(Alpine)와 dpkg(Debian/Ubuntu)만 지원합니다. RPM 및 언어‑별 관리자(npm, pip 등)는 아직 구현되지 않았습니다.
- 단일 Go 바이너리이며, MIT‑licensed.
설치
사전 빌드 바이너리 (Linux/macOS/Windows, amd64 + arm64)
curl -sSfL https://github.com/mk668a/layer-blame/releases/latest/download/layer-blame__linux_amd64.tar.gz \
| tar -xz layer-blame
Go로 빌드
go install github.com/mk668a/layer-blame@latest
저장소:
언제 사용할까
다음에 PR이 이미지 크기를 부풀리고 리뷰어가 “왜 이게 800 MB인가요?”라고 물으면, 한 번의 명령만 실행해 문제를 일으키는 바이트에 연결된 패키지 이름을 확인하세요—dive로 수동으로 파고들며 보낸 오후를 절약할 수 있습니다. 특이한 이미지에서 예상치 못한 귀속을 발견하면 이슈를 열어 주세요; 가장 흥미로운 경우는 (RPM, 단계 간 COPY 등) 가장자리 케이스입니다.