Finding Where the Cache Breaks
한국어 원문으로 표시합니다.
이 실습은 진짜 VM 에서 돕니다
이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이
따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라
진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가
되고 docker exec 도 docker logs 도 그대로 동작합니다.
예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라 컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어 보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.
알아 둘 것이 둘 있습니다.
- 처음 뜨는 데 1분 남짓 걸립니다. VM 이 부팅하고 도커를 설치하기 때문입니다. 파드 실습(보통 40초)보다 느립니다.
- 브라우저 미리보기가 없습니다. VM 으로 들어오는 연결은 채점 포트
하나만 열려 있습니다. 웹 서버를 띄웠다면 VM 안에서
curl로 확인하세요.
목표
캐시가 어디서 깨지는지 이미지 ID 로 증명하고, 유니온 파일시스템에서 삭제가 크기를 줄이지 못한다는 것을 두 이미지의 크기 차이로 확인합니다.
왜 중요한가
"빌드가 느려요"라는 문제는 캐시 설정을 켜고 끄는 문제가 아니라 순서 문제인 경우가 압도적으로 많습니다. 캐시 키가 부모 다이제스트에 연쇄로 묶여 있다는 사실 하나만 이해하면, 고칠 지점은 언제나 "캐시가 처음으로 깨지는 그 한 줄"이라는 것이 자명해집니다. 그 위는 건드릴 필요가 없고 그 아래는 손댈 방법이 없습니다. 크기도 마찬가지입니다. 레이어는 되감기지 않으므로, 최종 이미지에 들어가면 안 되는 것은 애초에 그 레이어에서 만들지 않아야 합니다.
단계
/root/build2를 만들고,nginx:1.27-alpine의 레이어 개수를 세어/root/build2/nginx-layers.txt에 숫자만 적습니다.alpine:3.20의 레이어별 히스토리를 잘리지 않게/root/build2/alpine-history.txt로 저장합니다.docker history가 보여 주는 표는 이미지 config 블롭의history배열 그 자체입니다. 이 상자에서는skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]'로 같은 값을 읽습니다./root/build2에deps.txt와src/main.txt를 만들고, 두 파일을 각각 COPY 하는Dockerfile을 작성합니다.labhub/cache:v1로 빌드한 뒤 이미지 ID 를/root/build2/id1.txt에 적고, 아무것도 바꾸지 않고 다시 빌드해 ID 를/root/build2/id2.txt에 적습니다. 두 값은 같아야 합니다.deps.txt의 내용을 바꾸고labhub/cache:v2로 빌드해 ID 를/root/build2/id3.txt에 적습니다.id1.txt와 달라야 합니다./root/build2/ordered.Dockerfile을 만들어COPY deps.txt가COPY src/보다 먼저 나오게 하고labhub/cache:v3로 빌드합니다./root/build2/big.log를 만들고,/root/build2/.dockerignore에 로그 파일 제외 규칙을 넣습니다. 컨텍스트 전체를/ctx로 복사하는 Dockerfile 로labhub/cache:v4를 빌드하면 이미지 안에/ctx/deps.txt는 있고/ctx/big.log는 없어야 합니다.- 20MB 파일을 만든 뒤 다음 RUN 에서 지우는 이미지를
labhub/fat:bad로, 같은 RUN 안에서 지우는 이미지를labhub/fat:good으로 빌드합니다. /root/build2/cache.md에 아래 세 줄을 실제 값으로 적습니다.
bad_mb=<labhub/fat:bad 크기(MB)>
good_mb=<labhub/fat:good 크기(MB)>
diff_mb=<두 값의 차>
참고
docker image inspect <이미지> | jq -r '.[0].Id'로 이미지 ID 를,jq -r '.[0].Size'로 바이트 크기를 얻습니다.- 20MB 파일은
dd if=/dev/zero of=/big bs=1M count=20으로 만들 수 있습니다. - 흔한 실수 1: 3단계에서 빌드 사이에 파일을 건드리면 ID 가 달라집니다.
- 흔한 실수 2: 8단계의 MB 는 1048576 바이트 기준입니다. 소수점을 버린 정수로 적으세요.
이미지의 레이어 수 세기
/root/build2 를 만들고, nginx:1.27-alpine 의 레이어 개수를 세어 /root/build2/nginx-layers.txt 에 숫자만 적습니다.
inspect 결과의 RootFS.Layers 는 배열입니다. jq 로 길이를 구할 수 있습니다.
레이어별 명령 들여다보기
alpine:3.20 의 레이어별 히스토리를 잘리지 않게 /root/build2/alpine-history.txt 로 저장합니다.
docker history 가 보여 주는 표는 이미지 config 블롭의 history 배열 그 자체입니다. 이 상자에서는 skopeo inspect --config oci-archive:/opt/images/alpine_3.20.tar | jq -r '.history[]' 로 같은 값을 읽습니다.
각 레이어가 어떤 명령으로 만들어졌고 얼마를 차지하는지 보여 주는 하위 명령이 있습니다. 잘리지 않게 저장하세요.
바뀐 게 없으면 같은 이미지
/root/build2 에 deps.txt 와 src/main.txt 를 만들고, 두 파일을 각각 COPY 하는 Dockerfile 을 작성합니다. labhub/cache:v1 로 빌드한 뒤 이미지 ID 를 /root/build2/id1.txt 에 적고, 아무것도 바꾸지 않고 다시 빌드해 ID 를 /root/build2/id2.txt 에 적습니다. 두 값은 같아야 합니다.
빌드를 두 번 하고 각각의 이미지 ID 를 파일에 남기세요. 모든 레이어가 캐시에 적중하면 결과 이미지도 동일합니다.
앞 레이어를 깨뜨려 보기
deps.txt 의 내용을 바꾸고 labhub/cache:v2 로 빌드해 ID 를 /root/build2/id3.txt 에 적습니다. id1.txt 와 달라야 합니다.
가장 위에서 COPY 하는 파일을 고치면 그 아래가 전부 다시 실행됩니다. 새 태그로 빌드하고 ID 를 비교하세요.
변경 빈도 순으로 배치하기
/root/build2/ordered.Dockerfile 을 만들어 COPY deps.txt 가 COPY src/ 보다 먼저 나오게 하고 labhub/cache:v3 로 빌드합니다.
거의 안 바뀌는 것(의존성 목록)을 위에, 자주 바뀌는 것(소스)을 아래에 둡니다. 채점은 두 COPY 의 행 번호를 비교합니다.
빌드 컨텍스트에서 빼기
/root/build2/big.log 를 만들고, /root/build2/.dockerignore 에 로그 파일 제외 규칙을 넣습니다. 컨텍스트 전체를 /ctx 로 복사하는 Dockerfile 로 labhub/cache:v4 를 빌드하면 이미지 안에 /ctx/deps.txt 는 있고 /ctx/big.log 는 없어야 합니다.
제외 규칙 파일은 빌드 컨텍스트 루트에 둡니다. 필요한 파일까지 빼면 빌드가 깨집니다.
삭제해도 줄지 않는다는 증거
20MB 파일을 만든 뒤 다음 RUN 에서 지우는 이미지를 labhub/fat:bad 로, 같은 RUN 안에서 지우는 이미지를 labhub/fat:good 으로 빌드합니다.
만들고 다음 RUN 에서 지운 이미지와, 만들고 같은 RUN 안에서 지운 이미지를 각각 빌드해 크기를 비교하세요.
측정값 정리
/root/build2/cache.md 에 아래 세 줄을 실제 값으로 적습니다.
세 값 모두 실제 이미지 크기에서 계산합니다. 1MB = 1048576 바이트 기준으로 정수 MB 를 적으세요.