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 など。
しかしながら、VM/VMイメージの実際の運用はなかなか骨が折れる。まずVMを起動するまでに時間がかかる。パソコンを普通に立ち上げることと変わらない。またカスタマイズしたVMイメージは往々にしてサイズが大きくなる。VMイメージのサイズが1GBを超えることは珍しくない。従って、例えば「VMイメージに梱包したアプリケーションを1行変更したものを、再度VMイメージにしてファイルサーバにあげて、それを本番環境でダウンロードしなおして動かす」なんて運用は、かったるすぎてまずやろうと思わない。
VMの「独立したサーバ環境を提供する」という点は非常に魅力的だ。サーバにインストールされたライブラリのバージョンと、そのサーバで動いている複数のアプリケーションの依存関係を気にする、という考慮にかかる膨大な時間を取り除いてくれる。ただ、もう少し起動時間が短ければ、もう少しサイズが小さければ、もっとその良さを活かせるのに。
この願望をかなえるのがContainerである。このような理由でContainerを"小さなVM"と例えた。
Containerの利点:
- 起動と停止が速い
- Containerイメージが軽量
- Container毎に独立した環境
- どこでも動く
- アプリケーションとミドルウェアのデプロイが同じ操作で行える
以下では、上にあげた利点を説明する。尚、Containerと呼ばれるものは沢山あるが、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など。例えば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)、JenkinsのようなContainerのCI/CDツールは沢山ある(例えばJenkins Xなど)。開発者はGitHubにコードをpushするだけで、勝手にContainerが作成され、リリースまでやってくれる。
設計
Containerの良さは起動の速さや軽量さにあることを説明した。従って、Containerを自分で作るときは、軽量さを意識しなければ、Containerの良さを引き出せない。
Containerを軽量にしたいときにもっともシンプルな考え付く方法は、アプリケーションをいれないことだ。全くアプリケーションをいれなければ意味がないので、Container作成で言われているよい設計は「一つのContainerにつき、小さなアプリケーション一つ」という考え方だ。
また、Containerは動かしているときにログインして設定を変えることを想定していない。先のデモ、psやviなどの基本的なコマンドがないContainerもあることがそれを示唆している。Containerをアップデートするときは、Containerを一度停止、removeして、再度デプロイしなおす、という運用が想定されている。
Containerの出現により、設計のあり方もこれまでと変わる。これはmicroserviceアーキテクチャとして、別途記載する。
Dockerを取り巻く状況
色々な企業がDockerをいれている。例えばPaizaという、オンラインプログラミングサイトは、問題に対してユーザが回答したコードを評価するための実装として、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の登場である。