【図解】Dockerの全体像を理解する -中編-


この記事は何か

イメージやコンテナなどの基本からdocker-compose、docker-machine, docker swarmなどのDocker周りの様々な概念の全体像を整理して、Dockerの仕組みを理解するための記事

対象読者

・Dockerって何?
・Dockerちょっと勉強したけどDocker compose? Docker machine? Docker Swarm? 色々ありすぎて意味不明

という方

中編では、「データマウント(volume), Docker Network, Docker Compose」 について書いて行きます。
前編はこちら

④ Dockerにおけるデータ管理

起動したコンテナ内で扱う動的なデータは、読み書き可能な最上レイヤー(コンテナレイヤー)に置くこともできますが、

・コンテナが削除された時点でそのコンテナ内のデータは消える
・コンテナ間でデータ共有できない
・コンテナレイヤーへのデータ書き込みは、通常のファイルシステムと異なるユニオンファイルシステムが使われているため、書き込み速度が遅い

というデメリットがあります。

そのため、Dockerではホストマシン上にデータを管理し、それをコンテナにマウントする手法が使われます。

手法は主に3つあり、以下で説明して行きます。

volume

ホストマシン上に自動生成される指定ディレクトリ(/var/lib/docker/volumes)をコンテナにマウントする手法

ホストマシン上で

$ docker volume create volumeの名前

とすることで、volume(/var/lib/docker/volumesディレクトリ)を作成し、コンテナを起動する際に

$ docker run -itd --name 作成するコンテナ名 --mount source=[マウントするvolume名],target=[コンテナ上のマウント先ディレクトリ] イメージ名

具体例:
$ docker run -itd --name mount-test --mount source=volume1,target=/app nginx

のように、--mountオプション(-vオプションでも可)をつけることで指定したvolumeをマウントすることができます。

マウントしたvolumesディレクトリは、マウント元であるホスト上のディレクトリからは直接操作するべきではない点に注意が必要です。

また、同じホスト内で立てている異なるコンテナでも、それぞれ同じvolumeをマウントすることでファイルの共有ができます。

さらに、volumeを複数のコンテナで共有させる場合には、コンテナごとに編集権限を設定することもできます。

$ docker run -itd —name mount-c4 —mount source=copy-vol, destination=/etc/nginx,readonly nginx

のように , で区切って編集権限を指定します。

volumeの管理コマンド

volumeの一覧を確認
$ docker volume ls
volumeの詳細を確認
$ docker volume inspect volume名
volume を削除
$ docker volume rm volume名

コンテナを削除してもvolumeは残り続けるので、$ docker volume rm volume名 で削除する必要がある点に注意が必要です。

bind mount

bind mount は、ホストマシン上の任意のディレクトリをマウントでき、ホスト側のディレクトリを直接操作をしても良いという点がvolumeと異なるマウント手法です。

volumeのように事前に設定する必要はなく、以下のコマンドでコンテナの起動時にオプションで指定してマウントします。

$ docker run -itd --name [コンテナ名] --mount type=bind,source=[マウント元ディレクトリ],target=[マウント先ディレクトリ] イメージ名

具体例:
$ docker run -itd —-name bind-mount-test —-mount type=bind,source=“$(pwd)”/mount,target=/app nginx

source(マウント元)のディレクトリが存在しない場合は、エラーになるので、事前に作成しておきましょう。(-vオプションを使用した場合は自動作成される)

bind mountでは、もしホスト上の空のディレクトリをコンテナ上の/userなどにマウントしたりした場合は、コンテナ上のデータが消えてしまい、コンテナがまともに動作しなくなることもあるので注意が必要です。

volume の時と同様の方法で、コンテナごとに編集権限を設定することもできます。

tmpfs(テンプfs)

ホストマシンのメモリ領域をコンテナ上にマウントする手法

ホストマシンが終了した場合も、コンテナが終了した場合も、保持していたデータは解放されます。

$ docker run -itd --name [コンテナ名] --mount type=tmpfs,destination=[マウント先ディレクトリ] イメージ名

具体例:
$ docker run -itd --name tmpfs-test --mount type=tmpfs,destination=/app nginx

のようにコンテナ起動時に、--mountオプションのtypeにtmpfsを指定することでマウントができます。

また、ホスト上のメモリを無制限に使用してしまう可能性を排除するために、

$ docker run -itd --name tmpfs-test --mount type=tmpfs,destination=/app,tmpfs-size=800000000,tmpfs-mode=800 nginx

のようにオプションで使用可能なメモリサイズを制限することもできます。

ここまでの全体像

⑤ Dockerネットワーク

これまで

・多様なイメージからコンテナというアプリケーションの実行環境を起動して、環境構築が簡単にできる

といったことを見てきましたが、複数立ち上げたコンテナ間で通信する手法についてここで解説します。

例えば、Webアプリケーションサーバーの運用を行う場合、APIサーバコンテナとMySQLコンテナを立ち上げるとすると、APIサーバコンテナはMySQLコンテナと通信をする必要があります。

ここでのコンテナ間通信の手法として、複数のコンテナを接続し通信するのがDocker ネットワークです。

Docker networkにはデフォルトで存在する3つのネットワークと独自で定義するネットワークがあります。

Bridgeネットワーク

デフォルトで存在するネットワークで、作成されたコンテナがデフォルトで接続されるのが、bridge networkです。
bridge driverを使用しています。

Bridgeネットワークでは、同じネットワーク内に存在するコンテナとIPアドレス指定で通信することができます。
ただ、Bridgeネットワークでは、DNSが定義されていないのでコンテナ名では他のコンテナに通信できません。

つまり、Network指定をせずに起動したコンテナ同士は、IPアドレス指定で通信することは可能だが、コンテナ名指定で通信することはできないということです。

また、デフォルト状態では外部に公開されていないネットワークですが、-p オプションで指定したポートを解放することで外部からコンテナにアクセスできるようにもできます。

Hostネットワーク

ホストネットワークは、host driver を使用したデフォルトで存在するネットワークです。

接続したコンテナはdocker hostと同じネットワーク設定になります。
例えば、host ネットワークに接続したコンテナでnginxコンテナを実行した場合、
hostマシンのIPの80番ポートでlistenしているのと同じことになります。

そのため bridgeネットワークのようにコンテナ起動時に-p オプションを設定して外部にポート公開しなくても、コンテナを起動しただけで、Docker hostのIPの80番に接続すればそのコンテナに接続できることになります。

Noneネットワーク

noneネットワークはデフォルトで存在するネットワークで、接続されたコンテナはネットワーク・インターフェースが無くなります。
noneネットワークに接続する場合は、他のネットワークから全て切断しなくてはならない点に注意が必要です。

独自ネットワーク

独自のネットワークを作成することで、コンテナ名でコンテナ間通信が可能になります。

$ docker network create ネットワーク名

で新しくネットワークを作成(デフォルトのdriverはbridgeになります)し、

$ docker network connect 接続するネットワーク名 コンテナ名

で、指定したコンテナを指定したネットワークに接続していきます。

ユーザー定義の独自ネットワークでは、Docker Daemonの組み込みDNSが機能してコンテナ名で名前解決してIPと紐づけてくれるので、コンテナ名で他のコンテナに接続できるようになっています。

Dockerネットワーク(コンテナ間通信)のイメージ

ネットワークの管理コマンド

ネットワーク一覧を確認

$ docker network ls

ホストマシン上で実行することで、そのホストマシンのネットワーク一覧を表示

ネットワークの詳細を確認

$ docker network inspect ネットワーク名

新しくネットワークを作成

$ docker network create ネットワーク名

デフォルトのdriverはbridgeになる

コンテナをネットワークに接続

$ docker network connect 接続するネットワーク名 コンテナ名

コンテナをネットワークから切断

$ docker network disconnect ネットワーク名 コンテナ名

ここまでの全体像

⑥ Docker Compose

Docker Composeは複数コンテナのDocker アプリケーションを事前定義して実行するためのツールです。

コンテナ1つを起動するにも、volumeなどのマウント設定や所属させるDockerネットワークの設定などを明記して

$ docker run -itd —name mount-test —mount source=volume-test, destination=/etc/nginx,readonly nginx

のような複雑なオプション付きのコマンドを打ち込まなければなりません。

これはとても面倒ですし、ミスにも繋がり、円滑に環境の共有が出来ない原因になりかねません。

そこで、それらを自動化できるのがDocker Composeです。

あるWebサービスの実行環境をDockerで構築する場合、Webサーバー, DBサーバー, Cacheサーバーなどの定義を一つのdocker-compose.ymlファイルに記述しておくことによって、それを元に実行に必要なコンテナをまとめて起動・設定することができます。

手順は以下の通りです。

  1. Dockerfileを用意する または Docker Hubなどに使用するイメージを用意する
  2. docker-compose.ymlを定義する
  3. ymlファイルがあるディレクトリで、$ docker-compose upを実行する

Docker Composeを利用しない場合

各コンテナを一つ一つ、それぞれオプション設定をして起動しなければならない

Docker Composeを利用する場合

docker-compose.ymlを定義し、docker-compose upコマンド一つで複数コンテナを起動することができる。

docker-compose管理コマンド一覧

docker-composeで起動したコンテナの一覧を表示

$ docker-compose ps

docker-compose.ymlに記述した設定からコンテナ起動

$ docker-compose up

既にコンテナが起動中であっても再起動してくれる

docker-composeで作成されたコンテナやネットワークを削除

$ docker-compose down
# $ docker-compose down -v

-vオプションでvolumeも一緒に削除される

指定したサービスコンテナ内でコマンドを実行

$ docker-compose run [コンテナ名]  [コマンド]

一連のコンテナ停止

$ docker-compose stop

一連のコンテナ起動

$ docker-compose start

docker-composeによるアプリ実行環境構築例

PythonのWAFであるDjangoを使用したWebアプリの環境構築を例に具体的なフローを見て行きます。

1. imageの用意

作業ディレクトリを作成してその中にDockerfileを作成します。

Dockerfile
FROM python:3  # image名にpython3実行環境のイメージを指定
ENV PYTHONUNBUFFERED 1 # pythonの標準出力をバッファにため込まないための環境変数設定
RUN mkdir /service  # serviceディレクトリを作成
WORKDIR /service # serviceディレクトリに移動
COPY requirements.txt /service/  # requirements.txt(事前に作成)をserviceディレクトリに置く
RUN pip install -r requirements.txt # pip  installでパッケージをインストール
COPY . /service/ # buildコンテキストの内容を全て/service内に置く

docker-compose.ymlの作成

以下のようにdocker-compose.ymlファイルを作成します。

docker-compose.yml
version: '3' # docker-composeのversion指定
services: # 起動するサービスコンテナを書いていく
  db: # dbサーバーコンテナを起動
   image: postgres # イメージはdockerhub上のpostgres:latestを使用
  web: # webサーバーコンテナを起動
   build: . # カレントディレクトリにあるdockerfileからimage作成
   command: python3 manage.py runserver 0.0.0.0:8000  # コンテナ起動時に実行されるコマンドを指定
   volumes:
    - .:/app # currentディレクトリを/appディレクトリにbind mountする
   ports:
    - "8000:8000" # コンテナの8000番を公開
   depends_on: # webサーバーコンテナを立ち上げる前にdbサーバーコンテナを立ち上げるようにする
    - db

depends_onは立ち上げコマンドを実行する順番を規定するだけで、「dbが起動完了してからwebを実行する」訳ではないです。
このことが原因でdocker-compose upが上手く行かない場合があるので、その場合はentrypoint:で起動待ちをするshell scriptを実行するなどして対処します。

参考: http://docs.docker.jp/compose/startup-order.html

3. docker-composeの実行

$ docker-compose run web django-admin.py startproject test .

でdocker-compose.ymlに定義したwebサービスコンテナを一時起動し、djangoプロジェクトを作成します。

$ docker-compose up -d

でデタッチドモード(-d : バックグラウンド)で一連のコンテナを起動します。

このdocker-compose.ymlファイルを共有することで、別のマシンでも $ docker-compose up コマンド一つで一連のコンテナを実行することができるのです。

ここまでの全体像

後編に続く

間違っている点などあればコメント頂けると嬉しいです。