Dockerfile 명령어

mohadang·2022년 6월 5일
0

도커

목록 보기
22/26
post-thumbnail

ENV

Dockerfile에서 사용될 환경변수를 지정. ${ENV_NAME} 또는 $ENV_NAME의 형태로 사용할 수 있다.
EX)

FROM ubuntu:14.04
ENV test /home
WORKDIR $test
RUN touch $test/mytouchfile
$ docker build -t myenv:0.0 ./
$ docker run -i -t --name evn_test myenv:0.0 /bin/bash
$ echo $test
/home

run 명령어에서 -e 옵션을 사용해 같은 이름의 환경변수를 사용하면 기존의 값은 덮어 쓰여진다.

$ docker run -i -t --name env_test_override -e test=myvalue
$ echo $test
myvalue

Dockerfile에서 환경변수의 값을 사용할 때 배시 shell에서 사용하는 것처럼 값이 설정되지 않은 경우와 설정된 경우를 구분해 사용할 수 있다.

FROM ubuntu:14.04
ENV my_env my_value
RUN echo ${my_env:-value} / ${my_env:+value} / ${my_env_2:-value} / ${my_env_2:+value}

==> my_value / value / value / 

${env_name:-value} : env_name이라는 환경변수의 값이 설정되지 않았으면 이 환경변수의 값을 value로 사용
${env_name:+value} : env_name의 값이 설정돼 있으면 value를 값으로 사용하고 값이 설정되지 않았다면 빈 문자열을 사용

VOLUME

빌드된 이미지로 컨테이너를 생성했을 때 호스트와 공유할 컨테이너 내부의 디렉터리를 설정
사용방식

  • VOLUMD ["home/dir", "home/dir2"] JSON 배열
  • VOLUMD home/dir home/dir2

EX) 컨테이너 내부의 /home/volume 디렉터리를 호스트와 공유하도록 설정

FROM ubuntu:14.04
RUN mkdir /home/volume
RUN echo test >> /home/volume/testfile
VOLUME /home/volume
$ docker build -t myvolume:0.0 .
$ docker run -i -t -d --name volume_test myvolume:0.0
$ docker volume ls

volume 디렉터리 접근

  • /var/lib/docker/volumes/6fa6e5a637bd5fc812f326294171bd52d8f8ea81d161e8b9ae0255303b6c886f/_data#

ARG

build 명령어를 실행할 때 추가로 입력을 받아 Dockerfile 내에서 사용될 변수의 값을 설정

FROM ubuntu:14.04
# my_arg, my_arg_2 라는 이름의 변수를 추가로 입력받을 것이라고 ARG를 통해 명시
# my_arg_2는 기본값 설정됨
ARG my_arg
ARG my_arg_2=value2

RUN touch ${my_arg}/mytouch
$ docker build --build-arg my_arg=/home -t myarg:0.0 ./

ARG와 ENV 값을 사용하는 방법은 ${}로 같으므로 Dockerefile에서 ARG로 설정한 변수를 ENV에서 같은 이름으로 다시 정의하면 --build-arg 옵션에서 설정하는 값은 ENV에 의해 덮어쓰여진다.

USER

USER로 컨테이너 내에서 사용될 사용자 계정의 이름이나 UID를 설정하면 그 아래의 명령어는 해당 사용자 권한으로 실행된다.
일반적으로 RUN으로 사용자의 그룹과 계정을 생성한 뒤 사용. 루트 권한이 필요하지 ㅇ낳다면 USER를 사용하는 것을 권장

...
RUN groundadd -r author && useradd -r -g author mohadang
USER mohadng
...

기본적으로 컨테이너 내부에서는 root 사용자를 사용하도록 설정된다. 이는 컨테이너가 호스트의 root 권한을 가질 수 있다는 것을 의미하기 때문에 보안 측면에서 매우 바람직하지 않다.
root 소유한 호스트의 디렉터리를 컨테이너에 공유했을 때, 컨테이너 내부에서는 공유된 root 소유의 디렉터리를 마음대로 조작할 수도 있다.
docker run 명령어 자체에서도 --user 옵션을 지원하지만 가능하다면 이미지 자체에 root가 아닌 다른 사요자를 설정해 놓는 것이 좋다.

Onbuild

빌드된 이미지를 기반으로 하는 다른 이미지가 Dockerfile로 생성될 때 실행할 명령어를 추가

  • Dockerfile - parent
FROM ubuntu:14.04
RUN echo "this is onbuild test"
ONBUILD RUN echo "onbuild!" >> /onbuild_file
  • Dockerfile2 - child
FROM onbuild_test:0.0
RUN echo "this is child image!"
$ docker build ./ -t onbuild_test:0.0
$ docker run -it --rm onbuild_test:0.0 ls /
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

$ docker build -f ./Dockerfile2 ./ -t onbuild_test:0.1
$ docker run -i -t --rm onbuild_test:0.1 ls /
bin   dev  home  lib64  mnt           opt   root  sbin  sys  usr
boot  etc  lib   media  onbuild_file  proc  run   srv   tmp  var
onbuild_file이 생성됨

ONBUILD는 ONBUILD, FROM, MAINTAINER를 제외한 RUN, ADD등 이미지가 빌드될 떄 수행돼야 하는 각종 Dockerfile의 명령어를 나중에 빌드될 이미지를 위해 미리 저장해 놓을 수 있다.

단, 이미지의 속성을 설정하는 다른 Dockerfile의 명령어와는 달리 ONBUILD는 부모 이미지의 자식 이미지에만 적용되며, 자식 이미지는 ONBUILD 속성을 상속받지 않는다.

ONBUILD를 활용하는 좋은 방법 중 하나는 이미지가 빌드하거나 활용할 소스코드를 ONBUILD ADD로 추가해 좀 더 깔끔하게 Dockerfile을 사용하는 것이다. 예를 들어, 도커 이미지 중 메이븐은 다음과 같은 Dockerfile을 가지고 있다.

FROM maven:3-jdk-8-alpine
RUN mkdir -p /user/src/app
WORKDIR /user/src/app
ONBUILD ADD . /user/src/app
ONBUILD RUN mvn install

이 이미지를 사용하는 개발자는 프로젝트 폴더에 Dockerfile을 위치시키고, 
아래의 Dockerfile로부터 빌드된 이미지를 FROM 항목에 입력함으로써 메이븐을 쉽게 사용할 수 있다.

STOPSIGNAL

컨테이너가 정지될 떄 사용될 시스템 콜의 종류를 지정. 아무것도 설정하지 않으면 기본적으로 SIIGTERM로 설정되지만 Dockerfile에 STOPSIGNAL을 정의해 컨테이너가 종료되는 데 사용될 신호를 선택할 수 있다.

FROM uguntu:14.04
STOPSIGNAL SIGKILL # STOPSIGNAL을 SIGTERM이 아닌 SIGKILL로 지정

HEALTHCHECK

이미지로부터 생성된 컨테이너에서 동작하는 어플리케이션의 상태를 체크하도록 설정
컨테이너 내부에서 동작 중인 어플리케이션의 프로세스가 종료되지는 않으나 어플리케이션이 동작하고 있지 않은 상태를 방지하기 위해 사용될 수 있다.

FROM nginx
RUN apt-get update -y && apt-get install curl -y
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1
$ docker run -d -P nginx:healthcheck
# docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS                            PORTS                                     NAMES
ba471a2d4c93   nginx:healthcheck   "/docker-entrypoint.…"   6 seconds ago    Up 4 seconds (health: starting)   0.0.0.0:49154->80/tcp, :::49154->80/tcp   hardcore_mestorf
84f4ea54e783   myvolume:0.0        "/bin/bash"              54 minutes ago   Up 54 minutes                                                               volume_test

(health: starting) : 상태 체크에 대한 로그는 컨테이너의 정보에 저장되므로 어플리케이션에 장애가 있을 때 해당 로그를 확인할 수 있다. docker inspect의 출력 중 State - Health - Log 항목에서 확인 가능

SHELL

Dockerfile에서 기본적으로 사용하는 shell은 "/bin/sh -c", 윈도우에서 "cmd /S /C"

RUN echo "hello, world!"
# 리눅스 : /bin/sh -c echo hello, world
# 윈도우 : cmd /S /C echo hello, world
FROM node
RUN echo hello, node! 
SHELL ["/usr/local/bin/node"]
RUN -v

이미지를 비륻하면 RUN -v 부분에서 node의 버전을 출력하는 것을 알 수 있다.

JSON 배열 형식으로 사용해 기본 shell을 비활성화하고 사용하는 shell을 명시할 수 있음
shell 뒤에 사용하고자 하는 shell을 명시하는 방법이 좀더 편리할 것이다.

ADD, COPY

COPY는 로컬 디렉터리에서 읽어 들인 컨텍스트로부터 이미지에 파일을 복사

COPY test.html /home/
COPY ["test.html", "/home"]

ADD와 COPY의 차이점

  • COPY는 로컬의 파일만 이미지에 추가할 수 있지만 ADD는 외부 URL 및 tar 파일에서도 파일을 추가할 수 있다.
  • COPY의 기능이 ADD에 포함된다.
ADD https://raw.~~~/master/test.html /home
ADD test.tar /home # tar 파일을 자동으로 해제해서 추가

ADD를 사용하는 것은 그다지 권장하지 않는다.

  • ADD로 URL이나 tar 파일을 추가할 경우 이미지에 정확이 어떤 파일이 추가될지 알 수 없다.

ENTRYPOINT, CMD

CMD는 컨테이너가 시작될 때 실행할 명령어를 설정. ENTRYPOINT와 CMD는 역할 자체는 비슷하지만 서로 다른 역할을 담당함

ENTRYPOINT와 CMD의 차이점

  • 둘다 동일하게 컨테이너가 시작될 때 수행할 명령을 지정
  • ENTRYPOINT는 커맨드를 인자로 받아 사용할 수 있는 스크립트의 역할을 할 수 있다
ENTRYPOINT가 없는 경우 맨 뒤에 CMD로 배쉬 실행하도록 설정 하였으므로 배쉬를 통해 입출력 가능
$ docker run -i -t --name no_entrypoint ubuntu:14.04 /bin/bash
root@96ea7e54d428:/# root@red-virtual-machine:~#

ENTRYPOINT가 설정되었으므로 맨 마지막에 입력된 cmd를 인자로 삼아 명령어를 출력
$ docker run -i -t --entrypoint="echo" --name yes_entrypoint ubuntu:14.04 /bin/bash
/bin/bash

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

entrypoint에 하나의 명령어만 입력할 수도 있지만 일반적으로는 스크립트 파일을 entrypoint의 인자로 사용해 컨테이너가 시작될 때마다 해당 스크립트 파일을 실행하도록 설정

$ docker run -it --name entrypoint_sh --entrypoint="/test.h" ubuntu:14.04 /bin/bash

단 실행할 스크립트 파일은 컨테이너 내부에 존재해야 한다.
1. 어떤 설정 및 실행이 필요한지에 대한 스크립트로 정리
2. ADD 또는 COPY로 스크립트를 이미지로 복사
3. ENTRYPOINT를 이 스크립트로 설정
4. 이미지를 빌드해 사용
5. 스크립트에서 필요한 인자는 docker run 명령어에서 cmd로 entrypoint의 스크립트에 전달

EX)

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"]
# entrypoint.sh
echo $1 $2 # CMD로 받을 인자
apachectl -DFOREGROUND
$ docker build -t entrypoint_inmage:0.0 ./
$ docker run -d --name entrypoint_apache_server entrypoint_inmage:0.0 first second
$ docker logs entrypoint_apache_server
first second # CMD로 전달된 인자

JSON 배열 형태와 일반

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

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

ENTRYPOINT /entrypoint.sh
# /bin/sh -c /entrypoint.sh

CMD ["echo", "test"]
# echo test

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

Dockerfile로 빌드할 때 주의점

  • 하나의 명령어를 \로 나눠서 가독성 높이기
  • .dockerignore 파일을 작성해 불필요한 파일을 빌드 컨텍스트에 포함하지 않기
  • 빌드 캐시를 이용해 기존에 사용했던 이미지 레이어를 재사용

Dockerfile을 아무렇게나 작성하면 저장 공간을 불필요하게 차지하는 이미지나 레이어가 생성될 수 있다.

EX) 나쁜 예

FROM ubuntu:14.04
RUN mkdir /test
RUN fallocate -l 100m /test/dumy # 더미 파일 생성
RUN rm /test/dumy # 다시 제거
$ docker build -t falloc_100mb:0.0 ./
root@red-virtual-machine:~/dockerfile# docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
falloc_100mb        0.0       d281ad3101cb   5 seconds ago    301MB
ubuntu              14.04     13b66b487594   14 months ago    197MB

더미 파일은 제거 되었지만 이미지는 용량이 전혀 줄어들지 않았다.
이는 컨테이너를 이미지로 생성할 때 컨테이너에서 변경된 사항만 새로운 이미지 레이어로 생성하는 방식의 단점이다.
RUN rm /test/dummy 명령어를 수행해 100mb 크기의 파일을 삭제하더라도 이는 "파일을 삭제했다"라는 변경사항으로서의 레이어로 새롭게 저장될 뿐, 실제 100mb 크기의 파일은 이전 레이어에 남아있기 때문이다.

이를 방지하는 방법은 &&로 각 RUN 명령을 하나로 묶는 것이다.
EX) 좋은 예

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

RUN이 하나의 이미지 레이어가 된다는 것을 생각해보면 매우 간단한 해결책이다
여러 개의 RUN 명령어가 하나로 묶일 수 있다면 이미지 레이어 개수 또한 하나로 줄어들기에 RUN 명령어를 가급적이면 줄여야 한다.

다른 사람이 빌드한 이미지에 불필요한 이미지 레이어가 들어있다면 해당 이미지로 컨테이너를 생성하고 docker export, import 명령어를 사용해 컨테이너를 이미지로 만듦으로써 이미지의 크기를 줄일 수 있다.(컨테이너는 변경 사항이 다 반영되어 더미 파일도 삭제 되었을 것이다)
export 된 파일을 임포트해서 다시 도커에서 저장하면 레이어가 한개로 줄어든다.

그러나 이전 이미지에 저장돼 있던 각종 이미지 설정은 잃어버리게 되므로 주의해야 한다.

$ docker run -i -t -d --name temp falloc_100mb:0.0
$ docker export temp | docker import - falloc_100mb:0.1
sha256:fe7f50d34f2fc2e19d34d0f638239d8b34d3b83f669821bac9d3edd5df0c037c
$ docker run -i -t -d --name temp2 falloc_100mb:0.1
docker: Error response from daemon: No command specified.

베이스 이미지인 ubuntu:14.04 이미지의 커맨드 명령어가 손실되어 설정되지 않았고, entrypoint 또한 설정되지 않아 에러를 출력하며 컨테이너가 생성되지 않는다.
profile
mohadang

0개의 댓글