Dockerも始めました2 ~Kubernetes編~
こんにちは、北野です。今回は、前回のDockerの流れに乗り、コンテナ管理システム「Kubernetes」について調べてみました。
Kubernetesとは
Kubernetesは、Dockerをはじめとするコンテナクラスタ管理システムです(長くてわかりづらい綴りなので「k8s」と略されることもあります)。Go言語で作成され、Apacheライセンスで配布されているオープンソースソフトウェアです(ソースコードは、https://github.com/googlecloudplatform/kubernetesにあります)。
名前はギリシャ語の単語で、ネット上での情報を調べた限り、「クーバネィテス」もしくは「クーベルネィテス」というのが近い読み方のようです。意味は「船の操舵手」なのですが、Dockerのシンボルがクジラなので捕鯨船からの連想なのか、コンテナを運ぶ輸送船からの連想なのかは不明です。
Kubernetesは、Googleの社内で開発・運用されているコンテナクラスタ管理システム「Borg」でのノウハウを結集して作られたもので、2014年6月の公開以降、同社が中心になって開発してきました。ただし、2015年7月にバージョン1.0となったタイミングで、Linux Foundation傘下の組織である「Cloud Native Computing Foundation」(CNCF)に管理が委譲されています。
しばらくはGoogleが開発の中心であることには変わりないと思いますが、CNCFのパートナーにはGoogleのほかにも、AT&T、Cisco、CoreOS、Docker、IBM、Intel、Red Hat、VMwareなど錚々たる企業が名前を連ねており、将来的にはよりニュートラルなコンテナクラスタ管理システムになっていく可能性があります。
蛇足ながら、同じLinux Foundation傘下にはコンテナ技術の標準化を進めることを目的とした「Open Container Initiative」という組織もあり、こちらにはCNCFに参加していないMicrosoft、Amazonが参加している点が興味深いです。
なぜKubernetesが必要なのか
前回ブログでも書いたとおり、Dockerではベストプラクティスとして「1つのコンテナでは1つのプロセスだけを動かす」(Run only one process per container)ことが推奨されています。とはいえ、1つのプロセスだけで機能的なシステムを構築することは難しいので、実際のシステムでは複数コンテナの連携が不可欠となります。
同一マシン内で複数コンテナを連携させるなら、前回説明したlink機能を用いて環境変数を受け渡す方法や、Docker Compose(旧名Fig)を用いる方法があります。また、複数マシンにまたがるコンテナを連携させるなら、トラフィックを転送するだけのコンテナであるAmbassadorパターンを用いる方法や、Docker専用のオーバーレイネットワークを構築するweaveというミドルウェアを利用する方法もあります。
しかし、運用という観点で考えると、コンテナを利用したシステム構築だけでなく障害時の対策なども不可欠となります。Kubernetesは、次のような機能を提供し、より本格的な分散コンテナシステムの構築を実現してくれます。
- フェイルオーバー(コンテナが異常終了したことを検知し再起動させる)
- スケーリング(起動しているコンテナの数を自由に変更できる)
- スケジューリング(コンテナを任意のホストに割り振る)
- ロードバランス(複数のコンテナにリクエストを振り分けて負荷分散する)
- サービスディスカバリ(サービスへのルーティングを自動で提供する)
今回は、これらのKubernetesの機能の中から、フェイルオーバーとスケーリングについて検証してみます。
Kubernetesの構成
Kubernetesを利用した典型的なシステム構成図は次のとおりです。
Kubernetesのアーキテクチャ(Kubernetes Design Overviewより引用)
ただし、Kubernetes独自の用語やなじみの薄いミドルウェアが数多くあるため、これだけでは理解が難しいと思います。構成を把握するために、まずそれらの説明をしておきます。
Kubernetesの用語
コンテナ(Container) 同一ホスト上で動作しているプロセスのリソースを隔離することにより、異なる環境のように見せた仮想環境。
ポッド(Pod) 1つ以上のコンテナを協調動作する単位でまとめたもの。同一ノード(後述)上での動作が前提となる。
ノード(Node) Dockerコンテナのホストとなる物理的もしくは仮想的なマシン。
ミニオン(Minion) マスター(後述)からの指示でポッドを動作させるノード。
マスター(Master) 1つ以上のミニオンで動作するポッドを集中管理するためのノード。
サービス(Service) ポッドの集合によって実現されるシステムの機能を表す抽象的なもの。ロードバランサ的な役割も果たす。
レプリケーションコントローラ(Replication Controller) 定義ファイルに指定されている数を保つようポッドを複製し、サービスの実装を行うもの。
キューブレット(Kubelet) 定義ファイルにしたがってポッドを起動したり、ポッドが起動していない場合は再起動を行ったりするエージェント。
cAdvisor Container Advisorの略で、コンテナのリソース監視を行う。Googleが開発しており、ソースコードはhttps://github.com/google/cadvisorにある。
プロキシ(Proxy) 簡易なL3プロキシ
APIサーバ(APIs) kubectl
コマンドからの命令を受けて、ポッド、サービス、レプリケーションコントローラを操作する機能を提供する。
スケジューラ(Schedular) コンテナをどのミニオン上で動かすかを決定する。
関連ソフトウェア
- etcd
-
etcdは分散型のKVS(Kye-Valueストア)。設定情報の共有とサービスディスカバリを目的として、CoreOS社によってGo言語で作成され、Apacheライセンスで配布されているオープンソースソフトウェア(ソースコードは、https://github.com/coreos/etcdにある)。リーダー(マスター)に障害が発生したとき、「Raft」と呼ばれるコンセンサスアルゴリズムを用いてリーダーを再選出し、信頼性の高い分散環境を実現する。構成図では真ん中の「Distributed Watchable Storage」の部分を担っており、さまざまなミドルウェアから参照されている。
- flannel
-
flannelはetcdをバックエンドにして、コンテナ間通信を可能にする仮想ネットワークを提供する。こちらもCoreOS社がGo言語で作成しており、Apacheライセンスで配布されているオープンソースソフトウェア(ソースコードは、https://github.com/coreos/flannelにある)。Kubernetesは元々同様の機能を備えていたが、特定環境だけでしか利用できなかったため、汎用的に利用できるようにするためflannelを利用するようになった。
コマンド
kubectl
マスター上でkubenetesを操作するための管理コマンド(以前はkubecfg
という名前のコマンドで同様の操作を行っていた)。主要なサブコマンドは次のとおり。
kubectl
の主要なサブコマンド
サブコマンド | 機 能 |
---|---|
create |
ファイル(JSON/YAML)などの定義を元にリソースを作成 |
get |
指定されたリソース(例:pods、services、rc)の状態を表示 |
log |
引数で指定したポッドのログを表示 |
describe |
リソースの詳細情報の表示 |
scale |
多重度の変更など |
delete |
リソースの削除 |
Kubernetesを動かしてみる
では、実際にKubernetesを動かしてみましょう。KubernetesはGoogleが開発していたためか、Google Compute Engine(GCE)上で動かすのが最も手軽で、kubernetesで検索するとGCEを利用した構築例が多く見つかります。
しかし、GCEはAWSのような有料サービスで(ただし、執筆時点では60日間または300ドル分の無料試用の期間があります)、Kubernetesにもう1つ別のレイヤーがかぶさることでKubernetesそのものの理解が難しくなるような気がしたので、今回は(kubernetes-vagrant-coreos-cluster)というVagrant用環境を利用してKubernetes実行環境を構築します。
なお、Vagrantの環境はすでに用意されているという前提で説明していきますので、Vagrant環境の構築が必要な方は、私の過去のブログなどを参照ください。
構築手順
VagrantでKubernetes実行環境の構築する手順は次のとおりです(Windows上で実行しましたが、他OSでも手順は同じです)。
- kubernetes-vagrant-coreos-clusterリポジトリをcloneする。
> git clone git@github.com:pires/kubernetes-vagrant-coreos-cluster.git
- vagrant upで環境を構築する。
> cd kubernetes-vagrant-coreos-cluster > vagrant up
これだけです。少し待たされますが、これでマスター1台(master)、ミニオン2台(node-01、node-02)からなるミニDockerクラスタ環境が構築され、kubectl
コマンドでKubernetesを操作できるようになります。実行ごとにコンテナの配置などは変わりますが、テスト環境構築直後は次の図のような構成になっていました。
角丸の四角がプロセスで、丸がDockerコンテナです。
kubernetes-vagrant-coreos-clusterの構成図
前述したとおり、flannelがetcd経由で他のノードと協調して、コンテナに割り当てるIPアドレス帯を自動で設定してくれます。割り当てるIPアドレス帯の長さは24ビットで、その先頭アドレスがdocker0に割り当てるIPとして/run/flannel/subnet.env
に書き込まれます。
core@master ~ $ cat /run/flannel/subnet.env FLANNEL_SUBNET=10.244.89.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=true
そして、そのIPアドレスをネットワークブリッジのIPを指定するオプションである--bip
の引数としてDockerに渡して起動してくれます。
core@master ~ $ ps auxf | grep bi[p] root 1552 0.0 1.8 288964 19112 ? Ssl 05:28 0:01 docker --daemon --host=fd:// --bip=10.244.89.1/24 --mtu=1450 --ip-masq=false
このように、kubernetes-vagrant-coreos-clusterを利用すれば非常に簡単にテスト環境を構築できますが、一度はKubernetesやetcdを単体でインストールして手動で環境を構築したほうが、それぞれのミドルウェアの仕組みがつかめるのでお勧めします。特に、Kubernetes Advent Calendar 2014は、タイトルにもあるとおり2014年の記事なのですでに古くなっている部分もありますが、Kubernetesがある場合とない場合を比較しながら順を追って説明されていて、より深い理解を得るには非常に参考になると思います。
なお、図中にあるfleetdというプロセスは、ローカルのsystemdに相当する機能をクラスタ化された分散環境上で管理するためのツールです。CoreOSが独自に開発しており、Kubernetesとは直接関係はありません。また、Dockerコンテナとして作動しているSkyDNSはDNSサーバーで、DNSをベースにしたサービスディスカバリに利用されます(バックエンドにetcdを使用)。kube2skyは、KubernetesとSkyDNSのブリッジを行っています。いずれのミドルウェアもバックエンドにはetcdを利用しています。
テスト環境の構築
これでKubernetesのテスト環境が整いました。では、Kubernetesの公式リポジトリに含まれているサンプルプログラムである「Guestbook」を実行してみましょう。この「Guestbook」の構成は前述のKubernetes Advent Calendar 2014-12日目に、とてもわかりやすい図が掲載されています(もちろん、解説もお勧めです)。とはいえ、そのまま掲載させていただくわけにもいきませんので、その図を参考にほぼ同様の内容で再作成させていただいたのが次の図です。
Guestbookの構成図(Kubernetes Advent Calendar 2014-12日目掲載の図を参考に作成)
Kubernetes上でサンプルプログラムを動かす手順は次のとおりです(基本的には、README.mdの手順どおりです。
- Kubernetesの公式リポジトリをcloneして、そこに移動します。
core@master ~ $ git clone git://github.com/GoogleCloudPlatform/kubernetes.git core@master ~ $ cd kubernetes/
- redis(master)のレプリケーションコントローラを作成します。
-f
オプションで用意されている定義ファイルを指定して実行するだけです(当該ファイルのreplicas
エントリの値が「1」なので、実際は多重化はされず、ポッドは1つだけ起動します)。core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-master-controller.yaml replicationcontrollers/redis-master
- redis(master)のサービスを作成します。
core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-master-service.yaml services/redis-master
- redis(slave)のレプリケーションコントローラを作成します(当該ファイルの
replicas
エントリの値が「2」なので、ポッドは2つ起動します)。core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-slave-controller.yaml replicationcontrollers/redis-slave
- redis(slave)のサービスを作成します。
core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-slave-service.yaml services/redis-slave
- frontendのレプリケーションコントローラを作成します(当該ファイルの
replicas
エントリの値が「3」なので、ポッドは3つ起動します)。core@master ~/kubernetes $ kubectl create -f examples/guestbook/frontend-controller.yaml replicationcontrollers/frontend
- frontendのサービスを作成します。
core@master ~/kubernetes $ kubectl create -f examples/guestbook/frontend-service.yaml services/frontend
これで準備は完了しました。それぞれの設定を確認してみます。
- まず、レプリケーションコントローラの確認です。
core@master ~/kubernetes $ kubectl get rc CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS frontend php-redis kubernetes/example-guestbook-php-redis:v2 name=frontend 3 …… redis-master master redis name=redis-master 1 redis-slave worker kubernetes/redis-slave:v2 name=redis-slave 2
- 指定したredis-master、redis-slave、frontendのレプリケーションコントローラが作成されています。
replicas
列の値も指定どおりです。次に、サービスの確認です。core@master ~/kubernetes $ kubectl get services NAME LABELS SELECTOR IP(S) PORT(S) frontend name=frontend name=frontend 10.100.92.59 80/TCP …… kubernetes component=apiserver,provider=kubernetes 10.100.0.1 443/TCP redis-master name=redis-master name=redis-master 10.100.243.72 6379/TCP redis-slave name=redis-slave name=redis-slave 10.100.121.4 6379/TCP
- こちらも指定どおりに起動しています。続いて、ポッドを確認してみます。
core@master ~/kubernetes $ kubectl get pods NAME READY STATUS RESTARTS AGE frontend-cw59s 1/1 Running 0 21m frontend-kv2qe 1/1 Running 0 21m frontend-rdi04 1/1 Running 0 21m …… redis-master-p5g3d 1/1 Running 0 23m redis-slave-ddbro 1/1 Running 0 22m redis-slave-wac7h 1/1 Running 0 22m
- redis-maserが1つ、redis-slaveが2つ、frontendが3つ、指定どおりに起動しています。frontendの詳細情報を確認してみます。
core@master ~/kubernetes $ kubectl describe services frontend Name: frontend Namespace: default Labels: name=frontend Selector: name=frontend Type: ClusterIP IP: 10.100.92.59 Port: <unnamed> 80/TCP Endpoints: 10.244.10.3:80,10.244.30.5:80,10.244.30.6:80 Session Affinity: None No events.
- 上記の詳細情報に記載されているエンドポイント(frontendのサービス表示の「
Endpoints
欄にあるアドレス)にアクセスして、Guestbookが表示されるか確認します。ロードバランサーが用意されている環境なら、それを定義ファイルに指定してエンドポイントにすることもできます。サンプルアプリケーションの表示自体が今回の目的ではありませんので、ここでは簡易的にcurlでの動作確認だけ行います。core@master ~/kubernetes $ curl 10.244.10.3:80 …… <div style="width: 50%; margin-left: 20px"> <h2>Guestbook</h2> <form> <fieldset> <input ng-model="msg" placeholder="Messages" class="form-control" type="text" name="input"><br> <button type="button" class="btn btn-primary" ng-click="controller.onRedis()">Submit</button> </fieldset> </form>
実際にWebブラウザで表示させると、次のようなWebページが表示されます。
Guestbookの画面
擬似障害のテスト
では、いよいよここからが本番です。フェイルオーバーの検証としてポッドと異常終了とノードの異常終了という2つの擬似障害を起こしてみます。検証時は、Guestbookの起動で指定した3つのポッドはすべてnode-02に偏って存在していました。
core@node-02 ~ $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7eccfa01fdc2 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 39 minutes ago Up 39 minutes k8s_php-redis.6169257a_frontend-dy8s4_default_f83b36c8-3a64-11e5-9637-080027d6ab00_a19dbc21 3ff63dfd5d95 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 39 minutes ago Up 39 minutes k8s_php-redis.6169257a_frontend-wd9ku_default_f83b2a4d-3a64-11e5-9637-080027d6ab00_35cb282d f0d01aac2902 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 39 minutes ago Up 39 minutes k8s_php-redis.6169257a_frontend-8wznd_default_f83af1e1-3a64-11e5-9637-080027d6ab00_0ddf7d06 ……
擬似障害1~ポッドの異常終了
ここで一番上にあるポッド(7eccfa01fdc2)をkillします。
core@node-02 ~ $ docker kill 7eccfa01fdc2 7eccfa01fdc2
しばらく待つと、Kubernetesが新しいポッド(3e666e4eee83)を自動で起動させて、設定の多重度である「3」を維持してくれます。
core@node-02 ~ $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3e666e4eee83 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 2 seconds ago Up 1 seconds k8s_php-redis.3b2b255c_frontend-kv2qe_default_25986f1c-3f1a-11e5-bc4c-080027fdae07_82a7f2d5 3ff63dfd5d95 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 42 minutes ago Up 42 minutes k8s_php-redis.6169257a_frontend-wd9ku_default_f83b2a4d-3a64-11e5-9637-080027d6ab00_35cb282d f0d01aac2902 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 42 minutes ago Up 42 minutes k8s_php-redis.6169257a_frontend-8wznd_default_f83af1e1-3a64-11e5-9637-080027d6ab00_0ddf7d06 ……
擬似障害2~ノードの異常終了
続いて、ノードが異常終了したときを想定して、すべてのポッドが実行されていたnode-02をshutdownさせてみます。
core@node-02 ~ $ sudo shutdown -h now
node-01に3つの新しいポッドが起動されました。
core@node-01 ~ $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2989b86292fc kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 19 seconds ago Up 18 seconds k8s_php-redis.6169257a_frontend-0cqew_default_3e7c9054-3a6e-11e5-9637-080027d6ab00_b379f384 a4b6918335ba kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 19 seconds ago Up 19 seconds k8s_php-redis.6169257a_frontend-bld3k_default_3e716c8a-3a6e-11e5-9637-080027d6ab00_69197ef9 c5b6841df416 kubernetes/example-guestbook-php-redis:v2 "/bin/sh -c /run.sh" 19 seconds ago Up 19 seconds k8s_php-redis.6169257a_frontend-1ruo8_default_f4fea493-3a6c-11e5-9637-080027d6ab00_367a52a7 ……
ポッドも確認してみます。
core@master ~/kubernetes $ kubectl get pods NAME READY STATUS RESTARTS AGE frontend-0cqew 1/1 Running 0 7s frontend-1ruo8 1/1 Running 0 9m frontend-bld3k 1/1 Running 0 7s ……
多重度の変更
最後に、スケーリングの検証として多重度の変更も行ってみます。これまで「3」だった多重度を「5」に変更してみます。
core@master ~/kubernetes $ kubectl scale --replicas=5 rc frontend scaled
ポッドを確認すると、5つに増えています。
core@master ~/kubernetes $ kubectl get pods NAME READY STATUS RESTARTS AGE frontend-0wgwc 1/1 Running 0 30s frontend-6g3zp 1/1 Running 0 30s frontend-kv2qe 1/1 Running 1 41m frontend-r113w 1/1 Running 0 2m frontend-x71h9 1/1 Running 0 2m kube-dns-5tof8 3/3 Running 0 4h ……
今度は「1」に減らしてみます。
core@master ~/kubernetes $ kubectl scale --replicas=1 rc frontend scaled
ポッドを確認すると、1つに減っています。
core@master ~/kubernetes $ kubectl get pods NAME READY STATUS RESTARTS AGE frontend-r113w 1/1 Running 0 6m ……
最後に
Kubernetes周りは動きが早いため、ネット上にある説明どおりにやっても意図どおりに動いてくれないということも多々あり、今回の検証はかなり苦労しました。しかし、実際に使ってみると、Dockerコンテナを実運用には欠かせないツールという印象も持ちました。2015年7月にGoogleがOpenStack Foundationのスポンサーになったことからも、今後はOpenStackにKubernetesが統合されていく可能性もあります。今後も、しばらくKuberenetesから目が離せなさそうです。