Dockerfile은 Docker 이미지를 빌드할 때 사용하는 스크립트다.
이미지를 빌드할 때 Docker는 Dockerfile 의 각 명령어를 실행하고 그 결과를 이미지 Layer 별로 캐싱한다.
이미지 Layer 캐싱을 통해 빌드 시간을 절약할 수 있다.
도커 이미지 의 Layer 을 적절히 나누어 캐싱하기 위해서는 캐싱 가능한 명령어를 최상단에 배치하고, 변경 가능성이 높은 명령어를 뒤로 배치하는 두가지만 지켜도 좋다. 또한 도커 공식 홈페이지에서 안내하는 멀티스테이지 빌드와 이미지 레이어를 적절히 분리하는 과정까지 추가하여 더 좋은 퍼포먼스를 낼 수 있으며 이러한 전략을 통해 이미지 빌드 성능을 높이고 불필요한 시간과 리소스를 절약할 수 있다.
Multi-stage builds
Learn about multi-stage builds and how you can use them to improve your builds and get smaller images
docs.docker.com
캐싱 가능한 명령어를 최상단에 배치
Dockerfile에서 자주 변경되지 않는 부분을 최상단에 배치해야 한다.
예를 들어 패키지 설치처럼 의존성 설치 단계를 최상단에 위치시켜 변경 사항이 있는 경우에만 재실행되도록 하고, 환경변수 등의 작업도 최상단에 위치시켜야 한다.
FROM node:18 # 베이스 이미지
WORKDIR /app # 작업 디렉토리 설정 ~/ 디렉토리라 보면 됨
ENV NODE_ENV=development # 환경변수 등록 (캐싱 가능한 영역)
COPY package.json package-lock.json ./ # 파일 복사 (파일이 수정되지 않으면 캐싱 가능한 영역)
RUN npm install # 의존성 설치 (파일이 수정되지 않으면 캐싱 가능한 영역)
...
변경 가능성이 높은 명령어를 뒤로 배치
변경 가능성이 높은 명령어는 Dockerfile의 뒤에 배치해야 한다. 이렇게 하면 변경하지 않은 부분은 캐싱을 활용하고, 나머지만 다시 빌드된다.
예를 들어 파일 복사, 어플리케이션 빌드 등이 있을 것 같다.
...
COPY package.json package-lock.json ./ # 파일 복사 (파일이 수정되지 않으면 캐싱 가능한 영역)
RUN npm install # 의존성 설치 (파일이 수정되지 않으면 캐싱 가능한 영역)
COPY . . # 코드 복사 (변경 가능성이 높아 캐싱이 어려운 영역)
RUN npm run build # 빌드 (캐싱이 어려운 영역)
CMD ["npm", "start"] # 실행 커멘드 (캐싱이 어려운 영역)
멀티스테이지 빌드 활용하기
멀티스테이지 빌드를 활용하면 최종 이미지에는 필요한 바이너리만 포함시킬 수 있다. 이럴 경우 이미지의 크기를 줄일 수 있고, 빌드 속도를 높일 수 있습니다. 이때 마지막 이미지 빌드할 때는 필요한 의존성 패키지만 존재해야 하고, 불필요한 파일 등은 제외시켜야 한다.
# 실행파일을 만들고 실행한다.
FROM golang:1.21
WORKDIR /src
COPY <<EOF ./main.go
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go
CMD ["/bin/hello"]
# 실행파일을 만든다.
FROM golang:1.21
WORKDIR /src
COPY <<EOF ./main.go
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go
# 실행할 파일만 복사해가서 실행한다. (멀티스페이지 빌드)
FROM scratch
COPY --from=0 /bin/hello /bin/hello
CMD ["/bin/hello"]
첫 번째 도커 파일을 사용하여 생성한 이미지의 용량은 849MB 이며, 두번째 도커 파일의 이미지의 용량은 1.85MB 이다.
적절한 이미지 레이어 분리
Docker 이미지를 여러 레이어로 나누면 캐싱을 적극 활용할 수 있다. 변경 빈도가 다른 부분을 개별 레이어로 나누어서 변경 사항이 있는 부분만 다시 빌드하고 재사용할 수 도 있다.
FROM node:18 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
# 멀티스테이지 빌드를 통해 레이어를 나누었다.
# 위에는 빌드용이고 아래는 실행용이다.
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
아래는 도커 허브에 있는 우분투 이미지의 레이어이다.
캐시 무효화하기
캐시된 이미지를 무효화하여 새로운 빌드를 강제로 실행해야 할 때가 있다. 이때는 --no-cache 플래그를 사용하여 빌드하면 된다.
아래는 캐싱이 되어가는 과정이다.
-- 도커파일 내부의 명령어가 모두 실행되었다.
$ docker build -t stage-builds-1 .
[+] Building 16.9s (13/13) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 258B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1 3.1s
=> docker-image://docker.io/docker/dockerfile:1@sha256:dbbd5e059e8a07ff7ea6233b213b36aa516b4c53c645f1817a4dd18b83cbea56 1.1s
=> => resolve docker.io/docker/dockerfile:1@sha256:dbbd5e059e8a07ff7ea6233b213b36aa516b4c53c645f1817a4dd18b83cbea56 0.0s
=> => sha256:7938f7c0984b428949317499eff7773a7294fdc03c085c649e24d1e3a452fba7 11.23MB / 11.23MB 1.0s
=> => sha256:dbbd5e059e8a07ff7ea6233b213b36aa516b4c53c645f1817a4dd18b83cbea56 8.40kB / 8.40kB 0.0s
=> => sha256:ab4d84ec8cad8a7b0e8b6ad1fb49536f000b86e30993c74a251bb16f3e6e3f5b 482B / 482B 0.0s
=> => sha256:6bb827dc58ef706c3d261e31d3a2ebd9965fa640ec3bc1cef4c98d8068b55685 1.26kB / 1.26kB 0.0s
=> => extracting sha256:7938f7c0984b428949317499eff7773a7294fdc03c085c649e24d1e3a452fba7 0.1s
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load metadata for docker.io/library/golang:1.21 2.2s
=> [internal] load .dockerignore 0.0s
=> [internal] preparing inline document 0.0s
=> [1/4] FROM docker.io/library/golang:1.21@sha256:81811f8a883e238666dbadee6928ae2902243a3cd3f3e860f21c102543c6b5a7 7.5s
=> => resolve docker.io/library/golang:1.21@sha256:81811f8a883e238666dbadee6928ae2902243a3cd3f3e860f21c102543c6b5a7 0.0s
=> => sha256:81811f8a883e238666dbadee6928ae2902243a3cd3f3e860f21c102543c6b5a7 2.13kB / 2.13kB 0.0s
=> => sha256:d606747c3d6f91a6e4a073efb2478c1dd5b422a4ad5b7fd8dffc3f61cc396a14 1.79kB / 1.79kB 0.0s
=> => sha256:374850c6db1702573c7004d630027931be318b2d71cb28e890e2fcd0f0730712 23.58MB / 23.58MB 2.7s
=> => sha256:8abc7e3032aeb13a3562d096ef133a12f4741e7fec06c0f7e22f3c23229cc90c 2.89kB / 2.89kB 0.0s
=> => sha256:1e92f3a395ff98a929e797a3c392bb6d0f05531068d34b81d3cd41ed6ce82ca4 49.60MB / 49.60MB 1.5s
=> => sha256:421c44fab18bc9f4c62ca481e074d50b3a036e7c95c7607b6d036c34d67c5264 63.99MB / 63.99MB 3.2s
=> => extracting sha256:1e92f3a395ff98a929e797a3c392bb6d0f05531068d34b81d3cd41ed6ce82ca4 0.9s
=> => sha256:6cbe4bb4a0d8b43491c9cd09a1939fa98f6e0c6bb41b83b515758995342fc910 86.41MB / 86.41MB 4.3s
=> => sha256:0a12a9cc36370776dd253aef34583c7f7faf7f322a4276ac347cf1fc956889e8 64.11MB / 64.11MB 6.1s
=> => extracting sha256:374850c6db1702573c7004d630027931be318b2d71cb28e890e2fcd0f0730712 0.3s
=> => sha256:45fbff829e9908764b70d1bd05228741beb91f547505b2f1c395d05a427371bb 174B / 174B 3.4s
=> => extracting sha256:421c44fab18bc9f4c62ca481e074d50b3a036e7c95c7607b6d036c34d67c5264 1.1s
=> => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B 3.7s
=> => extracting sha256:6cbe4bb4a0d8b43491c9cd09a1939fa98f6e0c6bb41b83b515758995342fc910 1.2s
=> => extracting sha256:0a12a9cc36370776dd253aef34583c7f7faf7f322a4276ac347cf1fc956889e8 1.1s
=> => extracting sha256:45fbff829e9908764b70d1bd05228741beb91f547505b2f1c395d05a427371bb 0.0s
=> => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 0.0s
=> [2/4] WORKDIR /src 0.5s
=> [3/4] COPY <<EOF ./main.go 0.0s
=> [4/4] RUN go build -o /bin/hello ./main.go 2.2s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:c0bb4d7e57b5daaaeeacd9e63b6fb1b848a692c335e8b188cfd4daed833a5daa 0.0s
=> => naming to docker.io/library/stage-builds-1 0.0s
-- 두번째 빌드에는 CACHED 가 있다.
docker build -t stage-builds-2 .
[+] Building 2.0s (14/14) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 307B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1 0.9s
=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:dbbd5e059e8a07ff7ea6233b213b36aa516b4c53c645f1817a4dd18b83cbea56 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load metadata for docker.io/library/golang:1.21 0.8s
=> [internal] load .dockerignore 0.0s
=> [internal] preparing inline document 0.0s
=> [stage-0 1/4] FROM docker.io/library/golang:1.21@sha256:81811f8a883e238666dbadee6928ae2902243a3cd3f3e860f21c102543c6b5a7 0.0s
=> CACHED [stage-0 2/4] WORKDIR /src 0.0s
=> CACHED [stage-0 3/4] COPY <<EOF ./main.go 0.0s
=> CACHED [stage-0 4/4] RUN go build -o /bin/hello ./main.go 0.0s
=> [stage-1 1/1] COPY --from=0 /bin/hello /bin/hello 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:dfecaec40d1d53ae19877dee6e30cfcfd46ad221bdb2c1c76fbfd9830ff3d0e3 0.0s
=> => naming to docker.io/library/stage-builds-2
--no-cache 옵션
Dockerfile에 지정된 모든 캐시를 무시하여 모든 레이어를 새로 생성하여 이미지를 빌드한다. 즉 강제로 새로운 이미지를 빌드할 때 사용된다.
추가적으로 캐싱 전략에 대해 알고 싶을 경우 speed-up-your-docker-builds-with-cache-from 포스팅을 참고하면 좋을 것 같다.
Speed up your Docker builds with –cache-from
Speed up your Docker builds with –cache-from
lipanski.com
'DevOps > 도커 (Docker)' 카테고리의 다른 글
[Docker] - 006. Docker Swarm 이란 (0) | 2024.05.06 |
---|---|
[Docker] - 005. Docker Compose 활용하기 (0) | 2024.05.04 |
[Docker] - 004. Multi Stage Build 와 Base 이미지 만들기 (0) | 2024.04.19 |
[Docker] - 002. Docker CLI 와 이미지 만들기 (0) | 2024.04.17 |
[Docker] - 001. 도커 vs 가상화(VM)의 차이 (0) | 2024.04.09 |