Kubernetesの非機能要件定義の検討(可用性)

※当ブログではアフィリエイト広告を利用しています

最近はITベンチャー系やネット企業だけでなく、一般企業でもKubernetesへの移行プロジェクトが多くなってきました。

しかしながら、SIerを含むプロジェクト関係者は皆手探りで進めているケースが非常に多いと感じています。(個人的な感想です)

まだ事例も少なく、経験のないベンダーがほとんどですから黎明期には仕方がないことです。

そこで、今回は私の実際のプロジェクト経験をもとに、「Kubernetes環境での非機能定義って何をすれば良いの?今までと同じ?」「Kubernetesの設計ってどうすればよいの?」といった疑問に答えつつ、Kubernetesの考慮点を解説をしていきたいと思います。

本記事では、可用性について扱います。

はじめに

  • Kubernetesのリソース名は英語で記載します。例えば、一般的なWebサービスなどで使われる「サービス」とKubernetesのリソースである「Service」といった形で使い分けています。
  • この記事はSIer(顧客に対してKubernetesを導入する人たち)の視点で書かれています。たまに「顧客」や「お客様」という言葉が出てきた場合は、Kubernetesの実際の導入先と考えてください。
  • SIerだけでなく、社内SEとしてKubernetesを導入する人が読んでも問題ない内容になってるはずです。
  • この記事を参考にしていただければ嬉しいですが、そのせいでどうなってもこちらでは責任を負いかねますので、最終的な取捨選択はご自身の責任にてお願いします(予防線)

非機能要求の分類

非機能要求として考える観点は今までと変わりません。

そのため、IPAが公開している「非機能要求グレード」に則り解説します。

https://www.ipa.go.jp/sec/softwareengineering/reports/20100416.html#L2

可用性

継続性

ここは今までとほとんど変わらず、結局要件次第となりますが、あえてポイントを挙げるとすれば、「計画停止の有無」です。

Kubernetesは複数のノードからなるクラスター構成をとることが前提であるため、クラスター全体を計画停止するケースは無いと考えてよいと思います。

Kubernetes上で動作するアプリケーションについては、様々な方法を使って無停止でのリリースが可能です。

  • 複数のPodを順番に更新していく「ローリングアップデート」(Kubernetesの標準)
  • 新バージョンのDeploymentを作成し、Service(Ingress)の変更で切り替えを行う「Blue-Green Deployment」
  • Replica数の少ない新バージョンのDeploymentを作成し、旧バージョンのデプロイメントと同時に動かしながら徐々に新バージョンの割合を増やしていく「Canary Deployment」
    • Istioを使うともっと簡単に実現できる

KubernetesのホストのOS自体の更新が必要となり、再起動が必要となった場合でも、クラスターのノードを順番に停止し、適用していけばアプリケーションを停止する必要がありません。

Kubernetes自体の更新もローリングアップデートもしくはBlue-Green Deploymentで実現できるため、アプリケーションを停止する必要がありません。

とすると「計画停止は無いものとする」と決めてしまってよさそうですが、過去の経験上、少しだけ逃げ道を残しておくことをお勧めします。(それで昔痛い目を見たのですがまた別の機会に、、、、、)

計画停止がゼロならもちろん顧客はハッピーですが、今後予期せぬ事態により、Kubernetesクラスター全体を停止せざるを得ない状況に追い込まれる可能性も全くゼロとは言えません。

例外的にKubernetesクラスター全体を停止できるような記述にしておくと後々ハッピーになれると思います。

耐障害性

サーバー

まずは vSphereなどのハイパーバイザーが導入される物理サーバーについて考えます。(ベアメタルの場合は読み飛ばしてください)

このサーバーに障害が発生した場合、当然ですがこのサーバー上で稼働するVMはすべて停止します。

つまり、Kubernetesもその上で動くアプリケーションもすべて停止します。

そうならないように、ハイパーバイザーは複数の物理サーバーに分けるようにする必要があります。ここまでは今までも同じようにしているかもしれません。

Kubernetesでは2種類のノードがあります。「Master」と「Node(Worker)」です。

Quorumの観点から、Masterは必ず3(1台の障害までOK),5(2台までOK),7(3台までOK)…と奇数台用意しましょう。そして、同じハイパーバイザー上で複数のMasterが動作しないように設計しましょう。

NodeについてはQuorumの考え方はありませんが、Kubernetes上で動かすアプリケーションの必要リソースよりも多めに準備しましょう。Nodeに障害が発生した場合、そのNodeで稼働していたPodは別のNodeで起動しようとします。そこでリソースが足りないとPodが立ち上がらないだけでなく、Nodeのリソース不足によりKubernetes全体の障害に繋がりかねません。(この辺はVMWareとかでの話と似ていますね)

更に、Kubernetes外部からのアクセスのためにIngressを定義することになると思いますが、それを実現しているIngress Controllerの冗長化も必要です。

KubernetesのPodのスケジューリング機能は、(DaemonSetは別として)どのNodeで実行されるかはKubernetesが空いているリソース状況を見ながら自動的に決定します。

そのため、Ingress Controllerが動作するPodが同じNodeや同じハイパーバイザー上のNodeに配置されてしまった場合は単一障害点となってしまいます。そうならないように、Nodeに適切なLabel設定を行い、podAntiAffinityを定義することで同じNodeや同じハイパーバイザー上のNodeに配置されないような設計としましょう。以下公式ドキュメントです。

https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

このNode Affinityの機能はBetaとなってからかなり長い期間が経過しており、機能上の問題はほとんどないと言っていいと思います。様々なOSSや企業が提供するHelmチャートの中にもこの設定は散見されており、通常使用には問題ないと言ってもよいでしょう。

しかし、未だにベータである理由として、まだすべての機能が実装されていなかったり、パフォーマンス上の問題があるようです。(以前100以上のノードでKubernetesクラスターを組む場合でも問題が発生する~みたいなコメントを見た記憶があるのですが探し出せませんでした・・・)

100以下のクラスターであれば問題ないと思いますが、迷ったら検証しましょう。

おそらくNode Selectorの機能だけだと「Deploymentのレプリカ(Pod)を同じNodeで起動させない」みたいな設定ができない、気がします。

https://github.com/kubernetes/kubernetes/issues/25319

外部からアクセスするうえでIngressが稼働するノードは固定して置く必要があります。また、Ingress Controllerはアクセスが集中するポイントになるため、個人的なおすすめとしてはIngress Controller専用ノードを複数準備し、同じノードにスケジューリングされないような設計としたほうが良いと思います。

また、Kubernetes上で動かすアプリケーションの冗長化についてもIngress Controllerと同様に注意が必要です。

何も設定しない場合、Schedulerの機嫌によっては同じノードや同じハイパーバイザーされるのでやはり単一障害点となってしまいます。書籍や研修、試してみた系記事ではレプリカ数を増減させて終わりの場合が多いですが、「Deploymentのレプリカ数を3つにしたから冗長化OK!」とはならないことは覚えておいてください。

端末

あまり考慮する点はないと思いますが、Kubernetesを操作する専用の端末(=kubectlやhelmコマンドを実行する端末)はKubernetesが動作するノードとは分けるようにしましょう。

ネットワーク機器/ネットワーク

従来と同様に考えればよいと思います。以下については検討する必要があると思います

  • Masterの前段に立つロードバランサーの冗長化
  • Ingress Controllerの前に配置するロードバランサーの冗長化

ストレージ

ここでは2つの観点があります。

  • Kubernetesが稼働するサーバー群のストレージ
  • Podがデータ永続化のために使うストレージ(データベース情報など)

前者に関しては今まで通り検討すればよいでしょう。

後者に関しては、Kubernetesクラスター外に専用のストレージ・サーバーを用意することになります。アプリケーションが使用するデータになるため当然冗長化が必要になる箇所です。

Kubernetesでは様々なストレージサービスやソフトウェアとの連携ができるようになっています。以下を参考にしてください。

https://kubernetes.io/docs/concepts/storage/

KubernetesではNFSよりもCephやGlusterFSなど用いた分散ストレージソフトウェアが使用されることが多いような印象です。

1TBのNFSサーバー1台よりも200GBのサーバー15台でクラスターを作り、耐障害性を高めるやり方です。

分散ストレージでは複数台のサーバーでKubernetesのようにクラスターを作り。各サーバーで決められた数のデータのレプリカを保持しています。

そのため、1台のディスクの故障があっても他のディスクにあるデータを取得することができるため、問題なくサービスを提供できるというわけです。(かなりざっくり書いています)

パブリック・クラウドのサービスを使用することが可能であれば、AWS EBS VolumeやAzure Data Diskの使用も検討してみるのもよいかもしれません。

データ

ストレージと同様なので割愛します。

回復性

サービスが停止した場合の復旧作業やその際の代替業務を検討する箇所ですが、Kubernetesの観点ではあまり検討することがありません。というのも、障害からの復旧はKubernetesがある程度自動でやってくれるからです。

Kubernetesでは、あらかじめ定義した状態をSchedulerが維持しようと試みます。

例えば、Deploymentの定義で「Podのレプリカ数は3つ」と定義した場合、KubernetesのSchedulerはその状態を維持しようと試みます。

例えば、NodeがダウンしてPodの稼働数が2つになった場合、別のノードで3つ目のPodが自動で立ち上がります。

Kubernetesではインフラ自体に問題が生じた場合でも、サービス自体の停止が最小限になるような仕組みとなっています。

では、サービス障害が発生するのはどのようなケースでしょうか?色々あると思いますが、代表的なものを挙げてみます。

アプリケーションのリリースに問題があった場合

新しいバージョンのアプリケーションをデプロイした時、そのアプリケーション自体に問題があった場合、サービスは当然停止します。いくらKubernetesといえども、アプリのバグを自動で直してくれるわけではありません。

ただし、Podが使用するコンテナイメージのバージョンを元に戻すだけでよいため、Kubernetesでのリリース失敗時のロールバックは非常に簡単です。

本当はそうならないように、Canary Deploymentなどの技法を使ってリリースするべきかもしれませんが・・・。

アプリケーション用いるデータに問題が生じた場合

アプリケーションが使用するデータに問題がありアプリケーションが動作しなくなった場合、これもKubernetesの力をもってしても修正はできません。

例えば以下のケース。

  • アプリケーションのバグで不正なデータが書き込まれてしまった
  • データベースのメンテナンスに失敗した

こうした場合に備えて、ストレージのバックアップは当然ですがしっかり取得することを検討しましょう。

バックアップについては、「運用・保守性」の部分でも触れようと思います。

災害対策

Kubernetesクラスターとしては大きく分けて二つのパターンがあると思います。2はKubernetesならでは、という形態ですが、どちらもメリット・デメリットあると思いますので、災害時の復旧時間などの要件に応じて検討しましょう。

複数のデータセンターごとにKubernetesクラスターを作成する

同じ内容のKubernetesクラスターを複数のデータセンターで保持しておき、災害時に切り替えを行います。

ごく一般的な方法だと思いますが、Kubernetesの構成情報(etcd)などを本番と災対環境でミラーリングしておく仕組みが必要となります。

複数のデータセンターをまたがるKubernetesクラスターを作成する

この場合、Kubernetesの仕組みでetcdの情報は複数のデータセンター間で同期されます。

更に、片方のデータセンターが丸ごと使用不可となった状態でも、Kubernetesのオートヒーリング(Auto Healing, 自動回復)機能が働き、もう片方のデータセンターにて自動でPodが立ち上がってきます。そのため、人の手を介在せずに災害対策が可能となります。

また、災対サイトは普段あまり使われることがありませんが、この方式だと災対サイトも本番環境の一部として動かすことができます。

※もちろん、ストレージ部分は別途検討する必要があります

一見素晴らしい構成に見えますが、複数のデータセンターをまたがった通信の速度は同じデータセンターと比べ遅くなりがちです。通信速度がボトルネックになる可能性があるため、通信速度が重要なコンポーネント(コンテナ)同士は同じPodにまとめるなど考慮が必要です。

災害時のみ災対サイトを使いたい場合、NodeにはPodがScheduleされないようにしておき(cordon)、災害時に解除する(uncordon)運用とすることも可能です。(何か仕組みを考えない限り、手動での切り替えが必要です)

まとめ

Kubernetes自体の機能である程度の可用性は担保することができますが、それでも個別に考慮することはそれなりにあるので、設計から漏れないように気を付けましょう。

今後見直しながら図を足したり更新していければと思います。

参考になれば幸いです!