KubernetesからみたDocker

# Kubernetesとは？

**Containerの利点を引き上げるもの。**

間違ってはいないが中身の無い回答である。まずはKubernetes云々という前にContainer(Docker)について考える。

# Containerとは？

**Containerとは小さなVMである**という比喩から始めよう。

VMと言えば、VMWareやVirtualBox等のハイパーバイザーを使って、サーバ上で別のOSを動かす技術のことであるが、これの良さは、それぞれのVM毎に独立して、かつカスタマイズしたサーバ環境を容易に実現できるところにある。例えば以下のような使い方ができる。

* あらかじめ開発に必要なjavaやDBをインストールしておいたVMを、OVAなどのVMイメージとしてチームメンバーに配布する。チームメンバーは手元のPCでそのVMイメージをもとにVMを立ち上げる。このようにすれば、チームメンバー間の開発環境の差異(OSによる挙動の差異やらインストールしたミドルウェアのバージョンの違いなど)を意識せず、開発に取り組める。気に入らなければ、壊して作り直せばよい。
* ソフトウェアを予めセッティングしして本番稼働可能な状態でVMイメージとして保存しておく。顧客のリクエストがあれば、そのVMを起動するだけでデプロイ完了。
* NginxやMariaDBをチューニングしまくって、超高速にWordPressを動かせるサーバ環境を作った。これをVMイメージとして公開して、多くの人が手元のVMで使用できる状態にする -> 例えば [KUSANAGI for AWS](https://kusanagi.tokyo/cloud/kusanagi-for-aws/) など。

しかしながら、VM/VMイメージの実際の運用はなかなか骨が折れる。まずVMを起動するまでに時間がかかる。パソコンを普通に立ち上げることと変わらない。またカスタマイズしたVMイメージは往々にしてサイズが大きくなる。VMイメージのサイズが1GBを超えることは珍しくない。従って、例えば「VMイメージに梱包したアプリケーションを1行変更したものを、再度VMイメージにしてファイルサーバにあげて、それを本番環境でダウンロードしなおして動かす」なんて運用は、かったるすぎてまずやろうと思わない。

VMの「独立したサーバ環境を提供する」という点は非常に魅力的だ。サーバにインストールされたライブラリのバージョンと、そのサーバで動いている複数のアプリケーションの依存関係を気にする、という考慮にかかる膨大な時間を取り除いてくれる。ただ、もう少し起動時間が短ければ、もう少しサイズが小さければ、もっとその良さを活かせるのに。

この願望をかなえるのがContainerである。このような理由でContainerを"小さなVM"と例えた。

Containerの利点：

* 起動と停止が速い
* Containerイメージが軽量
* Container毎に独立した環境
* どこでも動く
* アプリケーションとミドルウェアのデプロイが同じ操作で行える

以下では、上にあげた利点を説明する。尚、Containerと呼ばれるものは[沢山ある](https://en.wikipedia.org/wiki/Container_(virtualization))が、kenji-kondoはDockerしか知らないので、以下Doockerに限った話しかしない。

# Dockerの利点

## 起動停止の速さ

実際にdockerを動かしてみる。

```
[vagrant@base ~]$ docker run centos echo "Hello World"
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
a02a4930cb5d: Pull complete
Digest: sha256:184e5f35598e333bfa7de10d8fb1cebb5ee4df5bc0f970bf2b1e7c7345136426
Status: Downloaded newer image for centos:latest
Hello World
```

`docker run centos echo "Hello World"`というコマンドだけで、以下のようなことが行われた。

* CentOS7ベースのContainerイメージをインターネット上のrepositoryからダウンロード
* Containerを起動
* `Hello World`をecho
* Container停止

Containerイメージを取得する部分でkenji-kondoの自宅のネットワーク環境では30秒ほどかかった。この時点でmvnでjavaのアプリケーションをビルドするよりも速い。

Containerイメージはローカルに存在しなければインターネット上から探して取得するようになっている。なので、すでにダウンロードしておいた状態でもう一度同じコマンドを実行すると、Container起動 -> `Hello World`をecho -> Container停止 の動作しか行わない。

```
[vagrant@base ~]$ time docker run centos echo "Hello World"
Hello World

real    0m2.304s
user    0m0.094s
sys     0m0.116s
```

Container起動 -> `Hello World`をecho -> Container停止 が2.3秒で完了した。

## 軽量

先ほどダウンロードしたContainerイメージのサイズを確認する。

```
[vagrant@base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              1e1148e4cc2c        2 months ago        202MB
```

`SIZE`のところに注目。202MBしかない。Centos7のminimal.isoでも900MBほどある。(https://www.centos.org/download/)

しかも、この202MBというのは、Containerとしては**大きい部類である**。さらに軽量なContainerイメージだと、以下のように1.2MBのものもある。

```
[vagrant@base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
busybox             latest              d8233ab899d4        10 days ago         1.2MB
centos              latest              1e1148e4cc2c        2 months ago        202MB
```

これほど軽量にできる理由の一端を示す。動かしたContainerにログインして、`/etc/`以下のファイルを除いてみる。

```
[vagrant@base ~]$ docker run -it redis /bin/sh
/ #
/ # ls -l /etc/
total 28
-rw-rw-r--    1 root     root           307 Jan 30 15:40 group
-rw-r--r--    1 root     root            13 Feb 25 16:57 hostname
-rw-r--r--    1 root     root           174 Feb 25 16:57 hosts
-rw-r--r--    1 root     root           127 Dec 31 09:43 localtime
lrwxrwxrwx    1 root     root            12 Feb 25 16:57 mtab -> /proc/mounts
drwxr-xr-x    6 root     root            79 Feb 14 18:58 network
-rw-r--r--    1 root     root           340 Jan 30 15:40 passwd
-rw-r--r--    1 root     root            78 Feb 25 16:57 resolv.conf
-rw-------    1 root     root           243 Jan 30 15:40 shadow
```

10個しかない。普通のサーバだとデフォルトで150～200個ほどある。また別のContainerにログインして`ps`コマンドをたたいてみる。

```
[vagrant@base ~]$ docker run -it redis  /bin/sh
# ps
/bin/sh: 1: ps: not found
# vi
/bin/sh: 3: vi: not found
```

psもviもできない。

このように、普通のサーバでは当然あるだろうものがない。本当にアプリケーションを動かすためだけに必要なものだけをContainerに詰め込んでる、というかんじ。だからサイズが小さくなる。

## 独立した環境

これはVMの利点と同じ。例えば`Container_A`にpython27のアプリを詰め込み、かつ`Container_B`にpython36で動くアプリを詰め込んで動かす。サーバから見ればいずれもただのDockerのプロセスにしか見えていない。サーバにpythonがインストールされていなくても動く。「python27がインストールされたサーバでpython36を動かしたいから、インフラエンジニアと調整してpyenvやvirtualenvで仮想python実行環境を作ったぜ」という配慮が必要なくなる。

## どこでもなんでも動く

"Write once, run anywhere."というjavaの標語のように、Dockerにも"Build once, run anywhere."という標語がある。これが示すように、Dockerもdockerがインストールされた環境ならばどこでも動くことを目指して設計・実装されている。

javaと違うのは、どこでも動く主体がjavaだけではないことだろうか。pythonでもrubyでも、はたまたnginxやredisでも、Containerイメージとなったものならば、確かに動く。

またアプリケーションやミドルウェアによらず、オペレーションが"Containerへの操作"で統一できるところも、俗人化の排除を手助けしてくれる。

nginxのContainerを動かす:

```
[vagrant@base ~]$ sudo docker run -d -p 80:80 nginx
854ce9264ed5f834309fefc4912735c479264f57c64bf5bf868a21ed8d83e7a7

[vagrant@base ~]$ curl -s localhost | head -n4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
```

`curl localhost:8080`で`Hello Docker`と返すだけのSpring Bootアプリケーションが梱包されたContainerを動かす：

```
[vagrant@base ~]$ docker run -d -p 8080:8080 kenchaaan/myjava
ad4ac112c83b82cd38145563243a00b6e5823496a6057a9f4e9244b34224f54c

[vagrant@base ~]$ curl localhost:8080
Hello Docker
```

# Dockerを使った開発と設計

ここからはDocker周辺の話題を。

## 開発

DockerでContainerイメージを作るときは、手動/自動にかかわらず、以下の操作がどこかで行われるはずだ。

* ContainerをBuildする。
* ContainerをregistryにPushする。

ContainerのBuildにはDockerfileを用いる。詳解はしないが、例えばjavaのアプリケーションを梱包したDockerのContainerイメージを作成するには、まず以下のようなDockerfileを作成する。

```
FROM fabric8/java-centos-openjdk8-jdk:1.5
ENV JAVA_APP_DIR=/deployments
EXPOSE 8080 8778 9779
COPY maven /deployments/
```

これを以下のbuildコマンドでDockerのContainerイメージを作成する。

```
[vagrant@base build]$ docker build -t kenchaaan/dockerapp .
Sending build context to Docker daemon  16.68MB
Step 1/4 : FROM fabric8/java-centos-openjdk8-jdk:1.5
 ---> 8a328b3d3aa8
Step 2/4 : ENV JAVA_APP_DIR=/deployments
 ---> Using cache
 ---> 50f4cae3cd9c
Step 3/4 : EXPOSE 8080 8778 9779
 ---> Using cache
 ---> 814a42ccd82b
Step 4/4 : COPY maven /deployments/
 ---> 0bc9ced02477
Successfully built 0bc9ced02477
Successfully tagged kenchaaan/dockerapp:latest
```

イメージができあがる。

```
[vagrant@base build]$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
kenchaaan/dockerapp                latest              0bc9ced02477        9 seconds ago       434MB
```

これをregistryにpushする。registryというのはGitHubのContainerイメージ版といったもので、作ったContainerイメージはここに保存する。有名なregistryは[Docker Hub](https://hub.docker.com/)など。例えばNginxやRedisのContainerイメージも置いてある。Nginxがrpmを提供するようなかんじで、ほとんどのソフトウェアがすでにContainerイメージを準備してくれている。

```
[vagrant@base build]$ docker push kenchaaan/dockerapp
The push refers to repository [docker.io/kenchaaan/dockerapp]
2c2915e2962a: Pushed
7aa6c4d600b7: Mounted from kenchaaan/myjava
f5c2b7b376f1: Mounted from kenchaaan/myjava
4e3206c5f5d6: Mounted from kenchaaan/myjava
3e51ff6f922f: Mounted from kenchaaan/myjava
b19c5338c05c: Mounted from kenchaaan/myjava
d930d319e704: Mounted from kenchaaan/myjava
9f59ad7680b3: Mounted from kenchaaan/myjava
f1bcfa48d09e: Mounted from kenchaaan/myjava
f4796ab1c9c9: Mounted from kenchaaan/myjava
latest: digest: sha256:c7bd45346655c3a297d2a907f153c6a979f408e35511fc84f86346c32a2eb380 size: 2409
```

するとこのようにregistry上にpushされたことが確認できる。

```
https://cloud.docker.com/repository/docker/kenchaaan/dockerapp
```

以上がContainerのbuildとpushだ。これをやれば、例えば別の環境で作成したContainerを動かしたければ、以下のようにrunコマンドを実行すればよい。

```
docker run -d -p 8080:8080 kenchaaan/dockerapp
```

上の例は最も原始的な操作かもしれない。実際は自動ビルドツールを使って開発を行うことが主となるだろう。例えばDockerfileを記述する手間を省くためのmaven-pluginも存在するし([fabric8](https://maven.fabric8.io/))、JenkinsのようなContainerのCI/CDツールは沢山ある(例えば[Jenkins X](https://jenkins.io/projects/jenkins-x/)など)。開発者はGitHubにコードをpushするだけで、勝手にContainerが作成され、リリースまでやってくれる。

## 設計

Containerの良さは起動の速さや軽量さにあることを説明した。従って、Containerを自分で作るときは、軽量さを意識しなければ、Containerの良さを引き出せない。

Containerを軽量にしたいときにもっともシンプルな考え付く方法は、アプリケーションをいれないことだ。全くアプリケーションをいれなければ意味がないので、Container作成で言われているよい設計は「一つのContainerにつき、小さなアプリケーション一つ」という考え方だ。

また、Containerは動かしているときにログインして設定を変えることを想定していない。先のデモ、psやviなどの基本的なコマンドがないContainerもあることがそれを示唆している。Containerをアップデートするときは、Containerを一度停止、removeして、再度デプロイしなおす、という運用が想定されている。

Containerの出現により、設計のあり方もこれまでと変わる。これはmicroserviceアーキテクチャとして、別途記載する。

# Dockerを取り巻く状況

色々な企業がDockerをいれている。例えば[Paiza](https://paiza.jp/)という、オンラインプログラミングサイトは、問題に対してユーザが回答したコードを評価するための実装として、Dockerを活用している -> https://www.slideshare.net/paiza_official/paizadocker

以前は金融でDockerを使うなんて狂気の沙汰だといわれていたこともあった。けれど最近ではvisaもDockerを使い始めた。https://www.publickey1.jp/blog/17/visadockerdockercon_2017.html

Dockerを使うことによって新たにでてくる問題というのも多く語られている。「docker デメリット」とかでググるとよい。

# Dockerだけではできないこと

Dockerだけでは以下のことをすることができない、あるいはこれをするために自分で仕組みを作らなければならない：

* Containerのローリングアップデート
* Containerを動かすホストのリソース管理、スケジューリング
* Containerの死活監視と、セルフヒーリング(自動復旧)
* ...

ここでKubernetesの登場である。