
개발한 프로그램을 컨테이너화 할때... 어떻게할까??
빈 이미지(ubuntu 같은거)로 컨테이너 만들어서, 프로그램을 위한 환경 설치하고, 소스코드 잘 돌아가는지 확인하고 만든 컨테이너를 이미지로 커밋??
조금 귀찮은 과정이라고 볼 수 있다.
도커는 이를 위해 빌드 명령어를 제공한다.
애플리케이션을 위해 컨테이너에 설치해야 하는 환경(패키지), 추가해야되는 소스코드, 명령어 등을 하나의 파일로 만들어두면 도커가 이를 읽고 컨테이너에서 알아서 작업한 뒤 이미지를 만들어준다.
이때의 파일은 Dokerfile이라고 한다.
즉! 빌드 명령어는 도커 파일을 읽어서 이미지를 생성하는것이다.
도커파일을 이용하면, 컨테이너 생성하고 이미지 커밋하는 수고를 덜고, Git같은 개발 도구를 통해 프로그램을 빌드하고 배포하는 것을 자동화 할 수 있따는 장점이 있다.
대부분의 사람들은 도커허브에 이미지만 올리지 않고, 어떻게 이미지를 생성하는지 기록해놓은 도커파일도 같이 push 해놓는다.
도커파일 사용 예시로, 웹 서버 이미지를 생성하는 상황을 가정해보자.
우선 디렉토리를 하나 만들고, 그 안에 html파일을 하나 만들자!
나는 dockerfile이라는f 디렉토리안에 test.html파일을 만들었다. dockerfile 디렉토리 안에 Dockerfile 파일을 만들었고, 그 안의 내용은 아래와 같이 채웠다.

이 파일은 이미지에 아파치 웹 서버를 설치한 뒤에, 로컬에 있는 test.html 파일을 웹 서버로 접근할 수 있는 컨테이너의 디렉토리인 /var/www/html에 복사한다.
** 도커엔진은 도커파일을 읽을 때 기본적으로 현재 디렉토리에 있는 Dockerfile이름의 파일을 선택한다. 따라서 이름을 동일하게 맞춘것이다. 도커파일은 빈 디렉터리에 저장하는게 좋다. 이미지 빌드할때 사용되는 context 때문인데, 이후에 더 알아보겠다.
도커파일에서는 한줄 단위로 명령어가 되고 위에서부터 순서대로 실행된다. 기본적인 명령어로는 from, add, run 같은게 있는데, 보통 대문자로 쓰는것이 일반적이다. 어떤 명령어인지 대문자로 명시하고 그 뒤에 소문자로 옵션을 추가하는 방식이다.
위에서 쓰인 명령어부터 살펴보자
| Dockerfile 명령어 | 설명 |
|---|---|
| FROM | 생성할 이미지의 베이스 이미지. 한 번 이상은 필수로 입력해야하는 명령어. 이미지 이름 형식은 run명령어에서와 동일. |
| MAINTAINER | 개발자 정보. 보통 연락 수단도 같이 적는데, 도커 1.13.0 ver 이후로는 사용x LABEL에 쓰기도 한다. |
| LABEL | 이미지에 추가되는 메타데이터. '키:값'의 포맷으로 저장. 라벨을 통해 원하는 조건의 컨테이너나 이미지를 쉽게 찾을 수 있으므로 유용하다. |
| RUN | 컨테이너 내부에서 명령어 실행. Dockerfile을 이미지로 빌드할때는 입력이 불가능해.. |
| ADD | 파일을 이미지에 추가. 추가할 파일은 Dockerfile이 위치한 디렉토리에 있어야함. |
| WORKDIR | 명령어를 실행할 디렉터리. cd랑 같은 기능. |
| EXPOSE | 빌드로 생성한 이미지에서 노출할 포트 설정. 바인딩 되는건 아니고, 컨테이너의 몇번 포트를 쓰겠다 선언만 하는것. |
| CMD | 컨테이너 시작될때 마다 실행할 명령어 설정. 최대 한번만 입력가능한 명령어. 위의 예시로는, 컨테이너 시작 시 자동으로 아파치 웹 서버가 실행되고, -d 옵션으로 컨테이너 생성해야 함. |
책에서 소개하는 추가 Dockerfile 명령어는 아래와 같다. 자세한 정보는 도커 공식 사이트의 Dockerfile 레퍼런스를 참고해야겠다.
➕
ENV
VOLUME
ARG
USER
ONBUILD
STOPSIGNAL
HEALTHCHECK
SHELL
COPY(ADD와 다른점)
->ADD는 외부 url이나 tar파일에서도 파일을 추가할 수 있지만, COPY는 로컬 파일만 이미지에 추가할 수 있따.
ENTRYPOINT(CMD와 다른점)
-> entrypoint는 커맨드를 인자로 받아 사용할 수 있는 스크립트의 역할을 할 수 있다.
-> docker run명령어 맨 끝에 cmd만 설정되어있으면 그대로 명령어를 실행하지만, entrypoint도 설정되어있으면 cmd는 단지 entrypoint의 인자역할일뿐..
둘 중 하나는 무조건 설정되어있어야 돼!
// 빌드 명령어
docker build -t [생성될 이미지 이름]
## -t 옵션 : 생성될 이미지의 이름 설정하는건데, 이 옵션을 안적으면 16진수 형태로 이름이 설정되므로 가급적 사용하는게 좋다.
빌드를 성공적으로 마쳤고, 빌드를 통해 생성한 이미지로 컨테이너를 실행해보자.

dockerfile을 작성할 때, CMD명령어로 -DFOREGROUND로 실행시켰기 때문에 detached 모드로 컨테이너를 실행해야 한다.
## -p 옵션 : dockerfile에서 EXPOSE 명령어로 설정된 포트를 호스트에 연결하도록 설정한다. 즉, 생성한 컨테이너의 80번 포트가 호스트의 포트와 바인딩 되는 것인데, 어떤 포트와 바인딩 됐는지 모르므로 docker inspect나 docker port로 확인해봐야 한다.
호스트의 32768번 포트와 연결된 것을 확인했고, dockerfile에서 ADD 명령어로 웹 서버 디렉토리에 test.html과 test2.html을 추가했으므로 아래와 같이 브라우저 주소창에서 확인할 수 있다.


빌드를 시작하면, 도커는 가장 먼저 빌드 컨텍스트를 읽는다.
빌드 컨텍스트는??
Dockerfile이 위치한 디렉터리를 말한다.
빌드 컨텍스트는 build 명령어의 마지막에 지정되어있는 경로에 있는 모든 파일을 전부 포함하기 때문에, Dockerfile이 있는 디렉토리에는 이미지 빌드에 필요한 파일만 있는것이 좋다.
Git에 .gitignore 파일이 있듯이, Docker에는 .dockerignore 파일이 있다. 해당 파일은 빌드 할 때 컨텍스트에서 제외된다.
.dockerignore파일은 Dockerfile과 동일한 디렉토리에 존재해야 한다.

빌드 명령어를 실행하면, 단 하나의 컨테이너에서 이미지가 생성되는 것이 아니다.
출력결과를 보면, 각 step마다 새로운 컨테이너가 생성되고, 이를 이미지로 커밋한다.
Dockerfile에서 명령어가 하나씩 실행될 때마다 이전 step에서 생성된 이미지에 의해 새로운 컨테이너가 생성되고, 명령어 수행하고, 다시 새로운 이미지 레이어 단위로 저장하는 것이다.
step2를 예시로 설명하면, step1에서 생성된 이미지에 의해 ID가 fd82e01df3c0인 임시 컨테이너가 생성되고 step2의 명령어를 수행한 뒤, 임시 컨테이너를 커밋하여 새로운 이미지 레이어인 ce27c7f20917이 생성되는 것이다. 임시 컨테이너는 중간에 삭제된다.
결과적으로 Dockerfile을 빌드하면 명령어 수 만큼 이미지 레이어가 생성되고, 중간에 임시 컨테이너도 명령어 수 만큼 생성됐다가 삭제되는 것이다.
이전에 빌드했던 Dockerfile에 동일한 내용이 있다면, 새롭게 빌드할때 캐시를 이용하여 이전에 사용한 이미지 레이어를 활용하여 이미지를 생성한다.
예를 들어 아까 Dockfile에서 명령어 일부를 지우고 Dockerfile2를 만들어 아래와 같이 빌드하게 되면 출력결과에서 캐시를 이용하는것을 볼 수 있을것이다.
docker build -f Dockerfile2 -t mycache:0.0 ./
## -f 옵션 : 빌드할 도커파일의 이름을 지정할 수 있다.
(나는 그냥 아까 빌드를 동일하게 한번 더 했다.)

물론! 캐시 기능이 효율성을 높여줄 때도 있지만, 안 좋을때도 있다. 예를 들어 RUN 명령어로 git clone를 사용해 이미지를 빌드하면, 해당 명령어에 대한 이미지 레이어를 계속 캐시로 사용하기 때문에 깃 저장소에서 코드 수정이 일어나도, 처음 받아왔던 소스코드와 동일하게 받아올 것이다.
따라서 캐시를 사용하지 않으려면, 빌드할 때 --no-cache 옵션을 붙여주자! 그러면 다시 Dockerfile을 처음부터 빌드한다.
docker build --no-cache -t mybuild:0.0 ./
애플리케이션을 빌드할때 필요한 패키지나 라이브러리가 많다. 예를 들어, c++로 작성된 코드를 빌드하기 위해서는 c++언어와 관련된 라이브러리들이 미리 설치 되어야 한다. 하지만 간단한 출력문을 위해서 불필요하게 라이브러리나 패키지를 가져올 필요는 없지 않을까??
이럴 때 사용하는 것이 멀티 스테이지 빌드방법이다. 하나의 Dockerfile안에 여러개의 From 이미지를 정의하여 최종 생성 이미지의 크기를 줄여준다.
alpine이나 busybox같은 이미지는 프로그램 실행에 필요한 필수적인 런타임 요소가 포함되어 있는 리눅스 배포판 이미지인데, 이를 잘 활용하면 가벼운 애플리케이션 이미지를 생성할 수 있다.
구글링 고고!!