실습으로 배우는 Docker 입문 | 2. Docker 이미지 빌드

앞에서는 Docker 개요 및 가장 기본 인 이미지와 컨테이너 관리에 대한 실제 조작 방법을 단계별로 소개하였다. 지금까지는 공개된 이미지를 이용했지만, 스스로 개발하고 있는 응용 프로그램을 Docker 이미지로 배포하기 위해서는 스스로 이미지를 만들 수도 있어야 한다. 그래서 이번에는 직접 이미지를 생성하고 컨테이너를 시작하는 방법을 소개하겠다. 또한 여기서에서 소개하는 샘플은 Docker 사가 공개하고 있는 초보자를 위한 튜토리얼의 내용을 기반으로 하고 있다.

Docker 이미지 빌드 구조

먼저 Docker 이미지를 만드는 방법을 대해 소개한다.

Docker 이미지 빌드는 기반이 되는 이미지에 어떤 기능을 추가하여 사용자 이미지(자신만의 이미지)을 일으키는 것을 말한다.

빌드 구조

기반 이미지는 일반적으로 Docker 사 또는 Docker 커뮤니티가 제공하는 OS의 이미지가 되는 경우가 많다. 이미지에 다양한 의존 라이브러리와 미들웨어를 설치하거나 자신의 응용 프로그램을 설치 및 구성하거나 하여 사용자 이미지를 만든다. 이 사용자 이미지를 제 3자에게 배포하여 라이브러리 미들웨어 애플리케이션 등을 모두 포함하는 형태로 배포 할 수 있다.

이 때 기본 이미지에 대해 수행할 작업을 설명하는 설정 파일은 “Dockerfile"이다. Dockerfile는 Docker 사에 의해 작성 방법이 규정되어 있다. 자세한 내용은 문서를 참조하여라.

샘플 이미지 빌드

여기에서는 flask라는 Python 용 Web 어플리케이션 프레임워크를 사용한 Web 응용 프로그램을 배포하는 Docker 이미지를 구축해 보도록 한다. 응용 프로그램은 무작위로 고양이의 GIF 이미지를 표시하는 것이다. flask 자체의 상세한 설명은 여기서는 생략하겠다.

빌드에 필요한 파일 준비

디렉토리를 작성하고 다음과 같이 이미지의 빌드에 필요한 4개의 파일을 받아온다.

% mkdir flask-app
% cd flask-app
% wget https://raw.githubusercontent.com/docker/labs/master/beginner/flask-app/app.py
% wget https://raw.githubusercontent.com/docker/labs/master/beginner/flask-app/requirements.txt
% wget https://raw.githubusercontent.com/docker/labs/master/beginner/flask-app/templates/index.html -P templates
% wget https://raw.githubusercontent.com/docker/labs/master/beginner/flask-app/Dockerfile

그런 다음에는 각 파일의 내용을 소개하겠다.

app.py 파일은 Web 어플리케이션의 엔트리 포인트가 되는 소스 코드이다. Web에서 GIF 이미지를 검색하여 무작위로 URL을 반환하는 작업을 수행한다.

from flask import Flask, render_template
import random

app = Flask(__name__)

# list of cat images
images = [
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-26388-1381844103-11.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-31540-1381844535-8.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-26390-1381844163-18.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-1376-1381846217-0.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-3391-1381844336-26.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-29111-1381845968-0.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-3409-1381844582-13.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-19667-1381844937-10.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-26358-1381845043-13.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-18774-1381844645-6.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-25158-1381844793-0.gif",
    "https://raw.githubusercontent.com/devkuma/docker-tutorial/main/flask-app/image/anigif_enhanced-buzz-11980-1381846269-1.gif"
]

@app.route('/')
def index():
    url = random.choice(images)
    return render_template('index.html', url=url)

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

requirements.txt 파일는 필요한 Python 모듈 기입한 파일이다. pip(패키지 관리자)를 사용하여 flask를 설치할 때 사용한다.

Flask==1.0

templates/index.html 파일는 Web 응용 프로그램에서 출력하는 HTML 템플릿이다. 고양이 이미지의 표시만한다.

<html>
  <head>
    <style type="text/css">
      body {
        background: black;
        color: white;
      }
      div.container {
        max-width: 500px;
        margin: 100px auto;
        border: 20px solid white;
        padding: 10px;
        text-align: center;
      }
      h4 {
        text-transform: uppercase;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h4>Cat Gif of the day</h4>
      <img src="{{url}}" />
      <p><small>Courtesy: <a href="http://www.catshaming.co.uk/20-best-cat-gif-posts">Catshaming</a></small></p>
    </div>
  </body>
</html>

Dockerfile는 Docker 데몬이 이미지를 만들 때 사용하는 명령어를 정리한 파일이다. 샘플 Dockerfile에서는 다음과 같은 내용의 처리를 실행하고 있다. Dockerfile의 문법에 대해서는 뒤에서 설명하겠다.

  • 기본 이미지로 Alpine Linux (Docker 용으로 개발 된 경량 Linux 배포판)을 지정한다.
  • Python과 pip (패키지 관리자)를 설치한다.
  • pip에서 응용 프로그램에 필요한 Python 모듈을 설치한다.
  • app.pyindex.html을 정해진 위치에 복사한다.
  • 포트 5000 번을 외부에 공개한다.
  • Web 응용 프로그램을 실행한다.
# our base image
FROM alpine:3.5

# Install python and pip
RUN apk add --update py2-pip

# upgrade pip
RUN pip install --upgrade pip

# install Python modules needed by the Python app
COPY requirements.txt /usr/src/app/
RUN pip install --no-cache-dir -r /usr/src/app/requirements.txt

# copy files required for the app to run
COPY app.py /usr/src/app/
COPY templates/index.html /usr/src/app/templates/

# tell the port number the container should expose
EXPOSE 5000

# run the application
CMD ["python", "/usr/src/app/app.py"]

(보충) Dockerfile에서 사용되는 명령

Dockerfile를 보면, 처음에는 shell 스크립트처럼 보이지만 실제로는 Dockerfile 특유의 명령이 사용되고 있다. 샘플 Dockerfile에서 사용되는 명령은 다음 표에 나와 있다. 문법의 자세한 내용은 매뉴얼에 기재되어 있으므로 참조해 하길 바란다.

명령 의미 보충
FROM 기본 이미지가되는 이미지 지정
RUN 이미지를 빌드하기위한 명령을 지정 RUN apk add -update py-pip의 경우, 실제로는 apk add -update py-pip 부분이 실행된다.
COPY 호스트에서 컨테이너에 파일을 복사한다.
EXPOSE 외부에 공개하는 컨테이너 포트를 지정한다. 기본적으로 컨테이너 포트가 닫혀있다.
CMD 이미지에서 컨테이너를 시작할 때 실행할 명령을 지정한다. Dockerfile에서 CMD는 1 개만 지정할 수 있다.

그밖에 Dockerfile 작성 모범 사례에 대해서는 Best practices for writing Dockerfiles에 정리하고 있어, 이쪽도 참조해 보길 바란다.

이미지 빌드

Docker 데몬이 실행되고 있지 않으면 빌드 할 수 없다. 미리 데몬을 시작하십시오.

현재 디렉토리에 Dockerfile가있는 상황에서 다음 명령을 실행하십시오. Dockerfile의 내용이 순서대로 실행되는 것을 알 수 있다고 생각합니다.

(주) 샘플 프록시 설정은 준비되어 있지 않아서 HTTP 프록시가 없는 환경에서 실행해야 한다.

% docker build -t myfirstapp .
Sending build context to Docker daemon  8.192kB
Error response from daemon: dial unix docker.raw.sock: connect: connection refused
kimkc@kimkcui-MacBookPro flask-app % docker build -t myfirstapp .
[+] Building 16.1s (12/12) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                 0.0s
 => => transferring dockerfile: 571B                                                                                                                 0.0s
 => [internal] load .dockerignore                                                                                                                    0.0s
 => => transferring context: 2B                                                                                                                      0.0s
 => [internal] load metadata for docker.io/library/alpine:3.5                                                                                        4.1s
 => [internal] load build context                                                                                                                    0.0s
 => => transferring context: 2.46kB                                                                                                                  0.0s
 => [1/7] FROM docker.io/library/alpine:3.5@sha256:66952b313e51c3bd1987d7c4ddf5dba9bc0fb6e524eed2448fa660246b3e76ec                                  0.5s
 => => resolve docker.io/library/alpine:3.5@sha256:66952b313e51c3bd1987d7c4ddf5dba9bc0fb6e524eed2448fa660246b3e76ec                                  0.0s
 => => sha256:66952b313e51c3bd1987d7c4ddf5dba9bc0fb6e524eed2448fa660246b3e76ec 433B / 433B                                                           0.0s
 => => sha256:f7d2b5725685826823bc6b154c0de02832e5e6daf7dc25a00ab00f1158fabfc8 528B / 528B                                                           0.0s
 => => sha256:f80194ae2e0ccf0f098baa6b981396dfbffb16e6476164af72158577a7de2dd9 1.51kB / 1.51kB                                                       0.0s
 => => sha256:8cae0e1ac61cead281f41115cc0ebd39117f7e54dffc8fd5e05a7590dca3cd4e 1.97MB / 1.97MB                                                       0.3s
 => => extracting sha256:8cae0e1ac61cead281f41115cc0ebd39117f7e54dffc8fd5e05a7590dca3cd4e                                                            0.2s
 => [2/7] RUN apk add --update py2-pip                                                                                                               4.9s
 => [3/7] RUN pip install --upgrade pip                                                                                                              2.8s
 => [4/7] COPY requirements.txt /usr/src/app/                                                                                                        0.0s
 => [5/7] RUN pip install --no-cache-dir -r /usr/src/app/requirements.txt                                                                            3.0s
 => [6/7] COPY app.py /usr/src/app/                                                                                                                  0.0s
 => [7/7] COPY templates/index.html /usr/src/app/templates/                                                                                          0.0s
 => exporting to image                                                                                                                               0.7s
 => => exporting layers                                                                                                                              0.7s
 => => writing image sha256:b012c41a03101ad398f22ed50e569d3b5277f608847ca3275a555d6d21babf0c                                                         0.0s
 => => naming to docker.io/library/myfirstapp                                                                                                        0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

컨테이너의 시작

이미지 빌드가 완료되면 다음 명령으로 이미지에서 컨테이너를 시작한다.

% docker run -p 8888:5000 --name myfirstapp myfirstapp
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

위 명령의 경우 옵션은 컨테이너의 포트 5000 번을 호스트의 8888 번으로 지정하고 있다. Web 브라우저를 열고 http://localhost:8888에 액세스해 보면, 앞에서 소개한 Web 응용 프로그램이 표시될 것이다.

컨테이너의 정지 · 삭제

시작 컨테이너를 중지하려면 다음 명령을 실행한다

% docker stop myfirstapp

그리고, 정지한 컨테이너를 삭제하려면 다음 명령을 실행한다.

% docker rm myfirstapp

정리

여기에서는 구체적인 예제 응용 프로그램을 통해 Dockerfile을 사용하여 이미지를 빌드하여 이미지의 컨테이너를 시작하는 방법을 소개하였다. 이번에는 이미지 빌드의 기초적인 부분은 커버하고 있어 이미지 빌드 요약 파악하고 받을 수 있었다. Docker의 공식 문서에 매우 충실하게 되어 있기에 해당 도큐먼트도 함께 참조하면 다양한 경우에도 대응할 수 있을 것이다.

이번에는 로컬에서 빌드 로컬에서 컨테이너를 시작하는 작업을 실시하였지만 본인 외에 다른 사람에게 이미지를 이용할 수 있도록 하기 위해서는 이미지를 제 3자가 이용할 수 있는 형태로 공개해야 한다. 다음은 이미지의 공개 방법을 소개하겠다.