Search

Dockerfile

Created
2021/05/28
Tags
Docker
Dockerfile

Subjects

1. Dockerfile

1) 필요성

기존 글에서 소개된 방법들로 Image를 만드는 과정은 다음과 같다.
1.
운영체제에 해당하는 Image를 이용하여 Container를 생성한다.
2.
Container 내부에서 필요한 소스코드를 가져오고, 필요한 스크립트들을 실행하는 등 환경을 구성한다.
3.
commit 명령어를 통해 Container에 해당하는 새로운 Image를 생성한다.
위와 같은 과정은 모두 다 수작업으로 이뤄지다보니 동일한 Container를 여럿 구성하고 조금씩 다른 설정을 주어 각각 다른 Image를 생성할 때는 반복되는 많은 명령어들을 입력해줘야 하고, 해당 명령어들을 입력하기 위해서는 단계 별 작업이 마칠 때까지 기다려야 하는 일이 발생한다.
물론 직접 모든 과정을 수행하는 것이 작업을 일일이 확인해보고 Image를 만들기 때문에 Image의 확실한 동작을 보장할 수 있다는 장점은 있지만, 작업의 비효율성을 생각하면 그리 바람직한 방법은 아니다.
이와 같은 비효율적인 작업을 극복하기 위한 것이 Dockerfile이다. Dockerfilebuild 명령어를 이용하여 손쉽게 Image를 만들 수 있다. Dockerfile을 이용하면 어떤 동작을 할지 하나 하나 명시하기 때문에 생성하려는 Container에서 어떤 패키지가 요구되고, 어떤 코드를 이용하며, 어떤 명령어들이 실행되는지 등 Image를 생성하는 방법에 대해 명확하게 기재함으로써 한 눈에 확인할 수 있으며, 유지 보수적인 측면에서도 서비스에서 사용하는 어플리케이션의 빌드와 배포를 자동화하여 득을 볼 수 있는 점이 많다. 이와 같은 이유 때문에 Docker HubImage를 배포할 때 Dockerfile을 함께 제공하는 경우가 정말 많다.

2) 작성 방법 및 주의할 점

FROM ubuntu:14.04 LABEL author bigpel66 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
Docker
복사
위 예시에서 기재된 FROM, RUN, ADD, EXPOSE 등의 키워드들은 Dockerfile에서 사용하는 명령어이고, 위의 Dockerfile을 이용하여 간단한 Apache Web Server에 대한 Image를 생성할 수 있다. 해당 스크립트에서 사용된 Dockerfile의 명령어를 이해하지 못하더라도 글 아래에서 자세하게 다루기 때문에 문맥에 대해서만 이해하고 넘어가도 무방하다.
Dockerfile에서는 test.html을 요구하므로 Dockerfile이 위치한 곳에 아무 내용이라도 들어간 test.html을 미리 만들어 둬야 build 명령어가 정상적으로 동작한다.
Dockerfile은 기본적으로 스크립트이므로 한 줄 한줄 해석되어 실행되며, 작성된 한 줄이 곧 하나의 명령어를 이루게 된다. Docker Engine이 인식할 수 있는 Dockerfile에서의 명령어는 대문자와 소문자를 구별하지는 않지만, 쉘에서 동작하는 명령어와 구분짓기 위해 일반적으로 대문자로 표기한다.
Dockerfile은 한 번 build 명령어로 Image로 만들기 시작했을 때 사용자와 상호작용을 보장하지 않기 때문에, 스크립트 내부에 사용자와 상호작용을 요구하는 명령어가 실행되어 표준 입력이 활성화되면 build 명령어가 이를 오류로 처리하여 스크립트의 진행을 멈추게 된다. 따라서 RUN 명령어에서 apt-get install apache2와 같이 사용자의 문답을 요구하는 명령어에 대해선 -y 옵션처럼 표준 입력으로 사용자와 상호작용을 하지 않도록 문답에 모두 y로 처리하는 식으로 사용자와의 상호작용이 없게 만들어줘야 한다.
docker build <path>
제시된 예시를 Image로 만들기 위해 사용되는 build 명령어는 위와 같다. 자세히 보면 build 명령어를 이용할 때 Dockerfile의 파일 이름을 인자로 주지 않는 것을 확인할 수 있다. 이는 Docker Engine<path>로 주어진 값을 Context로 이용하여, 해당 경로에 존재하는 Dockerfile이라는 파일을 자동으로 찾기 때문이다. 즉, Dockerfile의 이름은 반드시 Dockerfile이어야 한다는 것이다.
Context에 대한 자세한 내용은 동작 원리 부분에서 다룬다. <path> 대신에 <url>을 기재할 수도 있는데, <url>을 기록한 경우에는 기재한 URL에서 Dockerfile 내용을 가져와서 build 명령어를 수행할 수 있다. 그리고 Dockerfile을 다른 이름으로 유지하고 있다면 -f 옵션을 기재함으로써 Dockerfile의 파일 이름을 입력할 수 있는데, 기본적으로는 Dockerfile이라는 이름을 찾아서 사용하도록 되어 있다.
위 형태의 명령어를 이용하면 Context에 해당하는 경로에서 Dockefile을 찾아 Image를 만들게 되고, 이 때 Docker Engine에 의해 임의로 16자리 이상의 HashingDigest값이 <image-id>로 할당된다. 따라서 별도의 <image-name>이 정해지지 않았기 때문에 Dangling Image로 생성된다. Dangling Image로 생성되는 상황을 피하고 싶다면, -t 옵션을 이용하여 <image-name>에 대해 기재해야 한다. 결론적으로 -t 옵션은 거의 필수적이라고 볼 수 있다.

3) 동작 원리

Dockerfile을 이용하여 Image를 생성할 때, 출력으로 찍히는 로그들이 굉장히 많은 것을 볼 수 있다. Image를 생성하는 과정을 단계별로 나누어 동작 원리를 알아보자.

Context 및 .dockerignore

build 명령어로 Image를 만들기 시작하면 Docker Enginebuild 명령어에 대한 Context를 우선적으로 확인하게 된다. Contextbuild에 대한 Context를 의미하며, Image를 생성하는데 요구되는 각 파일 및 코드들의 위치를 이해하는데 사용된다. 여기서 말하는 ContextDockerfile이 존재하는 곳을 말하기 때문에 명령어의 인자로 받은 <path>가 곧 Context가 된다. 즉, <path>에 해당하는 경로에 앞 서 제시된 파일 및 코드들이 위치하게 되므로, 해당 파일 및 코드들은 Dockerfile과 동일한 경로에 위치하게 된다.
위 그림과 같이 Context에 대한 파악이 완료되면, ADDCOPY 같은 명령어를 이용할 때 기재한 파일 이름은 해당 Context를 기반으로 동작하게 된다. 따라서 Context를 기반으로 동작하는 파일들에는 디렉토리를 포함하게 되는데, 이 때문에 Context를 파악한다는 말은 인자로 받은 <path>의 모든 하위 디렉토리를 Context에 포함한다는 말이 된다. 결과적으로 Dockerfile이 존재하는 경로에는 Image를 생성하는데 필요한 파일들만 두는 것이 바람직한 것이 되므로 하위 디렉토리가 많이 존재하는 곳에 Dockerfile을 두지 않도록 주의해야 한다. 그렇지 않으면 불필요한 파일들의 포함으로 build 명령어 수행에 많은 시간이 요구될 수도 있을 뿐만 아니라, Host의 자원 중 Memory를 의도치 않게 많이 점유하는 상황이 발생할 수 있다.
하지만 Dockerfile을 작성하다 보면 최종적인 Image에는 포함되지 않지만 정상적인 동작을 보장하기 위해 테스트를 목적으로 둔 파일들이 여럿 존재할 수 있는데, 이런 파일들이 상황에 따라 Context에서 쉽게 제외될 수 있도록 돕는 것이 .dockerignore이다. .dockerignore.gitignore와 비슷한 기능을 하기 때문에 Context에서 제외할 파일을 .dockerignore에 명시만 해주면 된다.
Context에서 제외할 파일들을 .dockerignore에 명시했다면, .dockerignoreDockerfile에 함께 위치시키기만 하면 build 명령어를 이용할 때 위 그림처럼 Docker Engine이 알아서 이를 처리하게 된다.
예를 들어 이전에 주어진 Dockerfile을 이용할 때, .dockerignoretest.html을 명시하면 test.htmlContext에서 존재하지 않기 때문에 오류가 발생하는 것을 볼 수 있다.
.dockerignore에서 자주 사용하는 구문들은 *, ?, ! 등이 있는데 이를 간단한 예시로 알아보자.
# 현재 Context에서 모든 html 파일을 제외한다. # *은 모든 문자열을 의미한다. *.html # 현재 Context에서 모든 디렉토리의 html 파일을 제외한다. # */는 한 단계의 디렉토리를 의미하므로, 하위 디렉토리에 존재하는 html 파일은 제외하지 않는다. */*.html # 현재 Context에서 test.htmm, test.htmn, test.htmo 등을 제외한다. # ?은 한자리 문자를 의미한다. test.htm? # 현재 Context에서 test.html만은 제외하지 않는다. # !은 제외하지 않겠다는 것을 의미한다. !test.html
Docker
복사

Intermediate Container의 create, commit, remove

이전에 작성된 글에 따르면 Image를 만들기 위해서 Container를 하나 생성한 뒤 commit 명령어를 통해 Image를 생성할 수 있었다. Dockerfile을 이용할 때는 사용자가 직접 Container를 생성하지 않았지만, 내부적으로 Container를 생성해낸 뒤 이를 Image로 찍어내게 된다.
그렇다면 build 명령어를 수행하는 동안 Container는 1개만 유지되어 Image를 만들게 되는 것일까? Image의 생성은 1개의 Container에서만 일어나지 않는다. build 명령어의 수행 결과를 보면 대략적인 유추가 가능한데, 위 그림처럼 각 단계들이 수행될 때는 새로운 Container를 생성함으로써 최종적인 Image를 만들게 된다. 예시대로 추적해보면 FROM 구문에 해당하는 Container를 만들고 Image를 생성한 뒤 여기에 사용되었던 Container를 삭제하고, 이전 Image를 이용하여 RUN 구문에 해당하는 Container를 만들고 다시 Image를 만든 뒤 Container를 삭제하는 식으로 최종 단계까지 반복하게 된다.
Storage Driver에 대해 작성한 글을 읽었다면 더 자세히 이해할 수 있을 것인데, 위 그림과 같이 이전 단계에서 생성된 Image를 그대로 이용하여 그 위에 Container를 쌓고 이를 다시 Image를 만드는 식이 된다. 이를 통해 각 구문에 해당하는 Layer들을 계층적으로 유지할 수 있게 된다. Image를 구성하고 있는 세부적인 Layer들에 따라서 차이는 있겠지만, Dockerfile에 기재된 명령어 한 줄마다 적어도 하나의 Layer를 이루게 되고 그만큼의 Container가 생성되었다가 삭제되는 과정을 거치게 된다.
따라서 Dockerfile에 작성하는 명령어에 대해서는 각 명령어가 하나의 Layer를 이루게 되고 그에 따른 Container의 생성과 삭제가 병행되기 때문에 많은 명령어를 이용하는 것이 권장되지 않는다. 즉, Image를 만드는데 있어서 많은 시간을 절약하고 싶다면 명령어를 구성하는 각 줄들을 최대한 줄이는 노력이 필요하다.
FROM ubuntu:14.04 LABEL author bigpel66 RUN mkdir /test RUN fallocate -l 100m /test/dummy RUN rm /test/dummy
Docker
복사
특히 RUN 명렁어에 대해서 많은 단축을 만들어 낼 수 있는데, 위와 같이 작성된 구문이 있다면 아래와 같이 고침으로써 더 나은 build를 보장받을 수 있다. 이는 단순히 build 시간에 대한 득 뿐만 아니라 Layer의 수를 줄일 수 있으므로 Image의 크기도 줄일 수 있는 장점이 있다.
위 쪽의 명령어 묶음은 100MB의 파일을 생성하고 삭제 했음에도 각 명령어 행이 Layer를 이루고 있기 때문에 Image의 크기가 100MB를 웃도는 것을 볼 수 있따. 이는 100MB의 파일을 삭제했다는 변경 사항에 대한 기록만 있을 뿐 특정 Layer에는 100MB가 존재하는 형태로 Image가 만들어지기 때문이다. 따라서 실제로 사용하지 못하는 공간에 대해서 Layer로 존재하지 않도록 아래와 같이 작성하게 되면 Image의 크기를 확연히 줄일 수 있는 것을 볼 수 있다.
FROM ubuntu:14.04 LABEL author bigpel66 RUN mkdir /test && fallocate -l 100m /test/dummy && rm /test/dummy
Docker
복사

Image Layer Cache

명령어를 단축하도록 신경 써서 Dockerfile을 작성해야 하는 단점 아닌 단점 속에서 Dockerfile이 갖는 또 다른 장점으론 무엇이 있을까? Dockerfile에서 수행되는 명령어들은 새로운 Container를 만들고 이것으로 Image를 만듦으로써 최종 Image를 구성하는 Layer들을 둔다고 했는데, DockerImage가 갖는 장점을 잘 생각해보면 Image를 구성하는 Layer들은 재사용이 가능하다고 했었다. 즉, 중간 단계에서 사용된 Layer들은 모두 시스템 상에 유지되고 있고 이를 Cache로 이용하게 된다. 따라서 Dockerfile에서 수행된 동일한 명령어들은 모두 같은 Layer를 사용한다. 이를 통해 여러 차례 이용된 동일한 명령어 구문들은 build 명령의 수행 시간을 줄일 수 있게 된다.
FROM ubuntu:14.04 LABEL author bigpel66 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
Docker
복사
FROM ubuntu:14.04 LABEL author bigpel66 RUN apt-get update
Docker
복사
Apache Web ServerImage를 만드는 예제의 출력 결과를 보면 특별히 Cache에 대한 내용을 찾아볼 수 없었는데, 위와 같이 2개의 Dockerfile이 있다고 했을 때 이를 각각 실행해보자.
실행 결과를 살펴보면 첫 번째 build에서는 CacheLayer를 사용하지 않고서 수행되는 것을 볼 수 있고, 두 번째 build에서는 CacheLayer를 사용했음을 볼 수 있다. 이를 통해 위에서 설명한 것처럼 build에서 이전에 사용되었던 명령어와 동일한 명령어가 사용되었다면, 새로운 Layer를 생성하는 것이 아니라 이전에 만들어진 Layer를 활용한다는 것을 알 수 있다.
Layer의 재사용으로 build의 수행 시간을 단축하는 것에는 Dockerfile을 잘못 작성하여 오류가 발생했을 때 특히 더 많은 이득을 볼 수 있다. 예를 들어 위의 Apache Web Server에 대한 Image를 만들려고 했을 때 test.html이 요구되는 상황에서 test.html이 존재하지 않아 오류가 발생했다고 해보자. 오류 발생으로 인해 build 수행이 종료되더라도 오류가 발생하기 이전까지의 Layer는 생성된 상황이기 때문에, 오류를 고치고 다시 build를 수행하더라도 처음부터 Layer를 만드는 것이 아니라 test.html의 오류가 발생하기 이전은 Cache를 이용하고 그 이후부터 build를 수행하는 것을 볼 수 있다. 이를 통해 큰 큐모의 Image를 생성하는 Dockerfile을 긴 시간동안 build하다가 중간에 오류가 발생하더라도 다시 초기 상황의 긴 시간을 기다릴 필요가 없게 된다.
Image Layer Cache는 이와 같이 큰 장점도 있지만, 반대로 주의해야 할 점도 있다. GitHub에 관련된 명령어를 사용할 때가 이에 해당한다. 예를 들어서 1번 Image를 생성할 때 git clone이란 명령어를 통해 Repository에서 코드를 받아와서 환경을 구성했다고 해보자. 그리고 git clone의 대상 Repository에 코드를 변경함으로써 다시 build 하여 2번 Image를 생성하려는 상황이 되었을 때, 시스템 상에는 git clone이라는 명령어를 수행하여 기존의 코드를 보유하고 있는 Layer가 이미 존재하여 git clone이라는 명령어에 대해 새로운 Layer를 생성하지 않는다. 결과적으로 새롭게 buildImage에는 변경된 코드를 이용하지 않고 기존의 코드를 이용하고 있는 상황이 된다. 이와 같이 초기에 사용된 코드가 변경 되었을 때, 변경된 코드를 이용하여 Image를 만들고 싶다면 시스템 상에 유지 중인 Cache를 이용하지 않고 build를 수행해야 한다. 따라서 Cache를 삭제하거나, build 명령어에 Cache를 이용하지 않겠다는 옵션을 기재함으로써 이를 해결할 수 있다.
docker builder prune
시스템 상에 유지 중인 Layer들의 Cache를 삭제하는 명령어는 위와 같다.
docker build —no-cache <path>
build 시에 Cache를 이용하지 않고 Image를 생성할 때는 위와 같이 —-no-cache 옵션을 기재한다.
상황에 따라 맞는 명령어를 이용하여 Image를 생성하면 된다. 굳이 모든 Cache를 지울 필요가 없다면 아래 쪽 명령어를 이용하면 되고, build 명령어가 길어지는 것이 싫고 Cache를 삭제해도 상관 없다면 위 쪽 명령어를 이용하면 된다.

4) Multi-Stage

2개의 Dockerfile을 이용하여 2개의 Image를 만들고, 이를 이용하여 각각 Container를 생성했다고 해보자. 만약 두 Container가 동일한 환경을 구성하고 있다면, 이는 분명 환경 구성을 위한 LayerCacheImage를 생성하는데 이용했을 것이고, 해당 Layer를 공유하고 있을 것이다. 그렇다면 Image의 크기는 어떨까? 동일한 환경에 대한 Layer를 2개의 Image가 모두 포함하고 있어 환경에 대한 Layer 만큼을 더 차지하고 있을 것이다. 이 때 Image의 크기를 줄일 수 있는 방법이 Multi-Stage이다. 즉, Multi-Stage를 이용하면 Container의 서비스를 구성하기 위한 중복된 많은 DependenciesLibrary들을 시스템 상에 유지함과 동시에 최종적인 Image에는 포함시키지 않을 수 있다.

(1) Multi-Stage를 이용하지 않았을 때

// test1.go package main import "fmt" func main() { fmt.Println("Hello World") }
Go
복사
# Dockerfile1 FROM golang LABEL author bigpel66 ADD test1.go /root WORKDIR /root RUN go build -o /root/test /root/test1.go CMD ["./test"]
Docker
복사
// test2.go package main import "fmt" func main() { fmt.Println("Hello 42") }
Go
복사
# Dockerfile2 FROM golang LABEL author bigpel66 ADD test2.go /root WORKDIR /root RUN go build -o /root/test /root/test2.go CMD ["./test"]
Docker
복사
Docker를 만드는데 이용된 Golang으로 Hello World를 출력하도록 만드는 1번 DockerfileHello 42를 출력하도록 만드는 2번 Dockerfile이 있다고 해보자. (각 Dockerfile이 요구하는 go 파일도 생성해야 한다.)
1번 Dockerfile을 이용하여 build를 수행하여 test1이라는 Image를 만들 때는 별도의 CacheLayer들이 존재하지 않으므로 조금의 시간을 기다리면 Image가 생성된다. 이 때 Hello World라는 출력 프로그램을 생성하기 위해 Golang에 대한 환경을 구성하는 Layer들이 생성된다.
2번 Dockerfile을 이용하여 build를 수행하면 test2라는 Image를 만들게 되는데, 이 때는 test1을 생성할 때 만들어진 Layer들을 Cache로 이용하기 때문에 곧바로 Image가 생성되는 것을 볼 수 있다. 이 때 Hello 42라는 출력 프로그램을 생성하기 위해서 기존의 Golang에 대한 환경을 구성하는 Layer들을 재사용하는 것을 볼 수 있다.
서로 다른 Image를 생성할 때 동일한 환경에 대한 Layer를 재사용 했음에도, 생성된 Image들을 확인해보면 그 크기가 구성된 환경의 크기만큼 더해져 2개의 Image 모두 864MB의 큰 크기를 유지하는 것을 볼 수 있다.

(2) Multi-Stage를 이용했을 때

Multi-StageDockerfile 내부에 FROM 명령어 구문을 여럿 두고 이들을 각각의 Chunk로 보았을 때, 명령어들을 수행 후 마지막 FROM 구문의 Chunk만 최종적인 Image로 정의하는 것이다. 이전에 사용했던 예시를 그대로 이용해보자.
// test1.go package main import "fmt" func main() { fmt.Println("Hello World") }
Go
복사
# Dockerfile1 FROM golang LABEL author bigpel66 ADD test1.go /root WORKDIR /root RUN go build -o /root/test /root/test1.go FROM alpine LABEL author bigpel66 WORKDIR /root COPY --from=0 /root/test ./ CMD ["./test"]
Docker
복사
// test2.go package main import "fmt" func main() { fmt.Println("Hello 42") }
Go
복사
# Dockerfile2 FROM golang LABEL author bigpel66 ADD test2.go /root WORKDIR /root RUN go build -o /root/test /root/test2.go FROM alpine LABEL author bigpel66 WORKDIR /root COPY --from=0 /root/test ./ CMD ["./test"]
Docker
복사
1번 Dockerfile을 이용하여 build를 수행하여 test1이라는 Image를 만들 때는 별도의 CacheLayer들이 존재하지 않으므로 조금의 시간을 기다리면 Image가 생성된다. 이 때 Hello World라는 출력 프로그램을 생성하기 위해 Golang에 대한 환경을 구성하는 Layer들이 생성된다.
2번 Dockerfile을 이용하여 build를 수행하면 test2라는 Image를 만들게 되는데, 이 때는 test1을 생성할 때 만들어진 Layer들을 Cache로 이용하기 때문에 곧바로 Image가 생성되는 것을 볼 수 있다. 이 때 Hello 42라는 출력 프로그램을 생성하기 위해서 기존의 Golang에 대한 환경을 구성하는 Layer들을 재사용하는 것을 볼 수 있다.
서로 다른 Image를 생성할 때 동일한 환경에 대한 Layer를 재사용한 것은 Multi-Stage를 이용하지 않았을 때와 동일하지만, Image에는 그 크기가 포함되지 않은 것을 볼 수 있다. Dockerfile의 명령어들을 글 아래에서 자세하게 다루겠지만, Multi-Stage에서 사용된 Dockerfile을 간단히 짚고 넘어가보자.
Multi-Stage를 이용할 때 FROM 명령어 구문으로 내부의 명령어들을 여러 Chunk로 나눈다고 했는데, 이 때 각 Chunkindex로써 이용하는 것이 가능하다. 제시된 Dockerfile을 살펴보면 COPY 명령어에 —-from이라는 옵션이 사용된 것을 볼 수 있다. 여기서 —-from에 이용된 값이 FROM 명령어 구문의 Chunk를 지칭하는 index가 된다. 첫 번째 FROM 명령어 구문의 Chunk0index, 두 번째 FROM 명령어 구문의 Chunk1index를 가지는 식이다. 이처럼 나뉘어진 Chunk들은 각각 다른 Image를 결과물로 만들어 내는데, Dockerfile의 첫 번째 Chunkgolang이라는 Image를 이용하여 실행 파일을 가진 새로운 Image를 만들고, 두 번째 Chunkalpine이라는 LinuxImage를 이용하여 새로운 Image를 만든다. 이 때 두 번째 Chunk는 실행 파일을 첫 번째 Chunk로부터 복사하여 이용한다. 그리고 최종적으로는 두 번째 Chunkbuild의 결과물로 사용된다. 따라서 반드시 필요한 파일들만 최종 Image에 유지하면서 Image의 크기를 매우 줄일 수 있게 된다.
alpine이라는 ImageubuntuCentOS와 달리 프로그램 실행을 위해 필수적인 런 타임만 포함된 LinuxImage이다. 해당 Image를 이용하면 경량화된 Image를 생성하는 것이 가능하다.
FROM golang LABEL author bigpel66 ADD test.go /root WORKDIR /root RUN go build -o /root/test /root/test.go FROM alpine LABEL author bigpel66 WORKDIR /root COPY --from=0 /root/test ./ CMD ["./test"]
Docker
복사
FROM golang as base LABEL author bigpel66 ADD test.go /root WORKDIR /root RUN go build -o /root/test /root/test.go FROM alpine LABEL author bigpel66 WORKDIR /root COPY --from=base /root/test ./ CMD ["./test"]
Docker
복사
Multi-Stage를 이용하게 되면 최소 2개의 Image를 활용하게 되고, 각 Chunkindex로써 사용하는 것이 헷갈릴 수 있으므로 FROM 명령어 구문에 as를 이용하여 별칭을 붙이는 것이 가능하다. 이를 통해 위에 제시된 첫 번째 Dockerfile을 두 번째 Dockerfile처럼 작성할 수 있다.

2. Dockerfile Commands

1) FROM

Image를 생성하기 위해서 가장 처음에 이용될 Image를 의미한다. FROM 명령어는 Dockerfile을 작성할 때 최소한 1회는 입력되어야 한다. FROM으로 입력할 Imagedocker 명령어에서 입력했던 <image-name>과 동일한 형식으로 기재하면 되고, 로컬에 해당 Image가 없다면 pull 명령어를 자동으로 수행하여 Docker Hub에서 가져오게 된다.
# 버전 14.04의 ubuntu를 초기 Image로 사용 FROM ubuntu:14.04
Docker
복사

2) LABEL

Image의 메타 데이터를 추가할 때 이용되는 명령어이다. 메타 데이터는 key : value 쌍으로 구성되며, 이는 docker image inspect 명령어를 이용하여 확인할 수 있다. LABEL 명령어는 Dockerfile을 작성할 때 필수적으로 요구되는 명령어는 아니며, 여러 차례 이용하여 메타 데이터를 추가할 수 있다.
# Image에 "author" : "bigpel66" 이라는 메타 데이터를 추가 LABEL author bigpel66
Docker
복사

3) ADD & COPY

ADDCOPY는 공통적으로 Host에서 유지 중인 파일을 Image 내부에 복사하여 파일을 추가하는 명령어이다. 추가하려고 명시한 파일은 기본적으로 build 명령어의 <path>라는 Context를 기준으로 삼아 찾게 된다. 예를 들어 test.html을 명시하였고 build 명령어의 Context.이라면, Dockerfile이 위치하는 곳 기준으로 동일한 곳에 있는 ./test.html을 찾아 Image 내부에 추가하게 된다.
# build 명령어의 Context를 기준으로 test.html을 찾아 Image 내부의 /var/www/html에 추가 ADD test.html /var/www/html
Docker
복사
ADDCOPY의 동작은 복사라는 공통점이 있지만, ADD의 명렁어 수행이 COPY에 비해 조금 더 포괄적인 의미를 갖는다. COPYbuild 명령어의 Context를 이용하여 로컬의 파일을 복사할 때만 이용되지만, ADD는 그 활용 범위가 URLtar와 같은 아카이빙 파일에도 해당된다. ADD를 이용할 때 URL을 기재하게 되면, URL에 해당하는 파일을 받아와서 Image 내부에 추가하게 되고, tar를 기재하게 되면 자동으로 아카이빙을 풀어서 Image 내부에 추가하게 된다. 이에 대해서 URLtar 내부에 해당하는 파일들이 어떤 것들인지 명확히 할 수 없을 수도 있기 때문에 가급적이면 ADD 보다는 COPY를 이용하는 것이 좋다. COPY를 이용하게 되면 로컬의 Context에서 파일을 직접 추가하기 때문에 아카이빙을 풀지 않은 tar 자체를 추가하는 것도 가능하고, URLtar 내부에 해당하는 파일들이 모두 어떤 파일인지 명확히 할 수 있기 때문이다.
# URL에 해당하는 파일을 /var/www/html에 추가 ADD https://<some-repository>/<someone>/<file-name> /var/www/html # test.tar의 아카이빙을 풀어 /var/www/html에 추가 ADD test.tar /var/www/html
Docker
복사

4) RUN

Dockerfile을 이용하여 Image를 생성할 때 각 명령어 한 줄이 곧 하나의 Layer가 된다고 했었고, Layer를 만들어 내는 과정에서 Container가 생성되었다가 삭제된다고 했었다. RUN 명령어도 이와 같이 동작하기 때문에 RUN 명령어 수행 뒤에는 Image의 한 Layer로써 유지되지만, Layer를 생성하기 이전의 Container 상태에서는 RUN 명령어에 기재된 쉘 명령어들을 Container 내부에서 실행하게 된다. 즉, RUN 명령어에 기재된 쉘 명령어를 Container 내부에서 수행하고 난 다음의 상태가 Layer로 만들어지는 것이다.
# Container 내부에서 apt-get update 명령어를 수행 RUN apt-get update # Container 내부에서 apt-get install apache2 -y 명령어를 수행 # 사용자의 입력을 요구하지 않고 yes를 답하도록 -y 옵션을 이용 RUN apt-get install apache2 -y
Docker
복사
RUN 명령어 하나가 추가될 때마다 이에 대한 Layer를 만들게 되므로 RUN 명령어를 최소화 하는 것이 중요하고, 표준 입력을 받는 상황이 되면 build 명령어는 이를 오류로 간주하므로 상호작용이 이뤄지지 않도록 쉘 명령어의 적절한 옵션을 취해야 한다.
RUN 명령어를 이용할 때는 위와 같이 쉘 명령어를 입력하는 방법도 있지만, json 배열 형태로 작성할 수도 있다. json 배열 형태로 RUN 명령어를 이용하는 경우에는 RUN ["실행 가능한 파일", "인자 1", "인자 2", ...]과 같이 작성한다. 두 방법이 어떻게 다른지 이해하기 위해서 각 방법이 무엇을 어떻게 실행하는지 알아볼 필요가 있다.
기존에 작성한 RUN 명령어와 같이 json 배열 형태로 작성하지 않으면, sh -c를 이용하여 Dockerfile에 기재한 쉘 명령어를 실행하게 된다. -c 옵션을 이용하면 쉘을 실행한 상태로 유지하여 쉘 명령어를 처리하는 것이 아니라 인자로 받은 명령어를 수행만 한 후 그 역할을 마치게 된다. 즉, -c 옵션은 쉘 명령어를 쉘의 지속적인 실행 없이 처리하고 싶을 때 주로 이용하는 옵션이다. 따라서 json 배열 형태로 작성하지 않은 RUN 명령어는 sh -c가 생략이 되었을 뿐 이를 이용하여 쉘 명령어를 처리하는 구문이라고 이해하면 된다.
반면에 json 배열 형태로 RUN 명령어를 구성하게 되면 sh -c를 이용하지 않는다. 따라서 쉘 명령어를 실행시키는 구문이 아니라는 것을 알 수 있는데, 위에서 적힌 것을 잘 살펴보면 json 배열 형태로 구성된 명령어는 파일을 실행시키는 구문임을 알 수 있다. 물론 쉘 명령어들은 기본적으로 시스템 상에 파일을 통해 실행되기 때문에 json 배열 형태로 RUN 명령어를 구성할 때 쉘 명령어를 입력하여도 동작하는 것을 볼 수 있지만, 다음과 같은 예를 통해 json 배열 형태로 작성한 RUN 명령어 구문이 파일을 실행시킨다는 것을 더 명확히 이해할 수 있다.
# 제시된 두 방법의 차이를 확인하기 위해 이용되는 구문 RUN export NEW_ENV=docker # json 배열 형태가 아닌 경우 RUN echo $NEW_ENV # json 배열 형태인 경우 RUN ["sh", "-c" "echo $NEW_ENV"]
Docker
복사
json 배열 형태로 RUN 명령어를 구성해보면 알 수 있지만, echo 역시 실행 가능한 파일이기 때문에 RUN ["echo", "인자 1", "인자 2", ...]와 같은 형태로 이용하는 것이 가능하다. 하지만 예시에서는 이와 같은 형식으로 작성되지 않은 것을 볼 수 있다. 우선 json 배열 형태로 작성한 구문은 가장 첫 요소를 실행 가능한 파일로 받기 때문에 RUN ["echo $NEW_ENV"]로 이용할 수 없다. 이는 $NEW_ENV 부분도 파일의 이름으로 인식하기 때문이다. 또한 RUN ["echo", "$NEW_ENV"]와 같이 작성하더라도 쉘이 실행되지 않은 상태이므로 $NEW_ENV가 무엇인지 알 수 없기 때문에 실행 가능한 파일로 echo를 이용할 수 없는 것이다. 따라서 환경 변수를 이용하고자 하는 경우에는 예시에 작성된 것처럼 쉘을 파일로써 실행 시킨 뒤, 쉘 명령어들을 그 인자로 받는 식으로 작성하게 된다.

5) WORKDIR

Dockerfile에 기재된 명령어들을 실행할 위치를 변경할 때 사용된다. 이는 쉘에서 cd 명령어를 이용하여 현재 사용하고자 하는 위치를 옮기는 것과 동일한 개념이다. 따라서 여러 차례 WORKDIR을 이용하는 것은 여러 차례 cd를 이용하는 것과 동일하다. 예를 들어 WORKDIR /root와 같이 작성한 후에 RUN touch test를 작성하면, /root/test로 파일이 생성된다. 아래에 명시된 두 명령어 구문은 동일한 구문들이다.
# 한 번에 /root/some-directory로 이동 WORKDIR /root/some-directory # 두 번에 걸쳐 /root/some-directory로 이동 WORKDIR /root WORKDIR some-directory
Docker
복사

6) EXPOSE

Dockerfile로 생성될 Image를 사용하려고 할 때, 외부와의 통신을 위해 포트 포워딩을 하려는 Port를 명시하는 명령어이다. 다만 오해하면 안 되는 점이 있는데, EXPOSE 명령어를 이용한다고 해서 곧 바로 HostPort와 바인딩 되는 것이 아니다. 또한 최종적으로 생성된 Image를 이용하여 Container를 생성한다고 해도 HostPort와 바인딩 되는 것도 아니다. EXPOSE는 단순히 해당 Image가 특정 Port를 이용한다고 명시하고 이를 밝히는 역할만 하기 때문에, Container 생성 시 EXPOSE에 명시된 Port를 받아와 자동으로 바인딩하는 옵션을 사용하거나 직접 Port를 하나 하나 바인딩하는 과정이 필요하다. 전자가 docker run 명령어의 -P 옵션이고 이는 Host의 무작위 Port로 바인딩 되므로 어떤 Port를 이용하는지 알고 싶다면 별도의 확인이 필요하다. 후자는 docker run 명령어의 -p 옵션이고 사용하려는 Port들을 직접 명시해야 한다.
# 최종 Image는 80번 Port를 이용할 것임을 명시 EXPOSE 80
Docker
복사

7) ENTRYPOINT & CMD

CMD 명령어를 사용하여 Dockerfile로 만들어낸 Image를 사용하여 Container를 생성했을 때 실행할 쉘 명령어를 기재할 수 있다. Container 생성 및 실행 초기에 사용할 쉘 명령어만 받으므로 Dockerfile에서 1회만 사용할 수 있다. 이는 Dockerrun 명령어의 끝 인자로 Container에서 실행할 쉘 명령어를 받는 것과 동일한 기능을 수행하게 되는데, 만약 DockerfileCMD를 기록한 상태로 Image를 만든 후에 Dockerrun 명령어의 끝 인자로 Container에서 실행할 쉘 명령어를 추가하게 되면 DockerfileCMD 명령어는 무시된다.
Dockerrun 명령어의 끝 인자로 추가적인 쉘 명령어를 입력하지 않고 DockerfileCMD를 이용하여 Container에서 실행할 쉘 명령어를 기재했다면, CMD에 기록된 명령어에 따라서 Containerattach로 둘지 detach로 줄지가 중요해진다. 예를 들어 Apache Web ServerImage를 만드는 Dockerfile에서 CMDapachectl -DFOREGROUND와 같이 둔 경우에는, Container를 생성하여 실행했을 때 해당 명령어가 포그라운드로 사용되므로 Containerdetach로 두는 것이 바람직하다. Container를 생성하면서 사용될 쉘 명령어를 Dockerfile에 기재하는 명령어는 CMD 외에도 ENTRYPOINT라는 것이 있다.
ENTRYPOINTCMD는 비슷한 기능을 하지만 분명히 차이가 있다. ENTRYPOINTCMD 둘 중 하나만 사용된 경우에는 Dockerfile로 만들어낸 Image를 사용하여 Container를 생성했을 때 쉘 명령어를 실행한다는 점은 동일하지만, ENTRYPOINTCMD 모두 이용된 경우에는 ENTRYPOINTCMD를 인자로써 이용한다는 점에서 차이가 있다.
ENTRYPOINTCMD 둘 중 하나를 이용하여 ubuntu에서 /bin/bash를 실행하도록 Container를 실행해보자. 이와 같은 예시에서는 Dockerfile을 이용하지 않고 Dockerrun 명령어만을 이용하여 확인했는데, Dockerrun 명령어에서 ENTRYPOINT에 해당하는 —-entrypoint 옵션과 CMD에 해당하는 명렁어의 끝 인자를 활용했다.
ENTRYPOINTCMD를 모두 이용했을 경우엔 ENTRYPOINTCMD를 인자로써 이용한다고 했는데, 이를 확인하기 위해 위 그림과 같이 작성해볼 수 있다. 위의 경우엔 ENTRYPOINT가 곧 Container 실행 시 수행할 쉘 명령어가 되고, CMD는 쉘 명령어가 아닌 ENTRYPOINT의 인자로써 동작한다. ENTRYPOINT의 명령어를 실행한 뒤에 CMD의 명령어를 수행하는 것이 아니라, 정말 말 그대로 CMDENTRYPOINT의 인자로써 이용된다는 것이 중요하다.
ENTRYPOINTCMDubuntuImage를 이용하여 Container를 생성할 때 -i-t 옵션을 통해 상호작용을 할 수 있도록 한 경우를 제외하고는 적어도 둘 중 하나는 무조건 명시되어야 한다. Container가 실행 상태로 있기 위해선 내부에서 실행 중인 프로세스가 있어야 하고, 내부에서 실행 중인 프로세스를 둔다는 것은 파일을 실행하든 쉘 명령어를 수행하든 ENTRYPOINT 혹은 CMD의 역할을 통해서 이뤄지기 때문이다.
그렇다면 ENTRYPOINTCMD를 어떻게 활용할 수 있을까? 이제까지의 Dockerfile을 소개하는 예시들을 잘 살펴보면, ENTRYPOINTCMD에는 단순히 쉘 명령어만 올 수 있는 것이 아니라 실행 가능한 파일을 기재하는 것도 가능했다. 또한 CMD의 경우엔 Dockerrun 명령어의 끝 인자를 통해 명시하여 Container를 생성할 때 사용자의 입력을 통해서 사용하는 것도 가능했다. 따라서 ENTRYPOINT로 실행 가능한 파일을 지정한 뒤에, 해당 파일에서 요구하는 인자들을 Container의 생성 시점에 상황에 맞는 인자를 직접 사용자가 주도록 이용하는 것이 가능하다. 과정은 다음과 같다.
1.
Container 실행 시 내부적으로 수행해야하는 환경 설정에 대한 스크립트를 작성
2.
ADD 혹은 COPY를 이용하여 해당 스크립트를 Image 내부에 복사
3.
복사된 스크립트를 ENTRYPOINT로 지정
4.
build를 통해 Image를 생성
5.
생성된 Image를 이용하여 Container를 생성, 이 때 run의 끝 인자로 스크립트에서 필요한 인자를 전달
echo $1 $2
Shell
복사
FROM ubuntu:14.04 LABEL author bigpel66 COPY entrypoint.sh /root RUN chmod 755 /root/entrypoint.sh ENTRYPOINT ["/bin/bash", "/root/entrypoint.sh"]
Docker
복사
위 스크립트와 Dockerfile을 이용하여 주어진 과정대로 수행하여 직접 확인해보자. 스크립트의 이름은 entrypoint.sh이며 Dockerfile과 동일한 곳에 위치한다. 아래 그림으로 실행 결과를 확인해보면, 스크립트의 목적대로 Container 생성 시 기재한 인자들을 그대로 출력한 것을 볼 수 있다.
위의 예시로 사용된 Dockerfile를 잘 살펴보면, ENTRYPOINTjson 배열 형태로 작성된 것을 볼 수 있다. ENTRYPOINTCMDRUN 명령어 구문과 같이 json 배열 형태로 작성하는 것이 가능하고, json 배열 형태로 작성했는지 안 했는지에 따라서 그 동작 방식이 다르다. 이에 대해선 ENTRYPOINTCMDRUN에서 설명한 것과 동일하다.
ENTRYPOINTCMDjson 배열 형태로 적지 않은 경우에는 sh -c를 이용하여 쉘 명령어 혹은 실행 가능한 파일을 처리한다. json 배열로 명시한 경우에는 ENTRYPOINTCMDsh -c를 이용하지 않고 배열에 명시된 그대로 처리하게 된다. json 배열의 형식은 ["실행 가능한 파일", "인자 1", "인자 2", ...]로 나타낸다. 아래의 예시를 확인해보자.
# 실제로는 sh -c echo test로 처리 CMD echo test # 실제로는 sh -c ./entrypoint.sh로 처리 ENTRYPOINT ./entrypoint.sh # 실제로는 sh -c ./entrypoint.sh sh -c echo test로 처리 CMD echo test ENTRYPOINT ./entrypoint.sh # 실제로는 /bin/bash ./entrypoint.sh echo test로 처리 CMD ["echo", "test"] ENTRYPOINT ["/bin/bash", "./entrypoint.sh"]
Docker
복사
따라서 ENTRYPOINTCMD를 함께 명시하여 Dockerfile을 구성했는데 원하는 결과가 잘 나오지 않는 경우에는, 이와 같은 특성까지도 잘 고려하여 작성하는 것이 필요하다.

8) ENV

Dockerfile에서 사용될 환경 변수를 지정할 수 있다. ENV를 통해서 설정한 환경 변수는 Image 내부에도 저장되므로 Container를 생성하면 해당 환경 변수를 그대로 사용할 수 있다. ENV를 통해 설정된 환경 변수는 Dockerfile에서도 사용할 수 있는데, 그 형태를 ${ENV_NAME}으로 이용한다. Dockerrun 명령어에서 -e 옵션을 이용하여 동일한 이름의 환경 변수를 설정하는 경우에는 기존에 설정된 환경 변수를 덮어쓰게 된다.
FROM ubuntu:14.04 LABEL author bigpel66 ENV new_env config.json RUN touch /root/$new_env
Docker
복사
ENV에는 재밌는 기능이 있는데, ${ENV_NAME} :- ${VALUE}${ENV_NAME} :+ ${VALUE}이다. :-는 환경 변수가 설정되어 있지 않으면 VALUE를 환경 변수의 값으로 사용하라는 것이고, :+는 환경 변수가 설정되어 있다면 VALUE를 환경 변수의 값으로 사용하라는 것이다. 아래와 같은 Dockerfile을 작성하고 build하여 결과를 알아보자.
FROM ubuntu:14.04 LABEL author bigpel66 ENV no_env "" ENV new_env new_value RUN echo ${new_env:-value} / ${new_env:+value} / ${no_env:-nothing} / ${no_env:+nothing}
Docker
복사

9) VOLUME

VOLUME으로 특정 디렉토리를 설정하여 Container를 생성하게 되면 Host 공간에 Docker Volume을 생성하게 되고, 해당 디렉토리에 Docker Volumemount하여 Host와 공유하게 된다. 아래와 같이 VOLUMEjson 배열 형태로 2개의 디렉토리로 작성하면, Container 생성 후에 2개의 Docker Volume이 생성된 것을 확인할 수 있다.
FROM ubuntu:14.04 LABEL author bigpel66 RUN mkdir /root/test1 /root/test2 RUN echo test1 >> /root/test1/testfile && echo test2 >> /root/test2/testfile VOLUME ["/root/test1", "/root/test2"]
Docker
복사

10) ARG

ARGbuild 명령어를 사용할 때 별도의 옵션을 사용하여 인자를 받아 Dockerfile 내부에서 사용될 변수를 설정하도록 해준다. 이 때 ARG로 설정한 변수는 기본 값을 할당하여 build 명령어를 입력할 때 값을 설정하지 않아도 되도록 할 수 있다. build 명령어에서 ARG로 지정한 변수를 할당 받는 옵션은 —-build-arg이다.
FROM ubuntu:14.04 LABEL author bigpel66 ARG arg_1 ARG arg_2=value2 RUN touch /root/${arg_1} && touch /root/${arg_2}
Docker
복사
ARG로 설정한 변수는 ENV와 동일하게 ${ARG_NAME}Dockerfile 내에서 이용할 수 있다. ARG의 사용 방법이 ENV와 동일하기 때문에 가급적이면 그 이름이 겹치지 않도록 하는 것이 좋으며, 혹시나 Dockerfile 내에서 ARG로 설정한 변수를 ENV에서 다시 정의하게 되면 —-build-arg로 변수를 설정하더라도 ENV에 의해 갱신된다. 즉, ENVARG는 서로 같은 기능을 하므로 동일한 이름의 변수를 사용하면 Dockerfile 내부에서 최종적으로 정의된 값을 사용하게 된다. 두 명령어의 차이는 사용자에 의해 입력을 받는지 그렇지 않은지만 있는 것이 아니다. 주어진 그림을 보면 알 수 있듯이, ARGDockerfile에서 ENV와 동작만 같을 뿐 Container 내부에서 사용할 수 있는 환경 변수로 설정하지 않는다는 것이 ENV와의 가장 큰 차이점이다.

11) USER

Container를 생성하여 실행하면 초기 사용자는 root인 것을 볼 수 있다. 이에 대해서 Container의 초기 사용자를 설정하는 명령어가 USER이다. 사용자의 계정명이나 UID를 이용하여 USER를 설정할 수 있으며, Container 생성 시에는 root 외에는 별도의 사용자가 없기 때문에 Dockerfile에서 RUN 명령어를 이용하여 사용자의 그룹 및 그에 대한 계정명을 생성한 뒤 USER를 이용하는 것이 일반적이다. root를 꼭 이용할 필요가 없다거나 여러 사용자가 Container를 이용하는 상황이라면 이처럼 사용자를 나누어 이용하는 것이 좋다.
FROM ubuntu:14.04 LABEL author bigpel66 RUN groupadd new_group && useradd -g new_group bigpel66 USER bigpel66
Docker
복사

12) STOPSIGNAL

실행 중인 Container들은 정지 상태가 되면 특정 SIGNAL을 내보내게 되는데, 이 SIGNAL에 따라 호출되는 시스템 콜이 각각 다르다. Dockerfile에서 STOPSIGNAL을 명시하게 되면, Container가 정지되었을 때 STOPSIGNAL에 해당하는 SIGNAL을 내보내면서 이에 대한 시스템 콜을 호출하게 된다. STOPSIGNAL을 설정하지 않으면 기본적으로는 SIGTERM을 사용하도록 되어 있다. STOPSIGNAL 역시 다른 명령어들처럼 Dockerrun 명령어에서 —-stop-signal이라는 옵션으로 설정할 수 있다.
FROM ubuntu:14.04 STOPSIGNAL SIGKILL
Docker
복사
여기서 사용되는 SIGNAL들은 TERMINATION SIGNAL을 의미하는데, 이에 해당하는 SIGNAL들은 아래 링크에서 확인할 수 있다.

13) HEALTHCHECK

HEALTHCHECKbuildImageContainer를 생성했을 때 Container내부에서 동작하는 어플리케이션의 상태를 체크하도록 만드는 명령어이다. Container 내부에서 어플리케이션의 프로세스가 종료되지는 않았지만 어플리케이션이 제 기능을 못하는 경우에 이를 찾아내기 위해서 주로 이용된다. HEALTHCHECK라는 명령어는 Container 내부에서 동작하고 있는 어플리케이션에게 요청을 보내고 응답을 받는 방식으로 어플리케이션의 상태를 확인하게 되므로 사전에 curl이 먼저 설치되어 있어야 한다. 그리고 나서야 얼만큼의 주기로 확인할지, 최대 몇 회까지의 요청을 보낼지, 요청 당 얼만큼의 시간을 기다릴지 명령어를 작성한다.
얼만큼의 주기로 확인하는지가 —-interval 옵션이고, 최대 몇 회까지의 요청을 보낼지가 —-retries 옵션이고, 1회의 요청 당 기다릴 최대 소요 시간이 —-timeout 옵션이다. 그리고 이들을 조합하여 요청을 보낼 수 있게 하려면 curl이라는 명령어 앞에 CMD를 붙여줘야 한다. 1회 요청을 기다릴 수 있는 시간을 넘어서도 응답을 받지 못하면 1회의 실패로 간주하고, 주기마다 요청을 보내서 최대 요청 횟수까지 응답을 받는데 실패하면 HEALTHCHECKContainer 내부의 어플리케이션 상태를 unhealthy로 인식하게 된다. 이와 같은 조건들을 토대로 Dockerfile을 작성하여 Image를 만들고 Container까지 생성해보자.
FROM centos:7 RUN touch /etc/yum.repos.d/nginx.repo && echo -e '[nginx]\nname=nage repo\nbaseurl=http://nginx.org/packages/centos/7/$basearch/\ngpgcheck=0\nenabled=1' > /etc/yum.repos.d/nginx.repo RUN yum -y install nginx curl HEALTHCHECK --interval=10s --retries=3 --timeout=3s CMD curl http://127.0.0.1/ || exit 1 CMD ["nginx", "-g", "daemon off;"]
Docker
복사
위의 예시는 10초의 주기로 상태를 확인하고, 최대 3회까지의 요청을 보내게 되며, 한 번의 요청 당 3초가 넘게 걸리면 1회의 실패로 간주하도록 HEALTHCHECK를 작성했다. 또한 nginx를 로컬에서 돌리도록 했기 때문에 https://localhostcurl을 보내도록 했으며, 최대 요청을 넘어서면 unhealthy로 기록하기 위해 exit 1으로 주었다. (exit 0healthy를 의미하고 exit 2starting이라고 하여 예약된 코드를 의미한다.) 또한 nginx에서 필요로 할 수도 있는 Port들을 Host와 바인딩 하기 위해 -P 옵션을 이용했다.
HEALTHCHECK를 이용하여 Image를 만들고, 이를 이용하여 Container를 생성하게 되면 docker ps 명령어를 이용했을 때 STATUS 탭에서 어플리케이션의 상태를 healthyunhealthy로 확인할 수 있게 된다. 이와 같은 어플리케이션의 상태는 docker ps로 확인할 수 있을 뿐만 아니라, docker container inspect 명령어로 State를 확인하는 것이 가능하다.

14) SHELL

RUN, ENTRYPOINT, CMDjson 배열 형태로 작성하지 않으면 sh -c로 실행한다고 했는데, 기본적으로 이용하는 쉘을 sh가 아니라 다른 쉘로 지정할 때 SHELL이라는 명령어를 이용한다. RUN, ENTRYPOINT, CMD와 같은 명령어를 json 배열 형태로 작성하여 이용하고 싶은 쉘을 항상 명시하는 것도 한 가지 방법이 될 수 있으나, json 배열을 꼭 써야하는 상황이 아니라 단순히 쉘 명시만을 위해 json 배열을 이용하는 것보다는 SHELL 명령어를 이용하는 것이 좋다. SHELL 명령어는 반드시 json 배열 형태로 작성해야 한다.
FROM ubuntu:14.04 LABEL author bigpel66 RUN apt-get install zsh -y SHELL ["/usr/bin/zsh"]
Docker
복사

15) ONBUILD

ONBUILD 명령어는 ONBUILD, FROM과 명령어를 제외한 나머지 명령어들 앞에 붙을 수 있는 명령어이다. ONBUILD 명령어를 이용하면 최초 build를 수행할 때 처리되지 않고, buildImage를 이용하여 build를 수행할 때 해당 구문을 실행하게 된다. ONBUILD가 사용된 Dockerfiledepth0이라 하고 해당 Image를 이용하여 작성한 Dockerfiledepth1이라 할 때, 0에서 작성한 ONBUILD1에서만 동작하게 된다. 즉, 1을 이용한 Dockerfiledepth2라고 한다면 이는 0ONBUILD 구문에 대해 영향을 받지 않는다. 이와 같이 ONBUILDParent - Child 구조에서만 적용이될 뿐이고 Child에게 상속되는 명령어는 아니다. ONBUILD 명령어를 잘 활용한다면 추가할 코드를 미리 명시함으로써 Dockerfile을 조금 더 깔끔하게 사용할 수 있다.
FROM ubuntu:14.04 RUN echo "base echoing" ONBUILD RUN echo "onbuild echoing"
Docker
복사
FROM onbuild_testing RUN echo "onbuild finished"
Docker
복사
위 쪽 명령어가 첫 번째 Dockerfile을 이용하여 Image를 만든 것이고, 이렇게 만든 Image를 이용하여 아래 쪽 명령어로 build를 수행한 것이다.

3. Reference