kubernetes(読み方はクバネティス)は、多数のコンテナをクラスターとして一元管理するツールです(kとsの間に8文字あるため、”k8s”と略記されることもあります)。
原語”κυβερνήτης”[ギリシャ語]は「操舵手」の意味で、dockerやコンテナなど船に関連したモチーフをとっています(なお、サイバネティクスも同語源のようです)。
単機能のコンテナをつなげてシステムを組み上げる「オーケストレーション・ツール」と呼ばれる分野の管理ツールで、Googleが主体的に開発に参画しているオープンソース・ソフトウェアです。
dockerは実行環境をポータブルにコンテナ化する機能を持ち、kubernetesはコンテナの稼働継続やコンテナ間ネットワーク、ストレージなどの機能を提供しています。
kubernetesとdocker-composeが競合していましたが、kubernetesがNo.1のスピードで成長したため、今となってはDocker for Macなどdockerのディストリビューションにもkubernetesが同梱されています。
単体ツールとして実行する分にはdockerで足りますが、Webアプリケーションを構築する目的ではkubernetesのようなクラスタ管理ツールが必要になります。
kubernetes on dockerのメリット
kubernetesは、開発者から見るとdockerコンテナを実行するproduction(本番実行)環境に見えます。
複数台構成のWebサービスを作るために従来は1つひとつのサーバーや仮想マシンをOSレベルで設定する作業が欠かせませんでした。
kubernetes + dockerの環境では、ネットワーク設計や初歩的なクラスタリングがkubernetesで自動化され、OSの構成管理はdockerのコンテナ・イメージ管理に集約されます。
これによりシステム構築の標準化が進み、Apache / nginx / PHP / ruby / MySQL / PostgreSQL / redis / memcached /… といったミドルウェアレベルまで90%程度設定完了した状態からスタートできるようになります。
たとえばWebサービスをビジネスとして提供したい場合、拡張性や耐障害性を確保するためのネットワーク構成がハードルになりがちですが、kubernetesを使うと少なくとも60点程度の環境は比較的簡単に入手できます。
また、kubernetesには自動でサービスレベルを維持する機能があり、node(稼働サーバー)に余裕がある限り、あらかじめ指定した数のコンテナ数を起動する挙動になります。じっさいにnodeのサーバーをシャットダウンした場合、別のnodeサーバーでコンテナ作成・起動を経てサービスが再開されます。(そしてその後、nodeも再作成されて新たなコンテナ生成リクエストを待ち受けます)
Google Kubernetes EngineではpersistentDiskも新nodeで再マウントされるため、データベースでも一定の稼働水準を得られます。
そもそも開発したWebアプリケーションが流行するのかどうかは不確定なことが多く、インフラに足をとられないということは立ち上げの環境として非常に助かります。
kubernetesのシステム構築・運用手順
kubernetesのあるインフラ環境では、以下のようなシステム構築の手順となります。
新しいコンテナを作成する際のおおまかな流れは以下のとおりです。
- コンテナイメージを作成する。典型的には、一般的なdockerの手順と同じで、Dockerfileを編集して
docker build
コマンドを実行する。 - kubernetesからアクセスできる場所(レジストリ)にコンテナを置く。パブリック・レジストリで良ければdocker hubの無料アカウントが使えるが、事実上 プライベート・レジストリが必要になる。鍵ファイルやパスワードをイメージに同梱する場合など。
- kubernetesの設定ファイル(YAML形式)を作成する。多くのケースでは Replication ControllerとServiceをコンテナ別に定義する
kubectl create
コマンドでコンテナ群を起動する。
運用フェーズでも基本的なフローは同じです。コンテナイメージを更新してレジストリにプッシュ、kubectl delete
で古いコンテナを削除し、kubectl create
で新しいコンテナ群を起動します。
イメージ更新の際は、Replication ControllerやServiceの設定ファイルは更新する必要はありません。
なお、インフラ運用の概要については、「 kubernetesのnode運用の具体例」を参照してください。
kubernetesリソース管理の特長
kubernetesは、コンテナの起動設定ファイルでCPU・メモリーの割り当て量を設定できます。
たとえばJavaアプリケーションのように起動時にメモリを獲得するようなコンテナでメモリを制約すると、起動時にmallocのエラーが出力されコンテナ自体の起動が失敗することから、コンテナ単位で資源管理が動作していることを確認できます。
また、docker/kubernetesのコンテナは仮想マシン(VM)と異なり、OSイメージ部分の物理メモリの重複がなく、コンテナ内で動作しているアプリケーションの必要メモリの消費で基本的には済んでいます。
軽量なアプリケーションでは、docker stats
コマンドで確認すると、数MB程度のメモリで動作しているもののもあります。
ただし、リソースのクオータ設定にどのような実数値を設定すべきかを把握することは難しいため、物理サーバーのメモリを限界まで使い切る方向でチューニングすることは効果的ではありません。
起動時設定のカスタマイズは環境変数で
dockerやkubernetesのコンテナイメージは、作成する際に設定ファイルも含めて焼きこまれてしまいます。
起動時に設定をカスタマイズしたい部分は、Linuxの環境変数を使います。
docker run -e
オプションでコンテナ起動コマンドに指定できるほか、kubernetesの設定ファイル(envディレクティブ)や、docker-composeの設定ファイル(environmentディレクティブ)でも、シンプルなkey, valueの組み合わせ形式で指定できます。
コンテナ内の設定ファイルで環境変数を利用する方法については、PHPやRubyなど利用するツールや言語ごとの標準的な手順にしたがいます。
また、実際にどのような環境変数が指定されたかを確認するには、コンテナ内(またはdocker exec
コマンド)で、setコマンドを利用します。
出力リストに適切に環境変数が設定されている場合は、アプリ側の設定に難があります。
このほか、dockerでは、configファイルを予めイメージから取り出してカスタマイズしたものを起動時にCOPYするという手法もありますが、configを別途管理する必要が出てきます。
ただし、環境変数にも限界はあり、UTF-8で日本語の値をセットしてreplication controllerを起動したところ、異常終了しました。
Google Kubernetes Engineでセットアップ済の環境を使える
Google Kubernetes Engineを利用すると、セットアップ済みのkubernetesをすぐ使えます。基本構成では、Compute EngineのVM(+Persistent disk 100GB)が3台分とロードバランサーが課金対象になっているようです。
GKEは最新のkubernetesへのキャッチアップが早く、現状、kubernetesの最新リリースが切られるとほぼ同じタイミングでGKEでも利用可能になります(GKEの最新動向は、GKEサイトの Release Notesで確認できます)。
また、CLIツールのkubectl
はgcloudツールとともに配布されるため、GKEのエコシステムに乗っている限りkubernetesのツールチェーンを意識してインストール管理しなくてすみます。
dockerイメージ流用のポイント
kubernetesはdockerをホスティングする環境でもあるため、単体のコンテナを動作させる場合にはdockerのイメージをそのまま動作させられます。
実用的な構成では、マイクロサービスのように複数コンテナを接続して用いるケースが多くなりますが、この場合のポイントはコンテナ間接続にdocker互換の環境変数を使うことです。
docker互換の環境変数とは、docker runの–linkオプションで定義されるPG_PORT_5432_TCP_ADDRやMYSQL_PORT_3306_TCP_PORTのようなものです。この例の先頭のPGやMYSQLは–linkでインポートする際につけるコンテナ名称です。
kuberunetesも、Serviceの定義ファイルのnameやportの情報にもとづいて同じルールで環境変数がエクスポートされます。
アプリケーション側の接続設定にこの環境変数を利用することで、ローカル環境ではdocker、プロダクションではkubernetesというような異種環境でも同じコンテナイメージが動作します。
kubernetesのServiceはルーティングの機能を提供しているため、バックエンドのサービスを再起動しても接続先が変わりません。
dockerの場合は、単にdocker run --link
時の仮想IPが環境変数に埋まっているだけなので、バックエンドを再起動したらリンクしている側も再起動しないと接続が切れます。
アプリケーション・ログを標準出力に
podが起動しないケースには、大きく分けてサーバーリソースが不足している場合と、設定が適切でない場合の2系統があります。
リソース不足は特定しづらい部分があるため、設定が適切に完了していることを確保することが先決です。
デバッグのためには、コンテナで動作させるアプリケーションの設定でログを標準出力(/dev/stdout)・標準エラー(/dev/stderr)に出力されるよう設定しておくことが重要です。
これにより、kubectl logs your-pod-name
でエラー出力できます。アプリケーション標準設定のままでコンテナ内のログファイルにエラー出力されていると、podが起動していないためログを取り出せなくなります。
この点はdockerコンテナのデバッグと同じです。
ダイイングメッセージlogの追いかけ方
アプリケーションのバグが原因で起動しない場合にもコンテナは異常終了します。kubernetesのreplication controllerは再起動を繰り返しますが、異常終了したコンテナはkubectlコマンドで操作できないためlogを追うことが難しくなります。
この場合、アプリケーションログをdockerのVOLUMEに出力する設定であれば、ホストOSの/var/lib/docker/以下に残っているケースがあります。
一例として、以下のような手順でアプリのダイイングメッセージを特定できました。
kubectl get pods
でpod名を特定kubectl describe pod some-pod-name
の出力から実行nodeを推定- コンテナが実行されているnodeのホストOSにSSHログイン
docker ps -a | grep dockerイメージ名の一部
で異常終了したコンテナIDを探す。(なければ別のnodeにSSHログインして同様に探す)docker inspect コンテナIDの一部
でログのVOLUMEの実PATH(/var/lib/docker以下のどこか、など)を特定する- VOLUMEの実PATHにあるログを読む
また、アプリケーションログからもエラーの痕跡がうかがえない場合、リソース不足が疑われます。
リソース不足のケースは判然としないのですが、docker inspect
した際に、ExitCodeが137となっている場合メモリ不足を疑います。Unixの終了コードの読み方としてこれはkill -9
相当であるため、dockerのプロセスにより停止されたのだろうと推定しています。
"State": {
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 0,
"ExitCode": 137,
"Error": "",
"StartedAt": "2015-12-08T16:51:49.36859284Z",
"FinishedAt": "2015-12-08T20:18:37.206511143Z"
},
kubernetesのリソース管理
kubernetesでは起動用の設定ファイルでpodコンテナごとのCPU・メモリ割当てを設定できます。CPUは実行時間をミリ秒単位で、メモリはメガバイト単位で指定します。
各コンテナのリソース量の合計値が、1台のサーバーの容量を超えると、その後にkubectl create
で起動したpodがpending状態のまま起動待ちになります。
メモリについては、単純に足し算をした容量と実サーバーのメモリ量の比較で判定されます。CPU時間については、1台のサーバーの容量は1000ミリ秒として計算されます。
ただし、現状のこの機構でコンテナごとの性能をきめ細かく制御することは難しい面があります。
コンテナにオーケストレーション・ツールは不可欠
dockerのような単機能コンテナでシステム構築する場合、イメージの数が増えるためkubernetesのようなオーケストレーション・ツールは欠かせません。
kubernetes導入前に、dockerを手で運用してみたことがありますが、コンテナが1つ止まるとサービス停止するため、このままでは実用に耐えない、と思いました。
kubernetesはreplication controllerがコンテナの稼働数を監視&維持するため、簡単に高いサービスレベルを確保できます。
dockerを導入するのであれば、運用環境まで検証し切ることで、安価にスケールするインフラを入手できます。
Chuma Takahiro