Dockerfile
이전에 도커 이미지를 생성하는 방법으로, 실행중인 컨테이너에 commit을 통해 변경사항을 저장하는 방법을 소개했었다.
이 방법의 직접 이미지의 동작 여부를 확인할 수 있다는 장점이 있지만, 환경을 구성하기 위해 일일이 개발자가 수작업으로 작업을 해야된다는 단점이 있다.
Dockerfile은 변경 사항, 작업 내용들을 텍스트 파일로 기록할 수 있어 베이스 이미지로부터 어떤 변화가 있었고, 이미지의 구성을 좀 더 쉽게 알수있게 해준다.
장기적으로 본다면 이미지의 유지보수가 쉬워지고, 배포시에도 이미지 파일 대신 도커파일만 배포하면 된다는 장점을 가지고 있다.
Dockerfile 작성
바로 예시를 보자.
FROM ubuntu:14.04
LABEL "purpose"="pactice"
ENV my_env my_value
ARG my_arg my_value
RUN apt-get update
RUN apt-get install apache2 -y
ADD test.txt /var/lib
ADD https://my-cdn.com/test2.txt /var/lib
COPY test3.txt /var/lib
WORKDIR /var/lib
RUN ["/bin/bash", "-c", "echo hello >> test2.txt"]
EXPOSE 80
CMD apachectl -DFOREGROUND
구조는 크게 어려워 보이지 않는다. 줄 하나에 명령어 하나이며 이 Dockerfile을 이용해 나만의 이미지를 만드는것이다.
Dockerfile 명령어
명령어에 대해 좀 더 자세하게 알아보자.
대소문자의 구분은 없지만 관례상 대문자로 표기한다.
FROM
Dockerfile로 생성할 이미지의 베이스가 되는 이미지를 명시한다.
하나의 Dockerfile에 반드시 하나 이상의 FROM 명령어가 있어야 하며, docker run과 비슷하게 사용하려는 이미지가 로컬에 없다면 자동으로 pull 한다.
LABEL
생성할 이미지에 "키=값" 형태의 메타데이터를 추가한다.
여러 개의 메타데이터가 저장될 수 있으며 추가된 메타데이터는 docker inspect 명령어로 확인할 수 있다.
RUN
컨테이너 내부에서 실행할 명령어를 나타낸다. 위의 예시에서 2번 등장하는데, apache2를 install 하는 과정에서 -y 옵션을 추가하였다.
이는 Dockerfile로 이미지를 빌드하며 RUN 명령어를 실행할때 별도의 입력이 불가능하기 때문에 yes/no 를 묻는 install 과정에서 -y 옵션은 필수다.
RUN 명령어는 다음과 같이 JSON 배열 형태로도 작성할 수 있다.
RUN ["실행 가능한 파일", "ARG1", "ARG2", ...]
RUN ["/bin/bash", "echo hello >> test2.html"]
ADD
이미지에 파일을 추가한다. 추가하려는 파일은 Dockerfile이 위치한 디렉토리인 Build Context에서 가져오게 된다.
Build Context에 대한 자세한 설명은 아래에서 다룬다.
ADD 명령어도 마찬가지로 JSON 배열 형태로도 작성할 수 있다.
ADD ["test.txt", "test2.txt", "/bin/lib/my_dir"]
WORKDIR
명령어를 실행할 디렉토리를 나타낸다. Bash Shell에서의 cd 명령어와 똑같다.
EXPOSE
Dockerfile 빌드로 생성된 이미지에서 노출할 포트를 설정한다. 예시에 있는 apache web server는 80포트를 사용하므로 80포트를 노출해주었다.
주의할점은 호스트의 80포트와 바인딩 되는것이 아닌 외부에 노출할 포트로 지정한다는 것이다.
CMD
컨테이너가 시작될 때마다 실행할 명령어를 설정한다.
예시에서 적용한 CMD는 컨테이너가 시작될때 아파치 웹 서버가 자동으로 실행되도록 설정한것이다.
즉, docker run 명령어의 맨 뒤에 있는 커맨드와 같은 역할을 한다.
주의할점은 하나의 Dockerfile에서 한 번만 사용할 수 있고, docker run에서 맨 뒤에 다른 커맨드가 있다면 Dockerfile에 명시한 CMD 커맨드는 덮어쓰기가 된다는 점이다.
예시에서 베이스 이미지인 ubuntu:14.04는 자체 CMD로 /bin/bash가 적용되어 있는데, 이는 다시 우리가 지정한 CMD에 의해 override 된다.
ENV
컨테이너에서 사용할 환경변수를 지정한다.
예시로 주어진 Dockerfile을 빌드한 이후, 컨테이너에 접속한다음
echo $my_env
>>my_value
다음과 같이 결과가 나온다.
ARG
ENV와 다르게 Dockerfile 내에서만 유효한 변수의 값을 설정한다.
COPY
기능은 ADD와 매우 유사하지만, 차이점으로는 ADD는 로컬 파일뿐만 아니라 url, tar 파일도 자동으로 처리해 컨테이너에 넣어준다.
COPY는 로컬 파일만 넣어줄 수 있는데, 외부에서 파일을 받아와 컨테이너에 추가하게 되면 정확히 어떤 파일이 추가될지 확실하지 않기에 둘 중 권장하는 명령어는 COPY이다.
ENTRYPOINT
CMD와 비슷한 명령어이지만, 활용법에 있어서 차이가 있다.
가장 큰 차이점으로는 ENTRYPOINT는 스크립트 파일을 실행할 수 있다는 점이다.
docker run 명령어로 차이점을 알아보자.
먼저, entrypoint를 지정하지 않은채 cmd로 지정한 명령어이다.
컨테이너 내부에서 /bin/bash가 실행된 모습이다.
다음은 entrypoint와 cmd 둘 다 지정한 명령어이다.
컨테이너 내부에서 echo /bin/bash가 실행된 모습이다.
docker run -it --name no_entrypoint ubuntu /bin/bash
docker run -it --entrypoint="echo" --name yes_entrypoint ubuntu /bin/bash
이를 통해 entrypoint가 설정 됐다면, cmd는 entrypoint에 대한 인자 역할을 한다는것을 알 수 있다!
entrypoint는 또, 쉘 스크립트 파일을 실행시킬 수 있는데,
docker run -it --entrypoint="/test.sh" ubuntu /bin/bash
# test.sh
echo "A"
test.sh 파일을 만들고 위의 docker run 명령어를 실행하면 다음과 같은 에러가 나온다.
entrypoint로 쉘 스크립트를 실행하려면, 해당 스크립트 파일이 컨테이너 내부에 존재해야 한다.
우리는 현재 Dockerfile에 관해 다루고 있으므로 자연스럽게 ADD나 COPY 명령어가 떠오른다!
...
ADD test.sh /test.sh
RUN chmod +x /test.sh
ENTRYPOINT ["/bin/bash", "/test.sh"]
...
Dockerfile에 위의 내용을 추가해준다음, 빌드한 이후 컨테이너를 실행시켰을 때 test.sh 파일이 실행될것이다.
Dockerfile 빌드
위에서 Dockerfile을 빌드한다고 표현했는데, 빌드는 Dockerfile로부터 이미지를 만들어내는 과정이다.
명령어부터 살펴보자.
docker build [OPTIONS] PATH | URL | -
docker build -t my_first_build:0.0 ./
docker build라는 명령어를 통해 Dockerfile을 빌드한다.
- -t : 빌드로 생성될 이미지의 이름을 설정한다. 옵션을 주지 않으면 16진수 형태의 이름이 자동 생성된다.
- my_first_build : 생성될 이미지의 이름을 정한다.
- 0.0 : 생성될 이미지의 태그를 정한다.
- ./ : 빌드 타겟이 되는 Dockerfile의 경로(빌드 컨텍스트)를 지정한다.
docker build 명령어는 기본적으로 명령어가 입력된 디렉토리에서 Dockerfile을 찾아 빌드한다.
만약 Dockerfile이 현재 디렉토리에 없다면, -f 옵션을 통해 현재 디렉토리가 아닌 다른 디렉토리에 있는 Dockerfile을 빌드할 수 있다.
docker build -t -f /path/to/target_docker_file my_first_build:0.0 ./
Dockerfile 빌드 컨텍스트
빌드 컨텍스트는 이미지를 생성하는데 필요한 파일, 소스코드 등을 담고있는 디렉토리를 의미한다.
위의 예시에선 현재 디렉토리를 나타내는 ./ 을 통해 빌드 컨텍스트를 정의했다.
빌드 컨텍스트가 지정되면 Dockerfile을 빌드 하면서 해당 컨텍스트에서 필요한 리소스들을 가져온다.
해당 디렉토리 아래의 모든 파일들을 포함하므로 루트 디렉토리에서의 빌드는 호스트의 메모리 낭비, 빌드 속도 저하를 가져올 수 있다.
따라서 이미지 빌드에 필요한 파일만 있는 디렉토리로 지정하는것이 바람직하다.
.dockerignore
빌드 컨텍스트로 지정한 디렉토리에서, Dockerfile 빌드에 포함시키고 싶지 않은 파일이나 디렉토리가 있을 수 있다.
.gitignore처럼 .dockerignore를 통해 컨텍스트에서 제외시킬 수 있다.
아래 파일은 Dockerfile이 위치한 경로와 같은 곳에 위치해야 한다
# vim .dockerignore
test.txt // test.txt 제외
*.java // 모든 자바 파일 제외
!main.java // 단, main 자바 파일은 포함시킴
Dockerfile 빌드 과정
전체적인 흐름은 다음과 같다.
실제 빌드를 해보자.
FROM ubuntu:14.04
LABEL "purpose"="pactice"
ENV my_env my_value
ARG my_arg my_value
RUN apt-get update
RUN apt-get install apache2 -y
WORKDIR /var/lib
EXPOSE 80
CMD apachectl -DFOREGROUND
여기서 눈여겨 봐야할것은 intermediate container와 Step이다.
각 Step은 Dockerfile에 있는 명령어에 해당한다.
ADD나 RUN 등의 명령어가 실행될 때마다 새로운 컨테이너가 하나씩 생성되며 이를 이미지로 커밋한다.
즉, 이전 Step에서 생성된 임시 컨테이너에 현재 Step에 해당하는 명령어를 적용하고 새로운 이미지 레이어를 추가한다.
Docker image layer 구조에 대해서는 이전 포스트에서 다루었다.
명령어 한줄 마다 layer를 남기므로 보다 유연하게 리비전 관리를 할 수 있다.
빌드가 완료되면 이미지가 생성된것을 볼 수 있다.
Dockerfile 빌드 Cache
Dockerfile 빌드는 Dockerfile 파일에 쓰여진 명령어 한줄당 이미지 레이어 하나를 추가하며 이루어 진다고 설명했다.
만약 Dockerfile 마지막줄에 명령어 하나를 더 추가한 다음 빌드하면 어떻게 될까?
합리적으로 생각해본다면 이전 build된 레이어를 그대로 가져와 캐시처럼 사용해야 할것같다.
맞다. 실제로 cache처럼 작동한다.
똑똑하게 build 하는것 같지만 여기에는 함정이 있다.
만약 명령어가 로컬에 있는 파일을 다루는것이 아닌 github같은 remote 환경에 있는 파일을 다룰때는 얘기가 달라진다.
git remote repository에서 새로운 commit이 일어나도 Dockerfile 빌드는 이를 신경쓰지 않고 로컬에 남아있는 cache로 빌드 하게 된다.
이런 경우를 방지하고자 다음과 같이 --no-cache 옵션을 사용할 수 있다.
docker build --no-cache -t my_ubuntu:0.0 ./
Dockerfile 명령어 레퍼런스
Dockerfile 명령어에 대한 더 자세한 정보는 아래 documentation을 참고하면 된다.
https://docs.docker.com/engine/reference/builder/
Dockerfile reference
docs.docker.com
'Infra > Docker' 카테고리의 다른 글
[Docker] Docker Compose (0) | 2023.06.27 |
---|---|
[Docker] Docker Daemon (0) | 2023.06.27 |
[Docker] 도커 볼륨과 네트워크 (0) | 2023.05.19 |
[Docker] 도커 이미지와 컨테이너 (0) | 2023.05.18 |
[Docker] Docker란 무엇인가 (0) | 2023.05.18 |