Docker Images

Park Choong Ho·2021년 5월 28일
0

Docker

목록 보기
4/4

Docker Images

Making Docker Image

만일 필요한 서비스를 제공하는 이미지가 없다면 어떻게 해야 할까요? 이 경우에는 직접 이미지를 만들어야할 것입니다. 이번 장에서는 python flask를 이용한 간단한 웹 애플리케이션을 만들어 보도록 하겠습니다.

How to create my own image?

먼저 우리는 어떤 것을 컨테이화하고 이미지를 통해서 어떤 어플리케이션을 만들거고 어떻게 애플리케이션을 만들어나갈지를 이해해보도록 하겠습니다.

아래와 같은 스텝을 중점으로 진행하겠습니다.

  1. OS - Ubuntu
  2. Update apt repo
  3. Install dependencies using apt
  4. Install python dependencies using pip
  5. Copy source code to /opt folder
  6. Run the web server using "flask" command

이미지를 만들기 위해서 먼저 우리는 Dockerfile이 필요합니다. 아래와 같은 docker file을 만들어 줍니다.

FROM ubuntu

RUN apt-get update
RUN apt-get install python
RUN apt-get -y install pip

RUN pip install flask
RUN pip install flask-mysql

COPY . /opt/source-code

ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run

이렇게 docker file을 만들어 준 후에는 빌드를 진행해야합니다.

linux> docker build Dockerfile -t parkchoongho/my-flask-app

docker build 명령어에 파일과 해당 이미지에 붙을 태그 이름을 같이 넘겨줍니다. 이렇게 하면 우리 시스템에 이미지가 만들어질 것입니다. 만약 도커 허브에 이를 올리고 싶다면 docker push명령어를 입력해줍니다.

linux> docker push parkchoongho/my-flask-app

넘겨준 이름은 parkchoongho 계정 이름과 my-flask-app 이미지 이름으로 구성됩니다.

Dockerfile

Dockerfile은 특정 포맷으로 쓰여진 텍스트 파일입니다. 기본적으로 INSTRUCTIONARGUMENT로 구성됩니다.

INSTRUCTION ARGUMENT
FROM ubuntu

RUN apt-get update
RUN apt-get install python
RUN apt-get -y install pip

RUN pip install flask
RUN pip install flask-mysql

COPY . /opt/source-code

ENTRYPOINT FLASK_APP=/opt/source-code/app.py flask run

아까 살펴본 도커파일을 보면 왼쪽에 대문자로 인스트럭션이 작성되어 있는것을 볼 수 있습니다. FROM, RUN, COPY, ENTRYPOINT 모두 인스트럭션입니다. 이미지를 만드는 동안 각각의 인스트럭션은 도커가 특정한 일을 하게끔 지시합니다.

오른쪽에 있는 것들은 모두 ARGUMENT입니다. 첫번째 줄은 컨테이너가 어떤 OS 또는 어떤 이미지를 베이스로 하는지 나타냅니다. 모든 도커 이미지는 기본 OS 또는 다른 이미지를 기반으로 해야합니다. 도커 허브에서 릴리즈된 공식적인 운영체제들을 찾을 수 있습니다. 모든 도커 파일들이 RUN 인스트럭션부터 시작한다는 것을 기억하는 것은 중요합니다.

RUN 인스트럭션은 도커가 특정 명령어들을 base 이미지에서 실행하도록 합니다. 필요한 dependency들을 설치하고 난후, COPY 인스트럭션이 로컬 파일 시스템 현재 위치에 있는 모든 파일들을 도커 이미지 /opt/source-code로 복사합니다. ENTRYPOINT 인스트럭션은 이미지가 컨테이너화 된 후 동작할 때 실행할 명령어를 특정합니다.

Layered architecture

FROM ubuntu

RUN apt-get update && apt-get -y install python && apt-get -y install pip

RUN pip install flask flask-mysql

COPY . /opt/source-code

ENTRYPOINT FLASK\_APP=/opt/source-code/app.py flask run

도커가 이미지를 빌드할때 layered architecture로 빌드합니다. 각 줄의 인스트럭션은 전 단계에 있었던 층으로부터 도커 이미지의 새로운 층을 생성합니다.

Layer 1. Base Ubuntu Layer
Layer 2. Changes in apt packages
Layer 3. Changes in pip packages
Layer 4. Source code
Layer 5. Update Entrypoint with "flask" command

각각의 층은 바로 전에 있던 층에서 부터의 변화만을 저장하기에 각 층마다의 사이즈를 확인할 수 있습니다.

Layer 1. Base Ubuntu Layer 120MB
Layer 2. Changes in apt packages 306MB
Layer 3. Changes in pip packages 6.3MB
Layer 4. Source code 229B
Layer 5. Update Entrypoint with "flask" command

docker history [Docker Image name or Id]를 통해 위 정보에 대해서 얻을 수 있습니다.

linux> sudo docker history choonghopark/my-custom-app  
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT  
da05533686b3   3 minutes ago   /bin/sh -c #(nop)  ENTRYPOINT \["/bin/sh" "-c…   0B  
dec5842ef794   3 minutes ago   /bin/sh -c #(nop) COPY dir:19bebc10d64aceded…   203B  
3e16b15b4cf3   3 minutes ago   /bin/sh -c pip install flask flask-mysql        5.03MB  
0734cf4e7797   3 minutes ago   /bin/sh -c apt-get update && apt-get -y inst…   345MB  
7e0aa2d69a15   5 weeks ago     /bin/sh -c #(nop)  CMD \["/bin/bash"\]            0B  
<missing>      5 weeks ago     /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B  
<missing>      5 weeks ago     /bin/sh -c \[ -z "$(apt-get indextargets)" \]     0B  
<missing>      5 weeks ago     /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   811B  
<missing>      5 weeks ago     /bin/sh -c #(nop) ADD file:5c44a80f547b7d68b…   72.7MB

docker build 명령어를 통해 이미지를 생성하면 여러 단계를 거쳐서 이미지를 빌드하는 것을 확인할 수 있습니다.

Step 1/5 : FROM ubuntu  
 ---> 7e0aa2d69a15  
Step 2/5 : RUN apt-get update && apt-get -y install python && apt-get -y install pip  
 ---> Running in 4147d1f1509b  
Get:1 [http://security.ubuntu.com/ubuntu](http://security.ubuntu.com/ubuntu) focal-security InRelease \[114 kB\]  
Get:2 [http://archive.ubuntu.com/ubuntu](http://archive.ubuntu.com/ubuntu) focal InRelease \[265 kB\]  
Get:3 [http://security.ubuntu.com/ubuntu](http://security.ubuntu.com/ubuntu) focal-security/restricted amd64 Packages \[274 kB\]

...

각 단계마다 어떤 일이 발생하는지 우리는 확인할 수 있습니다. 모든 layer들은 캐싱됩니다. 따라서 layered architecture는 당신이 docker build를 실패하거나 특정 단계를 추가해도 그전에 수행된 단계들을 수행하지 않아도 되게끔 도움을 줍니다.

Environment Variables

import os
from flask import Flask

app = Flask(__name__)

...
...

color = 'red'

@app.route("/")
def main():
	print(color)
	return render_template('hello.html', color=color)

@app.route('/how are you')
def hello():
	return 'I am good, how about you?'

if __name__ == "__main__":
	app.run(host="0.0.0.0", port=8080)

위 코드를 보면 color = 'red' 코드를 통해서 해당 html 파일의 배경을 빨간색으로 만들려고 함을 확인할 수 있습니다. 그런데 만약 이 색깔을 바꾸자 한다면 개발자가 해당 애플리케이션의 코드를 변경해야할 것입니다. 이를 방지하기 위해 해당 정보들을 애플리케이션 밖으로 꺼내 환경변수에 셋팅하는 방법을 사용할 수 있습니다.

import os
from flask import Flask

app = Flask(__name__)

...
...

color = os.environ.get('APP_COLOR')

@app.route("/")
def main():
	print(color)
	return render_template('hello.html', color=color)

@app.route('/how are you')
def hello():
	return 'I am good, how about you?'

if __name__ == "__main__":
	app.run(host="0.0.0.0", port=8080)

이렇게 color 변수에 환경변수 값을 불러오게끔 설정을 한뒤

linux> export APP_COLOR=blue; python app.py

이렇게 실행하면 코드를 변경하지 않고도 배경 색깔을 변경할 수 있습니다. 만약 이것을 도커화하고 싶다면 어떻게 해야할까요? -e 옵션을 사용하면 가능합니다. -e 옵션을 활용하면 컨테이너의 환경변수를 설정할 수 있습니다.

linux> docker run -e APP_COLOR=blue simple-webapp-color

Inspect Environment Variable

동작하고 있는 컨테이너의 환경변수는 어떻게 알 수 있을까요? 앞서 배운 docker inspect 명령어로 알 수 있습니다.

linux> sudo docker ps -a  
CONTAINER ID   IMAGE              COMMAND                  CREATED          STATUS                        PORTS     NAMES  
86fc9b15ba18   my-simple-webapp   "/bin/sh -c 'FLASK\_A…"   44 minutes ago   Exited (137) 41 minutes ago             goofy\_turingcorretto-dev-1@corretto-dev-1 ~ ![:heavy_check_mark:](https://a.slack-edge.com/production-standard-emoji-assets/13.0/apple-medium/2714-fe0f@2x.png)  
❯❯❯ sudo docker inspect 86
...
...
"Config": {  
 	"Hostname": "86fc9b15ba18",  
 	"Domainname": "",  
 	"User": "",  
 	"AttachStdin": false,  
 	"AttachStdout": true,  
 	"AttachStderr": true,  
 	"Tty": false,  
 	"OpenStdin": false,  
 	"StdinOnce": false,  
 	"Env": [  
		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
	],
...

CMD vs Entrypoint

컨테이너에 동작하는 프로세스가 없을 경우 컨테이너가 자동으로 종료된다는 것은 앞서 설명드린바 있습니다. 컨테이너는 특정 프로세스를 동작하기 위해서 존재하는 것이라 봐도 무방한대요. 그것이 가상머신과 가장 구별되는 차이점이라 할 수 있습니다.

그렇다면 어떤 프로세스를 컨테이너에서 동작 시킬것인지 결정하는 것은 무엇일까요? Dockerfile에서 CMD 는 어떤 프로세스를 동작시킬것인지를 결정합니다. 만약 우리가 일반 우분투 이미지를 동작시킨다고 가정해봅시다. 해당 도커파일은

...
...

CMD ["bash"]

...
...

이렇게 되어 있기에 bash 프로그램을 동작 시킬것입니다. 하지만 도커가 터미널을 컨테이너와 기본적으로 연결시켜주지는 않기에 bash 프로그램은 터미널을 찾지 못하고 종료되기 때문에 프로그램이 종료됩니다. 이를 방지하기 위해서 기존의 명령어를 오버라이드 하고 실행하기 위해 명령어를 넘겨줍니다.

linux> docker run ubuntu [COMMAND]
linux> docker run ubuntu sleep 5

만약 이거를 고정적으로 바꾸고 싶을 떄는 어떻게 해야할까요? Dockerfile에 CMD sleep 5를 추가해주면 됩니다.

FROM ubuntu

...
CMD sleep 5
...

CMD sleep 5 같이 CMD command param1형태도 가능하지만 CMD ["sleep", "5"] 처럼 CMD ["command", "5"] 같은 형태도 가능합니다. 두번째 처럼 JSON array 형태로 줄때는 첫번째 인자는 반드시 실행 가능한 프로그램이어야합니다. 예를 들어, CMD ["sleep 5"] 처럼 작성할 수는 없습니다.

만약 프로그램이 sleep하는 초를 바꾸고 싶다면 어떻게 해야할까요? 우선

linux> docker run ubuntu-sleeper sleep 10

이런 식으로 새로운 명령어를 넘겨서 할 수 있지만 우리가 이미 Dockerfile에 sleep 5 명령어를 입력한 만큼 sleep 명령어를 2번 사용하는 것은 그만큼 효율적입니다. 우리는 아래와 같은 방식으로 10초로 늘리고 싶습니다.

linux> docker run ubuntu-sleeper 10

이런 경우에는 어떻게 해야 할까요? 이런 상황에서 ENTRYPOINT가 활용됩니다. entry point 인스트럭션은 컨테이너가 시작할 때 어떤 프로그램이 동작할지를 알려주는 인스트럭션입니다.

FROM ubuntu

ENTRYPOINT ["sleep"]

이렇게 작성한 후에

linux> docker run ubuntu-sleeper 10

이렇게 하면 해당 컨테이너가 10초 동안 sleep하는 것을 확인할 수 있습니다. 이렇게 ENTRYPOINT에서는 명령어 파라미터가 뒤에 붙는 형태로 동작하는 것을 확인할 수 있습니다.

그런데 위 상황에서 아래와 같이 아무런 parameter 없이 컨테이너를 동작하면 sleep 명령어만 있기에 컨터이너가 동작하지 않는 에러를 맞이하게 됩니다. 만약 어떤 parameter도 특정되지 않았다고 했을 때 기본 값을 설정하고자 한다면 어떻게 해야할까요? 이 경우에는 CMD와 ENTRYPOINT를 동시에 활용하면 됩니다.

FROM ubuntu

ENTRYPOINT ["sleep"]

CMD ["5"]

이렇게 하면 궁극적으로 명령어는 sleep 5가 됩니다.

linux> docker run ubuntu-sleeper

이렇게 하면 자동으로 sleep 5를 실행합니다.

linux> docker run ubuntu-sleeper 10

이렇게 하면 parameter 10이 CMD를 덮어써 최종적으로 sleep 10을 실행합니다. 그런데 만약 ENTRYPOINT를 덮어쓰고자 한다면 어떻게 해야 할까요?

linux> docker run --entrypoint sleep2.0 ubuntu-sleeper 10

--entrypoint를 활용하게 되면 ENTRYPOINT를 덮어쓸 수 있게됩니다. 위 명령어는 최종적으로 컨테이너에서 sleep2.0 10을 실행하게 됩니다.

profile
백엔드 개발자 디디라고합니다.

0개의 댓글