https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

 

Best practices for writing Dockerfiles

 

docs.docker.com

 

수시로 변경될 수 있는 컨테이너를 생성 (Create ephemeral containers)

컨테이너는 멈추고 파기될 수 있고 언제든 바뀔수 있기에 최소한의 셋업과 설정으로 구성해야 하므로

도커 이미지를 정의하는 Dockerfile은 이를 염두해두고 생성해야한다.

 

build context에 대한 이해 (Understand build context)

`docker build` 명령을 실행하는 현재 디렉터리를 build context라고 하고 이 디렉터리안에 Dockerfile이 있습니다.

이 명령을 실행할 때 옵션으로 -f --file string(Default is 'PATH/Dockerfile')을 사용하면 다른 디렉터리에 있는 Dockerfile을 지정할 수 있다.

Dockerfile이 저장된 디렉터리의 위치는 상관이 없고,

그 디렉터리 안에 있는 모든 파일과 디렉터리들이 build context로서 도커 데몬에 보내지게 된다.

# Create a directory for the build context and cd into it. 
# Write “hello” into a text file named hello and 
# create a Dockerfile that runs cat on it. 
# Build the image from within the build context (.):

 mkdir myproject && cd myproject
 echo "hello" > hello
 echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile
 docker build -t helloapp:v1 .

# Move Dockerfile and hello into separate directories 
# and build a second version of the image 
# (without relying on cache from the last build). 
# Use -f to point to the Dockerfile and specify the directory of the build context:


 mkdir -p dockerfiles context
 mv Dockerfile dockerfiles && mv hello context
 docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context

이미지를 만드는데 필요없는 파일을 포함시키면 build context가 커지게되고

도커 데몬에 전달할 데이터가 커지게 된다.

이로 인해 이미지의 size가 커지게 되면서 build에 소요되는 시간, push와 pull에 소요되는 시간, run time등

모든작업의 실행에 소요되는 시간에 영향을 준다.

그렇기 때문에 Dockerfile을 build할 때 결과 메시지에서 이 크기를 가급적 줄이도록한다.

Sending build context to Docker daemon  187.8MB

 

 

.dockerignore로 제외한다 (Exclude with .dockerignore)

.dockerignoer는 .gitignore와 유사하게 빌드에서 제외할 파일 패턴을 제외할 수 있도록 지원한다. 

멀티 스테이지 빌드를 사용 (Use multi-stage builds)

멀티 스테이지 빌드는 `FROM`이 1개가 아닌 2개 이상이 존재할 수 있고

이로 인해 빌드이미지와 실행이미지를 분리할 수 있어서 간편하게 이미지의 크기를 줄일 수 있다.

 

734.28MB -> 6.91MB로 감소

##############################################
FROM golang:1.19.3 AS builder
# Set Environment Variables
ENV HOME /app
ENV CGO_ENABLED 0
ENV GOOS linux

# go package install
WORKDIR /app
RUN go mod init mux
RUN go mod tidy
RUN go mod download
RUN go get github.com/gorilla/mux

COPY . .
RUN go build -a -o main .

##############################################
FROM scratch

WORKDIR /root/
# 이전stage에서 build했던 바이너리 파일을 복사
COPY --from=builder /app/main .
EXPOSE 8080

CMD [ "./main" ]

불필요한 패키지 설치하지 않는다. (Don’t install unnecessary packages)

복잡도, 의존성, 파일크기, 빌드 시간등을 줄이기 위해서 불필요한 패키지들은 뺀다.

반드시 필요하지 않는 패키지를 의미하는데 예를 들면 Database 이미지에 텍스트 에디터 패키지는 필요가 없다.

 

어플리케이션 디커플링 (Decouple applications)

각각의 컨테이너는 단일한 처리를 목적으로 한다.

어플리케이션을 디커플링해서 쪼개면 수평 확장과, 컨테이너 재사용이 쉬워진다.

예를 들어, 웹 어플리케이션을 스택을 3개의 컨테이너로 구성한다고 했을때

웹, 데이터 베이스, 인메모리 캐시로 분리하여 관리할 수 있다.

 

각 컨테이너를 디커플링하여 하나의 프로세스로 제한하는것은 경험상 좋은 규칙이지만 모두 그렇지는 않습니다.

Celery의 multiple worker process들과 Apache는 request마다 프로세스를 생서할 수도 있습니다.

(Spawned with an init process)

 

컨테이너를 가능하면 모듈화하고 명확하게 관리하면서 최선의 선택을 한다.

컨테이너간에 서로 의존적이라면 Docker container networks로 서로간 통신하게 할 수도 있다.

레이어의 수 최소화 (Minimize the number of layers)

  • RUN, COPY, ADD 로만 레이어를 생성한다. 다른 명령어는 임시로 중간 이미지를 생성하며, 빌드 사이즈에 영향을 안준다.
  • 가능하다면, 멀티 스테이지 빌드를 사용하고 필요한 아티팩트만 마지막 이미지에 복사하는 것이 좋다. 이렇게 하면 최종 이미지의 크기를 늘리지 않고도 중간 빌드 단계에서 각종 도구와 디버그 정보를 포함 시킬 수 있다.

멀티라인 인수 정렬 (Sort multi-line arguments)

가능하다면, 여러 줄의 인수를 알파벳순으로 정렬하는 것이 좋다. 이렇게 하면 패키지 중복을 방지하고 쉽게 업데이트 할 수 있다. 또한 PR을 읽고 검토하기에 용이해진다. '\' 앞에 공백을 추가하는 것도 도움이 된다.

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

레버리지 빌드 캐시 (Leverage build cache)

이미지를 빌드할때, 도커는 Dockerfile에 설정한 순서대로 실행한다.

각 명령을 검토할 때 도커는 새로운 (중복) 이미지를 만들지 않고 캐시에서 재사용할 수 있는 이미지를 찾는다.

캐시를 사용하지 않으려면 docker build 할때 --no-cache=true 옵션을 사용할 수 있다.

 

도커가 캐시를 활용할 수 있도록 하기 위해서는, 일치하는 이미지를 찾을 수 있는 경우와 없는 경우에 대해 이해하는 것이 중요하다.

도커가 따르는 기본적인 규칙은 아래와 같다.

  • 이미 캐시에 있는 부모 이미지를 시작으로, 다음 명령어를 해당 기본 이미지에서 파생된 모든 하위 이미지와 비교하여 동일한 명령어를 사용하여 빌드되었는지 확인한다. 그렇지 않으면 캐시가 무효화 된다.
  • 대부분의 경우 Dockerfile의 명령어를 하위 이미지 들과 비교하는 것으로 충분하다. 하지만, 어떤 명령어는 더 많은 검토가 필요하다.
  • ADD COPY의 경우 이미지 파일 내용을 검사하고 각 파일에 대한 체크섬을 추가로 계산한다. 여기에서 파일의 마지막 수정 시간이나 엑세스 시간은 고려하지 않는다. 캐시 조회 중에 체크섬을 기존 이미지의 체크섬과 비교한다. 파일에서 내용이나 메타데이터의 변경이 있으면 캐시가 무효화 된다.
  • ADD COPY 명령어 외에도 캐시 일치 여부를 확인하기 위해 컨테이너의 파일을 확인하지 않는다. 일례로 RUN apt-get -y update를 처리할때 컨테이너에서 업데이트된 파일을 검사하여 캐시와 치하는 경우가 존재하는지 확인하지 않는다. 이 경우는, 명령 문자열 자체만 일치하는지만 검토한다.

일단 캐시가 무효화되면 이후의 모든 Dockerfile 명령은 새로운 이미지를 생성하며, 캐시는 사용되지 않는다.

Pipe를 이용한 Standard Input사용(Pipe Dockerfile through `stdin`)

docker build를 수행할 때 pipe를 이용하여 Dockerfile을 disk에 만들지 않을 수도 있다.

일회성 빌드나 테스트 같이 가볍게 사용할때 유용하다.

# For example, the following commands are equivalent:


echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -

docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF

# You can substitute the examples with your preferred approach, 
# or the approach that best fits your use-case.

 

build context 전송없이 stdin으로 이미지 생성
(Build an image using a Dockerfile from stdin, without sending build context)

docker build 할때 `-` 하이픈 옵션을 사용하면 Dockerfile을 생성하지 않고 build context 전송하지도 않아서 

build 속도를 향상 시킬 수 있다.

  • .dockerignore 를 이용해서 docker build 할때 포함하지 않도록 제외할 수 있다.
docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

 

주의할점은 COPY 혹은 ADD를 사용하는 경우라면 실패

# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

docker build -t myimage:latest -<<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

# observe that the build fails
...
Step 2/3 : COPY somefile.txt ./
COPY failed: stat /var/lib/docker/tmp/docker-builder249218248/somefile.txt: no such file or directory

 

반응형

'Infra' 카테고리의 다른 글

인프라 개념 LACP  (1198) 2022.06.10
Python으로 Opendistro Account 생성하기  (326) 2021.03.17
Cloudfront와 S3를 이용한 웹 서비스 구성  (1655) 2021.03.10
Hashicorp Terraform (테라폼)  (454) 2020.10.28
AWS Subnet 생성하기  (2239) 2020.07.23

+ Recent posts