Kubernetesは複雑さを見事に隠してくれます。多くの人がKubernetesを好んで使う大きな理由のひとつです。シンプルなAPIを操作し、望む状態を定義すれば、あとは自動的に動いてくれます。
ただし、うまく動いている間だけの話です。
何かが壊れたとき、隠されていた複雑さが一気に現実のものになります。そして問題を解決するには、レイヤーを一枚ずつ剥がして、実際に何が起きているのかを理解しなければなりません。
Kubernetesについてエンジニアに意見を聞くと、答えはさまざまです。モダンなアプリケーションに不可欠だと絶賛する人もいれば、複雑すぎると考える人もいます。状況によっては便利だが不要な場面もある、数あるツールのひとつと捉える人もいます。
どの見方にも、一理あります。
しかし、ひとつ確かなことがあります。モダンなインフラに携わっているなら、Kubernetesはおそらくスタックの一部になっているはずです。そして問題が起きたとき、その内部がどう動いているのかを理解するメンタルモデルが必要になります。
このガイドの目的はまさにそこにあります。Kubernetesのさまざまなレイヤーをシンプルに理解し、問題のトラブルシューティングをより効果的に行えるようになることです。
各レイヤーの詳細なトラブルシューティングガイドをお求めの方には、ebook The Ultimate Kubernetes Troubleshooting Handbook もご用意しています。実際の問題に直面したときのために、手元に置いておくと便利です。
レイヤー1:ノード
Kubernetesのすべては、実際のマシン上で動作しています。物理サーバー、クラウドインスタンス、仮想マシンのいずれであっても、マシンこそがすべての土台です。
各ノードではkubeletというコンポーネントが動作しています。kubeletはコントロールプレーンからの指示を受け取り、コンテナが正常に稼働するように管理します。kubeletに問題が発生すると、Podがスタックし、アプリケーションが失敗し始めます。さまざまなツールからアラートが飛んでくるものの、どれも根本原因をはっきり示してくれない、ということが起こりがちです。
本格的なデバッグは、たいていここから始まります。マシンにログインし、kubeletのログを確認し、ディスク使用量やCPUの挙動、システム設定を調べます。コンテナランタイムやOSのようなわずかな違いでも、挙動に影響を与えることがあります。
マネージドKubernetesサービスを使えばノード管理は楽になりますが、そのレベルで何かが壊れたときにコントロールできる範囲は限られます。
ノードは、ラベルやtaintsを通じてスケジューリングの判断にも影響します。ノードに異常が発生すると、その上のすべてのPodが影響を受けます。だからこそ、ノードはクラスターの中で最も重要な要素のひとつなのです。
レイヤー2:ネットワーク
Kubernetesをある程度使ってきた方なら、ネットワークで一度は苦労した経験があるはずです。
すべてのPodにはIPアドレスが割り当てられます。Pod同士は直接通信できます。Serviceは安定したアクセスポイントを提供します。
しかし、実際の実装はFlannel、Calico、CiliumといったCNIプラグインに依存します。それぞれ挙動が異なり、固有の制約を持っています。
さらに、トラフィックのルーティングはkube-proxyが担っています。ルーティングルールが壊れたり古くなったりすると、トラフィックが正しいPodに届かなくなることがあります。アプリケーション自体は正常に動いているのに、壊れているように見えてしまうのです。
DNSもよくある問題のひとつです。CoreDNSはクラスター内で動作しているため、クラスターに負荷がかかるとDNSが失敗することがあります。PodはServiceを見つけられなくなり、しかもエラーメッセージからDNSが原因だとは分かりにくいのです。
Ingressは外部トラフィックのためのもう一枚のレイヤーを追加しますが、その設定は分かりづらいことがあります。サービスメッシュを導入すれば、さらに複雑になります。
ネットワークが壊れると、どこで失敗したのかを突き止めるだけのために、複数のコンポーネントをまたいでリクエストを追跡する羽目になります。
レイヤー3:ストレージ
Kubernetesはもともとステートレスなアプリケーションのために作られました。ストレージのサポートは後から追加されたもので、その影響は今も随所に表れています。
システムはPersistent VolumeとPersistent Volume Claimでストレージを管理します。しかし、実際のストレージはクラウドディスク、NFS、分散システムなど、さまざまなソースから提供されます。
ストレージの種類ごとに挙動が異なり、それぞれ固有の問題を抱えています。
Container Storage Interfaceによって標準化は進みましたが、その分、管理すべきコンポーネントも増えました。何か問題が起きると、ボリュームのマウントに失敗し、アプリケーションが起動しなくなることがあります。
データベースのようなステートフルなworkloadsは、さらに複雑さを増します。安定したアイデンティティとデータが必要なため、デプロイは遅く、繊細になります。
ストレージの問題の多くは、障害時、特にworkloadsが別のノードに移動するときにはじめて表面化します。それは往々にして、最も壊れてほしくないタイミングです。
レイヤー4:セキュリティ
Kubernetesのセキュリティは、ひとつの設定項目で完結するものではありません。複数の要素が連携して成り立っています。
RBACは誰が何にアクセスできるかを制御します。設定を誤ると、アクセスがブロックされたり、逆にセキュリティリスクが生まれたりします。
サービスアカウントはアプリケーションにアイデンティティを与えます。他のサービスに安全にアクセスするために使われます。
Secretsは機密データを保存しますが、デフォルトでは完全に安全とは言えません。多くのチームは、Secretsを適切に扱うために追加のツールを使っています。
また、rootアクセスの防止や権限の制限など、Podの実行方法を制御するルールもあります。これらは重要ですが、慎重に設定しないとworkloadsが動かなくなることがあります。
ネットワークポリシーはPod間の通信を制御しますが、ネットワークプラグインが対応している場合にしか機能しません。
要素があまりに多いため、Kubernetesのセキュリティ管理は圧倒されるように感じられることもあります。
レイヤー5:リソース割り当て
このレイヤーは、アプリケーションが使用するCPUとメモリの量を決めます。
リクエストとリミットを定義します。リクエストはKubernetesがPodの配置先を決める際の判断材料になり、リミットは使用量の上限を制御します。
リクエストが多すぎればリソースを無駄にし、コストが増えます。少なすぎれば、アプリケーションが遅くなったりクラッシュしたりする可能性があります。
ここで重要な違いがあります。CPUはリミットに達すると速度が抑制されるだけですが、メモリはそうではありません。コンテナがメモリのリミットを超えると、即座に強制終了されます。
ResourceQuotaやLimitRangeのような設定は、より上位のレベルで使用量を制御するのに役立ちます。Kubernetesはまた、負荷がかかったときにどのPodを先に排除するかを決めるpriority classを割り当てます。
リソース割り当てはパフォーマンスとコストの両方に直結するため、非常に重要です。
レイヤー6:オーケストレーション
Kubernetesが最もよく知られている理由がこれです。
望む状態を定義すれば、Kubernetesはその状態に合わせようとし続けます。常に差分をチェックし、修正します。
Deployment、StatefulSet、Jobといったworkloadタイプが、それぞれ異なるユースケースに対応します。
スケジューラーは、リソース、ルール、制約など多くの要素をもとに、Podの実行場所を決定します。
ローリングアップデートはシンプルなアプリケーションではうまく機能しますが、より複雑なworkloadsには慎重な対応が必要です。
カスタムリソースとオペレーターはKubernetesをさらに拡張します。複雑なシステムの管理が可能になる一方、保守すべきコンポーネントも増えます。
このレイヤーの問題をデバッグするには、目に見えている状態だけでなく、Kubernetesが何をしようとしているのかを理解する必要があります。
レイヤー7:オートスケーリング
オートスケーリングにより、Kubernetesは需要に応じてリソースを調整できます。
Horizontal Pod AutoscalerはPodの数を増減させます。Vertical Pod Autoscalerはリソース設定を変更します。Cluster Autoscalerはノードを管理します。
これらのツールは強力ですが、完璧ではありません。
スケーリングには時間がかかります。特に新しいノードが必要な場合はなおさらです。突発的なトラフィックの急増は、依然として問題を引き起こす可能性があります。
また、複数のスケーリングツールを併用すると、互いに競合することもあります。
イベントベースのスケーリングや予測スケーリングといった新しいアプローチもありますが、それらにも課題はあります。
オートスケーリングをうまく機能させるには、質の高いメトリクスと、自分たちのworkloadに対する明確な理解が必要です。
まとめ
ノード、ネットワーク、ストレージ、セキュリティ、リソース割り当て、オーケストレーション、オートスケーリング。これらのレイヤーを知ることで、Kubernetesの仕組みが見えてきます。
しかし実際には、これらは切り離されたものではありません。常に重なり合っています。だからこそ、問題のデバッグは難しいのです。
ネットワークの問題がストレージの問題に見えることがあります。リソースの問題がスケーリングの問題を引き起こすこともあります。セキュリティの変更が通信を壊すこともあります。
ひとつのレイヤーだけに集中するわけにはいきません。システム全体を俯瞰する理解が必要です。
モダンなツールが揃った今でも、この理解は重要です。そうでなければ、根本原因ではなく症状の修正に時間を費やすことになります。
私たちが学んできたことをすべて詰め込んだのが The Ultimate Kubernetes Troubleshooting Handbook です。実際に起こる問題、デバッグの手順、実践的な例を網羅しています。
たったひとつでも問題を早く解決できたなら、それだけで価値があります。皆さんのクラスターが安定して稼働することを願っています。