コンテナ技術 ハンズオン Vol.4 Docker管理編

# 本題の前に
---

## 位置付け・前提
本記事は、社内ワークショップ のために用意したものです。そのため、次の前提で進めることをご理解ください。

また、本稿はこちらの続きとなります。

[コンテナ技術 ハンズオン Vol.1 Docker操作編](https://qiita.com/syamane/items/65570b41b3d6d32691a5)
[コンテナ技術 ハンズオン Vol.2 Dockerイメージ編](https://qiita.com/syamane/items/1b552ce28932dbfd3c56)
[コンテナ技術 ハンズオン Vol.3 Docker Compose編](https://qiita.com/syamane/items/dcea30a7794f9755a59d)

目的や前提なども上記の内容に準拠します。

---

## docker ハンズオン環境

今回も [Play with Docker](https://labs.play-with-docker.com/) を活用してハンズオンを行います。

[![playwithdocker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107190/bc635cbe-c24f-6cec-c871-f4a8073cfffb.png)](https://labs.play-with-docker.com/)

※要：[Docker Hub](https://hub.docker.com/) アカウント

注意点として、この環境は4時間の制限があります。４時間経過すると環境が削除されてしまう点を注意して下さい。

もちろんローカル環境でも構いません。

---

# ハンズオン

---

## 今回のゴール

* Docker のリソースを制御できるようになる
* Docker のトラブルシューティングができるようになる

---

## 今回やらないこと

* kubernetes などのオーケストレーションツールは使いません。

---

## Docker リソース管理

---
### Docker ネットワーク管理

これまでなんとなくポートの紐付けを設定したりしてきました。実は、Docker 内でネットワークを形成しています。

[![Docker 標準ネットワークモデル](https://image.slidesharecdn.com/dockercomposeguidebook-190306051717/95/slide-67-1024.jpg)](https://www.slideshare.net/zembutsu/docker-compose-guidebook/67)

実は Docker 内でIPアドレスもポート番号も保有しており、TCP/IPネットワークを形成することが可能です。

仕組みとしては、brige / host / none の３パターンです。

**brige≒NAPT**、**host=ホストNIC共有**、**none=Docker内のクローズドネットワーク**と覚えると良いでしょう。（ポートフォワードするので、NATというよりNAPT）


[![コンテナのネットワーク接続](https://image.slidesharecdn.com/dockercomposeguidebook-190306051717/95/slide-68-1024.jpg)](https://www.slideshare.net/zembutsu/docker-compose-guidebook/68)

どの bridge に接続するかを選択でき、複数接続することも可能です。

ちなみに、これまで Dockerfile/Docker Compose で活用してきた links 指定は、今後廃止されていく可能性があるそうです。

詳しくは下記のドキュメントを読むと良いでしょう。

* [Docker container networking | Docker Documentation](https://docs.docker.com/v17.09/engine/userguide/networking/) ※英語
* [Docker コンテナ・ネットワークの理解 — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/userguide/networking/dockernetworks.html)
* [Compose のネットワーク機能 — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/compose/networking.html)
* [network - 入門 Docker](https://y-ohgi.com/introduction-docker/2_component/network/)
* [Docker の基本学習 ~ Docker のネットワーク](https://qiita.com/Arturias/items/b538e6bbf05dd3364397)
* [Dockerのネットワーク構成](https://kou.bz/blog/docker-network-constitution.html)

---
### Docker ネットワーク管理 ハンズオン

まず、現状のDockerネットワークを確認します。

```shell
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c248babe0bdd        bridge              bridge              local
97c31c8afe7b        host                host                local
e950afaacc2f        none                null                local
```

前述した３種類のネットワークが出力されます。

ホストではどう認識されているかを確認しましょう。

```shell
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN 
    link/ether 02:42:48:44:88:eb brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
40422: eth1@if40423: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 scope global eth1
       valid_lft forever preferred_lft forever
40424: eth0@if40425: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether f2:c8:73:a7:5c:9c brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.38/23 scope global eth0
       valid_lft forever preferred_lft forever
```

2 に docker 0 というネットワークがあります。これが docker ネットワークとなります。

レガシーですが、ifconfig の方がキレイに表示されます。

```shell
$ ifconfig -a
docker0   Link encap:Ethernet  HWaddr 02:42:48:44:88:EB  
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth0      Link encap:Ethernet  HWaddr F2:C8:73:A7:5C:9C  
          inet addr:192.168.0.38  Bcast:0.0.0.0  Mask:255.255.254.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1296 (1.2 KiB)  TX bytes:0 (0.0 B)

eth1      Link encap:Ethernet  HWaddr 02:42:AC:12:00:04  
          inet addr:172.18.0.4  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:842 errors:0 dropped:0 overruns:0 frame:0
          TX packets:502 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:138667 (135.4 KiB)  TX bytes:628366 (613.6 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
```

この docker0 が仮想L2スイッチ（ブリッジ）、つまり bridge ということになります。VMwareの世界なら仮想スイッチが近いイメージ。

なお、コンテナ/インスタンスには、Dockerが自動でアドレスを割り振るDHCPとなっています。

ちなみに、bridge の詳しい情報は下記で確認できます。

```shell
docker network inspect bridge
```

返り値は JSON 形式で得られます。

```json
[
    {
        "Name": "bridge",
        "Id": "c248babe0bdd29536ee37983ecc7467ad975a85d985523210deb1662f535c3d0",
        "Created": "2019-09-01T06:30:44.029906596Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "8f5fdb0b119bad4d9bfb791ceee9d1f0655a7fe9e4706c78fa6bb72c7146fd60": {
                "Name": "ngtest",
                "EndpointID": "d1a94602f456e2092124f9ec35973481dc21667c38cdd5bef8dc2e548ba52add",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
```

| 課題1                                                            |
|:----------------------------------------------------------------|
| 同じように host および none についても、詳細情報を表示してみましょう。 |

次に、インスタンスをブリッジに繋いでみましょう。

```shell
docker run -it -d --name ngtest -p 80:80 nginx
```

これで ホストの80版ポートに紐付いた Nginx コンテナが起動します。再度ネットワークを確認します。

```shell-session
$ ifconfig -a
（略）
vethb06656b Link encap:Ethernet  HWaddr F2:1C:5D:03:21:F5  
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:10 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:3904 (3.8 KiB)  TX bytes:3670 (3.5 KiB)
```

vethXXXXXXX が増えています。これはコンテナの仮想NICです。

次のコマンドだと、docker0 と接続していることが分かります。

```shell
$ ip a
（略）
2: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:48:44:88:eb brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
4: vethb06656b@if3: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 state UP 
    link/ether f2:1c:5d:03:21:f5 brd ff:ff:ff:ff:ff:ff
40422: eth1@if40423: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.4/16 scope global eth1
       valid_lft forever preferred_lft forever
40424: eth0@if40425: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether f2:c8:73:a7:5c:9c brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.38/23 scope global eth0
       valid_lft forever preferred_lft forever
```

端的な表示だとこちら。

```shell
$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242484488eb       no              vethb06656b
```
---

インスタンス情報からもネットワークについて確認できます。

```shell
$ docker inspect ngtest
```

かなり分量がありますが、今回見れば良いところは下記です。

```json
[
(略）
        "NetworkSettings": {
            "Bridge": "",
(略）
            "Ports": {
                "80/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "80"
                    }
                ]
(略）
            "Gateway": "172.17.0.1",
(略）
            "IPAddress": "172.17.0.2",
(略）
]
```

---
### Docker ネットワーク構築 ハンズオン
次に、新しいネットワーク(Bridge)を作ってみましょう。

```shell
$ docker network create b2
```

そうすると、下記のように追加されます。

```shell
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
188fc492cab7        b2                  bridge              local
c248babe0bdd        bridge              bridge              local
97c31c8afe7b        host                host                local
e950afaacc2f        none                null                local
```

次のコマンドでもそれぞれ確認すると良いでしょう。

```shell
ip a
ifconfig -a
```

作成した新しいネットワークに接続したコンテナを生成・起動します。

```shell
docker run --name ngtestb2 --network=b2 -p 8080:80 -d nginx
```

先程起動した Nginx コンテナとは別ポートにしている点は注意です。被ると怒られます。

それでは、この作成したネットワークに対して操作してみます。

```shell
$ docker run --network=b2 -it --name al1 alpine

# wget ngtestb2:80
Connecting to ngtestb2:80 (172.19.0.2:80)
index.html           100% |*************************|   612  0:00:00 ETA

# wget ngtest:80
wget: bad address 'ngtest:80'

# wget 172.17.0.2:80
Connecting to 172.17.0.2:80 (172.17.0.2:80)
```

これが何を意味しているかというと、下記の通りです。

* b2ネットワークに接続した alpineコンテナ al1 のシェルを起動した
* 同じb2ネットワークにある ngtestb2 のHTTPサーバへの接続が成功した
* 異なるネットワークにある ngtest のHTTPサーバへの接続が失敗した

このようにネットワークを分離することができました。このように、1つのdocker内で異なるネットワークを構成することが可能なことが分かりました。

最後に、コンテナを複数のネットワークに接続してみます。Ctrl+P、Ctrl+q でデタッチして、下記のコマンドを実行します。

```shell
docker network connect bridge al1
```

この状態で、再度疎通確認をしてみます。

```shell
$ docker attach al1
 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.3/16 brd 172.19.255.255 scope global eth0
       valid_lft forever preferred_lft forever
24: eth1@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.4/16 brd 172.17.255.255 scope global eth1
       valid_lft forever preferred_lft forever

# wget ngtestb2:80
Connecting to ngtestb2:80 (172.19.0.2:80)
index.html           100% |*************************|   612  0:00:00 ETA

# wget ngtest:80
wget: bad address 'ngtest:80'

# ping 172.17.0.2
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.173 ms
（略）
```

2つの仮想NICが接続されていることがわかります。
ネットワークが異なると名前解決はできませんが、IPでの疎通はできることがわかります。

このように、1つのコンテナに複数のNICを利用することが可能です。

| 課題2                                                           |
|:----------------------------------------------------------------|
| 次のコマンドを駆使して、作成した b2 ネットワークを削除しましょう |
| docker network rm |
| docker network disconnect |


---
### Docker ストレージ管理

これまでなんとなくコンテナ内にファイルを保存したり、操作したりしてきました。

しかし、実際のDocker 内のファイルシステムは、ホストOSに形成されています。

[![Docker データの扱い](https://image.slidesharecdn.com/dockercomposeguidebook-190306051717/95/slide-70-1024.jpg)](https://www.slideshare.net/zembutsu/docker-compose-guidebook/70)

Play with Docker のディレクトリは /var/lib/docker/overlay2/ 配下にコンテナを形成するファイル群が配置されています。

それぞれのコンテナは、ディレクトリごと分離されいるため、相互のファイルへ関与し合うことはありません。逆に、設定無しには操作し合うことできません。

[![Docker データボリューム](https://image.slidesharecdn.com/dockercomposeguidebook-190306051717/95/slide-71-1024.jpg)](https://www.slideshare.net/zembutsu/docker-compose-guidebook/71)

ホストOS および 複数のコンテナでは「ボリューム」と呼ぶ領域をマウントできます。そこでファイルの共有ができるわけです。

[![Docker データボリュームの構成](https://image.slidesharecdn.com/dockercomposeguidebook-190306051717/95/slide-72-1024.jpg)](https://www.slideshare.net/zembutsu/docker-compose-guidebook/72)

この仕組みを理解すると、コンテナ間のファイル共有はもちろん、データを永続化できます。つまり、一時的に作成するコンテナのデータをホストOSに保存することで、データを使い回すことが可能になります。

例えば、MySQL コンテナのデータベースデータをボリュームに作成することで、そのコンテナを停止しても、データ自体はホストに保存しておけるわけです。

詳しくは下記のドキュメントを読むと良いでしょう。

* [Use volumes | Docker Documentation](https://docs.docker.com/storage/volumes/) ※英語
* [Docker ストレージ・ドライバ — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/userguide/storagedriver/index.html)
* [CCompose ファイル・リファレンス — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/compose/compose-file.html#volume-configuration-reference)
* [volume - 入門 Docker](https://y-ohgi.com/introduction-docker/2_component/volume/)
* [Docker の基本学習 ~ Docker のネットワーク](https://qiita.com/Arturias/items/b538e6bbf05dd3364397)
* [Docker、ボリューム(Volume)について真面目に調べた - Qiita](https://qiita.com/gounx2/items/23b0dc8b8b95cc629f32)


---
### Dockerストレージ管理 ハンズオン1

コンテナとホストを繋ぐボリュームを作成してみます。

```shell
docker run -v /tmp/test alpine touch /tmp/test/test.txt
```

作成した結果のボリューム状態はこちら。

```
$ docker volume ls
DRIVER              VOLUME NAME
local               a9afbc9a643b984076e232f8d8f467f658ba2b24fdd848c6e6181a67e74d5a01
```

正直よくわからないので、上記の VOLUME NAME を指定して詳細を確認します。

```shell
$ docker volume inspect a9afbc9a643b984076e232f8d8f467f658ba2b24fdd848c6e6181a67e74d5a01
[
    {
        "CreatedAt": "2019-09-01T07:56:33Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/a9afbc9a643b984076e232f8d8f467f658ba2b24fdd848c6e6181a67e74d5a01/_data",
        "Name": "a9afbc9a643b984076e232f8d8f467f658ba2b24fdd848c6e6181a67e74d5a01",
        "Options": null,
        "Scope": "local"
    }
]
```

Mountpoint の値に指定してある箇所がホストと共有（マウント）している領域となります。

マウントできているか確認しましょう。

```shell
$ ls -l /var/lib/docker/volumes/a9afbc9a643b984076e232f8d8f467f658ba2b24fd
d848c6e6181a67e74d5a01/_data/test.txt 
-rw-r--r--    1 root     root             0 Sep  1 07:56 /var/lib/docker/volumes/a9afbc9a643b984076e232f8d8f467f658ba2b24fdd848c6e6181a67e74d5a01/_data/test.txt
```

ホストにマウントできていることは分かりました。しかし、このままでは使いづらいです。もうちょっと短い名前でマウントしてみましょう。

```shell
$ mkdir /tmp/test
$ docker run -v /tmp/test:/tmp/test --name al5 alpine touch /tmp/test/test.txt
$ ls -l /tmp/test/
total 0
-rw-r--r--    1 root     root             0 Sep  1 08:05 test.txt
```

-v {ホストのディレクトリパス （絶対パス）}:{コンテナのディレクトリパス} のように指定すると、任意のディレクトリを共有することができます。ただし、**使用するパスは絶対パス**にする必要があるので注意です。


---
### Dockerストレージ管理 ハンズオン2

コンテナ間でボリュームを共有してみましょう。

まず、ボリュームをマウントしたコンテナを起動しましょう。

```shell
docker run --name al10 -v /tmp/t1 alpine touch /tmp/t1/share-test1.txt
```

もう一つ、別のコンテナで先に作成したコンテナを起動しましょう。ポイントは --volumes-from です。

```shell
$ docker run --volumes-from al10 alpine ls -l /tmp/t1
total 0
-rw-r--r--    1 root     root             0 Sep  1 09:34 share-test1.txt
```

無事にファイル共有できていることが確認できました。

ただ、一時的に起動するコンテナ同士でデータ共有するには、ホストに保存した方が良いかも知れませんね。


---
## Docker 運用管理
---

### Docker コンテナ/イメージの管理コマンド

これまで紹介してきた以外に、実運用で利用するコマンドを紹介します。

これらは運用作業やトラブルシューティングに活用しますので、しっかり覚えましょう。

---

#### Docker コンテナの詳細情報

コンテナの詳細情報を確認します。

```shell
docker inspect {CONTAINER ID|NAMES}  （正式には docker container inspect {CONTAINER ID|NAMES}  ）
```

JSON形式で、かなり詳細なコンテナインスタンスの情報が確認できます。

```json
$ docker inspect XXXXXXXXXXXX
[
    {
        "Id": "f1a6b55da1b093da415e654c56207983170842de4ac303dde528c9db71106cf7",
        "Created": "2019-08-30T08:45:49.522153709Z",
        "Path": "/bin/sh",
        "Args": [],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 16528,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2019-08-30T08:45:50.044285658Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4",
        "ResolvConfPath": "/var/lib/docker/containers/f1a6b55da1b093da415e654c56207983170842de4ac303dde528c9db71106cf7/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/f1a6b55da1b093da415e654c56207983170842de4ac303dde528c9db71106cf7/hostname",
        "HostsPath": "/var/lib/docker/containers/f1a6b55da1b093da415e654c56207983170842de4ac303dde528c9db71106cf7/hosts",
        "LogPath": "/var/lib/docker/containers/f1a6b55da1b093da415e654c56207983170842de4ac303dde528c9db71106cf7/f1a6b55da1b093da415e654c56207983170842de4ac303dde528c9db71106cf7-json.log",
        "Name": "/musing_solomon",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "docker-default",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Capabilities": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/5deef75b0a4c64d1520520142487c22cd7c9fea8e3e7a86adcc00378394fe8e2-init/diff:/var/lib/docker/overlay2/cf8a2394f09105178932b8e6bdeb28ebfa4dd8c4d38a6fbf30f6c3e9d494bee0/diff",
                "MergedDir": "/var/lib/docker/overlay2/5deef75b0a4c64d1520520142487c22cd7c9fea8e3e7a86adcc00378394fe8e2/merged",
                "UpperDir": "/var/lib/docker/overlay2/5deef75b0a4c64d1520520142487c22cd7c9fea8e3e7a86adcc00378394fe8e2/diff",
                "WorkDir": "/var/lib/docker/overlay2/5deef75b0a4c64d1520520142487c22cd7c9fea8e3e7a86adcc00378394fe8e2/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [],
        "Config": {
            "Hostname": "f1a6b55da1b0",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": true,
            "OpenStdin": true,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh"
            ],
            "Image": "alpine",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "5fc2b5a75f31235374a65d3b676082c3acb185e1312a55902eb4e78048092f19",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/5fc2b5a75f31",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "f8684a1a1f53f7286bac6569f08b7d46e774447a01bc160ffde32eaec49c2f6d",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "4c20e0305bd0a133a24086e96de1dd17f814b600a4fc10ae46c9bdbf4e6139ec",
                    "EndpointID": "f8684a1a1f53f7286bac6569f08b7d46e774447a01bc160ffde32eaec49c2f6d",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]
```

このコマンドは、停止したインスタンスに対しても実行可能です。

リファレンス：[inspect — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/reference/commandline/inspect.html)


#### Docker イメージの詳細情報

イメージの詳細情報を確認します。

```shell
docker inspect {IMAGE ID| REPOSITORY} （正式には docker image inspect {IMAGE ID | REPOSITORY}  ）
```

JSON形式で、かなり詳細なイメージの情報が確認できます。

```json
$ docker image inspect alpine
[
    {
        "Id": "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4",
        "RepoTags": [
            "alpine:latest"
        ],
        "RepoDigests": [
            "alpine@sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2019-08-20T20:19:55.211423266Z",
        "Container": "0a80155a31551fcc1a36fccbbda79fcd3f0b1c7d270653d00310e6e2217c57e6",
        "ContainerConfig": {
            "Hostname": "0a80155a3155",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"/bin/sh\"]"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:06f4121dff4d0123ce11bd2e44f48da9ba9ddcd23ae376ea1f363f63ea0849b5",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "18.06.1-ce",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:06f4121dff4d0123ce11bd2e44f48da9ba9ddcd23ae376ea1f363f63ea0849b5",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 5581746,
        "VirtualSize": 5581746,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/cf8a2394f09105178932b8e6bdeb28ebfa4dd8c4d38a6fbf30f6c3e9d494bee0/merged",
                "UpperDir": "/var/lib/docker/overlay2/cf8a2394f09105178932b8e6bdeb28ebfa4dd8c4d38a6fbf30f6c3e9d494bee0/diff",
                "WorkDir": "/var/lib/docker/overlay2/cf8a2394f09105178932b8e6bdeb28ebfa4dd8c4d38a6fbf30f6c3e9d494bee0/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]
```

リファレンス：[inspect — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/reference/commandline/inspect.html)

---
#### Docker コンテナの差分表示

コンテナ上で、変更されたファイルやディレクトリの一覧を表示します。

```shell
docker diff {CONTAINER ID|NAMES}  （正式には docker container diff {CONTAINER ID|NAMES}  ）
```

```shell
$ docker diff XXXXXXXXXXXX
C /usr
C /usr/local
C /usr/local/lib
C /usr/local/lib/python3.6
C /usr/local/lib/python3.6/__pycache__
A /usr/local/lib/python3.6/__pycache__/io.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/_sysconfigdata_m_linux_x86_64-linux-gnu.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/_weakrefset.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/codecs.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/genericpath.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/site.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/stat.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/sysconfig.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/_bootlocale.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/abc.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/os.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/_collections_abc.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/_sitebuiltins.cpython-36.pyc
A /usr/local/lib/python3.6/__pycache__/posixpath.cpython-36.pyc
C /usr/local/lib/python3.6/encodings
C /usr/local/lib/python3.6/encodings/__pycache__
A /usr/local/lib/python3.6/encodings/__pycache__/aliases.cpython-36.pyc
A /usr/local/lib/python3.6/encodings/__pycache__/latin_1.cpython-36.pyc
A /usr/local/lib/python3.6/encodings/__pycache__/utf_8.cpython-36.pyc
A /usr/local/lib/python3.6/encodings/__pycache__/__init__.cpython-36.pyc
```

具体的に、どんなファイルが変更されたのかを知るのに役立ちます。

リファレンス：[diff — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/reference/commandline/diff.html)

#### Docker コンテナ構築の履歴表示

イメージの最新レイヤをどのように構築したか確認します。

```shell
docker history {REPOSITORY ID|NAMES}  （正式には docker image history {REPOSITORY ID|NAMES}  ）
```

実際の表示は下記です。

```shell
$ docker history alpine
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
961769676411        9 days ago          /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
<missing>           9 days ago          /bin/sh -c #(nop) ADD file:fe64057fbb83dccb9…   5.58MB 
```

docker:scm というようにタグを指定すると最新以外も指定可能です。

```shell
$ docker history docker:scm
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
2ac9d1098bf1        3 months ago        /bin/bash                                       241.4 MB            Added Apache to Fedora base image
88b42ffd1f7c        5 months ago        /bin/sh -c #(nop) ADD file:1fd8d7f9f6557cafc7   373.7 MB
c69cab00d6ef        5 months ago        /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar   0 B
511136ea3c5a        19 months ago                                                       0 B                 Imported from -
```

リファレンス：[history — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/reference/commandline/history.html)

---
#### Docker コンテナのログ表示

コンテナ（インスタンス）内に出力された標準出力（STDOUT）と標準エラー出力（STDERR）を確認できます。

```shell
docker logs {CONTAINER ID|NAMES}  （正式には docker image logs {CONTAINER ID|NAMES}  ）
```

コンテナが起動しないなど、トラブルシューティングの役に立ちます。起動しているコンテナについて実行してみましょう。

```shell
docker logs alpine
```

アクセスログなどを流しっぱなしにする tail -f と同じような使い方をしたい場合は、下記のコマンドで実行します。

```shell
docker logs -f nginx_http
```

利用パターンは下記のページで詳しく紹介されていますので、ご覧ください。

[Docker調査　~ログ編~](https://qiita.com/HommaHomma/items/f943fa3397bc3f386057)

リファレンス：[logs — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/reference/commandline/logs.html)

---
#### Docker コンテナ/イメージの掃除

停止しているコンテナ および 不要なイメージの一括削除ができます。

```shell
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
7215e569000e514defa362057f89c37b54c16a83f0b398ee3c456e6246c6c90c
23bdc90a405cf539adb5f7f6bb7c8e027640dd0e4cc03112b98e2d619a37099f
4942e9d768d8d0795a830848f7a98b21ebe0e4a9d1bda3cf6d75bc04e0f204bf
```

```shell
$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B
```

ちょっと危険なので、本番運用では使わないかも知れませんが、便利ではありますね。

---
### Docker サービス管理
---

#### Docker サービス情報

Docker サービス自体のシステム情報を表示します。

```
$ docker system info
Client:
 Debug Mode: false

Server:
 Containers: 1
  Running: 1
  Paused: 0
  Stopped: 0
 Images: 5
 Server Version: 19.03.1
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc version: 425e105d5a03fabd737a126ad93d62a9eeede87f
 init version: fec3683
 Security Options:
  apparmor
  seccomp
   Profile: default
 Kernel Version: 4.4.0-154-generic
 Operating System: Alpine Linux v3.10 (containerized)
 OSType: linux
 Architecture: x86_64
 CPUs: 8
 Total Memory: 31.4GiB
 Name: node1
 ID: 6U4B:34PI:LLBI:KGVS:KQ3W:VB25:GXGU:VOUE:TNDJ:VA4W:DXRC:2I72
 Docker Root Dir: /var/lib/docker
 Debug Mode: true
  File Descriptors: 29
  Goroutines: 47
  System Time: 2019-08-30T09:26:01.521391088Z
  EventsListeners: 0
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: true
 Insecure Registries:
  127.0.0.1
  127.0.0.0/8
 Live Restore Enabled: false
 Product License: Community Engine

WARNING: API is accessible on http://0.0.0.0:2375 without encryption.
         Access to the remote API is equivalent to root access on the host. Refer
         to the 'Docker daemon attack surface' section in the documentation for
         more information: https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
WARNING: No swap limit support
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled
```

他人が建てた Docker を利用する際には有効でしょう。

---

#### Docker ボリューム情報

Docker サービス自体のボリューム情報を表示します。

```shell
$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              5                   1                   2.702GB             2.696GB (99%)
Containers          1                   1                   0B                  0B
Local Volumes       2                   0                   249.5MB             249.5MB (100%)
Build Cache         0                   0                   0B                  0B
```

Docker 自体がどれだけ容量を使用しているのかが分かります。ディスク容量を減らしたい場合などに有効です。

---
### Docker / Docker-compose の豊富な機能

さらに Docker / Docker-compose の豊富な機能を活用するには、下記のリファレンスを眺めると良いでしょう。

[Engine リファレンス — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/reference/index.html)

さらに最新版のリファレンスが欲しい場合は下記を利用しましょう。

[docker | Docker Documentation](https://docs.docker.com/engine/reference/commandline/docker/)（注：英語）

---
### Docker の構造とセキュリティ

Docker 基盤の詳細な構成やセキュリティに関して、下記が分かり易いのでオススメです。

* [コンテナの作り方、壊し方 / Container Structure and Exploitation Method - Speaker Deck](https://speakerdeck.com/mrtc0/container-structure-and-exploitation-method)

---
# 問題（宿題）

WordPress サイトを次の状態で構築しましょう。

![WordPress Site](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107190/8fedbc99-671b-3701-2650-9c28a5c366d4.png)

* WordPress / MySQL / Nginx の３コンテナで構成する
* MySQL のデータベースデータをホストに保存する
* Nginx でリバースプロキシを構成する 
* WordPressの記事データは [theme-test-data-ja](https://github.com/jawordpressorg/theme-test-data-ja) を使用する
* 80番ポートに接続すると Nginx経由 、8080ポートは WordPress へ直接できるようにすること
* NginxコンテナからはMySQLコンテナに直接接続できないようにすること

---
# おわりに

今回は docker コンテナを管理する方法について、ハンズオンをしました。

ようやく現実的な運用に利用できる状態となってきましたが、**大量のコンテナ操作**や**定型的な運用操作の効率化**を考えると、Docker Compose では物足りません。
そこでいよいよ Kubernetes というオーケストレーションツールの出番となります。

それはまた次のハンズオンで学習しましょう。

[コンテナ技術 ハンズオン Vol.5 Kubernetes編](https://qiita.com/syamane/items/e5382a8bf9753f6a6c23)

---

# 参考

これまでに紹介した記事以外に、本ハンズオン資料を作成する上で下記の記事を参考にさせていただきました。

* [Overview of Docker Compose | Docker Documentation](https://docs.docker.com/compose/)
* [Docker Engine — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/toc.html)
* [Docker入門-基礎編 いまから始めるDocker管理【2nd Edition】](https://www.slideshare.net/zembutsu/introduction-to-docker-management-and-operations-2nd)
* [入門 Docker](https://y-ohgi.com/introduction-docker/)
* [Use the Docker command line | Docker Documentation](https://docs.docker.com/engine/reference/commandline/cli/)
* [Dockerのすべてが5分でわかるまとめ!(コマンド一覧付き) - paiza開発日誌](https://paiza.hatenablog.com/entry/docker_intro)
* [Dockerハンズオン基礎編：コンテナとイメージのライフサイクルを理解 - Qiita](https://qiita.com/zembutsu/items/ca0fa7b874c8375be8a4)
* [こうしたいんだぜ！ という時の逆引きdockerコマンド | 株式会社ビヨンド](https://beyondjapan.com/blog/2016/08/docker-command-reverse-resolutions/)

大変ありがとうございました。