【ペチオブ】仮想環境ハンズオン 第4回 Docker編

## ハンズオン資料

1. [Linux基礎編](https://qiita.com/ucan-lab/items/683658dbeb6f81c4c73f)
2. [VirtualBoxハンズオン](https://qiita.com/ucan-lab/items/d06f9ab9946598b53421)
3. [Vagrantハンズオン](https://qiita.com/ucan-lab/items/e14a26081229c8bef98a)
4. **Dockerハンズオン**
5. [Docker Composeハンズオン](https://qiita.com/ucan-lab/items/6809c83380af9afac216)

## 今回の目的

今回の目的は、VirtualBox上にVagrantを使ってUbuntu OSのインストール、PHPの実行環境の構築を行います。

## YouTube版 【ペチオブ】仮想環境ハンズオン 第4回 Docker編

[![【ペチオブ】仮想環境ハンズオン 第4回 Docker編](http://img.youtube.com/vi/Ndrr2DFeAzs/0.jpg)](https://youtu.be/Ndrr2DFeAzs)

動画を高評価、チャンネル登録いただけるとより多くの方へオススメ表示されるので良いと思った方はご協力いただけるとありがたいです🙏

## 完成版

https://github.com/ucan-lab/infra-handson-docker

完成版はGitHubにプッシュしてます。

## Dockerのインストール

https://www.docker.com/products/docker-desktop

Docker公式サイトからインストーラをダウンロードできます。

```
$ docker -v                   
Docker version 20.10.2, build 2291f61
$ docker-compose -v
docker-compose version 1.27.4, build 40524192
```

## Dockerとは

コンテナ型仮想化技術の一つです。

## コンテナと仮想マシンの比較

### 仮想マシンの概念

![vm](https://i.imgur.com/sBGgGaO.png)

- 図はハイパーバイザ型の概念図
  - ホスト型(VirtualBox等)はハイパーバイザ部分が仮想化ソフトウェアになる
- 仮想マシンはリソース集中型
- プロセッサ、メモリ、ストレージ等のハードウェアのエミュレートが必要
- 短時間での複製が難しい

### コンテナの概念

![container](https://i.imgur.com/FshzuzI.png)

- コンテナは１つのカーネルを共有できます。
- コンテナ・イメージに必要なのは、実行可能なものとパッケージの依存性に関する情報のみです。
  - ホストシステム上へパッケージをインストールする必要は一切ありません。
- コンテナ上のプロセスはホスト上のプロセスのように軽快に動作します。

## 仮想マシンと比べたコンテナのメリット

- 処理が速い
  - 仮想マシン毎にプロセッサやメモリ、ストレージをエミュレートする必要がなく、オーバーヘッドが少ない
- 起動が速い
  - 仮想マシンのようにゲストOSを立ち上げる必要がないため
- 可搬性が高い
  - Dockerエンジンさえあれば動く
- 環境構築が簡潔
  - Dockerfileやdocker-compose.ymlによるインフラのコード化
  - シェルスクリプトで書かれているより、可読性が高い
- イメージが軽い
  - Dockerイメージはレイヤで管理されている
  - Vagrantのboxファイルと比べると容量がとても少なく済む
- 構築＆共有が手軽
  - 起動が速い、可搬性が高い、イメージが軽いことから環境の破棄＆再構築しやすい

## 仮想マシンと比べたコンテナのデメリット

- ホストOSのカーネルと異なるOSは動かせない
- 特殊な環境を再現しないといけない等、要件によっては仮想マシンの方が都合が良い場合がある

## Docker ライフサイクル

![](https://i.imgur.com/Phm43ef.png)

[docker初心者がいまいち理解できず調べ直したところまとめ](https://qiita.com/chihiro/items/595d21f5e60f76f78a31)

- 主なライフプロセス
  - イメージ取得(docker pull)
  - コンテナ生成(docker create)
  - コンテナ起動(docker start)
  - コンテナ停止(docker stop)
  - コンテナ削除(docker rm)

## Docker コマンド

- Dockerコマンド一覧
  - http://docs.docker.jp/v17.06/engine/reference/commandline/index.html

## Docker search コマンド

Dockerイメージを検索するコマンド

```
$ docker search hello-world
NAME                                       DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
hello-world                                Hello World! (an example of minimal Dockeriz…   1158                [OK] 

... 省略
```

- https://hub.docker.com/_/hello-world

## Docker run コマンド

runコマンドはイメージ取得、コンテナ生成、コンテナ起動を行う。
`docker run イメージ名:タグ名` と指定できる。
イメージのタグを指定しない場合 `latest` がデフォルトとなる。

```
$ docker run hello-world

Hello from Docker!

... 省略
```

### 補足

`docker run` コマンドは、 `pull`, `create`, `start` をまとめて実行してくれます。

```
$ docker pull hello-world
$ docker create hello-world
$ docker start ede7a77c58214b2923267285ba3ef2acae314575248b272b18b5751d5a24e3d5
```

- pull, create はDockerイメージ名が引数
- start はDockerコンテナ名が引数

### コンテナの中に入る

コンテナの中に入って、コマンドを実行したい場合もあります。

```
$ docker run -it ubuntu bash
root@eaa10b69ea7f:/# ←のようなプロンプトが表示されていればok
ユーザー名@コンテナID:ディレクトリパス名# ←表示されてます
```

- `-i`, `--interactive` Keep STDIN open even if not attached
  - 標準入力(STDIN)を開き続ける
- `-t`, `--tty` Allocate a pseudo-TTY
  - 疑似tty(teletypewriter)を割り当てる

コンテナの中でbashプロセスを新規で立ち上げ操作します。

### コンテナから出る

```
$ exit
```

`control` + `d` でも抜けられます。

## docker ps コマンド

### 起動中のコンテナを表示

```
$ docker ps
```

先ほど生成したコンテナは表示されません。
`hello-world` のイメージはターミナルにメッセージを表示したらコンテナを停止する仕様になっているため。

### 停止中のコンテナを含めてすべて表示する

```
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
333008bb725a        hello-world         "/hello"            7 minutes ago       Exited (0) 7 minutes ago                       loving_lewin
```

## お試し

### オプションの有無での挙動

オプションの有無でどういった挙動になるか確認してみましょう

```
$ docker run ubuntu bash
$ docker run -i ubuntu bash
$ docker run -t ubuntu bash
```

`-t` の場合は標準入力が効かないので、ターミナル閉じちゃってもokです。

### コマンドの実行

bash 以外のコマンドも引数を渡して実行できます。

```
$ docker run ubuntu ls -l
```

### Apache ウェブサーバを立てる

```
$ docker run -d -p 8101:80 httpd
```

http://127.0.0.1:8101

お試し: helloのページを表示させてみる

```
$ docker exec -it <CONTAINER> bash
$ echo hello > /usr/local/apache2/htdocs/hello.html
```

http://127.0.0.1:8101/hello.html

```
$ docker ps
$ docker stop <CONTAINER>
```

### nginx ウェブサーバを立てる

```
$ docker run -d -p 8102:80 nginx
```

http://127.0.0.1:8102

お試し: helloのページを表示させてみる

```
$ docker exec -it <CONTAINER> bash
$ echo hello > /usr/share/nginx/html/hello.html
```

http://127.0.0.1:8102/hello.html

```
$ docker ps
$ docker stop <CONTAINER>
```

### 色々なイメージを取得

```
$ docker pull centos
$ docker pull debian
$ docker pull ubuntu
$ docker pull alpine
```

```
$ docker images
REPOSITORY                              TAG                  IMAGE ID            CREATED             SIZE
centos                                  latest               470671670cac        2 months ago        237MB
debian                                  latest               971452c94376        4 weeks ago         114MB
ubuntu                                  <none>               4c108a37151f        9 months ago        64.2MB
alpine                                  latest               a187dde48cd2        2 days ago          5.6MB
```

- centos(237MB)
- debian(114MB)
- ubuntu(64.2MB)
- alpine(5.6MB)

イメージは軽いほど、ダウンロード時間が減るので好まれる。

## PHPベースイメージ

- https://hub.docker.com/_/php

各OSのクリーンなベースイメージから作っても良いですが
DockerHubではPHPのベースイメージを用意してくれてますのでこれを使わない手はないでしょう。

### コンテナのバージョン指定

PHP5.3〜最新のPHP8.0までタグ付けされている

### PHPコンテナのタグ

- cli（Apacheを経由せず、直接コマンドライン上で実行）
  - ウェブサーバ用途ではない
  - 使い捨てコンテナ
  - 他のイメージを構築するためのベースイメージ
- apache
  - ウェブサーバ(Apache mpm_prefork) ＋ アプリケーションサーバ(Apache mod_php)の構成
  - PHPと組み合わせたDebianのApache httpdが含まれている
- fpm（FastCGI Process Manager）
  - PHPを実行するアプリケーションサーバのみの構成
  - [fpmとは](http://php.net/manual/ja/install.fpm.php)
  - [cgiとは](https://ja.wikipedia.org/wiki/Common_Gateway_Interface)
- zts（phpがZend Thread Safetyでビルドされている）
  - マルチスレッド環境に対応するためのPHPの機構です
  - PHPはシングルスレッド環境(NTS)で動かす方が一般的です。

補足: nginxを使う場合は、fpmを選択します。

### コンテナOS

- buster (Debian 10) (405MB)
- stretch (Debian 9) (493MB)
- alpine (Alpine Linux) (83.5MB)

用意されてるコンテナOS以外のOSを使いたい場合は自分で用意しましょう...！

## Dockerハンズオン

```
$ mkdir docker-handson
$ cd docker-handson
```

Vagrantハンズオンで作成した `shop` ディレクトリをコピペします。(`docker-handson/shop`)

```
$ git clone https://github.com/ucan-lab/infra-handson-shop shop
```

もしくはこちらリポジトリをクローンしてください。

## PHP の Docker イメージを作る

### php/Dockerfile

Docker内でコマンドを実行して、save, commit する方法もありますが、今回はDockerfileにカスタマイズ内容をまとめます。

- [Dockerfile reference](https://docs.docker.com/engine/reference/builder/)

```
$ mkdir php
$ vim php/Dockerfile
```

```
FROM php:7.4-apache-buster

ENV DB_HOST=shop_db
ENV DB_NAME=shop
ENV DB_USER=phper
ENV DB_PASS=secret

RUN apt-get update
RUN docker-php-ext-install pdo_mysql
```

- `FROM` 命令でベースイメージを指定する
- `RUN` 任意のコマンドを実行して結果をコミットして新しいレイヤを作成します。
- `docker-php-ext-install` PHP拡張ライブラリを簡単にインストール・有効化するために用意されている(便利！)
  - 他にも2つの便利コマンドがあります。
  - `docker-php-ext-configure` (phpizeやconfigure実行してくれる)
  - `docker-php-ext-enable` (有効化してくれる)

### ビルドしてPHPのDockerイメージを作成

```
$ docker build -t php_image ./php

Sending build context to Docker daemon  2.048kB
Step 1/7 : FROM php:7.4-apache-buster
 ---> d9ac5626debe
Step 2/7 : ENV DB_HOST=shop_db
 ---> Running in a0d08e6d6881
Removing intermediate container a0d08e6d6881
 ---> 007712462c10
Step 3/7 : ENV DB_NAME=shop
 ---> Running in b2f168dec5d2
Removing intermediate container b2f168dec5d2
 ---> a5b52fd1162b
Step 4/7 : ENV DB_USER=phper
 ---> Running in 75048f5ba3f1
Removing intermediate container 75048f5ba3f1
 ---> 4eb03c664718
Step 5/7 : ENV DB_PASS=secret
 ---> Running in 6c1d1c374cf2
Removing intermediate container 6c1d1c374cf2
 ---> 42a0d96e6301
Step 6/7 : RUN apt update
 ---> Running in 2880656f22a8

... 省略

Successfully built 252fe418472e
Successfully tagged php_image:latest
```

- `-t`, `--tag` Name and optionally a tag in the 'name:tag' format
  - イメージ名とタグ名を設定する

```
$ docker images
REPOSITORY                              TAG                  IMAGE ID            CREATED             SIZE
php_image                               latest               252fe418472e        2 minutes ago       432MB
```

## MySQL の Docker イメージを作る

### mysql/Dockerfile

```
$ mkdir mysql
$ vim mysql/Dockerfile
```

```
FROM mysql:8.0

ENV MYSQL_DATABASE=shop
ENV MYSQL_USER=phper
ENV MYSQL_PASSWORD=secret
ENV MYSQL_ROOT_PASSWORD=secret

COPY ./my.cnf /etc/my.cnf
```

### mysql/my.cnf

```
$ vim mysql/my.cnf
```

```
[mysqld]
character_set_server = utf8mb4 # 文字コード
collation_server = utf8mb4_ja_0900_as_cs # 照合順序
```

### ビルドしてMySQLのDockerイメージを作成

```
$ docker build -t mysql_image ./mysql

Sending build context to Docker daemon  3.584kB
Step 1/6 : FROM mysql:8.0
 ---> 791b6e40940c
Step 2/6 : ENV MYSQL_DATABASE=shop
 ---> Running in b10e9e956308
Removing intermediate container b10e9e956308
 ---> a8dd31ef7c77
Step 3/6 : ENV MYSQL_USER=phper
 ---> Running in e83a04cdb412
Removing intermediate container e83a04cdb412
 ---> e19c880a5c32
Step 4/6 : ENV MYSQL_PASSWORD=secret
 ---> Running in 19934b67c6bc
Removing intermediate container 19934b67c6bc
 ---> cb032acf278b
Step 5/6 : ENV MYSQL_ROOT_PASSWORD=secret
 ---> Running in 9c166ea51879
Removing intermediate container 9c166ea51879
 ---> cd6ff942321c
Step 6/6 : COPY ./my.cnf /etc/my.cnf
 ---> f98bb0415f59
Successfully built f98bb0415f59
Successfully tagged mysql_image:latest
```

- `-t`, `--tag` Name and optionally a tag in the 'name:tag' format
  - イメージ名とタグ名を設定する

## MySQLデータの永続化

コンテナが削除されてしまうと、MySQLのデータも一緒に消えてしまいます。
コンテナ内やコンテナ間でデータを管理するにはボリュームを使って、ホスト側のディレクトリをコンテナにマウントすると永続化ができます。

http://docs.docker.jp/engine/userguide/dockervolumes.html

Dockerボリュームには2種類あります。

- データ・ボリュームは、ホストのディレクトリをMySQLコンテナ内にマウントする
- データ・ボリューム・コンテナは、データ格納専用コンテナを用意してMySQLコンテナ内にマウントする

```
$ docker volume create shop_db_storage
```

## ネットワーク

個々のコンテナは独立しており、 php のコンテナから MySQL のコンテナに接続するようにするためには同一ネットワークを設定する必要がある。

### ネットワークの作成

```
$ docker network create shop_network
2903e3148c552db54338803bdfecba5c3e8b603cef39e60ac6ad169a155652f6
```

### ネットワークの一覧

```
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
2903e3148c55        shop_network        bridge              local
```

### ネットワークの詳細

```
$ docker network inspect shop_network
[
    {
        "Name": "shop_network",
        "Id": "8b4373c22b4e47593b4c3942f12d435657ceb32cd7ef911c7196225d258c8fd8",
        "Created": "2020-03-31T15:34:46.7379855Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
```

## PHPコンテナの作成

```
$ docker run -d --name shop_web --network shop_network -p 8100:80 -v $(pwd)/shop:/var/www/html php_image
```

:::note info
$(pwd) を使うので、必ずdocker-handsonディレクトリ直下で実行してください。
:::

- `-d`, `--detach` Run container in background and print container ID
  - バックグランドでコンテナを起動します。(コンテナIDのみ出力します)
- `--name` Assign a name to the container
  - コンテナに名前を付けます。
  - 指定しない場合は 形容詞_名詞 のランダムなコンテナ名になります。
  - https://deeeet.com/writing/2014/07/15/docker-container-name/
- `--network` Connect a container to a network
  - コンテナをネットワークに接続する
- `-p`, `--publish` Publish a container's port(s) to the host
  - コンテナのポートをホストに公開します。(ホスト:コンテナ)
- `-v`, `--volume` Bind mount a volume
  - ボリュームをバインドマウントします。(ホスト:コンテナ)
  - バインドマウント: Dockerホストのファイルやディレクトリをコンテナ上にマウントする機能です。
  - 絶対パスを指定する必要があります。
  - https://www.public.ne.jp/2019/02/08/docker-5/

## MySQLコンテナの作成

- https://hub.docker.com/_/mysql

```
$ docker run -d --name shop_db -v shop_db_storage:/var/lib/mysql --network shop_network mysql_image
```

- `-d` バックグランドでコンテナを起動します。(コンテナIDのみ出力します)
- `--name` コンテナに名前を付けます。
- `-v` ボリュームコンテナを指定する。
- `--network` ネットワークを指定する。

## ネットワークの確認

```
$ docker network inspect shop_network
[
    {
        "Name": "shop_network",
        "Id": "8b4373c22b4e47593b4c3942f12d435657ceb32cd7ef911c7196225d258c8fd8",
        "Created": "2020-03-31T15:34:46.7379855Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "f3c91438a5d3bd229ac43468c2a6c4ec8c60b4711dfefa8bf367d8dc833f544e": {
                "Name": "shop_web",
                "EndpointID": "bff8b3f921677250bbf0edad291e2dc85633f990778907604315a5fe54b893d0",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]
```

## MySQL 初期データ設定

```
$ docker exec -it shop_db bash
$ mysql -u root -p$MYSQL_PASSWORD $MYSQL_DATABASE
```

```
create table shop.items (
  id int primary key auto_increment,
  name varchar(30) comment '商品名',
  stock int default 0 comment '在庫数'
) comment='商品テーブル';

INSERT INTO items (name, stock) VALUE ("Apple", 10);
INSERT INTO items (name, stock) VALUE ("Bad Apple", 1);
```

```
desc shop.items;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int         | NO   | PRI | NULL    | auto_increment |
| name  | varchar(30) | YES  |     | NULL    |                |
| stock | int         | YES  |     | 0       |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
```

```
select * from shop.items;
+----+-----------+-------+
| id | name      | stock |
+----+-----------+-------+
|  1 | Apple     |    10 |
|  2 | Bad Apple |     1 |
+----+-----------+-------+
2 rows in set (0.00 sec)
```

## 確認

- http://127.0.0.1:8100

![](https://i.imgur.com/Gi251CB.png)

## おまけ：お掃除

### Docker コンテナを削除

```
$ docker stop shop_web shop_db
$ docker rm shop_web shop_db

or

$ docker rm -f shop_web shop_db
```

```
$ docker ps -a
```

### Docker イメージを削除

```
$ docker rmi php_image mysql_image
$ docker images
```

### Docker ボリュームを削除

```
$ docker volume rm shop_db_storage
$ docker volume ls
```

### Docker ネットワークを削除

```
$ docker network rm shop_network
$ docker network ls
```

### 停止コンテナ、タグ無しイメージ、未使用ボリューム、未使用ネットワーク一括削除

```
$ docker system prune -f
```

## おまけ：Dockerコマンドまとめ

```
$ docker ps -as              # コンテナ一覧(停止コンテナ含む、サイズ表示)

$ docker start <CONTAINER>   # コンテナを起動
$ docker restart <CONTAINER> # コンテナ再起動
$ docker stop <CONTAINER>    # コンテナを停止
$ docker kill <CONTAINER>    # コンテナ強制終了

$ docker attach <CONTAINER>  # コンテナへ接続

$ docker top <CONTAINER>     # コンテナのプロセスを表示
$ docker logs -f <CONTAINER> # コンテナのログを表示(ログ出力をfollow)
$ docker inspect <CONTAINER> # コンテナの情報を表示

$ docker rm <CONTAINER>      # コンテナを削除
```

## さいごに

Docker ハンズオンいかがでしたでしょうか。
仮想マシンに比べて起動や構築が爆速で行えるようになりましたね。

しかし、コンテナを作ったり、停止するには個々にコマンドを打つ必要があり、管理するコンテナが増えれば増えるほど大変になってきます。

Docker Compose ハンズオンに続く...

## 参考

- http://docs.docker.jp/get-started/index.html#a-brief-explanation-of-containers