Dockerハンズオン　[1/2]

勉強会用に以下の資料を翻訳しました。
`docker run -dp 80:80 dockersamples/101-tutorial`

# 0. 事前準備
当ハンズオンは ［ Play　with　Docker ］（ https://labs.play-with-docker.com/ ） の環境で行うことを前提にしています。
また、本ハンズオンの実施前提条件として以下のコンテナイメージが起動している必要があります。
コマンドを実行して80ポートでチュートリアルのWebページを開いておいてください。
```docker run -dp 80:80 dockersamples/101-tutorial```
Play　with　Docker上の`８０`のリンクを押下する。

# 1. コンテナとDocker
簡単に言うとコンテナは、ホストマシン上の他のすべてのプロセスから分離された、マシン上の単なる別のプロセスです。
その分離には、カーネルのnamespacesとcgroupを活用します。
これは、Linuxに長い間存在していた機能の一つです。
そして、Dockerは、これらの機能を使いやすくよろしくしてくれるプラットホームです。
コンテナの特徴は柔軟で軽く、よりセキュアにOS上でリソースを隔離することができます。
以下の図はVMとコンテナの比較なります。
コンテナはVMとくらべゲストOSがない分早く起動することができます。
代わりに、起動スペックはホストマシンに依存します。
また、コンテナ内のプロセスをステートレスにすることで環境に依存せずに実行することが可能になります。

<img width="779" alt="スクリーンショット 2019-12-03 22.29.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472784/7209d2ec-3995-7f4e-6ab7-3701f4224a4d.png">

# 2. コンテナイメージ
コンテナは隔離されたファイルシステムを使用してを実行されます。
このカスタムファイルシステムをコンテナイメージと呼びます。

イメージにはコンテナのファイルシステムが含まれているため、アプリケーションの実行に必要なすべてのもの（すべての依存するパッケージ、構成、スクリプト、バイナリなど）を含む必要があります。
このイメージには、環境変数、実行するデフォルトのコマンド、その他のメタデータなど、コンテナの他の構成も含まれます。

## 2.1. アプリの取得
以下の手順に従ってアプリケーションを取得します。

1.zipファイルをダウンロードする。
`dockersamples/101-tutorial`を起動して開いたチュートリアルの`Our Application`リンク内からapp.zipを以下のコマンドにてダウンロードする。

```
curl -LO <Download the zip のURL>
```


2.zipファイルを解凍する。

```
unzip app.zip
```

3.解凍した`app`フォルダに入る。

```
cd app
```

4.アプリケーションが取得できたことを確認する。

```
ls
package.json  spec          src           yarn.lock
```

## 2.2. アプリケーションをコンテナイメージにビルドする。
アプリケーションをビルドするには、Dockerfileを作成する必要があります。 
Dockerfileは、コンテナイメージを作成するために使用されるテキストベースで構成された命令スクリプトです。

1.以下の内容で`Dockerfile`という名前のファイルを作成する。

```
FROM node:10-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]
```

2.`docker build`コマンドを使用して、コンテナイメージをビルドする。

```
docker build -t docker-101 .
```
このコマンドで、Dockerfileを使用して新しいコンテナーイメージを作成しました。
この時、多くの「レイヤー」がダウンロードされていると思います。
これは、node：10-alpineイメージを基に使用してビルドすることをビルダーに指示したためです。
しかし、マシンにはそれがなかったため、そのイメージをダウンロードする作業が発生しました。
その後、アプリケーションをコピーし、`yarn`を使用してアプリケーションに依存関するパッケージをインストールしました。 
CMDディレクティブは、このイメージからコンテナーを開始するときに実行するデフォルトのコマンドを指定します。

## 2.3. アプリコンテナの実行
コンテナイメージができたので、アプリケーションを実行しましょう！
アプリケーションを実行には、`docker run`コマンドを使用します。

1.`docker run`コマンドを使用してコンテナを起動する。

```
docker run -dp 3000:3000 docker-101
```
- -d：コンテナをバックグラウンドでで実行します。
- -p：ポートの指定です。ホストのポート3000をコンテナーのポート3000にマップします。
- docker-101：使用するコンテナイメージの名前です。

2.アプリケーションを開く。
Play　with　Docker上の`3000`のリンクを押下する。
以下の画面が開けばOK！
<img width="453" alt="スクリーンショット 2019-12-04 23.01.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472784/20a14c0b-57e2-7976-cd9d-6bb117934d0f.png">

# 3. アプリの更新
todoリストアイテムがないときに表示されるテキストを以下のように変更するように依頼されたとします。
`You have no todo items yet! Add one above!`
かなり簡単ですね。変更しましょう。

## 3.1. ソースコードの更新
1.`〜/app/src/static/js/app.js`ファイルで、新しいテキストを使用するように56行目を更新する。

```
<p className="text-center">You have no todo items yet! Add one above!</p>
```
2.前に使用したのと同じコマンドを使用して、更新されたバージョンのイメージを作成する。

```
docker build -t docker-101 .
```
3.更新されたコードを使用して新しいコンテナを実行する。

```
docker run -dp 3000:3000 docker-101
```
!!!
以下のようなエラーが発生したと思います。

```
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell 
(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.
```
そうです！古いコンテナが実行中のため新しいコンテナを実行する事ができないのです。
古いコンテナがホストのポート3000を使用しており、新しいコンテナがポートを使用できないため発生します。
特定のポートをリッスンできるのは1つのプロセス（コンテナを含む）だけであるためです。
このエラーを修正するには、古いコンテナを削除する必要があります。

## 3.2. 古いコンテナの削除
コンテナを削除するには、まず停止する必要があります。その後、削除できます。

1.`docker ps`コマンドを使用してコンテナのIDを取得する。

```
docker ps
```
2.`docker stop`コマンドを使用して、コンテナを停止する。

```
docker stop <コンテナID>
```

3.コンテナが停止したら`docker rm`コマンドを使用して古いコンテナを削除します。

```
docker rm <コンテナID>
```
4.更新したアプリを起動する。

```
docker run -dp 3000:3000 docker-101
```
アプリを開くと、更新されたヘルプテキストが表示されます。

<img width="380" alt="スクリーンショット 2019-12-07 10.24.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472784/9ff21e08-039e-eca0-5dc4-f9522f3b9f44.png">

# 4. アプリのシェア
イメージを作成したので、共有しましょう！
Dockerイメージを共有するには、Dockerレジストリを使用する必要があります。デフォルトのレジストリはDocker Hubであり、ここで使用したすべてのimageの元となっています。

## 4.1. リポジトリを作成する
imageをプッシュするには、まずDocker Hubでリポジトリを作成する必要があります。

1. Docker Hubにアクセスしてログインします。
2. [リポジトリの作成]ボタンをクリックします。 
3. リポジトリ名には、101-todo-appを使用します。
4. 可視性がパブリックであることを確認してください。
5. [作成]ボタンをクリックします！

ページの右側を見ると、Dockerコマンドという名前のセクションがあります。
これにより、このリポジトリにプッシュするために実行する必要があるコマンドの例が提供されます。

## 4.2. imageをPsuhする
コマンドを実行してみてください。次のようなエラーが表示されるはずです。

```
$ docker push dockersamples/101-todo-app
The push refers to repository [docker.io/dockersamples/101-todo-app]
An image does not exist locally with the tag: dockersamples/101-todo-app
```

なぜ失敗したのでしょう？
pushコマンドは、`dockersamples / 101-todo-app`という名前のimageをましたが、見つかりませんでした。
`docker image ls`を実行してみてください。
こちらの結果も`dockersamples / 101-todo-app`という名前のimageが見つからないはずです。
このエラーを修正するには、imageに「タグ」を付ける必要があります。
これは、作成したimageに別の名前を付けることを意味します。

では、コマンド`docker login -u YOUR-USER-NAME`を使用してDocker Hubにログインします。
`docker tag`コマンドを使用して、`docker-101`imageに新しい名前を付けます。 `YOUR-USER-NAME`をDocker IDで置換してください。

```
docker tag docker-101 YOUR-USER-NAME/101-todo-app
```

pushコマンドをもう一度実行してください。

```
docker push YOUR-USER-NAME/101-todo-app
```

## 4.3. 新しいインスタンスでイメージを実行する
imageが作成され、レジストリにプッシュされたので、PWDに戻り新しいインスタンスを作成して実行してみましょう。

1. PWDに戻り、[新しいインスタンスを追加]をクリックして新しいインスタンスを作成します。
2. 新しいインスタンスで、新しくプッシュしたアプリを起動します。

```
docker run -dp 3000:3000 YOUR-USER-NAME/101-todo-app
```

3.3000のバッジが表示されたらクリックしてください。変更したアプリが表示されます。

# 終わりに
今回のハンズオンはここまでとなります。
次回は以下の内容になります。

- DBの永続化
- マルチコンテナアプリケーション