ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

【初心者向け】実際に動かしながらDockerを学ぶ〜後編〜

f:id:taxa_program:20190218142843p:plain:w350

こんにちは。takapy(@takapy0210)です。

本記事は、転職カウントダウンカレンダー 5日目の記事です。

www.takapy.work

はじめに

本記事は下記記事の続編です。

www.takapy.work

www.takapy.work

本記事では、Dockerのネットワーク、Dockerのデータ管理などについてまとめてみようと思います。

Dockerのネットワーク

準備として、docker-machineでDockerホストを1台起動させておきます。

$ docker-machine create nw-vm1
$ docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER     ERRORS
default   -        virtualbox   Running   tcp://192.168.99.100:2376           v18.09.2
nw-vm1    -        virtualbox   Running   tcp://192.168.99.101:2376           v18.09.2

sshでdocker-machineにログイン

$ docker-machine ssh nw-vm1

Dockerのネットワーク一覧はdocker networkコマンドで確認できます。デフォルトでは下記3つのネットワークが構成されています。

docker@nw-vm1:~$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
dffd451dfe8d        bridge              bridge              local
f3c0a422255a        host                host                local
70a8f2a8b23f        none                null                local

ブリッジネットワーク

単一のDockerホスト内で構築されるネットワークです。比較的小規模なネットワークを構成する際によく用いられます。また、コンテナ起動時にデフォルトで設定されるのもこのブリッジネットワークです。

ブリッジネットワークの詳細情報を確認してみます。

docker@nw-vm1:~$ docker network inspect bridge
~~~
"Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
~~~

上記から、コンテナに割り当てられているIP帯域は172.17.0.0/16だということが分かります。ブリッジネットワークに接続されたコンテナは、このIP帯が割り当てられます。

そして今回のnw-vm1にはデフォルトゲートウェイとして172.17.0.1が割り当てられていることが分かります。

試しにalpineLinuxのコンテナを2つ起動し、そこからpingで通信できるか確認してみます。

$ docker@nw-vm1:~$ docker run -itd --name alpine1 alpine /bin/sh

これで、alpine1のコンテナがブリッジネットワークに接続されます。

docker@nw-vm1:~$ docker inspect bridge
~~~
"Containers": {
            "8b9d6310738547cce85c80eaf4b70a7b43babba617c78d006b4154d725b6823e": {
                "Name": "alpine1",
                "EndpointID": "0e8f3e55fad7b344145b8f4cf0df8fc9092ffbe630eae28b97d4ad7978eaf18d",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        }
~~~

このalpineコンテナには172.17.0.2/16のIPアドレスが割り当てられてることが分かります。

2つ目のコンテナを起動します。

docker@nw-vm1:~$ docker run -itd --name alpine2 alpine /bin/sh

docker@nw-vm1:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c34bd9a3f866        alpine              "/bin/sh"           8 seconds ago       Up 8 seconds                            alpine2
8b9d63107385        alpine              "/bin/sh"           4 minutes ago       Up 4 minutes                            alpine1

コンテナにattachして、alpine2→alpine1にpingを飛ばしてみます。

$ docker attach alpine2

# ping -w 3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.088 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.068 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.068 ms

--- 172.17.0.2 ping statistics ---
4 packets transmitted, 3 packets received, 25% packet loss
round-trip min/avg/max = 0.068/0.074/0.088 ms

alpine1と通信できていることが確認できました。

今度はコンテナ名でpingを飛ばしてみます。

# ping -w 3 alpine1
ping: bad address 'alpine1'

上記のように、DNSが存在しないため名前解決はできません。名前解決させたい場合に必要なのがユーザ定義のブリッジネットワークです。

ここで「Ctrl+P → Ctrl+Q」でコンテナを停止させることなくシェルを抜けて、Dockerホストに切り替えておきましょう。

ユーザ定義のブリッジネットワークを作成する

docker network create ネットワーク名で作成できます。

docker@nw-vm1:~$ docker network create my_nw
docker@nw-vm1:~$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
e4e91679ca88        bridge              bridge              local
c831bb29ba5c        host                host                local
419285d60fbd        my_nw               bridge              local
3feb4208d73d        none                null                local

ここで作成したブリッジネットワークに、alpine1とalpine2のコンテナを接続してみます。

docker@nw-vm1:~$ docker network connect my_nw alpine1
docker@nw-vm1:~$ docker network connect my_nw alpine2

上記以外にも、例えば最初からmy_nwに接続した状態でコンテナを起動することもできます。--network ネットワークオプションを追加します。

docker@nw-vm1:~$ docker run -itd --name alpine3 --network my_nw alpine

my_nwにどのようなコンテナが紐づいているか確認してみます。

docker@nw-vm1:~$ docker inspect my_nw
~~~
"Containers": {
            "8b9d6310738547cce85c80eaf4b70a7b43babba617c78d006b4154d725b6823e": {
                "Name": "alpine1",
                "EndpointID": "bf038e6873981d3ab76e25ba3e4d829dfa5b1efece4b55c12b5c6993d99b725a",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "a6d5894c8c3e88ab826eb11350106c998950c9df4059efd3e01175e42bc74b86": {
                "Name": "alpine3",
                "EndpointID": "1f70f8959ce2a101cbf7146d340ad1b07ed1b5b7e700765c9bfca9288d65bc0d",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "c34bd9a3f8663c3e757a7c25b17ee2c1719aeea294ddad64af80b7d0518417b4": {
                "Name": "alpine2",
                "EndpointID": "37f1fa549a99258533a55febdd9eb9a8272a31769be7bd268aaebaae4b0f1063",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        }
~~~

作成したalpine1〜alpine3のコンテナが接続されていることが確認できます。

ではコンテナ名で通信できるか確認してみます。

docker@nw-vm1:~$ docker attach alpine2
# ping -w 3 alpine1
PING alpine1 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.051 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.077 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.069 ms

--- alpine1 ping statistics ---
4 packets transmitted, 3 packets received, 25% packet loss
round-trip min/avg/max = 0.051/0.065/0.077 ms

無事できました!!

最後にコンテナをネットワークから切断してみます。現在alpine2が接続されているネットワークはbridgemy_nwの2つです。

docker@nw-vm1:~$ docker inspect alpine2
~~~
"Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "e4e91679ca8810e1766c4d7930e9f51c8ef7e7473ecb57247fee13cc121611f1",
                    "EndpointID": "141e582499c24de99801ce01978a0ebd2bf182c1d9cda8a44f366ff654719d83",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:03",
                    "DriverOpts": null
                },
                "my_nw": {
                    "IPAMConfig": {},
                    "Links": null,
                    "Aliases": [
                        "c34bd9a3f866"
                    ],
                    "NetworkID": "419285d60fbdb93c41e20cc5deb0db8fad04ff496a924c914d00b88978a5b726",
                    "EndpointID": "37f1fa549a99258533a55febdd9eb9a8272a31769be7bd268aaebaae4b0f1063",
                    "Gateway": "172.18.0.1",
                    "IPAddress": "172.18.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:12:00:03",
                    "DriverOpts": null
                }
~~~

ここからbridgeネットワークを切断させるには、disconnectを使用します。

docker@nw-vm1:~$ docker network disconnect bridge alpine2

Dockerのデータ管理

コンテナで扱う動的なデータは、起動中のコンテナの読み書き可能なレイヤーに置くこともできますが、下記のようなデメリットがあります。

  • コンテナ間でデータ共有できない
  • 書き込みのパフォーマンスがよくない
  • コンテナが破棄されたタイミングでデータも破棄されてしまう。

そこでコンテナでは、下記のようなホスト上のディレクトリをマウントしてデータ管理する手法が一般的となっています。

volumeを使用したデータ管理

volumeを利用したマウントは、同一のディレクトリやファイルを複数コンテナにマウントすることができたり、コンテナが破棄されてもデータが破棄されることはありません。(明示的に破棄することもできます)

volumeを使用するとホスト上のDockerエリアにディレクトリが生成されますが、ホスト上でこれらのファイルを編集したり移動したりすることは推奨されておらず、あくまでコンテナ上でファイルを管理するための機能して提供されています。

f:id:taxa_program:20190223081028p:plain
volumeを使用したデータ管理

bind mountを使用したデータ管理

volumeとの違いとしては、ユーザの管理しているファイルやディレクトリをコンテナにマウントできるという点です。こちらも同一のディレクトリやファイルを複数のコンテナにマウントすることができます。

基本的には、マウント元のファイルやディレクトリは事前に用意しておき、そのパスを指定してコンテナにマウントします。

プロジェクトのソースコードや設定ファイルをマウントしておくことにより、ホスト上で開発しながらその動作をコンテナで確認することができます。

f:id:taxa_program:20190223082006p:plain
bind mountを使用したデータ管理

tmpfsを使用したデータ管理

ホストのメモリ領域をファイルシステムとしてコンテナにマウントします。コンテナが停止した場合にはデータも合わせて破棄されてしまいます。用途としては、永続的なデータではなく一時的に保持したいデータを置く場所に使ったりします。(キャッシュやワンタイムパスワード)

volumeを使用したデータ管理

volumeの作成

docker-machineで仮想環境を作成して、その中で確認していきます。

$ docker-machine create vol-test
~~~
$ docker-machine ssh vol-test
   ( '>')
  /) TC (\   Core is distributed with ABSOLUTELY NO WARRANTY.
 (/-_--_-\)           www.tinycorelinux.net

volumeの作成はdocker volume create ボリューム名で行えます。また、存在するvolume一覧を確認したい場合はdocker volume lsコマンドで確認できます。

docker@vol-test:~$ docker volume create my-vol

docker@vol-test:~$ docker volume ls
DRIVER              VOLUME NAME
local               my-vol

作成したvolumeの詳細を確認したい場合はinspect ボリューム名です。

docker@vol-test:~$ docker volume inspect my-vol
[
    {
        "CreatedAt": "2019-02-22T23:28:26Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/mnt/sda1/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

volumeの削除

作成したvolumeを削除する場合はrm ボリューム名です。

docker@vol-test:~$ docker volume rm my-vol

volumeのマウント

実際にコンテナにマウントしてみる

マウントする方法としては2通りあります。

  • -vオプションを指定してコンテナを起動する
  • --mountオプションを指定してコンテナを起動する

-vオプションは、-v ボリューム名:マウントディレクトリという形で指定してあげます。下記例の場合だとvol1というボリュームは存在しないので、新規に作成されます。

docker@vol-test:~$ docker run -itd --name mount-c1 -v vol1:/app nginx:latest

docker@vol-test:~$ docker volume ls
DRIVER              VOLUME NAME
local               vol1

コンテナを詳細を確認することで、マウントの詳細も確認できます。

docker@vol-test:~$ docker inspect mount-c1
~~~
"Mounts": [
            {
                "Type": "volume",
                "Name": "vol1",
                "Source": "/mnt/sda1/var/lib/docker/volumes/vol1/_data",
                "Destination": "/app",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ]

コンテナにログインして、ファイルシステムを確認してみます。

docker@vol-test:~$ docker exec -it mount-c1 /bin/bash
root@d2b3bf2a7e84:/# df
Filesystem     1K-blocks   Used Available Use% Mounted on
overlay         18714044 165844  17559072   1% /
tmpfs              65536      0     65536   0% /dev
tmpfs             506548      0    506548   0% /sys/fs/cgroup
/dev/sda1       18714044 165844  17559072   1% /app
shm                65536      0     65536   0% /dev/shm
tmpfs             506548      0    506548   0% /proc/asound
tmpfs             506548      0    506548   0% /proc/acpi
tmpfs             506548      0    506548   0% /proc/scsi
tmpfs             506548      0    506548   0% /sys/firmware

/appがファイルシステムとしてマウントされていることがわかります。試しに、/app上にファイルを作ってみます。

root@d2b3bf2a7e84:/# cd app/
root@d2b3bf2a7e84:/app# touch hogehoge
root@d2b3bf2a7e84:/app# ls
hogehoge

この作ったファイルが、別のコンテナからも参照できるかどうか確認してみます。次は別コンテナを--mountオプションでマウントさせてみます。(Ctrl+P, Ctrl+Q でコンテナからログアウトしてください)

--mountコマンドでマウントする場合は、マウント元のボリュームをsource、マウント先をtargetに設定します。

docker@vol-test:~$ docker run -itd --name mount-c2 --mount source=vol1,target=/app nginx:latest
b7f098b3b774253d10890a157efd694b5cd9ecaade330b0ac562f95bfd4a1e5e

/appにファイルが存在しているか確認してみます。

docker@vol-test:~$ docker exec -it mount-c2 /bin/bash
root@ad5bac9c2be3:/# cd app/
root@ad5bac9c2be3:/app# ls
hogehoge

確認できました!コンテナを削除しても、ボリュームは残り続けるので破棄されません。

readonlyでマウントする

readonlyオプションをつけてコンテナを起動できます。,の後ろに半角スペースなどをつけるとエラーになるので、気をつけてください。(ここで少しハマりました)

docker@vol-test:~$ docker run -itd --name mount-c4 --mount source=copy-vol,destination=/etc/nginx,readonly nginx

bind mountを使用したデータ管理

bind mountでは、任意のファイルやディレクトリをマウントすることができます。

コンテナにマウントしてみる

-v によるマウント

試しにカレントディレクトリに存在しないディレクトリsourceを指定してマウントしてみます。

docker@vol-test:~$ ls
docker@vol-test:~$ docker run -itd --name bind-test1 -v "$(pwd)"/source:/app nginx
a20485241f54076a8155d0286308465cfc8a64c0b335cf91bacc4a227fdae31c
docker@vol-test:~$ ls
source

--mount によるマウント(推奨)

-vによるマウントと比較すると、--mountによるマウントは存在しないディレクトリなどを指定できないようになっています。存在しないディレクトリを指定すると、下記のようにエラーになります。

docker@vol-test:~$ docker run -itd --name bind-test2 --mount type=bind,source="$(pwd)"/source2,target=/app nginx
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /home/docker/source2.
See 'docker run --help'.

ちなみに、マウントの詳細を確認したい場合はinspectを使用します。

docker@vol-test:~$ docker inspect bind-test1
~~~
"Mounts": [
            {
                "Type": "bind",
                "Source": "/home/docker/source",
                "Destination": "/app",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ]
~~~

tmpfsを使用したデータ管理

tmpfsでは、-vによるマウントは推奨されていないため、--mountによるマウントを確認してみます。

コンテナにマウントしてみる

docker@vol-test:~$ docker run -itd --name tmptest --mount type=tmpfs,destination=/app nginx

また、マウントサイズやファイルモードをオプションで指定することもできます。

docker@vol-test:~$ docker run -itd --name tmptest2 --mount type=tmpfs,destination=/app,tmpfs-size=5000000000, tmpfs-mode=700 nginx

最後に

ボリュームが多くなってしまったので本記事では触れていませんが、Docker ComposeやDocker Swarmについても、後日まとめようと思っています。