現在位置: ホーム / よろずブログ / Dockerも始めました2 ~Kubernetes編~

Dockerも始めました2 ~Kubernetes編~

前回のDockerの流れに乗り、コンテナ管理システム「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でも手順は同じです)。

  1. kubernetes-vagrant-coreos-clusterリポジトリをcloneする。
    > git clone git@github.com:pires/kubernetes-vagrant-coreos-cluster.git
    
  2. 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の手順どおりです。

  1. Kubernetesの公式リポジトリをcloneして、そこに移動します。
     core@master ~ $ git clone git://github.com/GoogleCloudPlatform/kubernetes.git
     core@master ~ $ cd kubernetes/
    
  2. redis(master)のレプリケーションコントローラを作成します。-fオプションで用意されている定義ファイルを指定して実行するだけです(当該ファイルのreplicasエントリの値が「1」なので、実際は多重化はされず、ポッドは1つだけ起動します)。
     core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-master-controller.yaml
     replicationcontrollers/redis-master
    
  3. redis(master)のサービスを作成します。
     core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-master-service.yaml
     services/redis-master
    
  4. redis(slave)のレプリケーションコントローラを作成します(当該ファイルのreplicasエントリの値が「2」なので、ポッドは2つ起動します)。
     core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-slave-controller.yaml
     replicationcontrollers/redis-slave
    
  5. redis(slave)のサービスを作成します。
     core@master ~/kubernetes $ kubectl create -f examples/guestbook/redis-slave-service.yaml
     services/redis-slave
    
  6. frontendのレプリケーションコントローラを作成します(当該ファイルのreplicasエントリの値が「3」なので、ポッドは3つ起動します)。
     core@master ~/kubernetes $ kubectl create -f examples/guestbook/frontend-controller.yaml
     replicationcontrollers/frontend
    
  7. frontendのサービスを作成します。
     core@master ~/kubernetes $ kubectl create -f examples/guestbook/frontend-service.yaml
     services/frontend
    

 これで準備は完了しました。それぞれの設定を確認してみます。

    1. まず、レプリケーションコントローラの確認です。
      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
      
    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
      
    3. こちらも指定どおりに起動しています。続いて、ポッドを確認してみます。
      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
      
    4. 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.
      
    5. 上記の詳細情報に記載されているエンドポイント(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から目が離せなさそうです。