[1 Month Docker] 2. Dockerfile, Docker Image

Dockerfile과 Docker Image 개념을 소개하고, 핵심적인 내용을 설명한다. Docker에 대한 간단한 소개의 내용을 기본으로 가정하고 시작한다.


저번 글에선 Container와 Docker를 체험해보았다. Container는 어떠한 스택의 애플리케이션이든 배포 측면에서 일관된 경험을 제공하므로 사용하는 것이 좋지 않을까 생각한다.

Docker로 Container를 실행하려면 Docker Image가 필요한데, 이번 글에서는 최종적으로 Image를 직접 생성한다(공식적으로는 build 한다고 표현함.).

1. 기초 개념 설명

1. Dockerfile: 이미지 빌드 명령어의 입력으로 들어가는 스크립트이다. 아래와 같은 내용을 담는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Parent Image 지정
# Dockerfile은 이미지를 정의하는 파일이다.
# 새 이미지를 만들 때 다른 이미지의 내용에 기반해 덧씌우는 형태이다.
FROM diamol/node

# 환경 변수 3개 설정
# Docker와 같이 컨테이너 환경으로 앱이 배포되는 경우,
# 환경 변수를 arguments로 많이 활용한다.
ENV TARGET="blog.sixeyed.com"
ENV METHOD="HEAD"
ENV INTERVAL="3000"

# Working Directory를 /web-ping으로 지정 (폴더 생성 후 이동함. mkdir && cd)
WORKDIR /web-ping

# Host의 app.js 파일을, Working Directory(.)에 복사
COPY app.js .

# node로 다음의 js를 실행
# CMD 명령어는 컨테이너 실행 시에 1회 수행되는
# container.once('start', callback)과 같다.
CMD ["node", "/web-ping/app.js"]

아마 셸 스크립트에 익숙한 사람은 셸 스크립트와 다름 없다고 생각할 것이다. 맞다. 똑같다. 아마 셸 스크립트가 익숙하지 않으면 Dockerfile에 쉽게 친해질 순 없을텐데, 리눅스 환경 구성 기초 | T 아카데미리눅스 커맨드 라인 & 쉘 스크립트 #1 | ABCD DevOps라는 좋은 자료가 있으니 참고하자.

Dockerfile 안에서만 쓸 수 있는, Dockerfile에서 쓰일 만한, 명령어가 10개 정의돼있다. 이 명령어들이 주축이 돼서 Dockerfile의 내용을 구성하게 된다.

2. Image: 이미지는 Dockerfile에서 기술한 내용이 실행된 모습을 스냅샷 형태로 담은 파일이다.

  • 컨테이너 실행 시 이미지를 통해 Dockerfile에 정의된 내용이 그대로 재현된다.

3. Image 받아오기: 이미지를 직접 생성하지 않고, DockerHub 등의 Docker Registry (이미지 저장 서버)에서 받아올 수도 있다. 단순히 받아오기만 하는 명령어는 docker image pull 이다.

  • docker image pull diamol/ch03-web-ping 을 실행해 DockerHub에서 이미지를 받자.
  • 하나의 이미지를 받는데, 여러 Pull Complete가 표시돼있다. (나중에 설명한다.)

4. Image 빌드: docker image build 명령어를 실행하면, 이미지는 자동으로 빌드된다.

예: docker image build --tag web-ping . => web-ping이라는 이미지를 생성.

  • (Mandatory) .은 Dockerfile 및 COPY 등에서 Host의 기준 디렉토리로 사용된다.
  • (Mandatory) --tag는 이미지의 이름을 지정한다.
  • 주의: 파일을 Windows -> Linux로 복사하는 경우, 권한이 rwxrwx로 지정되는데, 이는 서로 권한 정보가 호환되지 않기 때문이다.
  • 로컬에서 직접 빌드된 이미지는 도커 엔진에 캐시돼 보관된다.
  • 새로운 버전을 빌드하려는 경우, --tag web-ping:v2와 같이 :으로 버전을 구분하여 명시하면 된다.

Docker Image Build Process Example

5. Image 실행(컨테이너로):

  • docker container run {image_name}으로 실행

6. Image Layer:

이미지에는 생성 과정에 대한 메타데이터도 포함된다. 이미지 생성 과정을 통해

  • docker image history web-ping
  • Image History Example

Docker Image는 Image Layer라는 더 작은 개념으로 구성되며, Dockerfile의 각 명령(CREATED BY) 마다 Layer가 생성된다.

  • 이미지는 각 Layer의 논리적인 집합이다.
  • Layer는 도커 엔진에 물리적인 파일의 형태로 캐시되는 단위이다.
  • 이미지 간에 Layer가 공유되므로 전체 용량 부하를 낮출 수 있다.
    • docker image ls로 논리적인 용량을 확인할 수 있지만, docker system df로 이미지가 차지하는 물리적인 용량을 확인할 수 있다.
    • docker system df

이런 Image Layer 캐시를 활용하려면 조건이 필요한데: Layer 이전의 Layer 들의 내용과 순서가 바뀌지 않아야 한다.

  • 이전 내용이 바뀌었는데, 이 명령(Layer)을 실행한 결과가 같음을 보장할 수 없다.
  • 만약 내용을 바꾸는 경우, 이 Layer에 의존하고 있던 모든 이미지에 영향을 끼친다.
  • 그러므로, 이전 Layer가 변경되는 경우, 이후 Layer는 캐시로 사용될 수 없게 되고, 새로 Layer를 생성하게 된다.

7. Layer 캐시 최적화 전략: Layer 캐시 활용을 통해 전체 용량과 이미지 빌드 시간을 줄일 수 있다.

  • 이미지에서 변하지 않는 부분을 최대한 먼저 실행해 새로 빌드할 Layer 수를 줄인다.

  • 캐시 사용 가능 여부는 Instruction의 내용과 Arguments(명령어 내용일 수도 있지만, COPY와 같은 경우 파일의 내용까지.)로 Hash 값을 만들고 비교하여 결정한다.

  • Hash가 일치하는 경우 빌드하지 않고 도커 엔진에 캐시된 Layer를 사용한다. 일치하지 않는 경우, 해당 Layer부터 최종 Layer까지 새로 빌드한다. (뒷 Layer의 해시가 같아도, 재사용할 수 없다.)

  • docker build image cache

  • app.js 파일을 수정한 후 (nano app.js) 빌드한 모습이다. COPY app.js를 수행하는 step 6가 다시 Layer를 만듦을 확인할 수 있고, 이후 Layer인 step 7은 바뀐 내용이 없지만 앞 Layer가 바뀌어서 다시 만들어짐을 확인할 수 있다.

8. Layer 캐시 최적화 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM diamol/node

# 시작 시 실행될 명령어를 지정하는 것이므로, 어디에 놓아도 상관 없다.
# 캐시를 위해 앞에 놓는다.
CMD ["node", "/web-ping/app.js"]

# 환경 변수 3개를 한 번에 등록해 Layer 개수를 줄였다.
# 개수를 줄인 것과 캐시 최적화는 큰 연관은 없지만...
ENV TARGET="blog.sixeyed.com" \
METHOD="HEAD" \
INTERVAL="3000"

WORKDIR /web-ping

COPY app.js .

이제 docker image build -t web-ping:v3를 실행해보자. 환경 변수 개수가 줄어들어 7단계에서 5단계로 줄었음을 확인할 수 있다.

이제부턴 app.js를 수정해도 마지막 Layer만 바뀐다.


2. 실습

1. 목표:

diamol/ch03-lab 폴더의 이미지에서 /diamol/ch03.txt 파일을 수정하고 새 Image를 생성하라. 이 때 Dockerfile을 수정해서는 안 된다.

2. 힌트:

  • -it으로 컨테이너에 키보드 I/O 가능
  • 컨테이너 파일 시스템이 Exit 상태에도 제거되지 않음을 활용
  • docker container --help로 모르는 명령어에 대해 공부할 것

3. 처음 생각한 접근 방법:

  1. Container에서 일단 파일을 수정한다.
  2. 컨테이너로 이미지를 생성해낸다. 명령어를 찾아보자.

4. 실제 수행 과정:


1. 일단 이미지를 빌드함

cd ../../lab (빌드를 위해 lab 폴더로 이동)

docker build image -t ch03-lab . (빌드 성공)


2. 이제 컨테이너를 실행해야 함

docker container run ch03-lab (실패)

docker container ls (없었음)

cat Dockerfile (CMD 등 명령어 실행이 없고, COPY 뿐이었음)


3. 컨테이너에서 수행할 명령어로 주어 실행해야 함

docker container run ch03-lab /bin/bash (실패)

docker container run ch03-lab /bin/sh (이미지에 bash가 없었음..)

vi ch03.txt (텍스트 파일 수정)

exit (sh 나옴)


4. 정지된 컨테이너를 이미지로 빌드해야 함

Docker Commit Reference를 참고해서 빌드 명령어 학습

docker container ls --all 로 종료된 컨테이너 ID 확인 (67a)

docker image commit 67a ch03-lab:v2 (무슨 해시값이 출력됨..)

docker image ls (v2로 생성됨을 확인)

docker container run ch03-lab:v2 cat ch03.txt (파일 갱신됨을 확인)

끝!


Lab 하면서 배운 점:

docker commit 명령어로 컨테이너 내용으로 이미지를 빌드할 수 있다는 점.

  • 다만 이렇게 되면 Dockerfile은 없는게 아닌가?

docker container run {IMAGE} {COMMAND}로 명령어를 실행할 수 있음

  • 다만 이는 이미지에서 수행하는 명령어가 없는 경우에 한한 것 같고, docker container exec으로 셸을 띄우는 것이 일반적인 것 같다.

TO DO:

  • 컨테이너에서 Commit으로 생성한 이미지에서 Dockerfile을 추출할 수 있을지 확인해보기

[1 Month Docker] 2. Dockerfile, Docker Image

https://jsqna.com/docker-diamol-2-dockerfile-image/

Author

Seongbin Kim

Posted on

21-01-07

Updated on

21-01-19

Licensed under

댓글