LoginSignup
11
16

More than 1 year has passed since last update.

[Docker 入門] 公式 チュートリアル (和訳)

Last updated at Posted at 2020-09-28

はじめに

dockerの公式チュートリアルがかなりまとまっていてわかりやすかったので、一通りやってみました。ただ、チュートリアルが英語で躊躇しがちなのかなと思ったので、どなたかの参考になればと和訳してみました。
当方、windows環境なのでwindowsのUIで説明しています。

前提

・dockerインストール済
・gitインストール済

チュートリアルを開始する

インストール済の状態からチュートリアルを開始する場合は、dockerのアイコンをクリックして、Learnをクリックするとチュートリアルが起動します。インストール前ならインストール完了時に自動でチュートリアルが開始されるはずです。
image.png

下記の画面が立ち上がるので、Startします。
image.png

clone

チュートリアル用のコードをcloneします

git clone https://github.com/docker/getting-started.git

Build

docker build -t docker101tutorial .

DokerfileからDockerイメージを作成する際はこのbuildコマンドを使用します。-t docker101tutorialはDockerイメージのタグ名を指定しています。tはtagの頭文字です。.Dockerfileがカレントディレクトリにあるということを表しています。
まとめると上記コマンドは、カレントディレクトリにあるDokerfileからdocker101tutorialというタグ名でDockerイメージを作成する、という意味になります。Dockerイメージは、アプリケーションの実行に必要なファイル群が格納されたディレクトリで、コンテナのもとになるものです。

Run

docker run -d -p 80:80 --name docker-tutorial docker101tutorial

runコマンドは、Dockerイメージからコンテナを生成し起動するために使用するコマンドで、生成と起動を同時に実行することができます。生成するコマンドdocker createと起動するコマンドdocker startもあり、createしてからstartすることはrunするのと同じ動作になります。
-dはdetachモードで起動することを指定しており、これを指定しておくとバックグラウンド起動できます。これを指定せずに起動するとターミナルに情報が出力されて占有されしまうので、これを避けたい場合は指定します。
上記コマンドは、docker101tutorialというDockerイメージからdocker-tutorialという名前のコンテナを起動します。
このときdockerの起動に失敗した場合は、ホスト側のポートを8080に変更してみてください。

1.さぁ、はじめよう

localhost:80にアクセスして下記画面が表示されれば、チュートリアル用のコンテナにアクセスできています。ここからはこのチュートリアルに沿って和訳していきます。ところどころ私のコメントも入れています。

(補足)
このように記載しているところは私のコメント(補足)です。

image.png

実行したコマンドについて

コンテナを生成、起動した下記コマンドの説明をします。

docker run -d -p 80:80 docker/getting-started
  • -d : バックグラウンドモードでコンテナを生成/起動します
  • -p 80:80: ホスト側のポート80をコンテナ側のポート80にマッピングする
  • docker/getting-started: 使用するイメージ

このコマンドは下記のように省略して記載することができます。
docker run -dp 80:80 docker/getting-started

The Docker Dashboard

ここで一旦、Docker Dashboardについて説明しておきます。Dashboardを起動することで、コンテナログを確認したり、コンテナ内のshellを起動したりできます。このほかにも、ブラウザで開いたり、再起動、削除することができ、いろいろな操作がUIで直感的に操作できます。

Dashboardにアクセスすると、下記のような画面が開き、コンテナがRUNNINGしていることが確認できます。「jolly_bouman」のところは、ランダムな名前になるので別の名前になっているはずです。
image.png

コンテナとは?

コンテナとは何か・・?簡単に言うと、ホストマシン上での、その他プロセスとは隔離されたマシン上のプロセスです。プロセスというのは、マシンで実行されているプログラムのことです。プロセスの隔離は、Linuxのnamespacesとcgroupsという機能を利用しています。

コンテナイメージとは?

コンテナイメージとは・・?イメージは実行環境で動くコンテナのもと(ひな形)です。コンテナイメージの正体は、アプリケーションの実行に必要なファイル群が格納されたディレクトリです。環境変数やデフォルトで実行するコマンド、その他メタデータが含まれています。

2.アプリケーション

このチュートリアルで実行するアプリケーションはNode.jsで動作するtodoアプリです。Node.jsになじみがなくても大丈夫です。
ここでは、todoアプリとして最低限動くものを用意します。下記の手順に沿ってアプリを起動し、動作を確認してください。

アプリケーションの取得

  1. http://localhost:80/assets/app.zipからソースコードをダウンロードします。

    (補足)
    上記アドレスをブラウザに入力するとソースコードがダウンロードされます。ポートは適宜変更してください。

  2. zipを解凍すると、package.jsonと2つのサブディレクトリ(srcとspec)があります。

コンテナイメージのビルド

アプリケーションをビルドするために、ここではDockerfileを使います。Dockerfileはテキストベースのスクリプトで、コンテナイメージを生成するための指示書のようなものです。
下記の手順にそってDockerイメージを作成しましょう。

  1. package.jsonと同じディレクトリ階層にDockerfileを作成し、下記を記載してください。dockerfileには拡張子は必要ありません。

    FROM node:12-alpine
    WORKDIR /app
    COPY . .
    RUN yarn install --production
    CMD ["node", "src/index.js"]
    
  2. ターミナル開き、appディレクトリまで移動して下記コマンドを実行してください。

    docker build -t getting-started .
    

    Dockerfileから新規のコンテナイメージを生成する場合はこのコマンドを使用します。実行後にターミナルを確認すると多数の"レイヤー"をダウンロードしていることがわかります。これはnode:12-alpieイメージをベースイメージとして使用したためで、マシン上にこのイメージがない場合はダウンロードする必要があるからです。
    ダウンロードが完了すると、アプリケーション内にコピー(COPY . .)してyarnを使ってアプリケーション依存関係をインストール(RUN yarn install --production)します。依存関係のインストールはRUN yarn install --productionのコマンドで実行され、package.jsonでdevDependenciesに記載されているもの以外をアプリケーション内にインストールします。devDependenciesに記載されているものは開発時に必要となるパッケージであり、製品版(--production)では必要ないので--productionを指定しています。
    CMDはコンテナイメージからコンテナを起動したときにデフォルトで実行されるコマンドです。つまり、docker run XXXXを実行したときにnode src/index.jsのコマンドが実行されるということです。
    docker buildコマンドの最後の.Dockerfileがカレントディレクトリにあるということを意味しています。

    (補足)
    dockerはDockerfileという名前をデフォルトで探しますが、-fを指定すれば別名も指定できます。Dockerfile.baseという名前にしたい場合は下記のようなコマンドになります
    docker build -t sample -f Dockerfile.base .

コンテナを開始する

イメージの準備ができたのでdocker runコマンドを使用してアプリケーションを実行します。

  1. イメージを指定してdocker runコマンドでコンテナを起動します

    docker run -dp 3000:3000 getting-started
    

    -dはコンテナをバックグラウンドで起動することを意味していて、-pによってホスト側のポート3000とコンテナ側のポート3000をマッピングしています。このポートマッピングがないとアプリケーションにアクセスすることができません。

  2. http://localhost:3000にアクセスするとアプリケーションにアクセスできます。

  3. アイテムが想定通り追加されることが確認できるはずです。完了マークをつけることができ、追加したアイテムを削除することもできます。

ここに少し変更を加えてコンテナの管理について学んでいきます。

3.アプリケーションの更新

下記の手順に沿ってアプリケーションを更新してください。

  1. src/static/js/app.jsの56行目を下記のように変更します。
    image.png

  2. アップデートしたバージョンのイメージをbuildしましょう。下記コマンドを実行します。
    docker build -t getting-started .

  3. 更新したコードを使った新しいコンテナを起動します。
    docke run -dp 3000:3000 getting-started

このコードを実行したとき、次のようなエラーが表示されたはずです。
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell
(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.

このエラーの原因は、古いコンテナがまだポート3000で起動したままになっているからです。ホスト側の一つのポートで占有できるプロセスは一つだけです。
これを解決するには、古いコンテナを削除すればよいです。

古いコンテナを差し替える

コンテナを削除するために、まず停止します。停止をしないと削除できません。
古いコンテナを削除する方法は2通りあるので好きなほうで削除してください。

コンテナの削除(CLIを使う)

  1. docker psコマンドでコンテナIDを取得します

    docker ps
    
  2. コンテナを停止するためにdocker stopを使います

    # <the-container-id> のところはdocker ps で取得したコンテナIDと差し替えてください
    docker stop <the-container-id>
    
  3. 停止したコンテナをdocker rmコマンドで削除します

    docker rm <the-container-id>
    

    下記コマンドを使用するとコンテナの停止と削除を1行のコマンドで実行することができます。
    docker rm -f <the-container-id>

コンテナの削除(Docker Dashboardを使う)

Docker dashboardを使うと、2クリックでコンテナを削除することができます。CLIを使う場合と比べて簡単で直感的にコンテナを削除することができます。

  1. dashboardを開き、削除したいコンテナにマウスオーバーすると右側にアクションボタンが表示されます
  2. ゴミ箱アイコンをクリックしてコンテナを削除します image.png
  3. コンテナがなくなったことを確認します

更新したアプリケーションを開始する

  1. 更新したアプリケーションを起動します

    docker run -dp 3000:3000 getting-started
    
  2. http://localhost:3000にアクセスして、テキストが更新されていることを確認してください
    image.png

(3章の要約)

アプリケーションの更新をしましたが、下記2点気づいたと思います。

  1. 初めに登録したアイテムがすべて消えています。これはtodoリストアプリとして良くないので、これについては後の章で触れます
  2. 小さな変更にしては、アプリを更新するのにたくさんのステップが必要でした。rebuildして新規のコンテナ起動をいちいちしなくてもよい方法を後の章で見ていきます。

アイテムが保持される方法について触れる前に、イメージを共有する方法について簡単に見ていきましょう。

4.アプリケーションの共有

ここまででイメージが完成したので、それをシェアしていきましょう。Dockerイメージをシェアするには、Dockerレポジトリを利用する必要があります。Docker Hubを使用していきましょう、ここには、私たちが使用するイメージのすべてが入っています。

レポジトリの作成

イメージをプッシュするためにDocker Hubにレポジトリを作成していきましょう。

  1. Docker Hubにログインします
  2. Create a Repositoryをクリックします
  3. レポジトリ名はgetting-startedとします。またVisibilityはPublicになっていることを確認します。
  4. Createボタンをクリックします

ページ右側を見ると、Docker commandsセクションがあり、イメージをプッシュするために実行するコマンドサンプルが表示されています。
image.png

イメージのプッシュ

  1. コマンドラインを開き、さきほど確認したプッシュ用のコマンドを入力します。このとき、dockerのところは自分のDocker IDに差し替えてください。

    $ docker push docker/getting-started
    The push refers to repository [docker.io/docker/getting-started]
    An image does not exist locally with the tag: docker/getting-started
    

    失敗してしまいました。pushコマンドはdocker/getting-startedという名前のイメージを探したはずです、しかし見つけることができなかったということです。docker image lsコマンドを実行してREPOSITORYを確認してみてください。確かにそのような名前のイメージはないですね。
    これを解決するためには、"tag"でイメージに別名を付与します。

  2. Docker Hubにログインします。docker login -u YOUR-USER-NAME (実行後パスワードを入力してください)

  3. YOUR-USER-NAMEは自分のDocker IDに差し替えてください。

    docker tag getting-started YOUR-USER-NAME/getting-started
    
  4. 再度プッシュコマンドを実行します。コマンドをDocker Hubからコピーしている場合は、tagnameはなにも記入しないでください。Dockerではtagを指定しなかった場合、latestタグが使われます。

    docker push YOUR-USER-NAME/getting-started
    

新しいインスタンスでイメージを起動する

ここまでで、イメージのビルドとレジストリへのプッシュが完了しました。プッシュしたイメージを完全に新規の環境で起動させてみたいと思います。ここではPlay with Dockerを使いましょう。

  1. Play with Dockerを開きます
  2. Docker Hubアカウントでログインしましょう
  3. ログインができたら左側のサイドバーにある+ ADD NEW INSTANCEをクリックしましょう。(もし表示されていない場合はブラウザのサイズを少し小さくしてみてください)少し待つと、下記のような画面がブラウザ上で表示されます。
    image.png

  4. ターミナル上で先ほどプッシュしたアプリケーションを起動してみましょう

     docker run -dp 3000:3000 YOUR-USER-NAME/getting-started

  5. 起動すると3000と書かれたボタンが表示されるのでクリックしてみましょう。Docker Hubにプッシュしたアプリケーションが起動できたのが確認できます。ボタンがない場合は、OPEN PORTをクリックして、3000と入力してください。

(4章の要約)

この章では作成したイメージをレジストリにプッシュして共有する方法について解説しました。Play with Dockerを使ってプッシュしたイメージを使って新規にアプリケーションを起動してみました。これはCIパイプラインと呼ばれるものと同じで、イメージを作成、プッシュすることでプロダクション環境で最新のイメージを利用することができるのです。

5.DBの永続化

気づいていないかもしれませんが、TODOアプリはコンテナを起動させるたびにデータが消えてしまいます。なぜこのようなことが起きるのか、コンテナがどのように動いているのかを確認しながら理解していきましょう。

コンテナのファイルシステム

コンテナはイメージの様々なレイヤーを使用して起動します。

(補足)
dockerイメージはdockerfile一つ一つの命令がイメージとして重なった状態で、ここではそれら重なった状態を"レイヤー(層)"と表現しているようです。
dockerはdockerfileの各命令一つ一つをイメージとして保持し、それらイメージを利用して、コンテナを起動します。

それぞれのコンテナは独自の"スクラッチスペース"を取得し、その中でファイルの生成、更新、削除を実施します。同じイメージであってもあらゆる変更は他のコンテナからはみることができません。

(補足)
スクラッチスペース:
ここでは他のプロセスから隔離されたメモリ上の空間を想像するとよさそうです

実際に手を動かして確認してみよう

上記を確認するために、2つのコンテナを起動して、それぞれでファイルを編集してみましょう。一方のコンテナで編集したファイルは、もう一方のコンテナからは利用できないことがわかると思います。

  1. ubuntuコンテナを起動します。このコンテナでは、1~10000のランダムな数値を/data.txtに出力します

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    このコマンドでは、bash shellを起動して2つのコマンドを呼び出しています(&&で2つのコマンドを繋げている)。最初のコマンドはランダムな数値を/data.txtに書き出しています。2つ目のコマンドは、コンテナを実行し続けるために単にファイルを監視しています.

    (補足)
    試しに2つ目のコマンドと&&を削除してrunしてみてください、起動したコンテナは直ちに停止してしまい、コンテナ内のファイルを確認することができません。

  2. 出力された値を確認しましょう。Dashboardを開いて、ubuntuイメージを起動しているコンテナにマウスオーバーし、一番左のボタン(CLI)をクリックしましょう。
    image.png
    コンテナ内に入ったら、下記コマンドを実行して/data.txtの中身を確認してみましょう。

    cat /data.txt
    

    もしコマンドラインを使いたい場合は、docker execコマンドで同じことができます。docker psでコンテナIDを取得後、下記コマンドを実行すればよいです。

    docker exec <container-id> cat /data.txt
    

    ランダムな数値が表示されているのがわかると思います。

  3. 次に、同じubuntuイメージを使って、もう一つコンテナを起動してみましょう。そうすると/data.txtが存在しないことが確認できます。

    docker run -it ubuntu ls /
    

    /data.txtが存在しないのは、このファイルがはじめのコンテナのスクラッチスペースに書き出されたからです。

  4. docker rm -fコマンドで不要なコンテナを削除してください。

コンテナVolumes

これまで見てきたようにコンテナはそれが起動されたときにイメージの内容に従って起動されるものです。コンテナを生成し、ファイルを更新、削除しても、それらの変更はコンテナが削除されてしまうと消失してしまいます。すべての変更は各コンテナで独立しているということです。
Volumesを使えば、消失しないようにすることができます。

Volumesを使えばコンテナの特定のファイルシステムパスをホストマシンに接続することができます。コンテナ側のディレクトリがマウントされていれば、変更はホストマシン側でも確認することができます。もしコンテナを起動したときに、同じディレクトリをマウントしておけば、同じファイルをコンテナ側で確認することができます。つまりデータは消失しないということです。

volumesは2つのタイプがあります。まずは、named volumesから確認してみましょう。

TODOデータを消えないようにする

デフォルトでは、TODOアプリケーションのデータは/etc/todos/todo.dbのSQLite Databaseに保存されます。SQLiteに詳しくなくても大丈夫です。単に関係データベースで、すべてのデータがひとつのファイル内に保存されています。大規模なアプリケーションではこの方法は適さないですが、今回のTODOアプリのような小規模アプリではうまく機能します。あとで、別のデータベースエンジンに切り替える方法についてもみていきます。

データベースが一つのファイルであるならば、そのファイルをホスト側に保持しておき、次回の新規コンテナで使用できるようにすれば、中断したところからコンテナを再開できるはずです。volumeを作成して、データを格納するディレクトリにvolumeをアタッチ(これを"マウント"と呼びます)することにより、データを持続的に利用することができます。コンテナがデータをtodo.dbファイルに書き込むと、それらのデータはホスト側のvolume内で保存されます。

さきほども言った通り、ここではnamed volumeを使います。named volumeは単なるデータのバケツと考えればよいです。これを使う場合、volumeの名前だけを覚えておけば十分で、物理的な記憶領域がどこであるかを意識する必要はなく、volumeの名前と記憶領域の紐づけはDockerが管理します。volumeを利用するたびに、実際のデータの所在をDockerが特定します。

  1. docker volume createコマンドを使ってvolumeを作成します。

    docker volume create todo-db
    
  2. 今回のnamed volumesを利用していないTODOアプリをDashboardで削除します。(あるいは、docker rm -f <container-id>で削除する)

  3. 続いてTODOアプリコンテナを起動するのですが、今回は、-vフラグによりvolume mountを指定してください。これによりnamed volumeが利用され、/etc/todosにマウントされます。これにより/etc/todosのパスに生成されたすべてのファイルをホスト側で保存することができます。

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    
  4. コンテナを起動したらいくつかアイテムを追加してみてください。

  5. TODOアプリのコンテナを削除します。Dashboardを使うか、docker psでコンテナIDを取得したあと、docker rm -f <id>で削除します。

  6. 上記で示したのと同じコマンドを再度入力し、実行します

  7. アプリを開き、先ほど追加したアイテムがあることが確認できるはずです

  8. 確認ができたらコンテナをさきほどと同様に削除します。

これでデータを保持する方法がわかりましたね。

(補足)
volumesにはnamed volumesbind mountの2タイプが標準で使用できます。それぞれの主な違いは、ホスト側でのデータ管理場所です。named volumesは上記で見たようにユーザーはvolume名だけ意識すればOKでした。つまりデータの実体をどこに保存するかはdockerにお任せということです。次にbind mountは、ホスト側のどのディレクトリをコンテナ側のどこのディレクトリにマウントするかを指定することができます。つまり、ホスト側でのデータ管理場所を任意に選択可能ということです。

 

(情報) Pro-tip
named volumesやbind mounts(次章で説明)はDockerエンジンをインストールすると標準でサポートされている2つの主なvolumesなのですが、このほかにもNFS,SFTP,NetApp,そのほかにもたくさん..をサポートしたvolumeドライバープラグインがあります。これは、Swarm, Kubernetesなどのクラスタ環境内のマルチホスト上でコンテナを起動するときに特に重要となる技術です。

Volumeの詳細を確認する

"named volumeを使ったとき、保存したデータはいったいどこにあるんだろう"と疑問に思うかもしれません。もしそれが知りたい場合は、下記コマンドを実行すれば解決できます。

docker volume inspect todo-db
[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpointが実際にデータが保存されている場所を示しています。多くのマシンでは、ホストからこのディレクトリにアクセスするためには管理者権限が必要になりますが、確かにデータはそこにあります。

(情報) Docker Desktop上で直接Volumeデータにアクセスするには
上記todo-dbのMountpointを見たとき、こんなディレクトリどこにも存在しない、と思ったかもしれません。Docker Desktopを起動している間、Dockerコマンドはマシン上の小さなVM内で実際は動いています。なのでもしMountpointのディレクトリ内のデータを確認したい場合は、VM内部に接続する必要があります。この方法についてはググると方法がたくさんでてきます。

(5章の要約)

コンテナを削除して再起動した場合にもデータが保持されているアプリケーションを作成できました。
しかし、前の章で見たとおり、イメージに変更を加えて再度ビルドするためにはいくらかの時間がかかってしまいます。bind mountsを利用すれば、より良い方法でアプリを構築できます。次章ではbind mountsについてみていきます。

6.Bind Mountsを使う

前章ではnamed volumeを使ったデータ保持の方法についてみてきました。named volumesはデータの実体がどこに保存されているかを意識しなくてよいので単にデータを保存したい場合にはとても役に立ちます。

bind mountsを使えば、ホスト上のどこにマウントするのかを確実に制御できます。おおよそこれはデータを保持するために使用されますが、新規データをコンテナに追加したい場合にも使用されます。アプリケーションを起動する際にソースコードをコンテナ側にマウントし、ソースコード変更や動作確認をリアルタイムに行うのにbind mountsを利用することができます。

Nodeベースアプリケーションでは、ファイル変更を監視し、変更時にアプリを再起動するといったことを実施するnodemonというツールがあり、他の言語やフレームワークにはこれに相当するツールがあります。

Volumeタイプ比較表

Bind mountsやnamed volumesはDockerエンジンで標準で使用できる主なvolumeなのですが、これに加えて他のユースケース(SFTP,Ceph,NetApp,S3,etc...)をサポートするために様々なvolumeドライバーを利用することができます。
image.png

開発モードコンテナを起動する

開発ワークフローをサポートしたコンテナを起動するために、下記を実施します。

  • ソースコードをコンテナにマウントする
  • すべてのdependenciesをインストールする("dev"dependenciesを含む)
  • ファイル変更を監視するためにnodemonを起動する
  1. getting-startedコンテナが起動していないことを確認してください
  2. 下記コマンドを実行してください。コマンドの意味は後ほど説明します
docker run -dp 3000:3000 \
    -w /app -v "$(pwd):/app" \
    node:12-alpine \
    sh -c "yarn install && yarn run dev"

もしPowerShellを使っている場合は下記コマンドを使ってください。

docker run -dp 3000:3000 `
    -w /app -v "$(pwd):/app" `
    node:12-alpine `
    sh -c "yarn install && yarn run dev"
  • -dp 3000:3000:backgroundで起動し、ホスト側のポート3000とコンテナ側のポート3000をマッピングします。
  • -w /app:作業ディレクトリを設定しています。コマンドを実行したとき、ここで指定したディレクトリがカレントディレクトリになります。
  • -v "$(pwd):/app":ホスト側のカレントディレクトリとコンテナ側の/appディレクトリをマウントします
  • node:12-plpine:使用するイメージです。これはDockerfileからのアプリケーションベースイメージであることに注意してください。
  • sh -c "yarn install && yarn run devshを使ってshellを起動します(alpineではbashはありません)。そしてyarn installをしてすべてのdependenciesをインストールし、yarn run devを走らせます。package.jsonをみればわかりますが、devスクリプトを走らせることでnodemonを起動しています。

3.docker logs -f <container-id>を使ってログを確認します。下記のような表示になっていれば準備OKです。

docker logs -f <container-id>
$ nodemon src/index.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000

ログが確認できたらCtrl+Cで終了します。

4.それではアプリに修正を加えましょう。src/static/js/app.jsの109行目を下記のように変更してください。
image.png

5.ページを更新(あるいは開く)して変更が即座に反映されていることを確認してください。Nodeサーバーが再起動するまでに数秒かかります。もしエラーとなった場合はリフレッシュしてみてください。
image.png

6.自由に変更を加えてみてください。満足したらコンテナを停止し、docker build -t getting-started .で新しいイメージをビルドしてください。

ローカル開発環境構築においてbind mountsはよく利用されます。利点は開発マシンにビルドツールが必要ないことです。単にdocker runするだけで、開発環境はプルされ、準備完了です。今後Docker Composeについて話す予定ですが、これによりコマンドを簡略化することができます。

(6章要約)

ここではデータベースを永続化して、さらにニーズや要求に対して迅速に対応する方法についてみてきました。
本番環境に備えるため、データベースをSQLiteから、より簡単にスケールできるものへと移行する必要があります。話を簡単にするためにここではリレーショナルデータベースを使い、アプリケーションでMySQLを利用するように更新します。コンテナが互いに通信を許可する方法などを次章から見ていきます。

7.マルチコンテナアプリケーション

ここまではシングルコンテナアプリケーションを扱ってきましたが、次はTODOアプリにMySQLを追加したいと思います。「MySQLはどこで起動させるのか?」「同じコンテナ内にインストールする?それともそれぞれ独立して起動する?」などの疑問があると思います。一般的にはそれぞれのコンテナ内では一つのことを実施すべきです。理由は次の通りです。

  • データベースとは関係なく、APIやフロントエンドをスケールさせたいという状況が十分に考えられます
  • 各コンテナを独立させることでアップデートやバージョン管理をそれぞれ独立して実施できます
  • アプリケーションにデータベースを内蔵する必要がなく、本番環境のデータベースにマネージドサービスを使用したい場合に相性がよいです
  • マルチプロセスを起動するにはプロセスマネージャーが別途必要です(コンテナは1つのプロセスしか開始できません)。それにより起動、シャットダウンが複雑になります。

このほかにも理由はたくさんあります。
なのでここではアプリケーションを下記の構成としましょう。
image.png

コンテナネットワーキング

コンテナはデフォルトでは他のプロセスとは独立して実行されていて、同じマシン上であっても他のコンテナ/プロセスと繋がることはできません。コンテナを他のコンテナとつなげるためにはどうすればよいでしょうか?答えはネットワーキングです。ネットワークエンジニア並みの知識は必要なく、下記だけ覚えておけば十分です。

2つのコンテナが同じネットワークにあれば互いに繋がることができる

MySqlを起動する

コンテナをネットワーク上に配置する方法は2つあります。1)起動時にネットワークを配置する 2)起動済のコンテナに接続する。ここでは始めにネットワークを作成して、MySQLコンテナ起動時にそれをアタッチします。

  1. ネットワークを作成します

    docker network create todo-app
    ``` 
    
  2. MySQLを起動してネットワークをアタッチします。データベースの初期設定をするためにいくつか環境変数を設定しています。(詳細を知りたい場合はMuSQL Docker Hub listingを確認してください。)

    docker run -d \
    --network todo-app --network-alias mysql \
    -v todo-mysql-data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=secret \
    -e MYSQL_DATABASE=todos \
    mysql:5.7
    

    もしPowerShellを使っている場合は下記のコマンドを使ってください。

    docker run -d `
    --network todo-app --network-alias mysql `
    -v todo-mysql-data:/var/lib/mysql `
    -e MYSQL_ROOT_PASSWORD=secret `
    -e MYSQL_DATABASE=todos `
    mysql:5.7
    

    データベースの初期設定用の環境変数以外にも、--network-aliasフラグがあることがわかります。これについては後ほど説明します。

    (Pro-tip)
    上記コマンドでは、todo-mysql-dataというnamed volumeを使いMYSQLのデータ保存先である/var/lib/mysqlにマウントしています。しかし、docker volume createコマンドを実行してvolumeを作成していないです。Dockerは、新規のnamed volume名が指定された場合は自動で作成してくれるのです。

  3. データベースが起動していることを確認するために接続してみましょう

    docker exec -it <mysql-container-id> mysql -p
    

    パスワードを聞かれるのでsecretと入力します。MySQLシェルに入ったらtodosデータベースを確認してください。

    mysql> SHOW DATABASES;
    

    次のような出力結果が得られるはずです。

    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | todos              |
    +--------------------+
    5 rows in set (0.00 sec)
    

    todoデータベースの準備が完了していることがわかりましたね。

MySQLに接続する

MySQLが起動していることが確認できたのでそれを使ってみましょう。ただどうやって・・?同じネットワーク上でもう一つのコンテナを起動しているとしても、どうやってそのコンテナを見つければいいんでしょう(それぞれのコンテナにはIPが割りあっているのは覚えているが)?

これを理解するためにnicolaka/netshootコンテナを使いましょう。このコンテナにはネットワークのトラブルシューティングやデバックをするのに便利なツール群がインストールされています。

  1. nicolaka/netshootイメージを使って新しいコンテナを起動します。同じネットワークに接続することを忘れないでください。

    docker run -it --network todo-app nicolaka/netshoot
    
  2. 上記コマンドを打つとコンテナ内にはるのでdigコマンドを実行しましょう(このコマンドは便利なDNSツールです)。ホスト名mysqlのIPアドレスを確認します。

    dig mysql
    

    次の出力が得られます。

    ; <<>> DiG 9.14.1 <<>> mysql
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    ;; QUESTION SECTION:
    ;mysql.             IN  A
    ;; ANSWER SECTION:
    mysql.          600 IN  A   172.23.0.2
    ;; Query time: 0 msec
    ;; SERVER: 127.0.0.11#53(127.0.0.11)
    ;; WHEN: Tue Oct 01 23:47:24 UTC 2019
    ;; MSG SIZE  rcvd: 44
    

"ANSWER SECTION"をみるとIPアドレス172.23.0.2に解決されたホスト名mysqlAレコードが確認できます(IPアドレスは環境によって異なります)。通常mysqlは有効なホスト名ではありませんが、Dockerはnetwork aliasを持ったコンテナのIPアドレスを解決することができるのです(--network-aliasフラグを覚えていますよね?)。
これが意味するところは、TODOアプリからホスト名mysqlに接続するだけで、データベースと接続することができるということです。これ以上簡単なことはないですね!

TODOアプリをMySQLに接続して起動する

TODOアプリはMySQL接続に関する環境変数の設定をいくつかサポートしています。下記の4つです。

  • MYSQL_HOST:MySQLサーバーを実行しているホスト名
  • MYSQL_USER:接続に使用するユーザー名
  • MYSQL_PASSWORD:接続に使用するパスワード
  • MYSQL_DB:使用するデータベース名

注意!
接続の設定を実施するのに環境変数を使うのは開発環境では問題ないのですが、本番環境でアプリを実行する場合には全く推奨されない方法です。Dockerの前セキュリティリーダーのDiogo Monicaはこの理由についてすばらしいブログ記事に記載してくれています。

それでは、開発環境コンテナをを起動しましょう。

  1. 上記の環境変数を指定して、MySQLコンテナをTODOアプリネットワークに接続します。

    docker run -dp 3000:3000 \
    -w /app -v "$(pwd):/app" \
    --network todo-app \
    -e MYSQL_HOST=mysql \
    -e MYSQL_USER=root \
    -e MYSQL_PASSWORD=secret \
    -e MYSQL_DB=todos \
    node:12-alpine \
    sh -c "yarn install && yarn run dev"
    

    PowerShellを使っている場合は次のコマンドを使用してください。

    docker run -dp 3000:3000 `
    -w /app -v "$(pwd):/app" `
    --network todo-app `
    -e MYSQL_HOST=mysql `
    -e MYSQL_USER=root `
    -e MYSQL_PASSWORD=secret `
    -e MYSQL_DB=todos `
    node:12-alpine `
    sh -c "yarn install && yarn run dev"
    
  2. docker logs <container-id>コマンドでコンテナのログを確認すると、mysqlデータベースを利用している旨の記載があることがわかります。

    # Previous log messages omitted
    $ nodemon src/index.js
    [nodemon] 1.19.2
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching dir(s): *.*
    [nodemon] starting `node src/index.js`
    Connected to mysql db at host mysql
    Listening on port 3000
    ``` 
    
  3. ブラウザでTODOアプリを開いて、いくつかアイテムを追加してみてください。

  4. mysqlデータベースに接続して、追加したアイテムが確かにデータベースに追加されていることを確認してみてください。パスワードはsecretです。

    docker exec -ti <mysql-container-id> mysql -p todos
    

    mysqlシェルに入ったら、下記コマンドを実行します。

    mysql> select * from todo_items;
    +--------------------------------------+--------------------+-----------+
    | id                                   | name               | completed |
    +--------------------------------------+--------------------+-----------+
    | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! |         0 |
    | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome!        |         0 |
    +--------------------------------------+--------------------+-----------+
    

    追加したアイテムによってnameは変わりますが、確かに保存されていることが確認できました。

Docker Dashboardを確認してみてください。2つのコンテナが起動していることがわかりますが、それらが1つのアプリにグループ化されていないことがわかります。それぞれのコンテナは独立して起動されていますが、TODOアプリはmysqlコンテナに接続しているのです。

(7章要約)

TODOアプリのデータを、独立した外部のmysqlコンテナ内に保存しました。そして、コンテナネットワーキングについても少し学び、サービスディスカバリがどのように実施されるのかをDNSを利用して確認しました。

しかし、アプリケーションを起動するだけなのに、ネットワークを作成したり、コンテナを起動したり、環境変数を指定したり、ポートを開放したり、そのほかにもたくさんのことをやらなければならず、すこし圧倒されたかもしれません。こんなに多くのことは覚えられないですし、誰かに伝えるのも大変です。
次の章では、Docker Composeについて説明します。Docker Composeを使うとはるかに簡単な方法でアプリケーションを他者と共有することができ、また単純なコマンドでこの章で扱ったアプリを起動することができるようになります。

8.Docker Composeを使おう

Docker Composeはマルチコンテナアプリケーションを定義したり、共有したりするときの手助けになるようにと開発されたツールです。Docker Composeを使うとYAMLファイルでサービスを定義でき、一つのコマンドでサービスを起動したり破棄したりできます。

Docker Composeを利用する利点は、アプリケーションを一つのファイルに定義することができ(ファイルはプロジェクトのルートディレクトに配置する)、他の誰かが簡単にプロジェクトに参加できるようになる点です。プロジェクトに参加したい場合は、レポジトリをクローンし、アプリケーションをDocker Composeのコマンドで起動するだけでよいです。GithubやGitLabにはこれを使用したプロジェクトがたくさんあります。

Docker Composeをインストールする

WindowsあるいはMacでDocker Desktop/Toolboxをインストールしている場合は、すでにDocker Composeがインストールされています。Play-with-Dockerについても同様にDocker Composeがすでにインストールされています。Linuxを利用している場合はこちらの手順に沿ってインストールします。

インストールすると、下記コマンドでバージョン情報が確認できます。

docker-compose version

Docker Composeファイルを作成する

  1. プロジェクトのルートディレクトリにdocker-compose.ymlファイル(コンポーズファイル)を作成する
  2. コンポーズファイルにはまずスキーマバージョンを記載します。ほとのどのケースでは最新のバージョンを利用するとよいです。Compose file referenceに現在のスキーマバージョンと互換性表が記載されています。

    version: 3.7
    
  3. 次は、アプリケーションとして起動させたいサービス(あるいは、コンテナ)のリストを定義します

    version: "3.7"
    services:
    

    次から、コンポーズファイルのサービスについて説明していきます。

アプリのサービスを定義する

アプリのコンテナを定義するために使用した下記コマンドを思い出してください。

docker run -dp 3000:3000 \
  -w /app -v "$(pwd):/app" \
  --network todo-app \
  -e MYSQL_HOST=mysql \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_DB=todos \
  node:12-alpine \
  sh -c "yarn install && yarn run dev"

PowerShellを使用している場合は下記コマンドでした。

docker run -dp 3000:3000 `
  -w /app -v "$(pwd):/app" `
  --network todo-app `
  -e MYSQL_HOST=mysql `
  -e MYSQL_USER=root `
  -e MYSQL_PASSWORD=secret `
  -e MYSQL_DB=todos `
  node:12-alpine `
  sh -c "yarn install && yarn run dev"
  1. はじめにサービスを登録し、コンテナイメージを定義しましょう。サービスの名前を任意に設定でき、その名前が自動的にネットワークのエイリアスになります。このエイリアスはMySQLサービスを定義するのに便利です。

    version: "3.7"
    services:
     app:
      image: node:12-alpine
    
  2. 通常はimageの定義の近くにcommandを配置しますが、順序に制約はありません。

    version: "3.7"
    services:
     app:
      image: node:12-alpine
      command: sh -c "yarn install && yarn run dev"
    
  3. 次に、portsで定義される-p 3000 3000コマンドについてみていきましょう。ここで記載しているのはshort syntaxで、より詳細なlong syntax変数もあります。

    version: "3.7"
    services:
     app:
      image: node:12-alpine
      command: sh -c "yarn install && yarn run dev"
      ports:
       - 3000:3000
    
  4. 次は、working_dirで定義する作業ディレクトリ(-w /app)と、volumesで定義するボリュームマッピング(-v "$(pwd):/app")です。ボリュームにもショートロングシンタックスがあります。

    version: "3.7"
    services:
     app:
      image: node:12-alpine
      command: sh -c "yarn install && yarn run dev"
      ports:
       - 3000:3000
      working_dir: /app
      volumes:
       - ./:/app
    
  5. 最後は、環境変数を定義しているenvironmentです。

    version: "3.7"
    services:
    app:
      image: node:12-alpine
      command: sh -c "yarn install && yarn run dev"
      ports:
       - 3000:3000
      working_dir: /app
      volumes:
       - ./:/app
      environment:
       MYSQL_HOST: mysql
       MYSQL_USER: root
       MYSQL_PASSWORD: secret
       MYSQL_DB: todos
    

MySQLサービスの定義

MySQLサービスを定義していきましょう。コンテナを作ったときは下記のコマンドを使用しました。

docker run -d \
  --network todo-app --network-alias mysql \
  -v todo-mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos \
  mysql:5.7

PowerShellの場合は下記です。

docker run -d `
  --network todo-app --network-alias mysql `
  -v todo-mysql-data:/var/lib/mysql `
  -e MYSQL_ROOT_PASSWORD=secret `
  -e MYSQL_DATABASE=todos `
  mysql:5.7
  1. 最初は、mysqlという名前で新規にサービスを定義しましょう。このときmysqlが自動的にネットワークエイリアスになります。appを定義したときと同様にイメージを指定しましょう。

    version: "3.7"
    services:
     app:
      # The app service definition
     mysql:
      image: mysql:5.7
    
  2. 次はボリュームマッピングの定義です。docker runコマンドでコンテナを起動したときは、named volumeは自動的に作成されます。しかし、docker composeの場合は自動的に作成されません。volumes:を定義する必要があり、マウントポイントを指定します。ボリューム名を指定するだけで、デフォルトのオプションが使用されます。ほかにもたくさんのオプションがあります。

    version: "3.7"
    services:
     app:
      # The app service definition
     mysql:
      image: mysql:5.7
      volumes:
       - todo-mysql-data:/var/lib/mysql
    volumes:
     todo-mysql-data:
    
  3. 最後に環境変数を指定します。

    version: "3.7"
    services:
     app:
      # The app service definition
     mysql:
      image: mysql:5.7
      volumes:
       - todo-mysql-data:/var/lib/mysql
      environment: 
       MYSQL_ROOT_PASSWORD: secret
       MYSQL_DATABASE: todos
    volumes:
     todo-mysql-data:
    

    ここまでをまとめると、docker-compose.ymlは下記のようになります。

    version: "3.7"
    services:
     app:
      image: node:12-alpine
      command: sh -c "yarn install && yarn run dev"
      ports:
       - 3000:3000
      working_dir: /app
      volumes:
       - ./:/app
      environment:
       MYSQL_HOST: mysql
       MYSQL_USER: root
       MYSQL_PASSWORD: secret
       MYSQL_DB: todos
     mysql:
      image: mysql:5.7
      volumes:
       - todo-mysql-data:/var/lib/mysql
      environment: 
       MYSQL_ROOT_PASSWORD: secret
       MYSQL_DATABASE: todos
    volumes:
     todo-mysql-data:
    

アプリケーションを起動しよう

docker-compose.ymlが作成できたので、アプリを起動してみましょう。

  1. ほかのアプリやデータベースのコピーが起動していないか確認してください。(docker psでidを調べて、rm if <ids>で削除してください)
  2. docker compose upコマンドを利用してアプリケーションを起動します。このとき-dフラグもつけてバックグラウンドで起動するようにしましょう。

    docker-compose up -d
    

    下記の出力が得られます。

    Creating network "app_default" with the default driver
    Creating volume "app_todo-mysql-data" with default driver
    Creating app_app_1   ... done
    Creating app_mysql_1 ... done
    

    ネットワークと同様にボリュームも作成されていることがわかると思います。Docker Composeではネットワークがデフォルトで自動的に作成されます。なのでdocker-compose.yml内にネットワーク作成を定義しませんでした。

  3. docker-compose logs -fコマンドでログを確認しましょう。各サービスごとに1行でログが出力されていることがわかります。このログは、時間に関する不具合を監視したいときに役にたちます。-fフラグはログに"フォロー(follow)"するというもので、ログが生成されたときに即座に出力されるようになります。

    mysql_1  | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections.
    mysql_1  | Version: '5.7.27'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
    app_1    | Connected to mysql db at host mysql
    app_1    | Listening on port 3000
    

    各サービスからのメッセージを区別するためにサービス名が先頭に記載されています。特定のサービスのみログに出力したい場合は、例えばdocker-comopse log -f appのコマンドのようにサービス名を最後に追加します。

    (お役立ち情報)アプリが起動するまでDBを待つ
    アプリケーションが起動しているとき、アプリ側はMySQLが起動して準備完了になるのを待って接続を開始します。Dockerには、別のコンテナが完全に起動し、準備完了になるのを待ってから別のコンテナを開始するための組み込みサポートがありません。Nodeベースのプロジェクトの場合、待機ポート(wait-port)の依存関係(dependency)を利用することができます。他の言語、フレームワークにも同様のプロジェクトが存在します。

  4. ここまでくると、アプリケーションが起動していることが確認できます。たった一つのコマンドで実行できることが確認できましたね!

Docker Dashboardでアプリを確認する

Docker Dashboardを開くと、appという名前でグルーピングされたものが確認できます。これは、Docker Composeが割り当てたプロジェクト名で、複数のコンテナが一つにまとめられています。プロジェクト名は、デフォルトではdocker-compose.ymlが配置されたディレクトリ名になります。
image.png

appを展開すると、コンポーズファイル内で定義した2つのコンテナが確認できます。それぞれの名前はわかりやすく表記されていて、<プロジェクト名>_<サービス名>_<レプリカ番号>の形式になっているので、どれがappのコンテナで、どれがmysqlのデータベースなのかが一目でわかります。
image.png

コンテナを停止し、削除する

コンテナを停止して削除したい場合は、docker-compose downコマンドを使用するか、Docker Dashboardでゴミ箱アイコンをクリックします。コンテナは停止し、ネットワークは削除されます。

ボリュームの削除
デフォルトの設定ではdocker-compose downコマンドで停止してもnamed volumeは削除されません。もし削除したい場合は--volumesフラグを追加する必要があります。
Docker Dashboardで削除した場合にもnamed volumeは削除されません。

停止して削除してしまえば、docker-compose upにより別のプロジェクトを開始することができます。

(8章の要約)

このセクションではDocker Composeについて学び、それによりマルチサービスアプリケーションを定義したり、共有することが劇的に簡単になることがわかりました。また、適切なコンポーズの形式に則ってコマンドを記載してコンポーズファイルを作成しました。

これでチュートリアルは終わりにしたいと思います。しかし、これまで使用してきたDockerfileは大きな問題を抱えているため、それらについて学ぶためにイメージビルドのベストプラクティスについて次章以降記載していきます。

とりあえず、ここまでです。
次の9、10章は随時加筆していこうと思います。

9章:イメージビルドのベストプラクティス
10章:次は何をすればよいか

11
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
16