本稿は、Christffer Noring さん (@chris_noring) の Learn Docker, from the beginning を翻訳し、分かりやすいように少しだけ追記、サンプルコードの実行上の補足等を行ったものです。Docker について教科書的な使い方を実際に実行して確認しつつ、Docker Composeを使ったオーケストレーションの基礎までを導いてくれます。今、Docker で注目されるのは、コンテナ同士が連携する、オーケストレーションです。本稿ではオーケストレーションの最も基礎となる Dokcer Compose までですが、オーケストレーションを活用するための基礎的部分の考え方を実地を以ってしっかりと理解できます。その意味で、原題は「from the beginning」ですが、初心者向けと言うよりも、今現在進行形の技術トレンドをキャッチアップするための実践的な基礎作りと言うニュアンスとして「超基礎からの」と冠を付けてみました。初心者向けの Docker 本でなんとなく概要は分かったけど、どうも活用の仕方が分からない、オーケストレーションって何なのか分からない・・と言う皆様に、次のステップへの一助となれれば幸いです。
#超基礎からの 速習 Docker シリーズ一覧
-
超基礎からの 速習 Docker (1)
- この文章。
-
超基礎からの 速習 Docker (2)
- Volume を用いたデータの永続化や、開発環境の Volume 化を通じて、開発をより手軽なものにします。
-
超基礎からの 速習 Docker (3)
- データベースをコンテナ化し、レガシーなやり方及び新しいやり方である Network を用いて他のコンテナから連絡可能にします。
-
超基礎からの 速習 Docker (4)
- Docker Compose を用いた複数サービスの管理方法を解説します(その1)
-
超基礎からの 速習 Docker (5)
- Docker Compose を用いた複数サービスの管理方法を解説します(その2)
今や Docker に関するたくさんの記事が出ていますが、実際何が起きているのか、きちんと解説出来ているものがありません(なお、これは私の印象に過ぎないので、反論は自由ですよ)。私は自身の理解のためにたくさんの記事を書いてきていますし、それ自体楽しんでいます。あなたにとってもこれらが役に立てば良いなと思います。
本稿では、みなさんが他では得られないであろうぐらい比較的深く掘り下げることにしました。TLDR、これが「超基礎からの 速習 Docker」の一番最初で、基本的事項と、君が Docker を使うべき理由について説明します。
この記事に書かれていることは Docker のちょうど出発点です。前提知識も何も要りません。Enjoy
本稿では、以下のトピックをカバーしています。
- なぜ?なに? Docker これはもしかすると一番大事なパートかも知れません。なぜ Docker なのでしょう?なぜ他のテクノロジーや現状維持ではないのでしょう?Docker とは何か、Docker の構成についてご説明します。
- Docker の実行 アプリケーションを実際に Docker 化して、僕らが理解して、Docker を作るコア・コンセプトを使えるようにするショーケースにします。
- セットアップの改善 僕らはそのソリューションを Static な変数に依存するべきではありません。僕らのアプリの中から読みだすことのできる環境変数を使うことが可能です。
- コンテナ―の管理 コンテナー化して実行することはまずまず簡単です。でも、どのように管理するかについても見てみましょう。僕らはコンテナーを永遠に動かしたいとは思わないでしょうね。非常に軽いことでも、作業が追加となり、他で使いたい Port をブロックしてしまいます。
これはシリーズ一番最初のパートだってことをお忘れなく。Volume や Link、マイクロサービス、そしてオーケストレーションといったことにも探求していくことになるだろうけど、それは将来のパートでカバーすることになります。
#リソース
Docker を使う事、コンテナー化は、一枚岩をマイクロサービスに分解していくことです。このシリーズのいたるところで僕らは Docker やそのコマンド体系をマスターするために学ぶことになります。そうすれば、きっと君は自作のコンテナーをプロダクション環境で使いたくなるでしょう。その環境は大抵クラウド上にあります。十分な Docker 経験を積んだと思ったなら、次のリンクで Docker をクラウドでどのように活用できるか、ご確認してみると良いと思います。
- Azure 無料アカウントのサインアップ プライベート レジストリのようなクラウドのコンテナーを使うには、無料 Azure アカウントが必要でしょう。
- クラウドのコンテナー クラウドのコンテナーについて他に知っておくべきことについて網羅する概要ページです。
- 自作コンテナーをクラウドにデプロイ 今の Docker スキルをレバレッジしてクラウド上でサービスを動かすことがいかに簡単かを示すチュートリアル。
- コンテナー レジストリの作成 自作 Docker イメージを Docker Hub に入れられますが、クラウドのコンテナー レジストリも可能です。自作イメージをどこかにストアして、一瞬でレジストリから実際のサービスをできるようにすることは凄くないですか?
#Docker なぜ?なに?
Docker は再生可能な環境を提供します。特定の OS、異なるライブラリーの正確なバージョン、異なる環境変数、その他変数をそこに特定することができます。重要なことは、その分離された環境の中でアプリケーションを実行できることです。
さて、なぜ僕らはそうしたいのでしょうか?
- オンボーディング プロジェクトで新しいデベロッパーをオンボードするときは、SDK のインストール、開発ツール、データベース、パーミッションの設定、などなど、いつもセットアップすることがたくさんあります。このプロセスは丸一日から二週間にも及びます。
- 環境を同じにする Docker を使うことで、DEV、STAGING、PRODUCTION に至るまで環境を同じにすることができます。Docker / コンテナー化 以前、似たような環境を持つことは出来ても、小さな違いがあり、君がバグを見つけると、バグの原因を追いかけることに大きな時間を費やす羽目になった。時にバグはソースコードの中にあり、時にバグは切り分けに大きな時間を必要とする環境の違いの中にあったりします。
- 自分のマシンで動く 上記に似ていますが、Docker が分離したコンテナーを作るため、君がどれだけ細かく特定したとしても、君はそのコンテナーを客先に届けて、君の開発マシンで行ったことを正確に同じ方法で実行することができます。
##それってなに?
OK、なぜ Docker を調査するべきか、僕らは大きな理由を先に書いたけど、Docker の実際何なのか、更にダイブして行こう。Docker は OS のような環境と、アプリとそれに必要な変数を指定できることを説明したけど、他に Docker について何か知ることはあるだろうか?
Docker は君のアプリケーションを動かすのに必要なすべてを持っているコンテナーと呼ばれるスタンドアロン パッケージを作ります。各コンテナーは自身の CPU、メモリー、ネットワーク リソースを取得し、特定の OS やカーネルに依存しません。こう言って最初に思いつくのは Virtual Machine ですが、Docker はリソースをシェアする方法が異なります。Docker はコンテナーに一般的なパーツをシェアすることを可能にする Layerd File System を利用し、結果として、Virtual Machine よりもリソースを消費しないコンテナーとなります。
要するに、Docker コンテナーは、君が書いたソースコードを含めて、アプリケーションを実行するのに必要なすべてを持っています。コンテナーはまた、分離されたセキュアなライト・ウェイト ユニットでもあります。このことが、同じ OS でも、異なるプログラミング言語で書かれたり、同じライブラリの異なるバージョンを利用している複数のマイクロサービス生成を可能にしています。
もし、Docker がどのように動くか正確に知りたい場合は、次のリンクを見てください。layerd file sytem、ライブラリ ranc、そして、wikipedia Docker 概要の個所。
Docker の実行
OK、Docker とは何で、どんな良いことがあるか説明してきました。最終的に自作のアプリケーションを実行するものはコンテナーと呼ばれることが分かりましたね。しかし、どうやって作るのでしょう?それには、まず Dockerfile と言うファイルを書くことから始めます。そこには、OS、環境変数、そしてアプリケーションをどのように取得するかと言った必要な情報すべてを記載します。
もうすぐ僕らは最も深いとこまで着きますよ。アプリをビルドして、Docker化し、外とは隔離され、開かれた Port によってのみ通信するコンテナーの中で、アプリを実行するのです。
それには以下のステップとなります。
- アプリケーションの作成 今回、REST API として動く Node.js Express アプリケーション を作ります。
- Dockerfile の作成 Docker が自作アプリケーションをどうビルドするかを記述したテキスト ファイルをつくります。
- イメージをビルド アプリケーションを実行する前段として、最初に Docker イメージと呼ばれるものを作ります。
- コンテナーの作成 アプリを実行する最終ステップとなります。Docker イメージからコンテナーを作成します。
訳注
以下、Docker で実際にコマンドを実行しながらの確認が出来ますが、筆者の環境は Mac のようです(ちゃんと書かれてないけど)。訳者によって Mac / Windows 両方で実行確認していますので、必要に応じて補足していきます。実行には、Node.js 及び Docker Desktop のインストールが必要です。Docker Desktop のインストールは少し追加の設定が必要ですので、こちらなどを参考にインストールください → Windowsの場合 / Macの場合
特に指定がなければ、Mac の場合はターミナル、Windows の場合は、PowerShell を使ってください。あ、PowerShell は PowerShell v7 以降 をご利用ください。default では v5 だと思います。
##自作アプリの作成
Express Node.js プロジェクトを作ります。ファイル構成は以下の通りとなります:
- app.js REST API コードになります。
- package.json プロジェクトのマニフェスト ファイルです。ここでは、Express と言った依存関係だけでなく、スクリプトの開始といったものも宣言します。
- Dockerfile 自作アプリをどのように Docker化するかを記述します。
package.json を作成するには、任意のプロジェクトフォルダに、以下のコマンドを打つだけです。
npm init -y
その上で、express ライブラリといった依存関係ファイルを、以下のように打ってインストールします。
npm install express --save
##コードを追加しましょう
package.json の生成、依存関係ファイルもインストールしたら、自作アプリを実行するのに必要なコードの追加です。以下のように app.js へコードを追加してください:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
自作アプリの実行には以下のようにコマンドをタイプします。
node app.js
ウェブ ブラウザ―で http://localhost:3000 とすれば、以下のように表示されます。
OK、動いてますね。よくできました
##Dockerfile の作成
次のステップは Dockerfile の作成です。このファイルはマニフェストとして動作しますが、どのように自作アプリがビルド、実行されるかと言うビルド指示でもあります。OK、ビルドと実行には何が必要でしょう?僕らが必要なのは:
- copy すべての app ファイルを docker container に入れます。
- install express ライブラリのような依存関係ファイル。
- open up a port 外からコンテナーの中にアクセスできるようにします。
- instuct 自作アプリの開始する方法
更に複雑なアプリケーションでは、環境変数の設定、データベースのクレデンシャル、データベースのシード データなども必要です。今回、僕らが必要な銃弾リストは上記リストにあるだけ。Dockerfile で express を試してみましょう。
FROM node:latest
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
ENTRYPOINT ["node", "app.js"]
上記各コマンドについて説明していきましょう。
-
FROM Docker Hub からの OS イメージの選択。Docker Hub は、私たちが pull できるイメージを持っているグローバル リポジトリです。今回、僕らは node と言う名前の Node.js がインストールされている Ubuntu ベースのイメージを選択しています。
:latest
タグで最も新しいバージョンを指定しています。 - WORKDIR ワーキング ディレクトリーの指定です。以降に示すコマンドのカレント ディレクトリに効果があります。
- COPY ディレクトリーから、コンテナーの WORKDIR で指定したディレクトリーにファイルをコピーします。
- RUN Node.js express アプリケーションを実行するのに必要なすべてのライブラリをインストールするため、ターミナルでコマンドを実行します。
- EXPOSE 僕らが作るコンテナーと通信するための Port を指定します。
-
ENTRYPOINT どのようにアプリケーションを開始するかを示します。
["node", "app.js"]
のように配列で示すと、node app.js
と解釈されターミナルで実行されます。
##Quick Overview
OK、僕らのプロジェクトで必要なファイルはすべて揃いました。こんな感じになるはずです:
app.js // 自作 express app
Dockerfile // Docker が読み込む指示ファイル
/node_modules // run npm install を実行するとできるディレクトリー
package.json // npm init を実行すると生成されるファイル
package-lock.json // NPM からライブラリをインストールすると生成される
##イメージのビルド
自作アプリケーションをビルドしてコンテナー内で実行するためには2つのステップがあります:
-
イメージの作成 Dockerfile で指示された内容を
docker build
コマンドでイメージを作成します。 - コンテナーの開始 上記で作成されたイメージから、コンテナーを作る必要があります。
一番最初の最初に、以下のコマンドでイメージを作成しましょう。
docker build -t chrisnoring/node:latest .
以上の指示でイメージが生成されます。最後の . は重要です。僕らの Dockerfile がどこにあるか、今回はカレント ディレクトリー にあることを示しています。もし FROM コマンドで指定する OS イメージを僕らが持っていない場合、Docker Hub から pull して、イメージを作成します。
ターミナルにはこんな風に表示されることでしょう:
% docker build -t chrisnoring/node:latest .
Sending build context to Docker daemon 2.008MB
Step 1/6 : FROM node:latest
latest: Pulling from library/node
99760bc62448: Pull complete
e3fa264a7a88: Pull complete
a222a2af289f: Pull complete
c1f89293f045: Pull complete
115b6fc5ace1: Pull complete
9eb516295c24: Pull complete
d23358c1492a: Pull complete
08d6736f797a: Pull complete
3dcecd6cc67a: Pull complete
Digest: sha256:101d1d7ba7562fcb36b23eeff46607107802f1a439a571d86cf490cf9fe2150e
Status: Downloaded newer image for node:latest
---> a511eb5c14ec
Step 2/6 : WORKDIR /app
---> Running in b2e78f622519
Removing intermediate container b2e78f622519
---> 2db1af3bb5bb
Step 3/6 : COPY . .
---> 1f59d1ad443f
Step 4/6 : RUN npm install
---> Running in 74c2a6423aad
npm WARN nodejs@1.0.0 No description
npm WARN nodejs@1.0.0 No repository field.
audited 126 packages in 1.06s
found 0 vulnerabilities
Removing intermediate container 74c2a6423aad
---> 46e3f497d0d9
Step 5/6 : EXPOSE 3000
---> Running in 5179b2faa286
Removing intermediate container 5179b2faa286
---> 9136884bf44b
Step 6/6 : ENTRYPOINT ["node", "app.js"]
---> Running in 21ae8fe96482
Removing intermediate container 21ae8fe96482
---> b709d5fe6392
Successfully built b709d5fe6392
Successfully tagged chrisnoring/node:latest
OS image node:latest
が Docker Hub から pull され、WORKDIR
や RUN
といったコマンドが実行されています。注目に値するのは、各ステップ後に中間コンテナーを削除するやり方です。Docker がスマートに異なるファイル レイヤーをキャッシュするので、より高速になっています。最後にすべての構築が成功したことを successfully build
と示しています。それでは作成されたイメージを見てみましょう:
docker images
% docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
chrisnoring/node latest b709d5fe6392 35 minutes ago 943MB
イメージが出来ていますね。成功しました
##コンテナーの作成
次のステップは、イメージからコンテナーを構築することです。コンテナーは自作アプリを中で動かす分離されたピースです。docker run
を使うことでコンテナーをビルドします。コマンドは以下のように行います:
docker run chrisnoring/node
これでは十分ではありません。 内部からの Port 番号と外部から(hostから)の Port 番号をマップする必要があります。このアプリは、ブラウザーでアクセスするアプリだったことを思い出してください。以下のようにして -p
フラグでマッピングします。
-p [external port]:[internal port]
今回の場合、以下のようにコマンドを入力します。
docker run -p 8000:3000 chrisnoring/node
訳注
上記コマンドを入力する前に、すでにdocker run chrisnoring/node
を走らせてしまっていると、余計なコンテナーが動いてしまっているので、コンテナーの管理で後述する通りdocker ps -a
で コンテナーの ID を取得の上、docker stop
docker rm
でコンテナーを削除します。
OK、このコマンドを走らせると、http://localhost:8000
でコンテナーに訪問できるということです。8000 はコンテナー内部 3000 番を 8000 番にマップした外部 Port です。さて、ブラウザーで開いてみましょう:
皆さん、コンテナーが動いてますよ
#環境変数を使ってセットアップを改善
OK、Docker イメージをどうやってビルドするか、どうやってコンテナーを実行するか、それによってその中のアプリをどうやって実行するかについて学びました。ですが、PORT の扱いはもう少しだけナイスにできます。現段階では、Dockerfile に書かれた Port 番号と express サーバー を起動する Port 番号が一致することを app.js のソースで確認する必要があります。
これを修正するために、環境変数を紹介したいと思います。二つやることがあります。
- 追加 Dockerfile の環境変数を追加します。
- 読む app.js から環境変数を読み込みます。
##環境変数を追加する
環境変数を追加するために、ENV コマンドを以下のように使います:
ENV PORT=3000
Dockerfile を次に示すように追加しましょう。
FROM node:latest
WORKDIR /app
COPY . .
ENV PORT=3000
RUN npm install
EXPOSE 3000
ENTRYPOINT ["node", "app.js"]
もう一つ、固定値を変数化するよう EXPOSE を以下のように更新します:
FROM node:latest
WORKDIR /app
COPY . .
ENV PORT=3000
RUN npm install
EXPOSE $PORT
ENTRYPOINT ["node", "app.js"]
EXPOSE コマンドのパラメータを $PORT に変更している点に注目してください。変数は $ を付ける必要があります。
EXPOSE $PORT
##App.js から環境変数を読み込む
Node.js で環境変数を読み込む場合、以下のように行います:
const express = require('express')
const app = express()
const port = process.env.PORT
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
注
app.js または Dockerfile を変更するとき、イメージをリビルドする必要があります。これはdocker build
をもう一度実行し、docker stop
とdocker rm
を使って、コンテナーを落とさなければならないことを意味します。より詳細は次のセクションでお伝えします。
##コンテナーの管理
OK、docker run
を始めようとして、コンテナーがシャットダウンできないことに気が付いたですか?パニックになるね 新しいターミナル(訳注:Windows の場合は PowerShell)を開いて、以下のようにします:
docker ps
これで、実行中のコンテナーの名前と ID が一覧できます:
% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cceed5dcd898 chrisnoring/node "node app.js" About an hour ago Up About an hour 0.0.0.0:8000->3000/tcp beautiful_curie
CONTAINER_ID または NAME カラムの値を使うことで、以下のようにコンテナーのストップできます:
docker stop cce
CONTAINER_ID を使ってますが、最初の3数字だけ(この例では cce)で実行できます。
訳注
docker stop
では、コンテナーが「停止」した状態ですので、削除はされていません。完全に削除するためには 、これに続けてdocker rm cce
などとします。
また、この節で解説した Dockerfile と app.js の変数化を試すには、もう一度イメージの作成docker build -t chrisnoring/node:latest .
を行った上で、docker run
(以下省略)する必要があります。
##Daemon モード
以上で示した別々のターミナル(訳注:Windowsの場合は PoserShell)を開くわけですが、Daemon モードで実行した方が良いでしょう。このモードはコンテナーをバックグラウンドで実行し、出力を見えなくするモードのことです。このモードにするには、単純に -d
フラグを付けます。以下試してみましょう。
% docker run -d -p 8000:3000 chrisnoring/node
6a43d94dfa397793438d6187b7d8e83c70fad3203548159265954eb0724fe409
コンテナー ID が戻ってきます。コンテナー ID はこれまでも出てきたものと同一です。止めたい時はこの ID を使って docker stop 6a4
と、最初の3数字を指定すれば良いので簡単です。
##Interactive モード
Interactive モードは、面白いです。これは実行中のコンテナーに入り、ファイルをリストしたり、追加・削除したり、例えば bash でできることを実行できます。今回、以下のようにして docker exec
コマンドを以下のように行います:
% docker exec -it 6a4 bash
root@6a43d94dfa39:/app# ls
Dockerfile app.js node_modules package-lock.json package.json
root@6a43d94dfa39:/app#
上記コマンドは:
docker exec -it 6a4 bash
コンテナーが実行中でなければなりません。すでにストップしている場合は、docker start 6a4
としてスタートしてください。6a4
には docker run
で戻ってきた値に置き換えてください。
訳注
docker rm
を用いて削除までしている場合は、docker run
(以下略) からやり直してください。なお、コンテナー ID は、前述の通り、別ターミナル(またはPowerShell)からdocker ps -a
としても取得できますね。
6a4
は、コンテナー ID の最初の3数字、-it
は Interactive モード、最後の bash
は bash shell を実行することを意味します。
ls
のようなコマンドにすることもできます。一度 bash shell を起動すれば、コンテナー内をリストしたりできます。ビルドが上手くいっているかどうか確認したり、デバッグしたりするにも良い方法です。
もし、node コマンドのように、コンテナー内で何か実行したいのであれば、例えば、以下のようにタイプします。
docker exec 6a4 node app.js
これは、コンテナー内で、node app.js が実行されます。
##Docker kill vs Docker stop
ここまで、コンテナーを止めるために docker stop
を使ってきましたが、止めるには docker kill
を使う方法もあります。違いは何でしょうか?
- docker stop このコマンドでは、SIGTERM シグナルを SIGKILL の然るべき前に送っています。要するに、コンテナーを落とす際にリソースの開放、状態の保存を行う、上品なやり方です。
-
docker kill 直ちに SIGKILL を送ります。このことは、意図的に、リソースの開放や状態の保存を行わないことを意味します。開発では、この二つのコマンドのどちらかを使うことは大きな問題ではありませんが、プロダクション環境下では、
docker stop
に頼る方が賢いかも知れません。
##クリーンアップ
このコースで開発中に作成されたたくさんのコンテナーをクリーンアップするには以下のようにタイプします。
docker rm <id-of-container>
#サマリー
OK、Docker を最初から体験しました。Docker を利用する基本的なコンセプトもカバーしています。更に、アプリを Docker 化するやり方を調査しつつ、使いやすい Docker コマンドをカバーしました。データベースや Volume、Link の仕方、複数コンテナーの起動、オーケストレーションといった、Docker には知るべきことがさらにあります。
しかし、この記事はシリーズになっています。どこかで止めないと記事が大きくなってしまいます。Volume とデータベースについて解説する次回パートにご期待ください。
#謝辞
Docker に関する驚くべきコースを提供してくれた、Dan Wahlin Twitter に感謝します。あなたのコースのお陰で、Docker のたくさんのことについて私をクリックしてくれました。
Twitter をフォローして、トピックへのあなたの問い合わせやご質問、提案を頂けるとハッピーです。