LabHub
배우기 러닝패스 코스

Building Images

Cutting It Down With Multi-Stage Builds

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

이 실습은 진짜 VM 에서 돕니다

이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이 따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라 진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가 되고 docker execdocker logs 도 그대로 동작합니다.

예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라 컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어 보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.

알아 둘 것이 둘 있습니다.

목표

빌더 스테이지에서 만든 산출물만 최종 이미지로 옮겨, 같은 결과물을 훨씬 작은 이미지로 만드는 과정을 크기 숫자로 확인합니다.

왜 중요한가

이미지 크기는 미관의 문제가 아니라 배포 속도와 롤백 속도의 문제입니다. 노드가 스케일아웃될 때마다, 롤백할 때마다 그 크기를 다시 받습니다. 그리고 크기를 줄이는 가장 큰 지렛대는 압축이나 정리가 아니라 "최종 이미지에 들어가면 안 되는 것을 애초에 그 스테이지에서 만들지 않는 것"입니다. 레이어는 되감기지 않기 때문에, 이 원칙을 어기면 나중에 아무리 지워도 크기가 돌아오지 않습니다.

단계

  1. /root/build3 을 만들고 /root/build3/Dockerfile 에 스테이지를 둘 만듭니다. 첫 스테이지는 python:3.12-alpine 을 쓰고 이름은 builder 이며, /out/app.txtbuilt-in-builder 를 써 넣습니다. 두 번째 스테이지는 alpine:3.20 입니다. labhub/ms:v1 로 빌드합니다.
  2. 최종 스테이지에서 빌더의 /out/app.txt/app/app.txt 로 복사하도록 고치고 labhub/ms:v2 로 빌드합니다. 실행해서 파일을 읽으면 built-in-builder 가 나와야 합니다.
  3. /root/build3/fat.Dockerfile단일 스테이지로 같은 결과물을 만드는 버전(베이스는 python:3.12-alpine)을 작성하고 labhub/ms:fat 으로 빌드합니다.
  4. 첫 스테이지만 빌드해 labhub/ms:builder 이미지를 만듭니다. 그 안에는 /out/app.txt 가 있어야 합니다.
  5. Dockerfile 최종 스테이지에 busybox:1.36 이미지에서 /bin/busybox/app/busybox 로 직접 복사하는 줄을 추가하고 labhub/ms:v3 로 빌드합니다.
  6. labhub/ms:v3 를 실행해 python3 가 없는지 확인합니다. 이미지 크기는 60MB 미만이어야 합니다.
  7. /root/build3/scratch.Dockerfile 을 만들어 FROM scratch 위에 busybox:1.36/bin/busybox 만 올리고, 인자로 받은 문자열을 그대로 출력하도록 ENTRYPOINT배열 형식으로 지정합니다. labhub/ms:scratch 로 빌드하고, scratch-works 를 인자로 주면 그 문자열이 출력되어야 합니다.
  8. /root/build3/size.md 에 아래 네 줄을 실제 값으로 적습니다.
fat_mb=<labhub/ms:fat 크기(MB)>
slim_mb=<labhub/ms:v3 크기(MB)>
scratch_mb=<labhub/ms:scratch 크기(MB)>
reduction_pct=<(fat_mb - scratch_mb) / fat_mb * 100 의 정수부>

참고

스테이지 두 개 만들기

/root/build3 을 만들고 /root/build3/Dockerfile 에 스테이지를 둘 만듭니다. 첫 스테이지는 python:3.12-alpine 을 쓰고 이름은 builder 이며, /out/app.txtbuilt-in-builder 를 써 넣습니다. 두 번째 스테이지는 alpine:3.20 입니다. labhub/ms:v1 로 빌드합니다.

FROM 을 두 번 쓰고 앞 스테이지에 이름을 붙입니다. 이름은 나중에 COPY 에서 참조합니다.

산출물만 가져오기

최종 스테이지에서 빌더의 /out/app.txt/app/app.txt 로 복사하도록 고치고 labhub/ms:v2 로 빌드합니다. 실행해서 파일을 읽으면 built-in-builder 가 나와야 합니다.

빌더 스테이지에서 만든 파일만 골라 복사합니다. 스테이지 전체를 복사하면 멀티스테이지를 쓴 의미가 없습니다.

단일 스테이지와 크기 비교

/root/build3/fat.Dockerfile단일 스테이지로 같은 결과물을 만드는 버전(베이스는 python:3.12-alpine)을 작성하고 labhub/ms:fat 으로 빌드합니다.

같은 결과물을 무거운 베이스 위에 그대로 남긴 이미지와 비교하세요. 30% 이상 줄어야 합니다.

중간 스테이지만 빌드하기

첫 스테이지만 빌드해 labhub/ms:builder 이미지를 만듭니다. 그 안에는 /out/app.txt 가 있어야 합니다.

디버깅할 때 빌더 스테이지만 따로 이미지로 뽑을 수 있습니다. 최종 스테이지까지 가지 않고 멈추는 옵션이 있습니다.

다른 이미지에서 직접 복사하기

Dockerfile 최종 스테이지에 busybox:1.36 이미지에서 /bin/busybox/app/busybox 로 직접 복사하는 줄을 추가하고 labhub/ms:v3 로 빌드합니다.

--from 은 스테이지 이름만 받는 것이 아닙니다. 이미지 참조를 그대로 줄 수도 있습니다.

빌드 도구 남기지 않기

labhub/ms:v3 를 실행해 python3 가 없는지 확인합니다. 이미지 크기는 60MB 미만이어야 합니다.

최종 스테이지에서 빌드에만 쓰인 런타임이 사라졌는지 실제로 실행해서 확인하세요.

빈 베이스 위에 올리기

/root/build3/scratch.Dockerfile 을 만들어 FROM scratch 위에 busybox:1.36/bin/busybox 만 올리고, 인자로 받은 문자열을 그대로 출력하도록 ENTRYPOINT배열 형식으로 지정합니다. labhub/ms:scratch 로 빌드하고, scratch-works 를 인자로 주면 그 문자열이 출력되어야 합니다.

scratch 에는 셸이 없으므로 ENTRYPOINT 는 반드시 배열 형식이어야 합니다. 정적으로 링크된 바이너리를 골라야 합니다.

크기 보고서

/root/build3/size.md 에 아래 네 줄을 실제 값으로 적습니다.

세 이미지의 실제 크기를 재서 적고 감소율을 계산하세요. 1MB = 1048576 바이트입니다.