実習で学ぶDocker入門 | 2. Dockerイメージのビルド

前回までは、Dockerの概要と、最も基本的なイメージおよびコンテナ管理の実際の操作方法を段階的に紹介した。これまでは公開されているイメージを利用してきたが、自分で開発しているアプリケーションをDockerイメージとして配布するには、自分でイメージを作れるようになる必要もある。そこで今回は、イメージを直接作成し、コンテナを起動する方法を紹介する。なお、ここで紹介するサンプルはDocker社が公開している初心者向けチュートリアルの内容を基にしている。

Dockerイメージビルドの仕組み

まずDockerイメージを作成する方法について紹介する。

Dockerイメージのビルドとは、ベースとなるイメージに何らかの機能を追加し、ユーザーイメージ(自分だけのイメージ)を作成することをいう。

ビルド構造

ベースイメージは一般的に、Docker社またはDockerコミュニティが提供するOSイメージになることが多い。イメージにさまざまな依存ライブラリやミドルウェアをインストールしたり、自分のアプリケーションをインストールおよび設定したりしてユーザーイメージを作成する。このユーザーイメージを第三者へ配布することで、ライブラリ、ミドルウェア、アプリケーションなどをすべて含む形で配布できる。

このとき、ベースイメージに対して実行する作業を記述する設定ファイルが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を見ると、最初はシェルスクリプトのように見えるが、実際には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の公式ドキュメントは非常に充実しているので、該当ドキュメントもあわせて参照すれば、さまざまなケースにも対応できるだろう。

今回はローカルでビルドし、ローカルでコンテナを起動する作業を行ったが、自分以外の人がイメージを利用できるようにするには、第三者が利用できる形でイメージを公開する必要がある。次回はイメージの公開方法を紹介する。