【Docker+Cloud Run勉強会】1. Dockerとは？

# この記事
先日Docker及びCloud Runについての勉強会を開催しました。
本記事は、その時用意した資料となります。

- [2. DockerでWordPressをデプロイする](https://qiita.com/r-dohara/items/c159d258fc4b0b3c09a2)
- [3. Cloud RunにWordPressをデプロイする](https://qiita.com/r-dohara/items/3d332c4e83e27b4517e7)

[YouTube](https://www.youtube.com/watch?v=bbmyN6--iJI)

# 目的
改めて、コンテナとは？というところから説明しつつ

- 代表的なコンテナエンジンであるDocker
- Google CloudのコンテナデプロイサービスであるCloud Run

について学んでもらえたらと考えています

# そもそもコンテナとは？
## そもそもそもそも仮想サーバ（VM）とは？
物理マシン（サーバー、PCなど）の中に、複数のOS（ゲストOS）を配置する技術です

大きく2種類の実装方法があります

![Docker+Cloud Run-vm.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2697194/1ec39d0c-7bb9-806f-0310-09b157f60f8f.png)

Google Cloud (GC)のGoogle Compute Engine (GCE)やAWSのEC2もベアメタル型ハイパーバイザーが用いられています

例えるなら「ビジネスホテル」？

- 各部屋ごとにシャワーやトイレを用意
- 内装はある程度自由に設計可能（カスタマイズ性が高い）
- 室内において、顧客はかなりプライベートな時間を過ごせる

**課題**
- 毎回ゲストOSが起動するため、オーバーヘッドが大きい（時間、リソース）
- ポータビリティ（物理サーバに比べたら遥かに高いですが）
    - ポータビリティ : 他の環境に移植して動作させる容易さ（= 移植性）
    - 開発はローカル、本番はクラウドなどの場合、ちゃんと環境を揃えるのが大変

## ということでコンテナ登場
VMであげた課題の解決策として、コンテナが注目されました

![Docker+Cloud Run-container.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2697194/36b8381e-b130-97a2-e545-49f97e243425.png)

コンテナとは、各アプリが使用するCPUやメモリ、ID情報（プロセスのIDであるPIDなど）を隔離させ、独立した環境を作り出す技術のことです
なので、1つのOSの中に作り出された、アプリ専用の独立した空間がコンテナとなります

※ 細かい話
僕が知る限りでは、コンテナはLinuxの機能を用いて実装されています
（更に細かいことをいうと、Linuxカーネルであるnamespaceやcgroupなど）
なのでLinux以外のOS（Windows、UNIX）では動きません
Docker Desktop for Macは地味にハイパーバイザーを使ってます（[参考](https://docs.docker.jp/docker-for-mac/faq.html#mac-components-of-docker-desktop))

例えるなら「防音個室のネットカフェ」？

- シャワーやトイレは共用（低コスト）
- 備品はある程度決まりきっている
- 室内において、顧客はある程度プライベートな時間を過ごせる

**利点**
- 新しくゲストOSを用意してないため軽量
- ポータビリティ
    - ホストOSがLinuxベースであれば基本的にはどこにでもデプロイできる（絶対ではない）
        - 必要なミドルウェアや設定をカプセル化して配布できる
        - Linuxの特性を活かし、コンテナエンジンがカーネルの差異をカバー

勿論VMのほうがコンテナより勝っている点もあります

## コンテナとDockerの位置づけ
Dockerとはコンテナを実現するための1技術です
地味にややこしいのですが「Docker ≠ コンテナエンジン」です
正確には「Docker ⊂ コンテナエンジン」です

ただ、現時点ではDockerがコンテナエンジンの事実上の標準（デファクト・スタンダードっていうらしい）みたいなところもあります

# Dockerでコンテナをデプロイするには
ここからは基本的にDockerを使うことを前提として話を進めます
コンテナといってるのもDockerコンテナのことだと思ってください

まずは一回デプロイしてみましょうか

※ Dockerのインストールが必要です
MacだとDocker Desktop for Macなどが候補となりますが、こちら企業レベルでの使用は有料になってます

```
docker run hello-world
```

すると以下のような出力がされると思います

```
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:13e367d31ae85359f42d637adf6da428f76d75dc9afeb3c21faea0d976f5c651
Status: Downloaded newer image for hello-world:latest

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.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

これでコンテナがデプロイされ、アプリを実行したあと、その役目を終えて停止しました
`docker ps`コマンドで現存しているコンテナを確認してみましょう
オプション`-a`をつけると起動中、停止中全てのコンテナが確認できます

```
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
1bb59d63131f   hello-world   "/hello"   36 seconds ago   Exited (0) 35 seconds ago             clever_buck
```

STATUSが`Exited …`となっていますが、つまりは停止中ということです

上記のことについてこれから説明していきます

## コンテナの特徴 : 揮発性
コンテナの特徴なんて言い出すときりがないとは思いますが、
まず意識しないといけないことは**揮発性**だと思います

コンテナにおける揮発性とは、コンテナの中の変更は保存されず、コンテナ破棄時には全て消えてしまうという特性です
もちろんコンテナが破棄されない限りは消えないですが

データを永続化したい場合は、ボリュームの設定・指定をしてあげる必要があります

## Dockerイメージ
全てのコンテナはDockerイメージから作成されます
Dockerイメージとはコンテナの「土台」らしいです

まぁ「冷凍食品」が近いのかなと勝手に思ってます

- ほぼ完成していて、あとは解凍するだけ
- 解凍して食べられる状態になったもの ≒ コンテナ

冷凍食品は食べたら無くなりますが、Dockerイメージは消えません

もうちょっとカチッとしたものだと「スナップショット」に近いんですかね
ただ、Dockerイメージは1から作成もできるので若干ずれてる気もしますが・・・

話を戻して、
さっきの例にしても、「hello-world」というDockerイメージをもとにコンテナが作成されてます
そんなもの用意してないと思いますが、このイメージは[Docker Hub](https://hub.docker.com/_/hello-world)から取得されてます

Docker Hubは名前から想像できる人もいるかも知れませんが、Dockerイメージ版のGitHubの様なもので、
クラウド上のレジストリサービスです（Dockerイメージを格納、配布できるサービス）
Dockerイメージは、レジストリの中に存在するリポジトリに格納していきます
更に1つのリポジトリ内には複数のDockerイメージが格納可能で、それらはタグで区別されます
今回でいうと、以下のようになります

- レジストリ : Docker Hub
- リポジトリ : hello-world
- タグ : latest (タグは指定しないとlatestとなります)

なので改めると、`docker run hello-world`を実行すると

1. Docker HubからDockerイメージ「hello-world」をダウンロード
2. Dockerイメージ「hello-world」からコンテナを作成
3. Dockerイメージによって定義されたコマンドを実行
4. コマンドが終了したのでコンテナも終了（破棄ではない）

といったことが行われます

## Dockerfile
コンテナはDockerイメージから作成されることはわかってもらえたかと思います
では、そのDockerイメージはどうやって作るんでしょうか？

コンテナからDockerイメージを作成する方法もありますが（これこそまさにスナップショット？）、
Dockerfileというもので1から作成することもできます

さっきのDockerイメージ「hello-world」のDockerfileは以下のとおりです

```
FROM scratch
COPY hello /
CMD ["/hello"]
```

見慣れない文字列が一気に出てきたかと思いますが、基本的にはこのように
`INSTRUCTION arguments`といったものを並べていきます（[公式ドキュメント](https://docs.docker.com/engine/reference/builder/#format)）

`FROM`はベースイメージを指定する命令です
Dockerfileは、まずは基盤となるイメージを引っ張ってきてからその上に記入をしていくという流れになります

`COPY`は、DockerfileからDockerイメージを作成するときに、指定したファイルをDockerイメージの中にコピーする命令です
このDockerfileの[GitHub](https://github.com/docker-library/hello-world/tree/3332fbee4210b41738d83f6cfdc301a42b96e30d/amd64/hello-world)を見ると、同じフォルダ内に「hello」が存在していますね
「hello」自体はC言語で作成された、先程のメッセージを出力するアプリのようです

最後に`CMD`で、コンテナ起動時に実行させるコマンドを指定します

これがDockerイメージ「hello-world」を形作っている全てです

上で紹介した3つの命令以外にも、任意のコマンド処理を挟むことができる`RUN`命令などはよく使います

# 参考
- [よくある質問と回答 [FAQ]](https://docs.docker.jp/docker-for-mac/faq.html)
- [hello-world](https://hub.docker.com/_/hello-world)
- [Dockerfile reference](https://docs.docker.com/engine/reference/builder)
- [docker-library/hello-world](https://github.com/docker-library/hello-world/tree/3332fbee4210b41738d83f6cfdc301a42b96e30d/amd64/hello-world)