Docker のチュートリアル的なものをやってみたメモ

以前、Docker を自力で学習しようとして挫折しました。コマンドもオプションもできることが多すぎて、どれがファーストステップなのか分かりませんでした。『[仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん（マイナビ出版）](https://book.mynavi.jp/ec/products/detail/id=120304)』を購入して、ようやくそれらしいことができるようになりました...。嬉しかったので叩いたコマンドをメモとして残します。

この記事は Docker v20.10、macOS Monterey でのハンズオンです。

## Hello World

公式サイトから [Docker Desktop for mac](https://www.docker.com/products/docker-desktop/) をダウンロードしてインストールします。

まずは Hello World を実行してみます。

```sh
docker run hello-world
```

以下のような結果が出力されれば、最初の一歩は完了です。

```sh
> Unable to find image 'hello-world:latest' locally
> latest: Pulling from library/hello-world
> 2db29710123e: Pull complete 
> ...

> Hello from Docker!
> This message shows that your installation appears to be working correctly.

> To generate this message, Docker took the following steps:
> 1. The Docker client contacted the Docker daemon.
> 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
```

## 仕組み

Docker は [docker hub](https://hub.docker.com/) などで公開された「イメージ」を PULL（ダウンロード）してローカルに保存します。イメージには、nginx やら mysql やら node やら、ローカルインストールするような各種ソフトウェアから OS までさまざまなものが揃っています。

<img width="600" alt="" src="https://user-images.githubusercontent.com/15980747/166930673-c50d61bf-7a6e-4def-90fe-ce49a008c8df.png">

イメージから「コンテナ」を作り START すると各種サービスが稼働します。「ネットワーク」を使うとコンテナ同士を連携させることもできます。ローカルマシン上に小さなPCがいくつも動いているようなイメージですね。

nginx など Web サーバーを稼働させた場合は HTTP を経由して小さな PC にアクセスします。利用が終わったら STOP で電源を切り、RM で廃棄します。

## runコマンド

<img width="600" alt="run" src="https://user-images.githubusercontent.com/15980747/166949674-97e1e20e-4b89-4df9-8dce-75a52193138c.png">

コンテナを動かすには、コンテナの作成、起動、イメージのプル、のステップが必要です。

- `docker container create`
- `docker container start`
- `docker container pull`

これらは `run` コマンドでまとめて実行することができます。

サンプルとしてWeb サーバーの nginx を `run` コマンドで稼働します。

```sh
docker run --name container01 -d -p 8080:80 nginx:1.21
```

> `--name` コンテナ名を指定する。
> `-d` バックグラウンドで実行を継続する。
> `-p 8080:80` ホストの 8080 番ポートをコンテナの 80 番ポートにバインドする。
> `nginx:1.21` バージョンを省略すると latest を指定したのと同じ。

http://localhost:8080 にアクセスすると「Welcome to nginx!」の画面が表示されます。

## cpコマンド

<img width="600" alt="cp" src="https://user-images.githubusercontent.com/15980747/166949659-3a51337d-e998-4985-8366-d12cc4646839.png">

`cp` コマンドでホストからコンテナにファイルをコピーすることができます。

稼働させたコンテナ "container01" のインデックスページを差し替えてみます。

```sh
# サンプルとして送り込む HTML を作成
echo "<html>
  <head></head>
  <body>test</body>
</html>" >index.html
```

```sh
docker cp ./index.html container01:/usr/share/nginx/html/
```

> `container01:/usr/share/nginx/html/`
> コンテナ名：コピー先のパス
> この例では nginx の公開フォルダにファイルを送り込んでいる

http://localhost:8080 をリロードするとコンテンツが「test」に変わります。

## execコマンド

<img width="600" alt="exec" src="https://user-images.githubusercontent.com/15980747/166949666-e40bbb07-4a35-4d96-b68d-dc43680bc7f6.png">

`exec` コマンドを使うと稼働させたコンテナの中でコマンドを実行することができます。コマンドとしてシェルを渡すと、SSH で接続したかのようにコンテナを操作することができます。

Docker イメージは Linux ベースで作られていてそのほとんどが bash を備えているため、稼働させたコンテナ "container01" で bash を実行してみます。

```sh
docker exec -it container01 bash
```

> `-i` ホストのキーボード入力をコンテナに送る。
> `-t` ホストとコンテナのターミナルをバインドする。
> 正確な補足ではないので tty,pts あたりで検索すると理解が深まるかもです。

`uname` コマンドを叩くと Linux らしきものが動いているのが分かります。

```sh
# root@4643ee0e17a5:/
uname -a
Linux 4643ee0e17a5 5.10.104-linuxkit #1 SMP Thu Mar 17 17:08:06 UTC 2022 x86_64 GNU/Linux
```

`ls` コマンドを叩くとさきほどの `cp` コマンドで送り込んだファイルが格納されていることが分かります。

```sh
# root@4643ee0e17a5:/
ls -l /usr/share/nginx/html/
> -rw-r--r-- 1 root root    497 Jan 25 15:03 50x.html
> -rw-r--r-- 1  501 dialout  51 May  5 12:10 index.html
```

確認が終わったら `exit` で抜けます。

```sh
# root@4643ee0e17a5:/
exit
```

## Dockerfile

<img width="600" alt="dockerfile" src="https://user-images.githubusercontent.com/15980747/166949663-d590b1f8-f867-432e-a3fb-8674185df37f.png">

イメージはプルするだけでなく、毎回ファイルコピーなどのカスタマイズを加えることが多くなりそうです。毎回実行する手順は `Dockerfile` というファイルにひとまとめにしておくことができます。

任意のフォルダを作成します。

```sh
mkdir image
```

サンプルとしてインデックスページを作成します。

```sh
echo "<html>
  <head></head>
  <body>foobar</body>
</html>" >image/index.html
```

`Dockerfile` を作成します。

```sh
touch image/Dockerfile
```

```sh
# ./image/Dockerfile
FROM nginx
COPY index.html /usr/share/nginx/html/
```

`build` コマンドでビルドします。

```sh
docker build -t image01 ./image/

> [+] Building 0.1s (7/7) FINISHED
> => [internal] load build definition from Dockerfile
> => => transferring dockerfile: 92B
> => [internal] load .dockerignore
...
```

> `-t` image01 という名前でイメージを作成する。

イメージのリストを確認すると、新しく「image01」が追加されています。

```sh
docker image ls

> REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
> image01       latest    a7ca92fca6b5   3 seconds ago   142MB
```

`run` コマンドで実行します。

```sh
docker run --name container02 -d -p 8081:80 image01
```

http://localhost:8081 にアクセスして「foobar」が表示されれば成功です。

## ネットワーク

<img width="600" alt="network" src="https://user-images.githubusercontent.com/15980747/166949671-7669ea74-1d5b-427b-9e20-db25459d7c53.png">

次は 2 つのコンテナを `run` してネットワークで連携させてみます。
サンプルとして WordPress とデータ保存の mysql のコンテナを稼働してみましょう。

```sh
# ネットワークの作成
docker network create network01

# mysql のコンテナ
docker run \
  --name container03 \
  -dit \
  --net=network01 \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -e MYSQL_DATABASE=db1 \
  -e MYSQL_USER=user1 \
  -e MYSQL_PASSWORD=pass \
  mysql:5.7 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci \
  --default-authentication-plugin=mysql_native_password
  
# wordpress のコンテナ
docker run \
  --name container04 \
  -dit \
  --net=network01 \
  -p 8082:80 \
  -e WORDPRESS_DB_HOST=container03 \
  -e WORDPRESS_DB_NAME=db1 \
  -e WORDPRESS_DB_USER=user1 \
  -e WORDPRESS_DB_PASSWORD=pass \
  wordpress:5.9
```

それぞれのコンテナは同じネットワーク「network01」上に存在し、mysql のコンテナに生成した「db1」というデータベースに wordpress のコンテナがアクセスします。

http://localhost:8082/ にアクセスすると WordPress のセットアップ画面が表示されます。任意のログイン情報を入れてセットアップすると mysql のコンテナの db1 にテーブルが作成されます。

サンプルとして `cp` コマンドでダンプファイルを送り込んで、`exec` コマンドで mysql に取り込んでみます。

```sh
# ダンプを吐き出す
mkdir db
mysqldump {ローカルのDB名} -p > ./db/restore.sql

# cp コマンドでコンテナに送り込む
docker cp ./db/restore.sql container03:/var/lib/mysql/

# mysql のコンテナでコマンドを実行
docker exec -it container03 mysql db1 -uroot -p
> Enter password
> # "rootpass" で入る

# ダンプを取り込む
mysql> source /var/lib/mysql/restore.sql

# データが取り込まれたか確認する
mysql> select user_login from wp_users;
> +------------+
> | user_login |
> +------------+
> | user1      |
> | user2      |
> +------------+

# 確認できたら抜ける
mysql> exit
```

## docker-compose.yml

<img width="600" alt="compose" src="https://user-images.githubusercontent.com/15980747/166949650-c5f6188d-6824-448b-8359-fdbaf05b8e38.png">

イメージに対する操作手順を `Dockerfile` に書いたのと同じように、複数コンテナの操作手順は `docker-compose.yml` にひとまとめにして書くことができます。

前述の wordpress と mysql の設定は以下のように書けます。

```yaml
# docker-compose.yml
version: "3"

services:
  container_mysql:
    image: mysql:5.7
    container_name: container05
    networks:
      - net000
    volumes:
      - type: bind
        source: $PWD/db
        target: /tmp/db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: db1
      MYSQL_USER: user1
      MYSQL_PASSWORD: pass
      
  container_wordpress:
    depends_on:
      - container_mysql
    image: wordpress:5.9
    container_name: container06
    networks:
      - net000
    ports:
      - 8083:80
    environment:
      WORDPRESS_DB_HOST: container_mysql
      WORDPRESS_DB_NAME: db1
      WORDPRESS_DB_USER: user1
      WORDPRESS_DB_PASSWORD: pass
      
networks:
  net000:
```

`docker-compose.yml` は `compose up` コマンドを使って一気にコンテナを稼働します。

```sh
docker compose -f ./docker-compose.yml up -d
```

さきほど `cp` コマンドで送り込んだダンプは、ボリュームをマウントしてコンテナ内に送り込むようにしました。

```yaml
# docker-compose.yml の抜粋
volumes:
- type: bind
  source: $PWD/db
  target: /tmp/db
```

> `type:bind` コピーでなくホストのファイルシステムを参照する。

コンテナの中に入るとダンプが送り込まれていることが確認できます。

```sh
docker exec -it container05 bash
> root@bd5bd4aeff49:/# 

ls -la /tmp/db
> drwxr-xr-x 4 mysql root    128 May  5 08:06 .
> drwxrwxrwt 1 root  root   4096 May  5 13:37 ..
> -rw-r--r-- 1 mysql root 140399 May  5 13:34 restore.sql
```

`compose up` コマンドで立ち上げたコンテナ群は `compose down` で停止します。

```sh
docker compose -f ./docker-compose.yml down
```

## stop/rmコマンド

<img width="600" alt="stop" src="https://user-images.githubusercontent.com/15980747/166949678-9b04d365-071f-4f7e-ae87-bfd4f056b323.png">

現在稼働しているコンテナ群を確認してみます。

```sh
docker container ls

> CONTAINER ID   IMAGE           COMMAND                  CREATED          STATUS          PORTS                  NAMES
> 76f3edbed706   wordpress:5.9   "docker-entrypoint.s…"   12 minutes ago   Up 12 minutes   0.0.0.0:8082->80/tcp   container04
> 05edbb9b4af8   mysql:5.7       "docker-entrypoint.s…"   13 minutes ago   Up 13 minutes   3306/tcp, 33060/tcp    container03
> ed0ab41237df   image01         "/docker-entrypoint.…"   15 minutes ago   Up 15 minutes   0.0.0.0:8081->80/tcp   container02
> 2884a2cd7478   nginx:1.21      "/docker-entrypoint.…"   21 minutes ago   Up 21 minutes   0.0.0.0:8080->80/tcp   container01
```

`run` コマンドで稼働したコンテナはローカルに保存されます。たまり続けるとストレージを圧迫するので、使い終わったら `stop` で停止し `rm` で削除します。

```sh
# コンテナの停止
docker container stop container01

# コンテナの削除
docker container rm container01
```

同じようにイメージやネットワークも削除します。

```sh
docker image ls

> REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
> image01      latest    51b935b7a7db   41 minutes ago   142MB
> mysql        5.7       8aa4b5ffb001   7 days ago       462MB
> wordpress    5.9       b44d413c437a   2 weeks ago      606MB
> nginx        1.21      fa5269854a5e   2 weeks ago      142MB

# イメージの削除
docker image rm image01
```

GUI でもクリック操作で停止や削除できるため、全てをかたっぱしから消したい時はこちらのほうが楽かもしれません。