티스토리 뷰

728x90
반응형

Dockerfile

이미지를 생성하는 방법

  1. 아무것도 존재하지 않는 이미지로 컨테이너 생성
  2. 애플리케이션을 위한 환경을 설치하고 소스코드 등을 복사해 잘 동작하는 것을 확인
  3. 컨테이너를 이미지로 커밋(commit)

위의 방법은 애플리케이션이 동작하는 환경을 구성하기 위해 일일이 수작업으로 패키지를 설치해야한다.

그래서 도커는 아래와 같이 일련의 과정을 손쉽게 기록하고 수행할 수 있는 빌드 명령어를 제공한다. 완성된 이미지를 생성하기 위해 컨테이너에 설치해야 하는 패키지, 추가해야 하는 소스코드, 실행해야 하는 명령어와 셸 스크립트 등을 하나의 파일에 기록해 두면 도커는 이 파일을 읽어 컨테이너에서 작업을 수행한 뒤 이미지로 만들어낸다.

example

이러한 작업을 기록한 파일의 이름을 Dockerfile이라 하며, 빌드 명령어는 Dockerfile을 사용하면 직접 컨테이너를 생성하고 이미지로 커밋해야 하는 번거로움을 덜 수 있을뿐더라 깃과 같은 개발 도구를 통해 애플리케이션의 빌드 및 배포를 자동화할 수 있다.

도커 허브로 배포할 때 이미지 자체를 배포하는 대신 이미지를 생성하는 방법을 기록해 놓은 Dockerfile을 배포할 수도 있다.

Dockerfile을 작성하는 것은 이미지를 생성하는 방법을 기록하는 것뿐만 아니라 이미지의 빌드, 배포 측면에서도 매우 유리하다. 애플리케이션에 필요한 패키지 설치 등을 명확히 할 수 있고 이미지 생성을 자동화할 수 있으며, 쉽게 배포할 수 있다.



Dockerfile 작성

Dockerfile을 사용하기 위한 간단한 시나리오로 웹 서버 이미지를 생성하는 예를 생각해보자.

$ mkdir dockerfile && cd dockerfile
$ echo test >> test.html
FROM ubuntu:14.04
LABEL "purpose"="practice"
RUN apt-get update
RUN apt-get install apache2 -y
ADD test.html /var/www/html
WORKDIR /var/www/html
RUN ["/bin/bash", "-c", "echo hello >> test2.html"]
EXPOSE 80
CMD apachectl -DFOREGROUND

도커 엔진은 Dockerfile을 읽어 들일 때 기본적으로 현재 디렉터리에 있는 Dockerfile이라는 이름을 가진 파일을 선택한다. Dockerfile은 한 줄이 하나의 명령어가 되고, 명령어를 명시한 뒤에 옵션을 추가한다.

  • FROM : 생성할 이미지의 베이스 이미지. 반드시 한 번 x`이상 입력해야함.
  • LABEL : 이미지의 정보를 키:값 형태로 저장
  • RUN : 컨테이너 내부 명령어. 이미지를 빌드할 때 별도의 입력을 받아야 하는 RUN이 있다면 build 명령어는 이를 오류로 간주하고 빌드를 종료한다. 따라서 -y 옵션을 사용해야 한다.
  • ADD : 파일을 이미지에 추가한다. Dokcerfile이 위치한 디렉터리에서 파일을 가져온다. ["추가할 파일 이름", ….. , "컨테이너에 추가될 위치"] 형식으로 마지막에 컨테이너에 추가될 위치만 쓰면 여러개가 가능하다.
  • WORKDIR : 명령어를 실행할 디렉터리.
  • EXPOSE : Dockerfile의 빌드로 생성된 이미지에서 노출할 포트를 설정.
  • CMD : 컨테이너가 시작될 때마다 실행할 명령어를 설정. Dockerfile에서 한 번만 사용가능하다. CMD는 run 명령어의 이미지 이름 뒤에 입력하는 커맨드와 같은 역할을 하지만 docker run 명령어에서 커맨드 명령줄인자를 입력하면 Dockerfile에서 사용한 CMD 명령어는 run의 커맨드로 덮어 쓰인다. 저 위에서는 ubuntu:14.04 기본 내장 커맨드 /bin/bash가 Dockerfile CMD에 의해 덮어 쓰여진다.



Dockerfile 빌드

이미지 생성

명령어로 Dockerfile을 빌드하고, 실행해보자. dockerfile디렉터리에서 진행한다.

$ docker build -t mybuild:0.0 ./

$ docker run -d -P --name myserver mybuild:0.0
# 아파치 웹서버는 하나의 터미널을 차지하는 포그라운드 모드로 실행되서 -d 옵션 사용

-P 옵션은 이미지에 설정된 EXPOSE의 모든 포트를 호스트에 연결하도록 설정한다. EXPOSE로 노출된 포트를 호스트에서 사용 가능한 포트에 차례로 연결하므로 이 컨테이너가 호스트의 어떤 포트와 연결됐는지 확인할 필요가 있다.

$ docker port myserver

Dockerfile에 이미지의 라벨을 purpose=practice로 설정했으므로 docker images 명령어의 필터에 이 라벨을 적용할 수 있다. --filter 옵션을 통해 해당 라벨을 가지는 이미지만 출력해보자.

$ docker images --filter "label=purpose=practice"

'ip주소:연결된포트'로 접속하면 아파치 웹서버가 실행된것을 확인할 수 있고, test.html, test2.html도 볼 수 있다.



빌드 과정 살펴보기

example

이미지 빌드를 시작하면 도커는 가장 먼저 빌드 컨텍스트를 읽어 들인다. 빌드 컨텍스트는 이미지를 생성하는 데 필요한 각종 파일, 소스코드, 메타데이터 등을 담고 있는 디렉터리를 의미하며, Dockerfile이 위치한 디렉터리가 빌드 컨텍스트가 된다.

빌드 컨텍스트는 Dockerfile에서 빌드될 이미지에 파일을 추가할 때 사용된다. Dockerfile에서 이미지에 파일을 추가하는 방법은 앞에서 설명한 ADD 외에도 COPY가 있는데, 이 명령어들은 빌드 컨텍스트의 파일을 이미지에 추가한다. 위 예제에서는 빌드 경로를 ./로 지정함으로써 test.html 파일을 빌드 컨텍스트에 추가했으며, ADD 명령어를 통해 빌드 컨텍스트에서 test.html 파일을 이미지에 추가헀다.

.dockerignore라는 파일을 사용해서 빌드 시 이 파일에 명시된 이름의 파일을 컨텍스트에서 제외할 수도 있다.

ADD, RUN 등의 명령어가 실행될 때마다 새로운 컨테이너가 하나씩 생성되며 이를 이미지로 커밋한다. 즉, Dockerfile에서 명령어 한 줄이 실행될 때마다 이전 Step에서 생성된 이미지에 의해 새로운 컨테이너가 생성되며, Dockerfile에 적힌 명령어를 수행하고 다시 새로운 이미지 레이어로 저장된다. 따라서 이미지의 빌드가 완료되면 Dockerfile의 명령어 줄 수만큼의 레이어가 존재하게 되며, 중간에 컨테이너도 같은 수만큼 생성되고 삭제된다.



캐시를 이용한 이미지 빌드

example

# vi Dockerfile2
FROM ubuntu:14.04
LABEL "purpose"="practice"
RUN apt-get update


$ docker build -f Dockerfile2 -t mycache:0.0 ./

로그를 보면 Using cache라는 출력 내용과 함께 별도의 빌드 과정이 진행되지 않고 바로 이미지가 생성된다. 이전에 빌드했던 Dockerfile에 같은 내용이 있다면 build 명령어는 이를 새로 빌드하지 않고 같은 명령어 줄까지 이전에 사용한 이미지 레이어를 활용해 이미지를 생성한다. 이는 같은 명령어를 여러 번 실행해야 하는 여러 개의 이미지를 빌드 도중 Dockerfile의 문법과 기타 오류가 발생했을 때 불필요하게 다시 명령어를 실행하지 않게 한다.

Dockerfile에 RUN git clone…을 사용하고 이미지를 빌드했다면 RUN에 대한 이미지 레이어를 계속 캐시로 사용하기 때문에 실제 깃 저장소에서 리비전 관리가 일어나도 매번 빌드를 할 때마다 고정된 소스코드를 사용하게 된다.

이 경우 캐시를 사용하지 않으려면 build 명령어에 --no-cache 옵션을 추가한다.

$ docker build --no-cache -t mybuild:0.0 ./

특정 Dockerfile을 확장해서 사용한다면 기존의 Dockerfile로 빌드한 이미지를 빌드 캐시로 사용할 수 있다. 에를 들어, 도커 허브의 nginx 공식 저장소에서 nginx:latest 이미지를 빌드하는 Dockerfile에 일부 내용을 추가해 사용한다면 nginx:latest 이미지를 캐시로 사용할 수 있다.

$ docker build --cache-from nginx -t my_extend-nginx:0.0



멀티 스테이지를 이용한 Dockerfile 빌드하기

// main.go
package main
import "fmt"
func main() {
        fmt.Println("hello world")
}
# Dockerfile_go
FROM golang
ADD main.go /root
WORKDIR /root
RUN go build -o /root/mainApp /root/main.go

FROM alpine:latest
WORKDIR /root
COPY --from=0 /root/mainApp .
CMD ["./mainAPP"]
docker build -f Dockerfile_go -t go_helloworld ./

위의 도커 파일에서 위쪽의 문단만 작성하고 이미지를 만들면 golang 패키지 및 라이브러리 때문에 이미지의 크기가 크다. 따라서 이미지의 크기를 줄이기 위해 멀티 스테이지 빌드 방법을 사용할 수 있다. 멀티 스테이지 빌드는 하나의 Dockerfile 안에 여러 개의 FROM 이미지를 정의함으로써 빌드 완료시 최종적으로 생성될 이미지의 크기를 줄이는 역할을 한다.

두 번째 FROM 아래에서 사용된 COPY 명령어는 첫 번째 FROM에서 사용된 이미지의 최종 상태에 존재하는 /root/mainApp 파일을 두 번째 이미지인 alpine:latest에 복사한다. --from=0은 첫 번째 FROM에서 빌드된 이미지의 최종 상태를 의미한다. 즉, 첫 번째 FROM 이미지에서 빌드한 /root/mainApp 파일을 두 번째의 FROM에 명시된 이미지인 alpine:latest 이미지에 복사하는것이다.



기타 Dockerfile 명령어

ENV, VOLUME, ARG, USER

  • ENV : 컨테이너에서 사용 할 환경변수. ${ENV_NAME}, $ENV_NAME 형태로 사용.
# vi Dockerfile_env
FROM ubuntu:14.04
ENV test /home
WORKDIR $test
RUN touch $test/mytouchfile
$ docker build -f Dockerfile_env -t myenv:0.0 ./
$ docker run -it --name env_test myenv:0.0 /bin/bash
$ root@1fsj948e18:/home# echo $test
  • VOLUME : 빌드된 이미지로 컨테이너를 생성했을 때 호스트와 공유할 컨테이너 내부의 디렉터리를 설정한다.
# vi Dockerfile_volume
FROM ubuntu:14.04
RUN mkdir /home/volume
RUN echo test >> /home/volume/testfile
VOLUME /home/volume
$ docker build -f Dockerfile_volume -t myvolume:0.0 ./
$ docker run -it -d --name volume_test myvolume:0.0
$ docker volume ls
  • ARG : build 멸영어를 실행할 때 추가로 입력을 받아 Dockerfile 내에서 사용될 변수의 값을 설정한다. 다음 Dockerfile은 build 명령어에서 my_arg와 my_arg_2라는 이름의 변수를 추가로 입력받을 것이라고 ARG를 통해 명시한다.
# vi Dockerfile_arg
FROM ubuntu:14.04
ARG my_arg
ARG my_arg_2=value2
RUN touch ${my_arg}/mytouch
$ docker build -f Dockerfile_arg --build-arg my_arg=/home -t myarg:0.0 ./
$ docker run -it --name arg_test myarg:0.0
root@cca2sefsc:/# ls /home/mytouch
/home/mytouch
  • USER : USER로 컨테이너 내에서 사용될 사용자 계정의 이름이나 UID를 설정하면 그 아래의 명령어는 해당 사용자 권한으로 실행된다. 일반적으로 RUN으로 사용자의 그룹과 계정을 생성한 뒤 사용한다. 루트 권한이 필요하지 않다면 USER를 사용하는 것을 권장한다.
RUN groupadd -r author && useradd -r -g author ihp001
USER ihp001
...
...

Onbuild, Stopsignal, Healthcheck, Shell

  • ONBUILD : 빌드된 이미지를 기반으로 하는 다른 이미지가 Dockerfile로 생성될 때 실행할 명령어를 추가한다.
# vi Dockerfile_onbuild
FROM ubuntu:14.04
RUN echo "this is onbuild test!"
ONBUILD RUN echo "onbuild!" >> /onbuild_file
$ docker build -f Dockerfile_onbuild -t onbuild_test:0.0 ./
$ docker run -it --rm onbuild_test:0.0 ls /
# onbuild_file 안보임
# vi Dockerfile_onbuild_use
FROM onbuild_test:0.0
RUN echo "this is child image!"
$ docker build -f ./Dockerfile_onbuild_use ./ -t onbuild_test:0.1
$ docker run -it --rm onbuild_test:0.1 ls /
# onbuild_file 보임
  • STOPSIGNAL : 컨테이너가 정지될 때 사용될 시스템 콜의 종류를 지정. 아무것도 설정하지 않으면 기본적으로 SIGTERM으로 설정됨.
FROM ubuntu:14.04
STOPSIGNAL SIGKILL
  • HEALTHCHECK : 이미지로부터 생성된 커넽이너에서 동작하는 애플리케이션의 상태를 체크하도록 설정한다. 컨테이너 내부에서 동작 중인 애플리케이션의 프로세스가 종료되지는 않았으나 애플리케이션이 동작하고 있지 않은 상태를 방지하기 위해 사용될 수 있다.

밑의 Dockerfile은 1분마다 curl -f ~를 실행해bginx 애플리케이션의 상태를 테크하며, 3초 이상이 소요되면 이를 한 번의 실패로 간주한다.

FROM nginx
RUN apt-get update -y && apt-get install curl -y
HEALTHCHECK --interval=1m --timeout=3s --retries=3 CMD curl -f http://localhost || exit 1
$ docker run -dP nginx:healthcheck
$ docker ps
  • SHELL : Dockerfile에서 기본적으로 사용하는 셸은 리눅스에서 "/bin/sh -c"이다. 이 셸을 따로 지정할 수 있다.
FROM node 
RUN echo hello, node!
SHELL ["/usr/local/bin/node"]
RUN -v

ADD, COPY 차이

COPY는 로컬의 파일만 이미지에 추가할 수 있지만 ADD는 외부 URL 및 tar파일에서도 파일을 추가할 수 있다는 점에서 다르다. ADD에서 tar파일은 그대로 추가되지 않고 자동 해제해서 추가한다.

ENTRYPOINT, CMD 차이

CMD는 컨테이너가 시작될 때 실행할 명령어를 설정한다. 이는 docker run 명령어에서 맨 뒤에 입력했던 커맨드와 같은 역할을 한다.

ENTRYPOINT도 CMD와 동일하게 컨테이너가 시작될 때 수행할 명령을 지정하는데 커맨드를 인자로 받아 사용할 수 있는 스크립트의 역할을 할 수 있다.

$ docker run -it --name no_entrypoint ubuntu:14.04 /bin/bash
$ docker run -it --entrypoint="echo" --name yes_entrypoint ubuntu:14.04 /bin/bash

두번쨰 명령어는 run 명령어의 맨 마지막에 입력된 cmd를 인자로 삼아 명령어를 출력한다.

entrypoint가 설정되지 않았다면 cmd에 설정된 명령어를 그대로 실행하지만 entrypoint가 설정됐다면 cmd는 단지 entrypoint에 대한 인자의 기능을 한다.

일반적으로는 스크립트 파일을 entrypoint의 인자로 사용해 컨테이너가 시작될 때마다 해당 스크립트 파일을 실행하도록 설정한다.

# 이런식으로 
$ docker run -it --name entrypoint_sh --entrypoint="/test.sh" ubuntu:14.04 /bin/bash

실행할 스크립트 파일이 컨테이너 내부에 존재해야한다. Dockerfile에서 COPY, ADD를 쓰면된다.

# vi Dockerfile_entrypoint
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install apache2 -y
ADD entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
$ docker build -f Dockerfile_entrypoint -t entrypoint_image:0.0 ./
$  docker run -d --name entrypoint_apache_server entrypoint_image:0.0 first second
$ docker logs entrypoint_apache_server
first second
...
...

JSON 배열 형태와 일반 형식의 차이점

CMD ENTRYPOINT에 설정하려는 명령어를 /bin/sh로 사용할 수 없다면 JSON 배열의 형태로 명령어를 설정해야 한다. JSON 배열 형태가 아닌 CMD ENTRYPOINT를 사용하면 실제로 이미지를 생성할 때 cmd와 entrypoint에 /bin/sh -c가 앞에 추가되기 떄문이다.

CMD echo test
$ /bin/sh -c echo test

CMD ["echo", "test"]
$ echo test

ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
$ /bin/bash /entrypoint.sh



Dockerfile로 빌드할 때 주의할 점

  • 하나의 명령어를 \로 나눠서 가독성을 높이기
  • .dockerignore 파일을 작성해 불필요한 파일을 빌드 컨텍스트에 포함하지 않기
  • 빌드 캐시를 이용해 기존에 사용했던 이미지 레이어를 재사용하는 방법
FROM ubuntu:14.04
RUN mkdir /test
RUN fallocate -l 100m /test/dummy
# fallocate는 100MB 크기의 파일을 가상으로 만듬
RUN rm /test/dummy

이 이미지로 만든 컨테이너는 /test/dummy 라는 파일은 존재하지 않는다. 근데 RUN rm /test/dummy 명령어를 수행해 100MB 크기의 파일을 삭제하더라도 이는 변경사항으로서의 레이어로 새롭게 저장되어서 저장 공간 자체는 차지하게 된다.

이를 방지하려면 &&로 각각 RUN 명령을 하나로 묶어서 하나의 레이어로 생성되게 하면 된다.

FROM ubuntu:14.04
RUN mkdir /test && \
fallocate -l 100m /test/dummy && \
rm /test/dummy





출처
시작하세요! 도커/쿠버네티스(용찬호 저, 위키북스)
example

728x90
반응형
댓글
반응형
250x250
글 보관함
최근에 달린 댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
링크