Docker Quickstartを超意訳する Part 2

アプリのコンテナ化

Part 1ではDockerについて簡単な説明と環境構築を行った.
Part 2では実際にアプリをコンテナ化してみる.


Get Started, Part 2: Containerizing an Application

https://docs.docker.com/get-started/part2/

もくじ

  • Prerequisites
  • Introduction
  • Setting Up
  • Build and Test Your Image
  • Conclusion

Prerequisites

https://docs.docker.com/get-started/part2/#prerequisites

  • このパートに入る前に, Part 1の内容を完了していること.

Introduction

https://docs.docker.com/get-started/part2/#introduction
Docker Desktopのおかげでコンテナオーケストレーターが既にセットアップされているので, コンテナアプリ開発の準備はできている.
コンテナアプリ開発は一般的に以下のフローに沿って行う.

  1. 作成したいアプリの各コンポーネントについてimageを作成し, それを元にコンテナの作成とテストを行う.
  2. コンテナとインフラの設定を組み合わせたアプリをDocker StackファイルまたはKubernetesのYAMLファイルで定義する.
  3. コンテナ化したアプリをテスト, 共有, デプロイする.

このパートでは上記フローの1つめに着目し, コンテナの元になるimageの作成を行う.
Part 1で学んだようにimageはコンテナ化されたプロセスを動かすためのプライベートなファイルシステムを提供するものであるから, 開発者はアプリケーションの動作に必要なものだけを内包するimageを作成すれば良い.

コンテナ化された開発環境ではアプリの依存関係をすべてimageに閉じ込めてしまうので, 開発マシンにDocker以外のものをインストールする必要がない.
したがって, 同じマシン上で複数のアプリを開発する際に異なるアプリ間での依存関係の競合がなくなる.
そのため, imageの作成方法さえ取得してしまえばコンテナ化された開発環境は一般的な開発環境よりも構築が容易で使いやすい.

Setting Up

https://docs.docker.com/get-started/part2/#setting-up

1: GitHubからサンプルプロジェクトをcloneしてくる.

# リポジトリをclone
$ git clone -b v1 https://github.com/docker-training/node-bulletin-board
Cloning into 'node-bulletin-board'...
remote: Enumerating objects: 190, done.
remote: Counting objects: 100% (190/190), done.
remote: Compressing objects: 100% (132/132), done.
remote: Total 190 (delta 79), reused 158 (delta 55), pack-reused 0
Receiving objects: 100% (190/190), 194.66 KiB | 432.00 KiB/s, done.
Resolving deltas: 100% (79/79), done.
# cloneしてきたリポジトリのbulletin-board-appディレクトリに移動
$ cd node-bulletin-board/bulletin-board-app
# ファイルを確認
$ ls
Dockerfile   app.js       fonts        package.json server.js
LICENSE      backend      index.html   readme.md    site.css

これはNode.jsで書かれた簡単な掲示板アプリケーションで, 今回はこのアプリをコンテナ化していく.

2: Dockerfileというファイルを見てみる.
Dockerfileにはコンテナで使用するプライベートファイルシステム(image)を構築するための手順と, このimageを元にコンテナを起動する際の情報(メタデータ)が書かれている.
今回使用する掲示板アプリのDockerfileは以下の内容になっている.

# node:6.11.5をベースimageとして使用
FROM node:6.11.5

# image内での作業ディレクトリを/usr/src/appに変更
WORKDIR /usr/src/app
# 開発マシンのpackage.jsonをimageの作業ディレクトリにコピー
COPY package.json .
# npm(Node.jsのパッケージマネージャ)によりアプリの依存パッケージをインストール
RUN npm install
# 開発マシンの現在のディレクトリの中身をimageの作業ディレクトリにコピー
COPY . .

# コンテナが起動した際に`npm start`コマンドを実行
CMD [ "npm", "start" ]

Dockerfileを書くことはコンテナアプリ開発の第一歩である.
Dockerfileに記載された各行のコマンドは独自のimageを作成するための操作を定義し, このDockerfileの場合は次の内容を定義している:

  • FROMで既に存在するimage(node:6.11.5)をベースとして指定する.
    node:6.11.5はNode.jsのベンダーによってビルド, Dockerチームによって検証された高品質なofficial imageであり, node 6.11.5のインタプリタと基本的な依存パッケージを内包している.
  • WORKDIRimage内の作業ディレクトリを**/usr/src/app**に指定する.
    以降のコマンドは全てこのディレクトリで行われる. このディレクトリは開発マシンのディレクトリとは無関係である.
    [追加]指定したディレクトリが存在しない場合は自動で生成される.
  • COPYで開発マシンのpackage.jsonimageの現在のディレクトリにコピーする.
    今回の場合は**/usr/src/app/package.json**に配置される.
  • RUNnpm installコマンドをimage内で実行する.
    このコマンドは現在のディレクトリにあるpackage.json(依存関係が定義されたファイル)を読み込み, アプリの依存パッケージをインストールする.
  • COPYで開発マシンの残りのソースコードをimageの現在のディレクトリにコピーする.

以上のコマンドは一般的な開発環境でNode.jsのアプリをインストールする際とほぼ同じである.
しかしDockerfileでこれらを定義することで, 開発マシンの環境には一切影響を与えずに必要な操作を全てimageに閉じ込めることができる.
[追加]この開発マシンにNode.jsがなくてもimageさえあればアプリが動く. すごい.

上記手順はファイルシステムの構築方法を定義しているが, このDockerfileにはもう1行コマンドが存在する.
CMDはこのimageを元にコンテナが起動する際のメタデータを定義する.
この例では, このimageを使用して実行するプロセスがnpm startコマンドであることを示している.
[追加]npm startはアプリを実行するコマンド.

上記はFROMでベースとなるimageを指定し, その後にファイルシステムの構築に必要なコマンドを1行ずつ記述, 最後にメタデータを定義するというシンプルなDockerfileの良いお手本である.
また, この例で示したのはDockerfileで使用できるコマンドのほんの一部であり, その他のコマンドについてはDockerfile referenceで説明されている.

Build and Test Your Image

https://docs.docker.com/get-started/part2/#build-and-test-your-image
ソースコードとDockerfileが用意できたので, いよいよimageをビルドして, それを元にコンテナが想定通り動作するか確認する.

1: node-bulletin-board/bulletin-board-app ディレクトリにいることを確認し, 掲示板アプリのimageをビルドする.

# 現在のディレクトリを確認
$ pwd
/path/to/node-bulletin-board/bulletin-board-app
# 現在のディレクトリ(.)にあるDockerfileを使用してimage(bulletinboard:1.0)をビルド
$ docker image build -t bulletinboard:1.0 .

ビルド時のログを見るとDockerfileに定義された操作を1行ずつ読み込んで実行し,
作成したimageにタグ(bulletinboard:1.0)をつけていることがわかる.
[追加]ビルド時に-tオプションでリポジトリ名:タグを指定できる.
imageは基本的にリポジトリ名とタグで管理され, リポジトリ名はバージョンに依存しないそのimageの名前, タグはバージョンなどの情報を表す.
(例: node:6.11.5の場合はnodeがリポジトリ名, 6.11.5がタグ)

ビルド時のログ(クリックで展開)
$ docker image build -t bulletinboard:1.0 .
Sending build context to Docker daemon  45.57kB
Step 1/6 : FROM node:6.11.5
6.11.5: Pulling from library/node
85b1f47fba49: Pull complete
ba6bd283713a: Pull complete
817c8cd48a09: Pull complete
47cc0ed96dc3: Pull complete
8888adcbd08b: Pull complete
6f2de60646b9: Pull complete
1666693bf996: Pull complete
2fe410df7942: Pull complete
Digest: sha256:fe109b92edafd9821fbc1c80fd7587a1b4e1ff76fec3af675869e23e50bbf45b
Status: Downloaded newer image for node:6.11.5
 ---> 852391892b9f
Step 2/6 : WORKDIR /usr/src/app
 ---> Running in 7d1414939508
Removing intermediate container 7d1414939508
 ---> 5d0751457bd4
Step 3/6 : COPY package.json .
 ---> 50081a240492
Step 4/6 : RUN npm install
 ---> Running in 3c9f159d46c7
vue-event-bulletin@1.0.0 /usr/src/app
+-- body-parser@1.19.0
| +-- bytes@3.1.0
| +-- content-type@1.0.4
| +-- debug@2.6.9
| | `-- ms@2.0.0
| +-- depd@1.1.2
| +-- http-errors@1.7.2
| | +-- inherits@2.0.3
| | `-- toidentifier@1.0.0
| +-- iconv-lite@0.4.24
| | `-- safer-buffer@2.1.2
| +-- on-finished@2.3.0
| | `-- ee-first@1.1.1
| +-- qs@6.7.0
| +-- raw-body@2.4.0
| | `-- unpipe@1.0.0
| `-- type-is@1.6.18
|   +-- media-typer@0.3.0
|   `-- mime-types@2.1.24
|     `-- mime-db@1.40.0
+-- bootstrap@3.4.1
+-- ejs@2.7.1
+-- errorhandler@1.5.1
| +-- accepts@1.3.7
| | `-- negotiator@0.6.2
| `-- escape-html@1.0.3
+-- express@4.17.1
| +-- array-flatten@1.1.1
| +-- content-disposition@0.5.3
| +-- cookie@0.4.0
| +-- cookie-signature@1.0.6
| +-- encodeurl@1.0.2
| +-- etag@1.8.1
| +-- finalhandler@1.1.2
| +-- fresh@0.5.2
| +-- merge-descriptors@1.0.1
| +-- methods@1.1.2
| +-- parseurl@1.3.3
| +-- path-to-regexp@0.1.7
| +-- proxy-addr@2.0.5
| | +-- forwarded@0.1.2
| | `-- ipaddr.js@1.9.0
| +-- range-parser@1.2.1
| +-- safe-buffer@5.1.2
| +-- send@0.17.1
| | +-- destroy@1.0.4
| | +-- mime@1.6.0
| | `-- ms@2.1.1
| +-- serve-static@1.14.1
| +-- setprototypeof@1.1.1
| +-- statuses@1.5.0
| +-- utils-merge@1.0.1
| `-- vary@1.1.2
+-- method-override@2.3.10
+-- morgan@1.9.1
| +-- basic-auth@2.0.1
| `-- on-headers@1.0.2
+-- vue@1.0.28
| `-- envify@3.4.1
|   +-- jstransform@11.0.3
|   | +-- base62@1.2.8
|   | +-- commoner@0.10.8
|   | | +-- commander@2.20.3
|   | | +-- detective@4.7.1
|   | | | +-- acorn@5.7.3
|   | | | `-- defined@1.0.0
|   | | +-- glob@5.0.15
|   | | | +-- inflight@1.0.6
|   | | | | `-- wrappy@1.0.2
|   | | | +-- minimatch@3.0.4
|   | | | | `-- brace-expansion@1.1.11
|   | | | |   +-- balanced-match@1.0.0
|   | | | |   `-- concat-map@0.0.1
|   | | | +-- once@1.4.0
|   | | | `-- path-is-absolute@1.0.1
|   | | +-- graceful-fs@4.2.2
|   | | +-- mkdirp@0.5.1
|   | | | `-- minimist@0.0.8
|   | | +-- private@0.1.8
|   | | +-- q@1.5.1
|   | | `-- recast@0.11.23
|   | |   +-- ast-types@0.9.6
|   | |   +-- esprima@3.1.3
|   | |   `-- source-map@0.5.7
|   | +-- esprima-fb@15001.1.0-dev-harmony-fb
|   | +-- object-assign@2.1.1
|   | `-- source-map@0.4.4
|   |   `-- amdefine@1.0.1
|   `-- through@2.3.8
`-- vue-resource@0.1.17

npm WARN vue-event-bulletin@1.0.0 No repository field.
Removing intermediate container 3c9f159d46c7
 ---> 64d35ff348c3
Step 5/6 : COPY . .
 ---> c8fa377b131a
Step 6/6 : CMD [ "npm", "start" ]
 ---> Running in f0aeac57fc19
Removing intermediate container f0aeac57fc19
 ---> 06f7fe6f1ca0
Successfully built 06f7fe6f1ca0
Successfully tagged bulletinboard:1.0

[追加]ホスト(今回は開発マシン)にあるimageの一覧はdocker image lsで確認できる.
今回の場合はbulletinboard:1.0とそのベースに使用したnode:6.11.5が存在していることがわかる.

# imageの一覧を表示
$ docker image ls
REPOSITORY          TAG          IMAGE ID          CREATED          SIZE
bulletinboard       1.0          06f7fe6f1ca0      51 minutes ago   681MB
node                6.11.5       852391892b9f      23 months ago    662MB

2: 作成したimageを使用してコンテナを起動する.

# ホストの8000番ポートをコンテナ(bb)の8000番ポートに割り当て,  
# image(bulletinboard:1.0)を用いてコンテナ(bb)をデタッチドモードで起動
$ docker container run --publish 8000:8080 --detach --name bb bulletinboard:1.0
bee46e68b8cf8db5c946060f887a9a4f5bcca18e6ac6958c76d35f34d3f330b5

ここでは複数のフラグ(オプション)を組み合わせて使用している:

  • --publishでホスト(この場合は開発マシン)の8000番ポートとコンテナの8000番ポートを疎通させるよう指定する.
    コンテナにはそれぞれ独自のポートが複数あり, ネットワークからコンテナにアクセスするためには使用するポートをこのように指定する必要がある.
    コンテナの指定していないポートを使う通信はデフォルトで設定されているファイアウォールによりすべて遮断される.
    [追加]-pオプションでも同じことができる.
  • --detachでこのコンテナをバックグラウンドで起動するよう指定する.
    [追加]-dオプションでも同じ. この起動方法をデタッチドモードという.
  • --nameでこのコンテナに名前(bb)をつける. この名前は以降のコマンドでこのコンテナを指定するときに使用できる.

また, 上記のコマンドでは実行した際にコンテナでどのプロセスを実行するかの指定がされていない.
これはDockerfileCMDでコンテナ起動時に自動実行するプロセス(npm start)が指定されていて, 改めて指定する必要が無いためである.

[追加]起動中のコンテナの一覧はdocker container lsで確認できる.

# 起動しているコンテナ一覧を表示
$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
cc1a42172625        bulletinboard:1.0   "npm start"         5 seconds ago       Up 4 seconds        0.0.0.0:8000->8080/tcp   bb

3: http://localhost:8000 をブラウザで開き, 掲示板アプリが実際に起動していることを確認する.
掲示板アプリの画面
ここまで来ればアプリの動作検証に必要な任意の作業(例: 単体テストの実行)を行うことができる.

4: 掲示板コンテナが問題なく動作することを確認したら, このコンテナを削除する.

# コンテナ(bb)を強制的に削除
$ docker container rm --force bb
bb

[追加]--forceを指定するとコンテナがプロセスを実行中でも強制的にコンテナを削除することができる.
また, コンテナを削除せず停止だけ行いたい場合はdocker container stopを使用する. また, docker container startで停止しているコンテナを起動できる.

# コンテナ(bb)を停止
$ docker container stop bb
bb
# 停止しているものも含め全てのコンテナを表示
$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
cc1a42172625        bulletinboard:1.0   "npm start"         2 minutes ago       Exited (0) 32 seconds ago                       bb
# 停止しているコンテナ(bb)を起動
$ docker container start bb
bb
# 起動しているコンテナ一覧を表示
$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
cc1a42172625        bulletinboard:1.0   "npm start"         6 minutes ago       Up 12 seconds       0.0.0.0:8000->8080/tcp   bb

Conclusion

https://docs.docker.com/get-started/part2/#conclusion
このパートでは簡単なアプリケーションのコンテナ化を行い, コンテナ化したアプリが正常に動作することを確認した.
次はKubernetes形式のYAMLファイルでコンテナの起動/管理方法を定義してKubernetes上で動作させるか(Part 3で説明), またはStackファイルを記述してこのパートと同じ内容をSwarm上で行う(Part 4で説明).