Docker & Container

[Docker] Dockerfile의 구조와 문법

아무일도없었다 2023. 4. 26. 11:11

Dockerfile을 작성하기 전에 image layer에 관련된 글을 읽고 오는 것을 추천한다.

(image를 구성하는 image layer)

 

[Docker] image를 구성하는 image layer

Docker image & conatiner Image의 정의 Container라는 독립된 환경에서 서비스가 실행 가능하도록 필요한 요소를(서버 환경, 실행 파일, 라이브러리, 구조 등...) 하나의 패키지 형태로 묶는 형태 Container의

hackerpark.tistory.com

 


 

Dockerfile

 

Dockerfile application을 container화 하기 위한 과정을 기록(layer)하는 것으로 docker는 이를 통해 image를 생성한다.

 

물론 최근(2023-04-23 기준)에는 docker가 아니더라도 dockerfile을 통해 image를 만드는 여러 도구가 있으니 docker만을 고집할 필요는 없다. (아래 내용 참고)

 

Dockerfile을 통해 image를 생성할 수 있는 도구

1. Docker build
 → Docker에 내장되어 있는 build 도구로 현재까지도 많은 사람들이 애용하고 있지만, 여러 문제점으로 인해 docker를 사용하지 않고 다른 도구로 갈아타는 사람들이 늘어나고 있다.

2. Docker (Buildkit)
 → Docker 18.09 버전에서 추가된 build engine으로 해당 버전 이상의 docker build시 DOCKER_BUILDKIT 환경변수를 1로 세팅하면 활성화된다. Dockerfile의 Multistage Build시 상당히 효율적이라고 하며 자세한 내용은 링크 참고

3. Kaniko
 → 무려 Google이 개발한 image build 도구로 docker daemon 없이 Dockerfile을 통해 image를 개발할 수 있다. 특히 Container 내부에서 권한 문제없이 image를 빌드할 수 있다는 장점이 있어서 원격 서버 또는 CI/CD 파이프라인에 image를 빌드하는 경우 유용하다.

4. Buildah
 → Red Hat에서 개발한 image build 도구로 Dockerfile을 통해 image를 만들수도 있고 Dockerfile 없이 image를 build 할 수도 있다. (물론 Dockerfile을 쓰던 관성이 매우 강력하기 때문에 여전히 Dockerfile을 애용하는 사람이 많다.) 당연히 docker daemon 없이 build가 가능하며 docker build 보다 가볍고 심플하며 podman이 buildah와 동일한 코드를 사용하여 개발되고 있다. (image를 세부적으로 제어하기 위해서는 podman이 아닌 buildah를 사용할 것을 권장한다.)

 


 

Dockerfile 문법 (명령어)

 

[FROM]

  • 생성할 image의 베이스가 되는 image를 설정한다.
  • Dockerfile 작성시 필수로 설정해야 한다.
  • 멀티 스테이지(Multi Stage) 빌드시 여러 개를 사용할 수 있다. (아래 추가 내용 기술)

 Example)

FROM mirror.gcr.io/library/alpine:3.16

 

 

[LABEL]

  • 생성할 image에 key=value 형식으로 metadata를 추가한다.
  • 다수의 metadata를 추가할 수 있다.

Example)

LABEL maintainer="hackerpark.tistory.com"
LABEL appversion="1.3.1"

 

 

[ENV]

  • 생성할 image에 추가할 환경변수를 설정한다.
  • ENV에 설정된 환경변수는 image를 통해 생성된 container 내부에서도 사용이 가능하다.
  • ENV가 설정된 image로 container를 생성할때 옵션을 사용하여 Dockerfile에 설정한 ENV를 오버라이드(override) 할 수 있다. (Default ENV를 설정한다고 생각하는게 좋다.)

Example)

ENV CUSTOM_ENV value
ENV MY_ENV="custom_value"

 

 

[ARG]

  • Dockerfile 내부에서 사용할 변수를 key=value 또는 key 형태로 설정한다.
  • ARG를 key만 입력하면 build시 해당 key 값의 argument가 입력될 것이라는 명시를 나타낸다.
  • ARG를 key=value까지 입력한 경우 build시 argument가 입력되지 않아도 value로 적용된다.
  • ARG를 key=value로 입력한 상태로 build시 argument를 입력할 경우 오버라이드(override)된다.

Example)

ARG ALPINEVER=3.16
FROM mirror.gcr.io/library/alpine:${ALPINEVER}

 

 

[RUN]

  • image를 만드는 과정(layer)에서 FROM으로 설정된 베이스 image에 추가로 실행할 명령어를 입력한다.

Example)

FROM mirror.gcr.io/library/alpine:3.16
RUN apk --no-cache add libc6-compat curl && \
  rm -rf /var/cache/apk/*

 

 

[USER]

  • image를 만드는 과정(layer)에서 사용할 사용자 계정을 설정한다.
    • USER 를 사용하기 위해서는 해당 USER가 기존 layer에 생성되어 있어야 한다.
  • USER 설정 이후 아래 Dockerfile(layer)에 모두 적용된다.
  • USER를 설정하지 않으면 슈퍼 유저로 진행된다.

Example)

RUN adduser hackerpark --disabled-password --gecos ""
USER hackerpark

 

 

[WORKDIR]

  • image를 만드는 과정(layer)에서 기본으로 작업할 디렉토리를 설정한다.
    • 쉘 스크립트의 cd와 유사하다.
  • WORKDIR 설정 이후 아래 Dockerfile(layer)에 모두 적용된다.

Example)

# create /tmp/test.txt
WORKDIR /tmp
RUN touch test.txt

 

 

[COPY]

  • image를 만드는 과정(layer)에서 추가할 파일과 추가될 경로를 입력한다.
    • 추가할 파일 Dockerfile이 있는 경로를 기반으로 입력한다. (context folder)
    • 추가할 파일의 경우 context folder의 상위 디렉토리에 접근할 수 없다. (context folder를 변경해야 한다.)

Example)

COPY my_file /tmp/test_my_file

 

 

[ADD]

  • image를 만드는 과정(layer)에서 추가할 파일과 추가될 경로를 입력한다.
    • 추가할 파일 Dockerfile이 있는 경로를 기반으로 입력한다. (context folder)
    • 추가할 파일의 경우 context folder의 상위 디렉토리에 접근할 수 없다. (context folder를 변경해야한다.)
  • ADD를 사용하여 tar 파일을 추가할 경우 tar 파일을 자동으로 해제해 준다.
  • ADD를 사용하여 추가할 파일을 URL로 입력할 수 있다.
  • 추가할 파일과 추가될 파일의 이름을 바꿀 경우 rename 된다.

Example)

ADD package.tar /tmp/

 

 

[EXPOSE]

  • image를 통해 생성되는 container에서 노출할 port를 명시한다.
  • 실제로 bind 하기 위해선 container 생성 시 옵션을 추가해야 한다.

Example)

EXPOSE 8080

 

 

[CMD]

  • image를 통해 생성되는 container에서 실행할 command를 입력한다.
  • Dockerfile 내부에서 한 번만 사용 가능하다. (생략 가능)
  • container 실행 시 cmd를 입력하는 경우 CMD 내용은 오버라이드(override)된다.
  • ENTRYPOINT 설정 시 CMD의 내용이 ENTRYPOINT의 파라미터로 변경된다.

Example)

CMD ["/bin/sh", "-c", "ls /"]

 

 

[ENTRYPOINT]

  • image를 통해 생성되는 container에서 실행할 command를 입력한다.
  • Dockerfile 내부에서 한 번만 사용 가능하다. (생략 가능)
  • container 실행 시 entrypoint를 입력하는 경우 ENTRYPOINT 내용은 오버라이드(override)된다.
  • CMD가 설정될 경우 ENTRYPOINT의 파라미터로 사용된다.

Example)

ENTRYPOINT ["/bin/sh", "-c", "ls /"]

 

 

[ONBUILD]

  • Dockerfile을 통해 생성된 image가 다른 Dockerfile에서 FROM을 통해 베이스 image로 사용되어 build 될 때 실행할 명령어를 입력한다.

Example)

FROM mirror.gcr.io/library/alpine:3.16
LABEL imagename="hackerpark"
LABEL version="1.0"
RUN echo "First Image Build"
ONBUILD RUN echo "First Base Image" >> /first.txt
ENTRYPOINT ["/bin/sh", "-c", "ls /"]

image build (hackerpark:1.0)
hackerpark:1.0 image로 실행한 container에서는 first.txt가 생성되지 않았다.

 

FROM hackerpark:1.0
LABEL imagename="hackerpark"
LABEL version="1.1"
RUN echo "Second Image Build"
RUN cat /first.txt

Step 3/3 에서 First Base Image가 출력되는것을 확인할 수 있다.
hackerpark:1.1 image로 실행한 container에서는 first.txt가 생성되어 있다.

 

 

[STOPSIGNAL]

  • image를 통해 생성되는 container 종료 시 사용될 SIGNAL을 설정한다.
  • 설정하지 않는 경우 기본 SIGTERM이 사용된다.

Example)

STOPSIGNAL SIGKILL

 

 

[HEALTHCHECK]

  • image를 통해 생성되는 container에서 실행되는 프로세스의 상태를 확인하기 위해 설정한다.
  • image에 curl이 포함되어야 한다.

Example)

FROM nginx:1.21.3-alpine
RUN apk --no-cache add curl && \
  rm -rf /var/cache/apk/*
HEALCHECK --interval=10s --timeout=5s --retries=3 CMD curl -f http://localhost || exit 1
  • --interval 옵션을 통해 healthcheck의 interval을 설정한다. (ex: --interval=10s, --interval=1m)
  • --timeout 옵션을 통해 healthcheck CMD의 timeout을 설정한다.
  • --retries 옵션을 통해 healthcheck CMD의 timeout 제한 개수를 설정한다.

 

 

[SHELL]

  • Dockerfile에서 사용할 기본 shell을 설정한다.
  • Linux의 경우 "/bin/sh -c"를 기본값으로 사용한다.

Example)

SHELL ["/bin/bash", "-c"]

 


 

Dockerfile Multi Stage Build (멀티 스테이지)

 

Dockerfile을 통해 image를 생성하는 과정에서 필요한 라이브러리와 패키지들을 모두 포함하기 때문에 최종 결과물로 나온 image의 크기가 GB가 넘어가는 일도 빈번하게 발생한다.

 

따라서 image를 만드는 과정과 image를 통해 container로 실행하기 위해 필요한 영역을 구분 짓는다면 최종 결과물인 image는 경량화가 될 수 있다.

 

이를 위해 Dockerfile에서 멀티 스테이지(Multi Stage) 기능을 지원하고 있다.

 


 

멀티 스테이지(Multi Stage)를 사용하기 위해서는 image를 만드는 과정에서 필요한 내용과 최종적으로 실행할 환경을 분리하는 작업이 필요한데, 이를 FROM을 사용하여 구분할 수 있다.

 

ARG GOVERSION=1.19
FROM golang:${GOVERSION} as builder
RUN apt-get update && apt-get install -y \
    btrfs-progs \
    crun \
    git \
    golang-go \
    go-md2man \
    iptables \
    libassuan-dev \
    libbtrfs-dev \
    libc6-dev \
    libdevmapper-dev \
    libglib2.0-dev \
    libgpgme-dev \
    libgpg-error-dev \
    libprotobuf-dev \
    libprotobuf-c-dev \
    libseccomp-dev \
    libselinux1-dev \
    libsystemd-dev \
    pkg-config \
    uidmap \
    unzip \
    && rm -rf /var/lib/apt/lists/*

ADD . /project
WORKDIR /project
RUN go mod download

ADD . /project
RUN go build -o my_go_app

#################################################
# FROM을 사용하여 Stage 분리
#################################################

FROM mirror.gcr.io/library/alpine:3.16
LABEL maintainer="hackerpark" \
      version="1.0"

RUN apk --no-cache add libc6-compat curl&& \
    rm -rf /var/cache/apk/*

COPY --from=builder /project/my_go_app /

EXPOSE 8080

ENTRYPOINT ["/my_go_app"]

 

위의 예시 Dockerfile을 보면 FROM이 두 번 사용된 것을 확인할 수 있다. (두 번 이상도 사용이 가능하다.)

 

go applicationbuild 하기 위해 필요한 모든 의존성 패키지를 설치를 진행하는데 이 파일들이 모두 최종 image에 포함된다면 DISK의 압박이 상당할 것이다.

 

따라서 첫 FROM golang image에 as builder라는 이름을 붙여서 아래의 FROM alpine image에서 활용하는 것을 볼 수 있다.

 

Go Application 빌드에 필요한 모든 과정은 위쪽 FROM의 stage에서 진행하고, 최종에는 아래의 FROM인 alpine 환경에 빌드되어 나온 go binary만 가져와서 image를 만드는 것이다.

 

이런 식으로 Dockerfile의 Stage를 분리하여 image의 크기를 상당히 줄일 수 있다.

 

※ 멀티 스테이지(Multi Stage) 환경에서 ARG 사용

FROMARG의 위치에 따라 ARG를 사용하는 방법이 다르다.

위에서 사용한 예시의 경우 FROM 보다 위에 ARG가 선언(정의)되어 있는데 이런 경우 ARG의 아래쪽 FROM에서 모두 사용이 가능하다.

만약 FROM 아래에 ARG가 선언(정의)되어 있다면 그 ARG를 포함하는 FROM에서만 사용이 가능하고 다른 FROM에서는 ARG를 다시 정의해서 사용해야 한다.

 

 


 

Dockerfile 작성 시 알아두면 좋은 점 (Cache)

 

Dockerfile application을 container화 하기 위한 과정을 기록(layer)하는 것이고 Dockerfile의 명령어(문법)가 한 개의 layer를 구성한다라고 이해하면 편하다.

 

또한 layer는 이전 과정에서의 변경되는 것을 기록하기 때문에 Dockerfile을 사용하여 image를 여러 번 build 하게 될 경우 layer의 변경점이 없다면 Cache 되어 이미 존재하는 layer를 재사용한다.

 

이 점을 활용하여 Dockerfile을 효율적으로 사용하려면 엔간하면 변하지 않는 명령어(layer)를 Dockerfile의 상단에 배치하여 cache 된 layer를 재사용하는 것이 유리하다.

 

여러 open source의 Dockerfile을 참고해 보면 FROM 절 거의 바로 아래에 RUN apt-get과 같은 명령어로 패키지들을 먼저 설치하고 그 이후에 필요한 작업을 하는 것을 볼 수 있는데, Dockerfile의 Cache를 활용하고 있는 것이다.

 

또한 layer 생성과정의 cache를 줄이고 step을 단축시키기 위해서 변경이 크지 않은 명령어(layer) 여러 개를 하나의 명령어(layer)로 압축하는 것이 좋다.

 

[예시 Dockerfile]

FROM mirror.gcr.io/library/alpine:3.16

LABEL maintainer="hackerpark" \
            email="abc" \
            version="1.0"

RUN apk --no-cache add libc6-compat curl &&\
         rm -rf /var/cache/apk/*

 

추가로 image 크기를 줄이기 위해 패키지 설치 후 container 내부의 cache 파일을 지우면 크기를 더 줄일 수 있다. (apt, yum 모두 가능)

 

반응형