Docker파일 참조
Docker는 Docker파일의 지침을 읽어 이미지를 자동으로 빌드할 수 있습니다. Docker파일은 사용자가 이미지를 빌드하기 위해 명령줄에서 호출할 수 있는 모든 명령이 포함된 텍스트 문서입니다. 이 페이지에서는 Docker파일에서 사용할 수 있는 명령어에 대해 설명합니다.
개요
Docker파일은 다음 명령어를 지원합니다:
명령어 | 설명 |
---|---|
ADD | 로컬 또는 원격 파일 및 디렉터리를 추가합니다. |
ARG | 빌드 시간 변수를 사용합니다. |
CMD | 기본 명령을 지정합니다. |
COPY | 파일 및 디렉터리를 복사합니다. |
ENTRYPOINT | 기본 실행 파일을 지정합니다. |
ENV | 환경 변수를 설정합니다. |
EXPOSE | 애플리케이션이 수신 대기 중인 포트를 설명합니다. |
FROM | 기본 이미지에서 새 빌드 단계를 만듭니다. |
HEALTHCHECK | 시작 시 컨테이너의 상태를 확인합니다. |
LABEL | 이미지에 메타데이터를 추가합니다. |
MAINTAINER | 이미지의 작성자를 지정합니다. |
ONBUILD | 이미지가 빌드에 사용되는 시점에 대한 지침을 지정합니다. |
RUN | 빌드 명령을 실행합니다. |
SHELL | 이미지의 기본 셸을 설정합니다. |
STOPSIGNAL | 컨테이너를 종료하기 위한 시스템 호출 신호를 지정합니다. |
USER | 사용자 및 그룹 ID를 설정합니다. |
VOLUME | 볼륨 마운트를 생성합니다. |
WORKDIR | 작업 디렉터리 변경. |
ADD
ADD명령과
COPY명령은 기본적으로 형식과 성격이 동일합니다. 그러나
ADD는
COPY`에 몇 가지 추가 기능을 추가합니다.
예를 들어, <source path>
는 URL
일 수 있으며, 이 경우 Docker 엔진은 이 링크에서 파일을 다운로드하여 <target path>
에 배치하려고 시도합니다. 다운로드한 파일의 권한은 자동으로 600
으로 설정됩니다. 원하는 권한이 아닌 경우 권한을 조정하려면 추가 RUN
레이어가 필요합니다. 또한 다운로드한 파일이 압축된 아카이브인 경우 압축을 풀기 위해서도 RUN
레이어가 추가로 필요합니다. 따라서 RUN
명령을 직접 사용한 다음 wget
또는 curl
도구를 사용하여 다운로드, 권한 처리, 추출 및 불필요한 파일 정리를 수행하는 것이 더 합리적입니다. 따라서 이 기능은 실제로 실용적이지 않으므로 사용하지 않는 것이 좋습니다.
만약 <source path>
가 tar
압축 파일이고 압축 형식이 gzip
, bzip2
또는 xz
인 경우 ADD
명령은 이 압축 파일을 <대상 경로>
에 자동으로 압축을 해제합니다.
경우에 따라 이 자동 압축 해제 기능은 공식 ubuntu
이미지와 같이 매우 유용합니다:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /.
...
그러나 경우에 따라 압축 파일을 추출하지 않고 복사하려는 경우 ADD
명령을 사용할 수 없습니다.
도커 공식 Dockerfile 모범 사례 문서에서는 COPY
의 의미가 매우 명확하여 파일을 복사하는 반면, ADD
는 더 복잡한 기능을 포함하고 있고 동작이 명확하지 않을 수 있으므로 가능하면 COPY
를 사용하도록 권장하고 있습니다. ADD`의 가장 적절한 사용 사례는 앞서 언급한 자동 압축 해제에 대한 필요성입니다.
또한 ADD
명령은 이미지 빌드 캐시를 무효화하여 이미지 빌드 프로세스를 느리게 할 수 있다는 점에 유의해야 합니다.
따라서 모든 파일 복사 작업에는 COPY
명령을 사용하고 자동 압축 해제가 필요한 경우에만 ADD
명령을 사용하는 원칙을 따르세요.
이 명령어를 사용할 때 --chown=<user>:<group>
옵션을 추가하여 파일의 소유자 사용자 및 그룹을 변경할 수도 있습니다.
ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/
ARG
형식: ARG <parameter_name>[=<default_value>]
빌드 인수는 ENV
와 동일한 효과를 가지며, 둘 다 환경 변수를 설정합니다. 차이점은 빌드 환경 중에 ARG
로 설정한 환경 변수는 컨테이너가 실행 중일 때는 존재하지 않는다는 것입니다. 그러나 도커 히스토리
에는 여전히 모든 값이 표시될 수 있으므로 이 때문에 비밀번호나 기타 민감한 정보를 저장하는 데 ARG
를 사용해서는 안 됩니다.
Docker파일의 ARG
명령어는 매개변수 이름과 기본값을 정의합니다. 이 기본값은 docker build
명령에서 --build-arg <parameter_name>=<value>
로 재정의할 수 있습니다.
ARG` 명령어를 사용하면 Docker파일을 수정하지 않고도 다양한 이미지를 유연하게 빌드할 수 있습니다.
ARG 명령어는 적용 범위가 있습니다. FROM
명령어 앞에 지정하면 FROM
명령어에서만 사용할 수 있습니다.
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo ${DOCKER_USERNAME}
위의 Dockerfile을 사용하면 ${DOCKER_USERNAME}
변수의 값이 출력되지 않는 것을 확인할 수 있습니다. 정상적으로 출력하려면 FROM
뒤에 ARG
를 다시 지정해야 합니다.
# 에서만 유효
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# FROM 뒤에 사용하려면 다시 지정해야 합니다.
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
다단계 빌드의 경우 이 문제에 특히 주의하세요.
# 이 변수는 각 FROM
ARG DOCKER_USERNAME=라이브러리
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 1
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 2
위의 Dockerfile의 경우, FROM
명령어 모두 ${DOCKER_USERNAME}
을 사용할 수 있습니다. 각 단계에서 사용되는 변수의 경우 각 단계에서 별도로 지정해야 합니다:
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# FROM 뒤에 변수를 사용하려면 각 단계에서 별도로 지정해야 합니다.
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
FROM ${DOCKER_USERNAME}/alpine
# FROM 뒤에 변수를 사용하려면 각 단계에서 별도로 지정해야 합니다.
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
CMD
CMD명령어는
RUN`과 비슷한 형식이며, 두 가지 형식이 있습니다:
shell
형식:CMD <command>
exec
형식:CMD [""executable", "param1", "param2"...]
- 매개 변수 목록 형식: CMD ["param1", "param2"...]`.
앞서 컨테이너를 소개할 때 Docker는 가상 머신이 아니며 컨테이너는 프로세스라고 언급했습니다. 컨테이너는 프로세스이기 때문에 컨테이너를 시작할 때 프로그램과 그 파라미터를 지정해야 합니다. 컨테이너의 주 프로세스에 대한 기본 시작 명령을 지정하는 데는 CMD
명령어가 사용됩니다.
런타임 중에 이미지에서 이 기본 명령 집합을 대체하는 새 명령을 지정할 수 있습니다. 예를 들어, ubuntu
이미지의 기본 CMD
는 /bin/bash
입니다. 도커 실행 -잇 우분투를 직접 실행하면
bash가 입력됩니다. 런타임 중에 실행할 다른 명령을 지정할 수도 있습니다(예:
docker run -it ubuntu cat /etc/os-release). 이렇게 하면 기본
/bin/bash명령이
cat /etc/os-release`로 대체되어 시스템 버전 정보를 출력합니다.
명령어 형식은 일반적으로 exec
형식이 권장되며, 구문 분석 시 JSON 배열로 파싱되므로 작은따옴표가 아닌 큰따옴표 "
를 사용해야 합니다.
셸형식을 사용하는 경우 실제 명령은 실행을 위해
sh -c`의 인수로 래핑됩니다. 예를 들어
CMD echo $HOME
실제 실행 중에는 다음과 같이 변경됩니다:
CMD [ "sh", "-c", "echo $HOME" ]로 변경됩니다.
환경 변수를 사용할 수 있는 이유는 이러한 환경 변수가 셸에서 파싱되고 처리되기 때문입니다.
CMD`를 언급하면 필연적으로 컨테이너의 애플리케이션이 포그라운드에서 실행되어야 하는지 아니면 백그라운드에서 실행되어야 하는지에 대한 문제가 제기될 수밖에 없습니다. 이것은 초보자들이 흔히 혼동하는 문제입니다.
Docker는 가상 머신이 아니며 컨테이너의 애플리케이션은 백그라운드 서비스를 시작하기 위해 systemd
를 사용하는 가상 머신이나 물리적 머신과 달리 포그라운드에서 실행되어야 합니다. 컨테이너 내부에는 백그라운드 서비스라는 개념이 없습니다.
일부 초보자는 CMD
를 다음과 같이 작성합니다:
CMD 서비스 nginx 시작
그런 다음 컨테이너가 실행 직후 종료되는 것을 발견합니다. 컨테이너 내부에서 systemctl
명령을 사용해도 전혀 실행되지 않는 것을 발견합니다. 이는 포그라운드와 백그라운드의 개념을 이해하지 못하고 컨테이너와 가상 머신의 차이를 구분하지 못한 채 여전히 기존 가상 머신의 관점에서 컨테이너를 이해하려고 하기 때문입니다.
컨테이너의 경우 시작 프로그램은 컨테이너 애플리케이션 프로세스입니다. 컨테이너는 메인 프로세스를 위해 존재하며, 메인 프로세스가 종료되면 컨테이너도 그 목적을 잃고 종료됩니다. 다른 보조 프로세스는 신경 쓸 필요가 없습니다.
service nginx start명령을 사용하는 것은 초기화 시스템이 백그라운드 데몬 프로세스로서 nginx 서비스를 시작하도록 하는 것입니다. 앞서 언급했듯이
CMD service nginx start는
CMD ["sh", "-c", "service nginx start"]로 해석되므로 실제로는
sh가 주 프로세스입 니다. service nginx start
명령이 종료되면 sh
도 종료되어 주 프로세스가 종료되고 컨테이너도 자연스럽게 종료됩니다.
올바른 접근 방식은 nginx
실행 파일을 직접 실행하고 포그라운드에서 실행되도록 하는 것입니다. 예를 들어
CMD ["nginx", "-g", "daemon off;"]
COPY
다음은 서식을 유지한 채 영어로 번역된 내용입니다:
COPY [--chown=<user>:<group>] <source_path>... <destination_path>
COPY [--chown=<user>:<group>] ["<source_path1>",... "<destination_path>"]
RUN` 명령어와 유사하게 명령줄과 유사한 형식과 함수 호출과 유사한 형식이 있습니다.
COPY` 명령은 빌드 컨텍스트의 `<소스 경로>`에서 이미지의 새 레이어에 있는 `<대상 경로>` 위치로 파일/디렉토리를 복사합니다. 예를 들어
```docker
COPY package.json /usr/src/app/
<source_path>
는 여러 경로가 될 수 있으며 와일드카드일 수도 있는데, 와일드카드 규칙은 다음과 같이 Go의 filepath.Match
규칙을 충족해야 합니다:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<destination_path>
는 컨테이너 내부의 절대 경로이거나 작업 디렉터리를 기준으로 하는 상대 경로일 수 있습니다(작업 디렉터리는 WORKDIR
명령어를 사용하여 지정할 수 있음). 대상 경로는 미리 만들 필요가 없습니다. 디렉터리가 존재하지 않으면 파일을 복사하기 전에 만들어집니다.
또한 COPY
명령어를 사용하면 읽기, 쓰기, 실행 권한, 파일 수정 시간 등 소스 파일의 다양한 메타데이터가 보존된다는 점에 유의해야 합니다. 이 기능은 특히 모든 관련 파일이 Git을 사용하여 관리되는 경우 이미지 커스터마이징에 유용합니다.
이 명령어를 사용할 때 --chown=<사용자>:<그룹>
옵션을 추가하여 파일의 소유자 및 그룹을 변경할 수도 있습니다.
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
소스 경로가 폴더인 경우 복사할 때 폴더를 직접 복사하지 않고 폴더의 내용을 대상 경로로 복사합니다.
ENTRYPOINT
ENTRYPOINT
의 형식은 RUN
명령과 같으며, exec
형식과 shell
형식으로 구분됩니다.
ENTRYPOINT
의 목적은 CMD
와 동일하며, 둘 다 컨테이너 시작 프로그램과 그 매개변수를 지정하는 데 사용됩니다. ENTRYPOINT
는 런타임에 대체될 수 있지만, CMD
보다 약간 더 번거롭고 docker run
의 --entrypoint
매개변수를 사용하여 지정해야 합니다.
ENTRYPOINT
가 지정되면 CMD
의 의미가 변경됩니다. 더 이상 직접 명령을 실행하지 않고 대신 CMD
의 내용을 ENTRYPOINT
명령의 인수로 처리합니다. 다시 말해 실행될 때는 다음과 같이 됩니다:
<ENTRYPOINT> "<CMD>"
그렇다면 CMD
가 있는데 ENTRYPOINT
가 왜 필요할까요? <ENTRYPOINT> "<CMD>"
의 장점은 무엇일까요? 몇 가지 시나리오를 살펴보겠습니다.
시나리오 1: 이미지를 명령어처럼 동작하게 하기
예를 들어 우리의 현재 공개 IP 주소를 알고 싶다면 CMD
를 사용하여 구현할 수 있습니다:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]
docker build -t myip .
로 이미지를 빌드하면, 현재 공개 IP를 확인하려면 다음과 같이 실행하면 됩니다:
$ docker run myip
Current IP: 8.8.8.8 from California, USA
잘 동작하는 것 같습니다. 이제 이 이미지를 명령어처럼 사용할 수 있군요. 하지만 명령어는 일반적으로 인수를 가지고 있습니다. 만약 인수를 추가하고 싶다면 어떻게 해야 할까요? 위의 CMD
를 보면 실제 명령어 는 curl
이므로, HTTP 헤더 정보를 표시하려면 -i
매개변수를 추가해야 합니다. docker run myip -i
로 직접 -i
매개변수를 추가할 수 있을까요?
$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
실행 파일을 찾을 수 없다는 오류가 발생했습니다. 앞서 언급했듯이, 이미지 이름 뒤에 오는 것이 command
이며, 이것은 런타임에 CMD
의 기본값을 대체합니다. 따라서 여기서 -i
는 원래 CMD
를 대체하는 것이지, curl -s http://myip.ipip.net
의 끝에 추가되는 것이 아닙니다. 하지만 -i
는 명령어가 아니므로 당연히 찾을 수 없게 됩니다.
그러므로 -i
매개변수를 추가하려면 전체 명령어를 다시 입력해야 합니다:
$ docker run myip curl -s http://myip.ipip.net -i
이것은 분명 좋은 해결책이 아닙니다. 하지만 ENTRYPOINT
를 사용하면 이 문제를 해결할 수 있습니다. 이제 이 이미지를 ENTRYPOINT
를 사용하여 다시 구현해 보겠습니다:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
이제 다시 docker run myip -i
를 해보겠습니다:
$ docker run myip
Current IP: 8.8.8.8 from: California, USA
$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive
Current IP: 8.8.8.8 from: California, USA
이번에는 잘 동작하는 것을 볼 수 있습니다. 이는 ENTRYPOINT
가 존재할 때 CMD
의 내용이 ENTRYPOINT
의 인수로 전달되기 때문입니다. 여기서 -i
가 새로운 CMD
이므로 curl
의 인수로 전달되어 원하는 효과를 달성할 수 있습니다.
시나리오 2: 애플리케이션 시작 전 준비 작업 수행
컨테이너를 시작하는 것은 main 프로세스를 시작하는 것입니다. 하지만 때로는 main 프로세스를 시작하기 전에 일부 준비 작업이 필요할 수 있습니다.
예를 들어 mysql
과 같은 데이터베이스의 경우 데이터베이스 구성 및 초기화 작업이 필요할 수 있으며, 이러한 작업은 최종 mysql 서버가 실행되기 전에 해결해야 합니다.
또한 root
사용자로 서비스를 시작하지 않고 보안을 향상시키고 싶을 수 있습니다. 하지만 서비스를 시작하기 전에 여전히 root
사용자로 필요한 준비 작업을 수행해야 하며, 마지막으로 서비스 사용자 ID로 전환하여 서비스를 시작해야 합니다. 또는 디버깅과
ENV
형식은 다음과 같습니다:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
이 명령 은 간단히 환경 변수를 설정합니다. 이어지는 RUN
명령이나 런타임에 실행되는 애플리케이션은 여기서 정의된 환경 변수를 직접 사용할 수 있습니다.
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
이 예제는 줄 바꿈하는 방법과 공백을 포함한 값을 따옴표로 묶는 방법을 보여줍니다. 이는 셸에서의 동작과 일치합니다.
일단 환경 변수가 정의되면 이후 명령에서 사용할 수 있습니다. 예를 들어 공식 node
이미지의 Dockerfile
에는 다음과 같은 코드가 있습니다:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
다음 명령은 환경 변수 확장을 지원합니다: ADD, COPY, ENV, EXPOSE, FROM, LABEL, USER, WORKDIR, VOLUME, STOPSIGNAL, ONBUILD, RUN.
이 명령어 목록을 살펴보면 환경 변수를 많은 곳에서 사용할 수 있다는 것을 알 수 있습니다. 이는 매우 강력합니다. 환경 변수를 사용하면 하나의 Dockerfile에서 다양한 이미지를 만들 수 있습니다.
EXPOSE
형식은 EXPOSE <port1> [<port2>...]
입니다.
EXPOSE
명령은 컨테이너가 실행될 때 서비스를 제공할 포트를 선언합니다. 이는 선언일 뿐이며, 이 선언으로 인해 컨테이너가 실행될 때 이 포트가 실제로 열리지는 않습니다. EXPOSE
를 Dockerfile에 작성하는 두 가지 이점은 다음과 같습니다: 하나는 이 이미지 서비스의 데몬 포트를 이미지 사용자가 이해하기 쉽게 만들어 포트 매핑 구성에 편리하게 사용할 수 있고, 다른 하나는 docker run -P
와 같이 런타임에 임의의 포트 매핑을 사용할 때 EXPOSE
로 선언된 포트를 자동으로 임의로 매핑해 줍니다.
EXPOSE
명령은 런타임에 -p <host port>:<container port>
를 사용하는 것과 구분해야 합니다. -p
는 호스트 포트와 컨테이너 포트를 매핑하는 것, 즉 컨테이너의 해당 포트 서비스를 외부에 노출하는 것이지만, EXPOSE
는 컨테이너가 사용하고자 하는 포트를 선언할 뿐 호스트에 자동으로 포트 매핑을 수행하지 않습니다.
FROM
Dockerfile에서 FROM
명령은 기본 이미지를 지정합니다. 기본 이미지는 새 이미지를 빌드하는 시작점입니다. 이것은 Dockerfile에서 가장 첫 번째 명령이며, 빌드 프로세스의 기본 환경을 정의합니다.
FROM
명령의 일반적인 사용 방법
-
공식 이미지에서 빌드하기:
FROM image:tag
이 사용법은 기존의 공식 이미지를 기본 이미지로 지정합니다.
image
는 이미지 이름이고,tag
는 버전 태그입니다. 예를 들어ubuntu:18.04
를 기본 이미지로 사용할 수 있습니다. -
사용자 정의 이미지에서 빌드하기:
FROM <username>/<imagename>:<tag>
이 사용법은 사용자 정의 이미지를 기본 이미지로 지정합니다.
<username>
은 Docker Hub의 사용자 이름,<imagename>
은 이미지 이름,<tag>
는 버전 태그입니다. -
다단계 빌드:
FROM <base-image> AS <stage-name>
이 사용법은 하나의 Dockerfile에서 여러 빌드 단계를 정의할 수 있게 해주며, 각 단계에서 다른 기본 이미지를 사용할 수 있습니다.
<base-image>
는 기본 이미지이고,<stage-name>
은 단계의 이름입니다.다단계 빌드는 일반적으로 빌드 프로세스 중에 다양한 도구와 종속성을 활용하고, 그 다음 단계로 필요한 파일이나 실행 파일을 복사하여 최종 이미지 크기를 줄이는 데 사용됩니다.
-
로컬 파일 시스템에서 빌드하기:
FROM scratch
이 사용법은 빌드를 비어 있는 이미지에서 시작한다는 것을 나타냅니다. 이 경우 필요한 파일과 구성을 직접 추가해야 합니다.
FROM
명령은 Dockerfile에서 한 번만 나타날 수 있으며, 반드시 첫 번째 명령이어야 합니다. 이 명령은 빌드 프로세스의 시작점을 정의하며, 이후의 모든 명령은 이 시
HEALTHCHECK
형식:
HEALTHCHECK [options] CMD <command>
: 컨테이너의 상태 확인을 위한 명령어를 설정합니다.HEALTHCHECK NONE
: 기본 이미지에 헬스 체크 명령이 있는 경우, 이 줄을 사용하여 해당 명령을 무시합니다.
HEALTHCHECK
명령은 Docker에게 컨테이너의 상태가 정상인지 판단하는 방법을 알려줍니다. 이는 Docker 1.12에 도입된 새로운 명령어입니다.
HEALTHCHECK
명령이 도입되기 전에는 Docker 엔진이 컨테이너 내부의 메인 프로세스가 종료되었는지 여부만으로 컨테이너의 비정상 상태를 판단할 수 있었습니다. 많은 경우 이것이 문제가 되지 않지만, 프로그램이 데드락 상태나 무한 루프에 빠지면 애플리케이션 프로세스가 종료되지 않지만 더 이상 서비스를 제공할 수 없게 됩니다. 1.12 이전에는 Docker가 이러한 컨테이너 상태를 감지하지 못하고 재배치하지 않아, 더 이상 서비스를 제공할 수 없지만 여전히 사용자 요청을 받는 컨테이너가 있었습니다.
1.12 이후 Docker는 HEALTHCHECK
명령을 제공하여, 메인 프로세스의 서비스 상태가 여전히 정상인지 확인하는 명령을 지정할 수 있게 되었습니다. 이를 통해 컨테이너의 실제 상태를 보다 정확하게 반영할 수 있습니다.
이미지에 HEALTHCHECK
명령이 지정되어 있고 이 이미지에서 컨테이너가 시작되면, 초기 상태는 starting
입니다. HEALTHCHECK
명령이 성공하면 상태가 healthy
로 변경됩니다. 연속으로 일정 횟수 이상 검사에 실패하면 상태가 unhealthy
로 변경됩니다.
HEALTHCHECK
는 다음과 같은 옵션을 지원합니다:
--interval=<interval>
: 두 번의 건강 검사 사이의 간격, 기본값은 30초입니다.--timeout=<duration>
: 건강 검사 명령 실행의 시간 제한. 이 시간을 초과하면 현재 건강 검사가 실패로 간주됩니다. 기본값은 30초입니다.--retries=<number>
: 지정한 횟수 연속으로 실패하면 컨테이너 상태가unhealthy
로 간주됩니다. 기본값은 3회입니다.
CMD
와 ENTRYPOINT
와 마찬가지로 HEALTHCHECK
도 한 번만 나타날 수 있습니다. 여러 번 나오면 마지막 항목만 적용됩니다.
HEALTHCHECK [options] CMD
뒤의 명령은 ENTRYPOINT
와 같은 형식으로, shell
형식과 exec
형식 모두 사용할 수 있습니다. 명령의 반환값으로 건강 검사의 성공 또는 실패를 판단합니다: 0
: 성공; 1
: 실패; 2
: 예약되어 있으므로 사용하지 말 것.
예를 들어 단순한 웹 서비스 이미지가 있고, 웹 서비스가 올바르게 작동하는지 확인하는 건강 검사를 추가하고 싶다고 합시다. curl
을 사용하여 이를 판단할 수 있습니다. Dockerfile
에 HEALTHCHECK
를 다음과 같이 작성할 수 있습니다:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
여기서 5초마다 검사를 실행하도록 설정했습니다(이 간격은 테스트 용도로 매우 짧으며, 실제로는 상대적으로 더 긴 간격을 사용해야 합니다). 3초 내에 건강 검사 명령이 응답하지 않으면 실패로 간주합니다. curl -fs http://localhost/ || exit 1
을 건강 검사 명령으로 사용합니다.
docker build
를 사용하여 이 이 미지를 빌드합니다:
$ docker build -t myweb:v1 .
빌드 후 컨테이너를 실행합니다:
$ docker run -d --name web -p 80:80 myweb:v1
이 이미지를 실행하면 docker container ls
를 통해 초기 상태가 (health: starting)
인 것을 볼 수 있습니다:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web
몇 초 기다린 후 docker container ls
를 다시 실행하면 상태가 (healthy)
로 변경된 것을 볼 수 있습니다:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web
건강 검사가 연속으로 지정된 재시도 횟수보다 많이 실패하면 상태가 (unhealthy)
로 변경됩니다.
문제 해결을 돕기 위해 건강 검사 명령의 출력(stdout
과 stderr
)은 상태에 저장되며 docker inspect
를 사용하여 확인할 수 있습니다.
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}
LABEL
LABEL
명령은 이미지에 키-값 쌍 메타데이터를 추가하는 데 사용됩니다.
LABEL <key>=<value> <key>=<value> <key>=<value> ...
이미지의 작성자, 문서화 주소 등을 선언하는 데 일부 라벨을 사용할 수도 있습니다:
LABEL org.opencontainers.image.authors="yeasy"
LABEL org.opencontainers.image.documentation="https://www.ubitools.com"
RUN
Dockerfile에서 RUN
명령은 컨테이너 내부에서 명령어를 실행하는 데 사용됩니다. 유효한 모든 명령어와 쉘 스크립트를 실행할 수 있습니다.
RUN
명령의 일반적인 사용 용도
-
단일 명령 실행:
RUN <command>
이 방식에서
<command>
는 컨테이너 내부에서 실행할 단일 명령어입니다. 예를 들어:RUN apt-get update
RUN apt-get install -y package이렇게 하면 각각 패키지 목록 업데이트와 패키지 설치 명령을 컨테이너 내부에서 실행합니다.
-
여러 명령 실행:
RUN <command1> && <command2>
이 방식은
&&
연산자를 사용하여 여러 명령을 연속해서 실행할 수 있게 합니다. 이전 명령이 성공해야 다음 명령이 실행됩니다. 예를 들어:RUN apt-get update && apt-get install -y package
이렇게 하면 패키지 목록 업데이트와 패키지 설치 명령을 순차적으로 실행합니다.
-
쉘 스크립트 실행:
RUN /bin/bash -c "<script>"
이 방식을 사용하면 복잡한 쉘 스크립트를 컨테이너 내부에서 실행할 수 있습니다. 스크립트는 큰 따옴표 안에 놓이며,
/bin/bash -c
를 사용하여 Bash가 스크립트를 해석하고 실행하도록 지정합니다. 예를 들어:RUN /bin/bash -c "source setup.sh && build.sh"
이렇게 하면
setup.sh
스크립트와build.sh
스크립트를 컨테이너 내부에서 실행합니다.
RUN
명령은 여러 번 사용할 수 있으며, 각 명령은 컨테이너 내부에서 명령을 실행합니다. 각 RUN
명령은 이전 이미지 레이어 위에 새로운 레이어를 생성합니다.
주의사항:
RUN
명령에서 실행된 명령은 컨테이너에 영구적으로 영향을 미치므로, 불필요한 파일과 데이터를 피하기 위해 정리 명령을 포함해야 합니다.RUN
명령에서 환경 변수를 사용해야 하는 경우 Dockerfile의ENV
명령을 사용하여 정의할 수 있습니다.
SHELL
형식: SHELL ["executable", "parameters"]
SHELL
명령은 RUN
, ENTRYPOINT
, CMD
명령에 사용할 쉘을 지정할 수 있습니다. Linux의 기본값은 ["/bin/sh", "-c"]
입니다.
SHELL ["/bin/sh", "-c"]
RUN ls ; ls
SHELL ["/bin/sh", "-cex"]
RUN ls ; ls
두 RUN
명령은 동일한 명령을 실행하지만, 두 번째 RUN
은 각 명령을 출력하고 오류 발생 시 종료합니다.
ENTRYPOINT
와 CMD
가 셸 형식으로 지정되면 SHELL
명령에 지정된 쉘이 이 두 명령에도 사용됩니다.
SHELL ["/bin/sh", "-cex"]
# /bin/sh -cex "nginx"
ENTRYPOINT nginx
SHELL ["/bin/sh", "-cex"]
# /bin/sh -cex "nginx"
CMD nginx
STOPSIGNAL
STOPSIGNAL
명령은 컨테이너에 중지 신호를 보낼 때 사용할 시스템 호출 신호를 설정하는 데 사용됩니다. 이 명령은 신호 값 또는 해당 신호 이름을 인수로 받습니다.
구문:
STOPSIGNAL signal
여기서:
signal
은 숫자 값 또는SIGKILL
과 같은 신호 이름일 수 있습니다.
STOPSIGNAL
명령을 지정하지 않으면, docker stop
명령을 사용할 때 SIGTERM
신호가 전송됩 니다. 컨테이너가 지정된 시간 내에 중지되지 않으면 SIGKILL
신호가 강제로 컨테이너를 종료하기 위해 전송됩니다.
예제 1:
FROM ubuntu:18.04
STOPSIGNAL SIGTERM
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]
위 예에서 우리는 SIGTERM
신호를 중지 신호로 설정했습니다. docker stop
명령을 실행하면 컨테이너는 먼저 SIGTERM
신호를 받게 되고, 정상적으로 중지되지 않으면 SIGKILL
신호가 전송되어 컨테이너를 종료합니다.
예제 2:
FROM ubuntu:18.04
STOPSIGNAL 9
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]
이 예에서는 신호 값 9
를 사용하여 SIGKILL
신호를 전송하도록 지정했습니다. 이는 정상적인 중지 과정을 기다리지 않고 컨테이너 프로세스를 직접 종료시킬 것입니다.
STOPSIGNAL
이 설정되어 있더라도 Docker는 컨테이너가 복구할 수 없는 상태인 경우 여전히 SIGKILL
신호를 전송할 수 있음에 유의하십시오.
USER
형식: USER <username>[:<group>]
USER
명령은 WORKDIR
과 유사합니다. 둘 다 환경 상태를 변경하고 이후 레이어에 영향을 줍니다. WORKDIR
은 작업 디렉토리를 변경하고, USER
는 후속 RUN
, CMD
, ENTRYPOINT
명령에서 실행할 사용자 ID를 변경합니다.
USER
는 지정된 사용자로 전환할 수 있을 뿐이며, 이 사용자가 사전에 생성되어 있어야 합니다. 그렇지 않으면 해당 사용자로 전환할 수 없습니다.
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
스크립트가 root
로 실행되지만 실행 중에 ID를 변경해야 하는 경우(예: 사전에 생성된 사용자로 서비스 프로세스 실행), su
나 sudo
를 사용하지 마세요. 이들은 복잡한 구성이 필요하고 TTY가 없는 환경에서 자주 실패합니다. gosu
를 사용하는 것이 좋습니다.
# redis 사용자를 생성하고 gosu를 사용하여 다른 사용자로 전환하여 명령 실행
RUN groupadd -r redis && useradd -r -g redis redis
# gosu 다운로드
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# CMD를 설정하고 다른 사용자로 실행
CMD [ "exec", "gosu", "redis", "redis-server" ]
VOLUME
형식:
VOLUME ["<path1>", "<path2>"...]
VOLUME <path>
앞서 언급했듯이, 런타임 중에는 가능한 한 컨테이너 스토리지 레이어를 읽기 전용으로 유지해야 합니다. 데이터베이스와 같이 동적 데이터를 저장해야 하는 애플리케이션의 경우, 데이터베이스 파일을 볼륨에 저장해야 합니다. 나중에 Docker 볼륨에 대해 더 자세히 설명할 것입니다. 런타임 중 동적 데이터를 마운트할 디렉토리를 사용자가 잊지 않도록 하려면 Dockerfile
에서 특정 디렉토리를 익명 볼륨으로 지정할 수 있습니다. 그러면 사용자가 마운트를 지정하지 않더라도 애플리케이션이 정상적으로 실행될 수 있습니다.
VOLUME /data
이 예에서 /data
디렉토리는 컨테이너 런타임 중 자동으로 익명 볼륨으로 마운트됩니다. /data
에 작성된 모든 데이터는 컨테이너 스토리지 레이어에 기록되지 않으므로 컨테이너 스토리지 레이어의 무상태성이 유지됩니다. 물론 이 마운트 설정은 컨테이너 실행 시 오버라이드할 수 있습니다. 예를 들어:
$ docker run -d -v mydata:/data xxxx
이 명령에서 이름이 지정된 볼륨 mydata
가 /data
위치에 마운트되어 Dockerfile
에 정의된 익명 볼륨 마운트 구성을 재정의합 니다.
WORKDIR
형식: WORKDIR <working directory path>
WORKDIR
명령어는 작업 디렉토리(또는 현재 디렉토리)를 지정하는 데 사용할 수 있습니다. 이 명령어 이후, 후속 레이어의 현재 디렉토리가 지정된 디렉토리로 설정됩니다. 디렉토리가 존재하지 않으면 WORKDIR
이 디렉토리를 생성합니다.
앞서 언급했듯이, 초보자들이 범하는 일반적인 실수 중 하나는 Dockerfile
을 셸 스크립트처럼 다루는 것입니다. 이러한 이해 부족은 다음과 같은 오류로 이어질 수 있습니다:
RUN cd /app
RUN echo "hello" > world.txt
이 Dockerfile
로 이미지를 빌드하고 실행하면 /app/world.txt
파일을 찾을 수 없거나 파일 내용이 hello
가 아님을 발견할 것입니다. 그 이유는 간단합니다: 셸에서 두 개의 연속된 줄은 동일한 프로세스 실행 환경의 일부이므로 이전 명령에서 수정된 메모리 상태가 다음 명령에 직접 영향을 줍니다. 그러나 Dockerfile
에서 이 두 RUN
명령은 완전히 다른 컨테이너 환경에서 실행되는 별개의 컨테이너들입니다. 이는 Dockerfile
빌드의 레이어드 스토리지 개념에 대한 이해 부족에서 비롯된 오류입니다.
앞서 언급했듯이, 각 RUN
은 새 컨테이너를 시작하고, 명령을 실행한 후 파일 변경 사항을 커밋합니다. 첫 번째 레이어 RUN cd /app
은 현재 프로세스의 작업 디렉토리만 변경하며, 이는 메모리 내 변경사항일 뿐 파일 변경으로 이어지지 않습니다. 두 번째 레이어에 도달하면 완전히 별개의 새 컨테이너가 시작되므로 이전 빌드 프로세스의 메모리 변경 사항을 상속할 수 없습니다.
따라서 후속 레이어에 대한 작업 디렉토리 위치를 변경해야 하는 경우 WORKDIR
명령을 사용해야 합니다.
WORKDIR /app
RUN echo "hello" > world.txt
WORKDIR
명령에 상대 경로를 사용하면 전환되는 디렉토리는 이전 WORKDIR
에 상대적입니다:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
RUN pwd
의 작업 디렉토리는 /a/b/c
가 됩니다.