Docker超入門ハンズオン〜Build/Ship/Runをコンプリートせよ〜

最近会社でDocker講座+ハンズオンを開催したので、内容をここで共有します。
講座のスライドはSpeakerDeckに挙げていますので良かったらご覧ください。
[Web開発における開発環境構築の歴史と変遷〜原始時代から現代(Docker)まで〜](https://speakerdeck.com/endam/webkai-fa-niokerukai-fa-huan-jing-gou-zhu-falseli-shi-tobian-qian-yuan-shi-shi-dai-karaxian-dai-docker-mate)

本記事ではハンズオンの内容を記載します。

---
■ハンズオンについて
ゴール(目的)：
Dockerのスローガンである「Build/Ship/Run」を経験して基本的な使い方を理解しよう

Step1,2ではDockerfile→イメージ→コンテナの作成を行います。(Build,Run)
Step3ではDockerHubにオリジナルのイメージを登録し、それをベースイメージとしたDockefileから新しいイメージ作成〜コンテナを起動します。 (Ship)

対象者：
Docker初心者(中級者以上でも良い復習になるかも)

前提条件：
・DockerHubアカウントがあること
・Docker for Windows/Macをインストール済でdockerコマンドを叩けること

Docker for Windowsのインストール
https://ops.jig-saw.com/techblog/docker-for-windows-install/

Docker for Macのインストール
https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81

---

それではハンズオンスタート！

## Step1. 「Hello, Docker World」
### Dockerfileを作ってイメージをビルドしてみよう
手始めにDockerfileを格納するためのディレクトリを作ります
※基本的にMacでのコマンドとなります。Windowsは特に注意する部分だけ補足説明を行いますので悪しからず。

例.

```
mkdir ~/workspace/docker/sample
cd sample 
```

作成したディレクトリ内でDockerfileを作成
内容は以下をコピペしてください

```Dockerfile
# 利用するベースイメージの指定
FROM ubuntu:18.04

# コマンド
CMD echo 'Hello docker world!'
```

作成したDockerfileを基にイメージをビルド！

```
docker build ./ -t sample
```

イメージが出来たかを確認

```
$ docker images | grep sample
sample                latest              50bca976fdec        1 minute ago          64.2MB
```

### イメージからコンテナを起動してみよう
コンテナよ、起動せよ(以下コマンド実行)

```
$ docker run sample
Hello docker world!
```

起動時にコンテナ内部に接続してみる

```
$ docker run -it sample /bin/bash
root@ecaf5cd9e646:/#
```
runコマンドで`-it`オプションをつけて`bash` を起動するように指定すると、ターミナルでコンテナ内部を操作できます。

オプションの詳細については以下をご参考に
[【図解】docker runのオプションいろいろ #マンガでわかるDocker 番外編](https://webdesign-manga.com/docker_run_option/)

なのでコンテナに接続してからじゃなくても、直接起動と同時にコマンドを実行可能。

```
$ docker run -it sample ls
bin   dev  home  lib64	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 media	opt  root  sbin  sys  usr
```

#### コンテナの起動について
これまでの操作で実はrunした分コンテナは作成されているのにお気づきでしょうか？

試しにコンテナリストを表示する `docker ps` コマンド を実行

```
$ docker ps -a | grep sample
804a41759e16        sample              "ls"                     22 seconds ago      Exited (0) 21 seconds ago                            elastic_mayer
1a8b5f7bee72        sample              "/bin/bash"              2 minutes ago       Exited (0) 24 seconds ago                            cocky_darwin
a98cd2ab3cfa        sample              "/bin/sh -c 'echo 'H…"   3 minutes ago       Exited (0) 3 minutes ago                             laughing_darwin
```

デフォルトでは起動しているコンテナだけ表示するが、`-a` オプションをつけると起動していないコンテナも含めた全てのコンテナを表示

#### 要らないコンテナ・イメージを削除
`docker rm コンテナID|コンテナ名`で指定したコンテナを削除できますが、めんどくさいので一括で削除しちゃいましょう

まずはこのコマンドで先程psコマンドとコンテナID群が一致している事を確認
`docker ps -a -f "ancestor=sample" -q`
`-f`: フィルターオプション。`ancestor=***`で指定したイメージ名から作成されたコンテナを絞り込むことができる
`-q`: コンテナIDだけ表示

こいつをrmコマンドで使うと一括で削除できる

```
docker rm `docker ps -a -f "ancestor=sample" -q`
```

イメージも削除

```
docker rmi sample
```


## Step2. Nginxコンテナを使ってみる
Nginxのコンテナを起動してホストPCからアクセスしてみよう
※今度はDockerfileを書く必要がなく公式イメージから直接コンテナを起動します

```
docker run -d --name nginx1 -p 8080:80 nginx
```
`-d`: デタッチドモード。バッググラウンドで実行
`-p`: ポートフォワーディング [ホスト側ポート番号]:[コンテナ側ポート番号]

[localhost:8080](http://localhost:8080)にアクセスしてnginxデフォルトページを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71113/a0a6ddbc-4440-e52f-1d75-27926e858191.png)


わずか数十秒でnginxの環境が出来上がった。これこそがDockerの手軽さ！

しかしここで一つの**重要な疑問**
「あれ？さっきのHello worldと違って**Nginxコンテナは動き続けてる**。これって何で？」
冒頭で共有した[スライド](https://speakerdeck.com/endam/webkai-fa-niokerukai-fa-huan-jing-gou-zhu-falseli-shi-tobian-qian-yuan-shi-shi-dai-karaxian-dai-docker-mate?slide=60)でも説明してますが、**コンテナが起動し続けるには何かしらのプロセスがフォアグラウンドで動く必要があります**

端末からの入力を受け付ける状態になっているプロセスはフォアグラウンドプロセスといい、そうでない他の全てのプロセスはバックグラウンドプロセスです。  
フォアグラウンドプロセスの例：topコマンドを叩いたらプロセスがフォアグラウンドで起動します。 Ctrl + Cを入力することによってプロセスにSIGINTを通知され、プロセスが終了します。

Nginxはデフォルトはデーモンで動きますが、実はNginxコンテナはフォアグラウンドで動くようDockerfileで設定しています。
Nginxの[Dockerfile](https://github.com/nginxinc/docker-nginx/blob/5971de30c487356d5d2a2e1a79e02b2612f9a72f/mainline/buster/Dockerfile#L103)を見てみると、最後に `CMD ["nginx", "-g", "daemon off;"]`の行がありますね。

このおかげでコンテナが起動し続けることが出来るわけです。


ハンズオンに戻り、今度はNginxコンテナに接続します
(runではなくexecで実行)

```
docker exec -it nginx1 /bin/bash
```

コンテナ内でNginxバージョンを確認

```
root@4ee30cf1b12c:/# nginx -v
nginx version: nginx/1.17.8
```

bash立ち上げなくても普通にコマンド実行もできる

```
docker exec -it nginx1 ls
```

## Step3. DockerHubを使ってみよう
### DockerHubへイメージを登録
まずはStep1で使用したHelloWorldのDockerfileを基に登録用のイメージを作成
さっきは`docker build ./ -t sample` で作りましたが、DockerHubに登録する為には`{DockerHubアカウント名}/{イメージ名}`にする必要があります

アカウント名はログインする時のユーザー名となります。
DockerHubのプロフィールページから確認もできます。

[DockerHub](https://hub.docker.com/)のページから「My Profile」をクリックするとプロフィールページに飛びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71113/1d57cc40-61f7-9ec4-256e-5cdd0d80d11c.png)

自分の場合、endamがアカウント名になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71113/051a6c7f-d9fa-8952-5634-61f18df07cb7.png)


`{DockerHubアカウント名}/sample`でイメージを作成しましょう。

```
docker build ./ -t endam/sample
```

作成したイメージを確認

```
$ docker images | grep sample
endam/sample          latest              50bca976fdec        3 days ago          64.2MB
```

コマンドラインからDockerHubにログイン 
DockerHubに画面上からログインする時のユーザー名(アカウント名)・パスワードと同じです。

```
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: xxxxxxxxxxxxxxx
Password:
Login Succeeded
```

既にログインしている人は「Login Succeeded」が表示されればOKです。

```
$ docker login
Authenticating with existing credentials...
Login Succeeded
```

イメージをDockerHubにアップローード！

```
docker push endam/sample:latest
```
DockerHubでイメージが登録されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71113/d9688907-f412-5408-c1cb-abb56e130ec2.png)


### DockerHubに登録したイメージを基に新しくコンテナを作る
新しいDockerfileを作るのでHelloWorldのDockerfileをコピー&退避させてあげましょう

```
cp Dockerfile Dockerfile.bak
```

Dockerfileを修正

```Dockerfile
# 利用するベースイメージの指定
FROM endam/sample

# ディレクトリ作成
RUN mkdir sample
```

Dockerfileからイメージをビルド

```
docker build ./ -t sample2
```

イメージからコンテナを起動すると「Hello docker world!」が出力されます
(ベースイメージがちゃんと自分が作ったオリジナルイメージだということが分かる！)

```
$ docker run sample2
Hello docker world!
```

ちなみに新しいDockerfileの内容であるsampleディレクトリの作成が行われていることも確認

```
$ docker run -it sample2 ls
bin   dev  home  lib64	mnt  proc  run	   sbin  sys  usr
boot  etc  lib	 media	opt  root  sample  srv	 tmp  var
```


### ホストPCのディレクトリをコンテナにマウントしてみる
今回はコンテナ起動時にホストPCのディレクトリをコンテナにマウントするのでその準備をする

Dockerfileがあるディレクトリ内でsrcディレクトリを作成。

```
mkdir src
```

srcディレクトリ内で適当に新しいファイルを作成する。(マウント確認用)
↓はMacの場合なのでtouchコマンドで空ファイルを作成しています。
Windowsの人は[【Windows】空のファイルを作成する](https://www.softel.co.jp/blogs/tech/archives/4596)の記事を参考にしてください。

```
cd src
touch a.txt
```


コンテナ起動時にマウントの指定

```
docker run -v /Users/***/workspace/docker/sample/src:/app --name sample2 -it sample2 /bin/bash
```

`-v`で指定するホストとコンテナのパスは**絶対パス**なので要注意

```
docker run -v [ホストディレクトリの絶対パス]:[コンテナの絶対パス] [イメージ名] [コマンド]
```

特にWindowsの人はここでハマりやすいです。
`C:¥**ではなく`、`/C/***`という記述になります。

例：`/C/Users/{ユーザディレクトリ}/Documents/workspace/docker/sample/src:/app`


コンテナに接続後はappディレクトリ内に事前準備で作ったファイルが置かれていることを確認しましょう。

```
root@da7bcd4255d1:/# ls app
a.txt
```


# ハンズオンComplete!
これでDockerのスローガンであるBuild/Ship/Runを全て体験完了しました!

もしやってみて詰まったり、疑問がある方、「勉強になったので***のハンズオン内容も載せて欲しい」という方は気軽にコメントをください。お待ちしています。