検索
連載

Kubernetes障害で泣かないための羅針盤、Observabilityを活用したトラブルシューティングフロー大公開Cloud Nativeチートシート(14)

Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、Observabilityを活用したトラブルシューティングフローを紹介する。

Share
Tweet
LINE
Hatena

※岡本、正野、宇都宮はNTTデータ所属

 Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。前回から複数回に分けて「Observability(オブザーバビリティ)」「可観測性」にフォーカスして解説しています。

 Kubernetesを使っていてトラブルが発生したけど、原因究明をどう進めればいいか分からない……ということはありませんか?

 コンテナを利用したシステムでは、マイクロサービス化が容易なので、コンポーネントやサービスの数が従来のシステムに比べて非常に多くなります。そのため、障害が発生した場合の原因の究明も大変になります。

 そこで今回は、「Observabilityでいろいろとデータが取れるのは分かったけど、何からどう見ていけばいいのか分からない」という方向けに、Kubernetesで実装されるクラウドネイティブなアーキテクチャに対するトラブルシューティングの羅針盤となる「トラブルシューティングフロー」を紹介します。

 トラブルシューティングフローの中では前回の記事で紹介したObservabilityの3シグナル(メトリクス、ログ、トレース)を活用します。

前回記事では、Observability、可観測性とは何か? と、その構成要素として3シグナル(メトリクス、ログ、トレース)や考慮点を説明したので、併せてご参照ください。

クラウドネイティブとKubernetesにおけるトラブルシューティングの難しさ

 クラウドネイティブなシステムといえど発生する障害自体は、次のような性能問題やHTTPエラーやコンポーネントの障害など、従来と同じ問題です。

  • 性能問題
    • サービスの遅延
    • サーバ障害(HTTPエラー503や504)
  • コンポーネントの障害
    • PodやNodeの障害やデプロイエラー
    • 各種ミドルウェア、アプリエラー

 ただし、これらの問題が分散したシステムのどこで発生しているのか、根本原因は何かを正しく迅速に突き止める「トラブルシューティング」については、従来と異なるアプローチが必要となります。

 従来はサーバの絞り込みが簡単(メトリクスやログを見れば一発)だったので、「サーバ上で何が起こっているのか」の解析が注力分野となることが多かったと思います。例えば、「DB(データベース)のスロークエリの状況を見ながらAPサーバかDBサーバどちらに原因があるかを切り分ける」「AP(アプリケーション)サーバが怪しいとなればCPUネックかどうかを切り分けて、プロファイリングやGC(Garbage Collection)の分析に注力する」という流れです。

 しかし、クラウドネイティブアーキテクチャでは大量のコンポーネントが分散かつ自律的に動作するので、コンポーネントやサービス数が従来のシステムに比べて多くなり、単一のメトリクスやログを見るだけでは問題のコンポーネントにたどり着くこと自体が難しくなります。

 加えて、コンポーネントのライフサイクルが短くなったことで、メトリクスやログなどの情報の永続化にも気を配る必要があります。例えば、大量にPodがある中からサービスの遅延に関係性のあるPodを迅速に特定するのは難しいです。問題のあるPodを発見しても再起動を繰り返してログが残っていないこともよくあります。

 ここで、前回紹介したObservabilityを利用することで、トラブルシューティングに必要な情報を保持しながら、コンポーネントと事象を正しく特定することができます。

クラウドネイティブトラブルシューティングフローの全体像

 クラウドネイティブ向けのトラブルシューティングフローを見ていきましょう。

 こちらがフローの全体像です。このフロー自体は、外形監視やサービス監視から取得するレスポンスタイムやエラー率(いわゆるREDメトリクス)から、問題を検知したところを起点として作成しております。全体像がかなり大きいので、3つのレイヤーに分けて説明します。本稿を読み終わった際に、もう一度この図を振り返ってもらえれば、全体像がつかみやすいと思います。


クラウドネイティブトラブルシューティングフローの全体像

 今回作成したフローでは、上図で色分けしている3レイヤー(ぼんやり特定レイヤー、しっかり特定レイヤー、かっちり判明レイヤー)に分けてトラブルシューティングをするように整理しています。理由としては、問題発生の被疑個所を徐々に絞ることで、詳細な情報を確認する範囲を狭めることができ、迅速に原因にたどり着けるからです。

 それぞれのレイヤーでどのように切り分けるかの概要を紹介します。

ぼんやり特定レイヤー

 最初の「ぼんやり特定レイヤー」では、ぼんやりとダッシュボードを見て、問題個所を「ぼんやりと把握」します。発生している問題について、まだ発生個所や事象自体を把握できていない段階から、KubernetesのNamespaceより細かい粒度まで絞り込みます。

 このフェーズでは、あまり個別の事象を深追いせず、おおよそどの辺りが怪しそうか“あたり”を付ける程度にして、短時間で済ませましょう。

しっかり特定レイヤー

 「しっかり特定レイヤー」では、前のレイヤーでぼんやりと特定された被疑個所から、さらに「しっかりと絞り込み」ます。より詳細なコンポーネントレベルの問題発生個所(PodやNodeのどの設定やCPUやメモリなどのリソースに問題があるか)に絞り込み、問題事象全容の正確な把握を目指します。

 このフェーズでは、Observabilityの3つのシグナルをフル活用します。3つのシグナルをどう活用して切り分けるかが肝になります。

かっちり判明レイヤー

 最後に、「かっちり判明レイヤー」では、特定された問題発生個所で発生している問題に対して「根本原因の深掘り」をします。問題発生コンポーネント上の細かな情報(リソース定義、デバッグ、プロファイルなど)を分析しながら根本原因を特定します。

 このフェーズでは、それぞれのトラブルに応じて細かな確認すべき情報を見ていきます。具体的に、どのような情報を確認すべきかが重要です。

 ここからは、それぞれのレイヤーでの具体的な切り分けフローや確認する情報、Tipsを紹介します。

ぼんやり特定レイヤー

 「ぼんやり特定レイヤー」では3種類の調査方法を使い分ける、あるいは併用することで、ぼんやりと怪しい被疑領域をService、Deployment、Podのレベルまで絞り込みます。

 このレイヤーで、おおよそどの辺りが怪しそうかの“あたり”を付けることで、トラブルシューティングの見通しと計画を立てることができ、影響範囲も想定できます。

 重要なのは前述した通り、あまり個別の事象を深追いせず、短時間で済ませる点です。できるだけ短時間でこのレイヤーを終わらせることで、今後の対応計画を迅速に検討できるようにしましょう。


ぼんやり特定レイヤー

 3種類の調査方法について、それぞれ解説します。

i. Kialiで依存関係グラフ確認

 「Kiali」というオープンソースソフトウェア(OSS)を利用することで、分散サービス間の依存関係グラフを使って視覚的に問題発生個所を特定できます。

 例えば下図のように、エラーが発生しているServiceやPodを赤く表示したり、サービス間の呼び出しレイテンシをグラフに重ねて表示したりすることが可能です。


Kialiによる分散サービス間の依存関係グラフ

※Kialiの依存関係グラフ(Topology)の詳細はドキュメントをご覧ください。

 これによって、一目で問題発生個所の把握が可能となります。しかし、これだけではどういうエラーが発生しているのか(HTTエラーコード以上の情報)、なぜ遅延が発生しているのかを分析することが難しいので、後続の「しっかり特定レイヤー」で事象の把握を進めます。

ii. メトリクスのサマリーダッシュボード確認

 Kialiで見られるような情報を可視化するには、サービスごとにレイテンシやリクエスト数/エラー数を収集する仕組み(※)が必要です。現実として必ずしもこういった情報がリッチに取れていないこともあります。

※アプリのライブラリの実装を全サービスに実施する、サービスメッシュを利用して「Envoy」によって収集するなど

 そういったケースにおいて、収集コストが低いリソースメトリクスから調査を開始する方法は、Observabilityシグナルの収集が整い切っていないタイミングでは有効です。

 この方法では、基本的に事前にメトリクスのダッシュボードを用意しておき、そのダッシュボードを利用して、怪しいService、Deployment、Podなどのコンポーネントを特定します。そのため、ダッシュボード上でうまくKubernetesクラスタやNamespaceの単位で情報をサマリーしておくことが重要です。

 メトリクスで利用できるOSSとしては、「Prometheus」による収集/蓄積、「Grafana」による可視化/ダッシュボード化がスタンダードです。例えば、次のように工夫しておくといいでしょう。

  • ServiceやDeployment単位で平均やパーセンタイルで集計したメトリクスを表示するグラフを用意する
  • アクセスログやアプリログに出力されるエラー数をカウントしたメトリクスを表示するグラフを用意する。例えばログ集約OSS「Loki」の「Promtail」機能によって、ログ収集時にログ集計メトリクスを送信できる(参考
  • ダッシュボードに表示する情報を事前に整理、設計しておく(詳細は「コラム ダッシュボードの設計」を参照)

コラム ダッシュボードの設計

 このコラムでは、可視化ツールにおけるダッシュボード設計の一例を紹介します。

 ダッシュボードは便利な機能ですが、無分別にあらゆる情報を1つのダッシュボードに含めると、情報を参照しづらいダッシュボードが出来上がってしまいます。そこで、ダッシュボードはどのような目的で分類すればよいか、その考え方のヒントとなる情報をお伝えします。

可視化ツールとダッシュボード

 メトリクス、ログ、トレースなどの収集したモニタリング情報を可視化するツールとしては次のようなものがあります。

  • OSS:「Grafana」「Kibana」など
  • パブリッククラウド:「AWS CloudWatch」「Cloud Monitoring」(Google Cloud)、「Azure Monitor」(Microsoft Azure)など
  • SaaS:「Datadog」「New Relic」など

 ツールごとに作成できるグラフや表示形式などは異なりますが、いずれのツールも「ダッシュボード」という概念を持っています。ダッシュボードでは、ユーザーがよく見る情報を都度検索する必要がないようにまとめて表示できます。

ダッシュボード設計の一例

 ダッシュボードは、システムを開発、運用する組織の構造と、サービスのSLA(Service Level Agreement)、SLO(Service Level Objective)、エラーバジェットに着目して設計すると、ユーザー視点の影響を捉えやすく、各チームで自律的に管理しやすくなると筆者は考えています。

 あくまでケースの一つとしてですが、例えば次のような3つの観点でダッシュボードを分割、作成できます。

  1. SLA、SLO、エラーバジェットを一目で確認できるボードを作り、毎日確認
  2. リソース拡張のプランニングに必要な指標をあらかじめ決めておき、定期的にチェック
  3. アプリやインフラのチームごとの文脈で参照する詳細な情報は個別のボードを作成

 1.は、サービスのユーザー影響を素早く認識するために必要な、最も重要なダッシュボードです。サービスの開発と運用に関わる全ての人がいつでもアクセスできるようにしておく必要があります。また、マルチテナント環境では、全てのテナントを一括して確認できるダッシュボードが欲しいところです。オンサイトの運用では、大きなモニターを置き、常に表示するといった方法もよくとられています。テレワーク前提の運用体制ならば、オンコールメンバがシフト中に必ずダッシュボードを開いておくなどして、いつでもすぐに確認できるようにしておくといいでしょう。

 2.は、アラートが出る前に予兆をつかむ必要がある情報を定期的に確認するダッシュボードです。中長期的な目線でリソースの増加傾向を把握し、リソースのオーナーが拡張計画を立てるために利用します。最低限の指標を月次や四半期ごとにチェックするといった運用が理想ですが、サービスが運用の初期段階にある場合は、もう少し細かい粒度、頻度でリソースを確認するダッシュボードを別途用意した方がいいかもしれません。

 3.は個々のチームのメンバーのみが参照する「詳細ビュー」のような位置付けのダッシュボードです。アプリを開発、運用するチームなら、ダッシュボードに表示すべき情報は「4 Golden Signals」「RED method」などが参考になるでしょう。インフラの場合は「USE method」などが知られています。RED/USE methodについては前回記事で紹介しています。

ダッシュボード設計時のポイント

 このコラムの最後に、ダッシュボードの構成を検討する際に筆者が重要と考えているポイントを紹介します。

  • ダッシュボードの管理ポリシーとオーナーを明確にする
  • SLA、SLO、エラーバジェットをダッシュボードに表示し、ユーザー影響を素早く把握できるようにしておく
  • アプリやインフラの発展に伴ってダッシュボードも随時ブラッシュアップしていく

 サービスによってその運用を行う組織の構造はさまざまです。正解は1つではないので、自分たちのチームに合ったダッシュボード構成を試行錯誤の中で見つけ出す必要があります。


iii. クラスタ上の全ログを該当時間内で眺める

 これまでとは毛色の違った「ぼんやり特定レイヤー」での調査方法として、クラスタで発生するログを眺めて特定させるやり方もあります。何かしらのエラーが出ていれば、そのエラーを出しているコンポーネントの周辺が怪しいので、コンポーネント数がまだ多くないような状況では迅速に問題を特定できます。

 従って、アプリやミドルウェア特有のエラーなど、問題の原因が明白なエラーまで特定できた場合は、そのまま「しっかり特定レイヤー」の「【2】-2 ログの確認」に進みます。

 一方で、「複数のエラーが頻発しているDeploymentやPodは分かったものの、それらのエラー発生には複数の原因が考えられる」といった問題特定にはまだ情報が足りない場合は、被疑個所を絞り込んだ上で「しっかり特定レイヤー」の「【0】 非機能 or 機能、どちらの可能性が高いか」の分岐に進みます。

 例えばLokiを活用することで、コンポーネントごとに付与されるラベルによって、「Grep」と同じ感覚で軽量にログをフィルタリングできるので、クラスタ上にある複数コンポーネントのログを横断的かつ効率的に確認できます(参考)。

 ログ分析において重要なのは、必要に応じてクラスタのメトリクスなども併せて確認し、効率良く事象を特定できるようにすることです。理由としては、ログはメトリクスと比較して情報量が多くなり、場合によっては事象の特定まで時間が要する可能性もあるからです。


Lokiを利用したログの確認/検索

しっかり特定レイヤー

 本レイヤーでは、「ぼんやり特定レイヤー」で特定された被疑個所から、さらに詳細な事象の把握(下記の、どの事象か)や関連コンポーネントの有無の確認を進めます。

  • 非機能系の問題
    • Pod、Nodeのリソース不足
    • ミドルウェア(MW)、アプリのロジックで遅延
    • Nodeの障害
  • 機能系の問題
    • Resource Quota抵触
    • Eviction
    • コンテナイメージPullエラー
    • Liveness prove失敗によるコンテナ停止
    • ミドルウェア、アプリエラー

 下図では「しっかり特定レイヤー」のフローの部分を抜き出しています。


しっかり特定レイヤー

 フローの中では青で色付けしたブロックのようにObservabilityの3シグナルを活用して切り分けていき、幾つかの分岐を経ながら、問題事象全容の把握と問題発生個所を特定します。オレンジ色の矢印からつながる一番下のオンレジ色のブロックまで行けば、本レイヤーのゴール、すなわち事象の特定および問題発生個所の特定です。

 ここから各切り分けの進め方について解説します。

【0】 非機能 or 機能、どちらの可能性が高いか

 最初に「性能などの非機能障害の可能性が高いか、Pod上で稼働するコンテナ自体やミドルウェア、アプリによる機能的な障害の可能性が高いか」をこれまで得た情報をベースに判断します。

 例えば、遅延やリクエストの一部のみエラーが返る、HTTPエラーの「503」(Service Unavailable)、「504」(Gateway Timeout)が返る場合は、サービスで遅延や障害が発生している可能性があるので、非機能関連の分析を進めます。

 Podが起動しない、再起動を繰り返す、上記以外のHTTPエラーが返る場合、Pod上で稼働するミドルウェアやアプリの障害が原因の可能性があるので、機能関連の分析を進めます。

 もちろん、この段階ではどちらかを明確に判断することは難しいことが多いので、あくまで「どちらの可能性が高そうか」という観点で進むとよいでしょう。

【1】 リソース使用量が高いかどうか(非機能関連)

 ここでは、サービスで遅延や障害が発生している可能性があるので、まずはリソースの使用量をざっと確認します。「ぼんやり特定レイヤー」のサマリーダッシュボードで見るようなクラスタレベル、Namespaceレベルでサマリーしたメトリクス情報から、リソース使用量が高いコンポーネントがあるかどうかを確認します。

 リソース使用量が高いコンポーネントがありそうなら「【1】-1 詳細メトリクス確認」に進みます。高いコンポーネントがぱっと見当たらなければ、「ぼんやり特定レイヤー」のサービス依存関係グラフや分散トレーシングを確認します。

【1】-1 詳細メトリクス確認

 リソースの使用量として気にするコンポーネントは基本的にコンテナかPodかNodeです。従って、「コンテナ、Podのリソースネックかどうか」「Nodeのリソースネックかどうか」でそれぞれ詳細にリソースの使用量を確認します。

 リソースとしてはCPU使用率、メモリ使用量、ディスクI/O、ネットワークI/Oなどがありますが、ここでは、それぞれのメトリクスを見られるように、メトリクスを収集しておくことが重要なポイントです。

・コンテナ、Podのリソースネックかどうか

 コンテナ、Podレベルのリソース使用量は特に「Prometheus Exporter」をデプロイせずとも「kubelet」によってメトリクスが収集、公開されているので、Prometheusからスクレイプすることで容易に収集できます。

 コンテナ、Podのリソースを分析する際に重要なのは、Pod内に配置される各コンテナのリソース要求です。

 リソース要求には、「requests」「limits」があります。requestsはNodeにPodをスケジューリングする際にPod内のコンテナが最低限必要とするリソースの要求量です。limitsは、Pod内のコンテナにおけるリソースのハードリミットです。

 従って、コンテナのリソース使用量を確認すると同時にハードリミットであるlimitsの定義がどうなっているかを合わせて確認する必要があります。limits、requestsについてもメトリクスとして収集できるので、ちゃんとグラフに可視化しましょう。


コンテナのCPU使用量がlimitsに抵触している例

 こちらの例だと、「front-end」コンテナのCPU使用量がlimitsの0.3に制限されており張り付いているのが確認できます。

 コンテナ、Podのリソースネックだと判明すれば「かっちり判明レイヤー」で「A. Podのリソース不足」を対応します。

・Nodeのリソースネックかどうか

 Nodeレベルのリソース使用量については、「Node Exporter」をデプロイすることでメトリクスを収集できます。

 基本的に確認する観点は従来のサーバリソースと同様です(CPU、メモリ、ネットワーク、ディスク)。


NodeのCPU使用率が100%になり、CPUリソースが枯渇している例

 Nodeのリソースネックと判明すれば「かっちり判明レイヤー」で「B. Nodeのリソース不足」を対応します。

 ここでは細かいメトリクスを紹介し切れませんが、それぞれのレイヤーでどのようなメトリクスを確認すべきかについては、記事「【暫定版】 Kubernetesの性能監視で必要なメトリクス一覧とPrometheusでのHowTo - kashinoki38 blog」を参照いただければ幸いです。

【1】-2 トレースの確認

 リソース使用量が高くない場合、次の2つの可能性が考えられます。

  • 調査対象のコンポーネントの内でPodのリソースを使わずに、遅延や障害を引き起こす何かが発生している可能性
  • 調査対象のコンポーネントから呼び出される後続コンポーネントで、遅延や障害が発生している可能性

 従って、まずは後続のコンポーネントが存在するかどうかを確認し、後続のコンポーネントがなければ当該コンポーネント上のミドルウェアやアプリが怪しい(ここまでのいきさつでリソースは枯渇していないことが分かっている)ので「かっちり判明レイヤー」の「C. ミドルウェア、アプリのロジックによる遅延」で詳細な分析に進みます。

 後続のコンポーネントが存在する場合、後続のコンポーネントの状況がどうなっているかを調査する必要があるので、分散トレーシングを確認し、後続コンポーネントによるレイテンシやエラーを確認しながら、後続のコンポーネントについても同様に本トラブルシューティングフローを適用して調査を進めましょう。

 例えば、「Jaeger」による分散トレーシングを実装した場合、下図のように呼び出し元のサービスのレイテンシにおいて、呼び出し先のサービスのレイテンシがどれくらいを占めているのかや、HTTPのエラーコードやメッセージを確認できます。


Jaegerによる分散トレーシング

【2】 Podのステータスの確認(機能関連)

 ここでは、Pod上で稼働するコンテナ自体やミドルウェア、アプリによる機能的な障害の可能性があります。

 まずはPodのステータスを確認します。Podのステータスは例えば「kubectl get pod」コマンドで取得できます。 なお、複数のPodがある場合はラベルで絞るとよいでしょう。

$ kubectl get pod                                                      
NAMESPACE       NAME              READY   STATUS    RESTARTS   AGE
hoge            hoge-pod          0/1     Pending   0          1m

 このPodのステータスが上記のように「Pending」「Failed」「Unknown」の場合、ここからより詳細に情報を確認していきます。

※Podのライフサイクルについては、「Podのライフサイクル | Kubernetes」をご覧ください。

※これらのPodやコンテナなどのKubernetesリソースのステータス情報は、マネージドKubernetesの場合は、各クラウドの持つモニタリング機能のWeb画面から確認できます。また、「kube-state-metrics」というPrometheusエクスポーターを利用することで、Prometheusからメトリクスとして確認することもできます。

・【2】-1 Nodeのステータスの確認

 Podがデプロイできなかった原因の一つとしてNodeに障害が起きた可能性があるので、Nodeのステータスを確認しておきます。

 Nodeのステータスは、例えば「kubectl get nodes」コマンドで取得できます。

$ kubectl get nodes                     
NAME     STATUS       ROLES     AGE   VERSION
hoge     NotReady     master    3d    v1.22.2

 このNodeのステータスが「NotReady」「Unknown」の場合、Node周りに障害が発生している可能性が高いので「かっちり判明レイヤー」の「D. Node障害」で根本原因を分析します。

・【2】-1-1 Podの詳細情報確認(Pod state reason、Event)

 Podの詳細な情報の確認は、次のようなフィールドから確認できます。

  • Podのステータス
    • status.phase:「Running」「Pending」「Succeeded」「Failed」「Unknown」
    • status.reason:「Evicted」など
    • Events「Insufficient cpu」など
  • コンテナのステータス
    • status.containerStatuses.state:「Running」「Waiting」「Terminated」
    • status.containerStatuses.state.waiting.reason:「ErrImagePull」「ImagePullBackOff」「CrashLoopBackOff」など
    • status.containerStatuses.state.terrminated.reason:「OOMKilled」など

 これらは例えば「kubectl describe pod」「kubectl get pod -o yaml」コマンドで確認できます。これによってPodやその上にデプロイされようとしているコンテナのステータス、またPodに対して発生したEventsの履歴を確認できます。

$ kubectl describe pod hoge
Name:         frontend-cpu
Namespace:    default
...
Status:       Pending
...
Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  22s (x14 over 13m)  default-scheduler  0/3 nodes are available: 3 Insufficient cpu.

 この例だと、Pod上で発生したEventsとして「Insufficient cpu」が記録されており、NodeのCPUが枯渇してPodをデプロイできなかったことが分かります。

※このケースでは上述したコンテナのrequestsにおけるCPU要求に対してNodeのCPUが余っているかどうかが重要です。

$ kubectl describe pod test                                          
Name:                 test
Namespace:            default
...
Status:               Pending
...
Containers:
  test:
...
    State:          Waiting
      Reason:       ErrImagePull
    Ready:          False
    Restart Count:  0
...
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
...
Events:
  Type     Reason           Age   From               Message
  ----     ------           ----  ----               -------
  Normal   Pulling          17s   kubelet            Pulling image "aaa"
  Warning  Failed           9s    kubelet            Failed to pull image "aaa": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/aaa:latest": failed to resolve reference "docker.io/library/aaa:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
  Warning  Failed           9s    kubelet            Error: ErrImagePull
  Normal   BackOff          9s    kubelet            Back-off pulling image "aaa"
  Warning  Failed           9s    kubelet            Error: ImagePullBackOff

 この例だと、「Containers」の「State: Waiting」の「Reason」(Podリソース定義上のパスは「status.containerStatuses.state.waiting.reason」)から「ErrImagePull」という理由でコンテナのデプロイが完了していないことが分かり、Eventとして「aaa」というイメージのPullに失敗していることが分かります。

 このように、Podの詳細情報を確認することで、どういった理由でPodのステータスがPending、Failed、Unknownのいずれかになっているかが分かります。フローの中ではよく発生する問題を分岐に含めています。こちらにPodの詳細情報の結果による分岐のみを抜き出しました。


しっかり特定レイヤー」におけるPodの詳細情報の結果による分岐

 具体的には、それぞれのエラーの意味は次のようになっており、それぞれのエラーに対して次のレイヤーで詳細に分析します。

  • 「OOMKilled」(status.containerStatuses.state.waiting.reason)
    コンテナに割り当てられたメモリが枯渇し、「OOM Killer」にプロセスをKillされた
    →「A. Podのリソース不足」として「かっちり判明レイヤー」で対応を検討する
  • 「...PodExceedsFreeCPU/Memory...」「...Insufficient cpu/memory...」(Events)
    Nodeの余剰リソース以上のリソースをPodが要求している
    →「B. Nodeのリソース不足」として「かっちり判明レイヤー」で対応を検討する
  • 「...exceeded quota...」「...failed quota...」(Events)
    Resource Quotaの設定されたNamespaceに対して、limitsやrequestsを設定していない、あるいはQuotaを超過した設定にしている
    →「E. Resource Quota抵触」として「かっちり判明レイヤー」で対応を検討する
  • 「Evicted」(status.reason)
    Podが退避(Evict)対象になった。Evictはノードのリソースが足りなくなった場合に行われる
    →「F. Eviction」として「かっちり判明レイヤー」で対応を検討する
  • 「ErrImagePull」「PullImageError」(status.containerStatuses.state.waiting.reason)
    イメージの取得に失敗している
    →「G. コンテナイメージPullエラー」として「かっちり判明レイヤー」で取得失敗理由の原因分析を実施する
  • 「...Liveness prove failed...」(Events)
    Liveness Proveが失敗してコンテナの再起動が繰り返されている
    →「H. Liveness Prove失敗によるコンテナ再起動」として「かっちり判明レイヤー」で対応を検討する
  • 「CrashLoopBackOff」(status.containerStatuses.state.terminated.reason)
    コンテナが再起動を繰り返している
    →アプリやミドルウェアのエラー、クラッシュによってコンテナが再起動している可能性があるので、「【2】-2 ログの確認」に進む


【2】-2 ログの確認

 コンテナやPodの詳細情報を確認したにもかかわらず停止/再起動の原因が不明な場合はログを確認します。

 まずはKubernetesの管理コンポーネント(コントロールプレーン、kube-proxy、corednsなど)がエラーを出しているかどうかを確認。次に、ミドルウェアやアプリがエラーを出しているかどうかを確認します。

※管理コンポーネントのエラーについては基盤の問題として、今回のフローのスコープから外しています。

 特にミドルウェアやアプリのエラーを確認できない場合でも、「kubectl port-forward」「kubectl exec」コマンドを利用してミドルウェア、アプリが正常に稼働しているかどうかを確認します。

 大方、ミドルウェアやアプリのどの部分のエラーが原因かを把握できれば「かっちり判明レイヤー」の「I. ミドルウェア、アプリのエラー」で詳細を分析します。

 ログを見る際に使うツールは、Lokiのようなログ集約基盤を使って横断的にログを見ることもできますし、Podが特定できていれば「kubectl logs」コマンドを利用して確認するのもいいでしょう。ツールの切り替えの容易さや対象コンポーネントの量に応じて使い分けるといいです。

$ kubectl logs nginx                            
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/03/02 21:38:29 [notice] 1#1: using the "epoll" event method
2022/03/02 21:38:29 [notice] 1#1: nginx/1.21.6
2022/03/02 21:38:29 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/03/02 21:38:29 [notice] 1#1: OS: Linux 4.14.262-200.489.amzn2.x86_64
2022/03/02 21:38:29 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:65535
2022/03/02 21:38:29 [notice] 1#1: start worker processes
2022/03/02 21:38:29 [notice] 1#1: start worker process 32
2022/03/02 21:38:29 [notice] 1#1: start worker process 33
kubectl logsコマンド

 ログには出ないがミドルウェアやアプリのエラーが起因で障害となっているケースもあります。そのため、ログが見つからない場合でも一度、ミドルウェアやアプリそのものの動作を確認することが重要です。例えば「kubectl port-forward」コマンドでエンドポイントのポートにポートフォワードし、リクエストの疎通が取れるかどうかを確認したり、「kubectl exec」コマンドでコンテナ内のシェルに入って動作を確認したりします。

コラム エフェメラルコンテナ

 利用しているコンテナイメージによっては、必要なコマンドが足りず動作確認が難航したり、bashやshが存在せず、そもそもシェルに入れなかったりといった場合があります。そういった場合にはエフェメラルコンテナの利用も検討しましょう。

 Kubernetes v1.17から「kubectl debug」コマンドで稼働しているPod内にエフェメラルコンテナを追加できるようになりました。これにより必要なコマンドが足りないコンテナにおいても動作確認を円滑に進められます。

 詳しくは下記をご参照ください。


かっちり判明レイヤー(根本原因特定)

 このレイヤーでは、「しっかり特定レイヤー」で絞り込んだ事象について、根本原因を分析、特定します。


かっちり判明レイヤー

A. Podのリソース不足

 Podのリソースが不足している場合、まずはリソース増強を検討することが多いでしょう。増強の方向性はlimitsを増加させるスケールアップと、レプリカ数を増やすスケールアウトがあります。アプリの特性とNodeのリソース状況を鑑みて判断しましょう。

 Nodeのリソース上、これ以上のPodリソース増強が難しい場合や、あまりにもPodのリソース使用量が大きい場合はチューニングを検討します。チューニングについては個々のプログラミング言語やミドルウェアによりますが、Javaを例に取ると、CPUバウンドな状況であればメソッドによるCPU使用か、GCによるCPU使用かを深掘りし、そうではない場合はスレッドダンプからスレッドのロックを疑います。

 この辺りは過去の知見を活用できるポイントです。Javaアプリのトラブルシューティングについては、@IT記事「Webアプリの問題点を『見える化』する7つ道具」などを参考にしてください。

B. Nodeのリソース不足

 Nodeのリソースが不足している場合、まずはリソースの枯渇原因がそのNode上で稼働するPodなのか、それ以外のプロセスなのかを切り分ける必要があります。これは、Podのリソース使用量の積み上げで確認できます。


特定のNode上で稼働するPodのCPU使用量積み上げグラフ

 例えば、上図のような状況だとPodのリソースが支配的なので、Podが不用意に特定のNodeにのみデプロイされる設定かどうか、「Taints」「Tolerations」などを確認し、必要に応じてスケールアップ/アウトします。Pod以外のプロセスが原因の場合、そのプロセスが稼働する必要があるものならば、より詳細に分析します。

C. ミドルウェア、アプリのロジックによる遅延

 この場合は、「A. Podのリソース不足」で言及したプログラミング言語やミドルウェアのチューニングの対応を進めます。

D. Node障害

 Node障害の場合、発生原因をより深掘りするために「kubectl describe nodes」コマンドで詳細情報を確認します。

 「Conditions」フィールドに各種Nodeのトラブルシューティングのヒントとなる情報が表示されるので、こちらをベースに対応します(参考)。

$ kubectl describe nodes hoge
Name:			hoge
...
Conditions:
  Type                  Status          LastHeartbeatTime                       LastTransitionTime                      Reason                  Message
  ----                  ------          -----------------                       ------------------                      ------                  -------
  OutOfDisk             Unknown         Fri, 08 Sep 2021 16:04:28 +0800         Fri, 08 Sep 2021 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  MemoryPressure        Unknown         Fri, 08 Sep 2021 16:04:28 +0800         Fri, 08 Sep 2021 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  DiskPressure          Unknown         Fri, 08 Sep 2021 16:04:28 +0800         Fri, 08 Sep 2021 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.
  Ready                 Unknown         Fri, 08 Sep 2021 16:04:28 +0800         Fri, 08 Sep 2021 16:20:58 +0800         NodeStatusUnknown       Kubelet stopped posting node status.

 この例では、各ステータスが「Unknown」となっています。その場合は、さらなる切り分けが必要です。「kube-api server」などのコントロールプレーンとNode間のネットワーク疎通があるかどうか、Node上で稼働するKubeletのログやステータスがどうなっているかを確認します。

$ sudo journalctl -u kubelet # Kubelet ログ確認
$ sudo systemctl status kubelet # Kubelet ステータス確認

 どうにも原因が不明な場合は、一度新しいNodeと差し替えたり(Drainによるノードの切り離しと併せて)、Nodeを再起動したりしてみるのも一案です。この際、再起動してしまうとメモリが飛びます。今後、原因を解析できなくなるので、注意してください。

 トラブルシューティングは費用対効果を意識するのが重要です。より細かく原因を追究するほどに工数がかさむからです。従って、影響範囲が見切れており、Node再起動やPod再作成で再発しなくなり、重要度が高くない(SLAへの影響なし)なら「根本原因を特定しない」という判断も必要です。


E. Resource Quota抵触

 Resource QuotaはNamespace単位でPodのリソース量(CPU、メモリ)の合計やストレージリソースの合計、オブジェクト数などについてハードリミットを定義できます。また、Resource Quotaが有効だとrequestsやlimitsの設定がない場合でもPodの作成が失敗するので注意してください。

 Resource Quotaに抵触してPodのデプロイができない場合は、まずは次のコマンドで Resource Quotaの設定を確認します。

$ kubectl -n dev describe resourcequotas

 その上で、Podのリソース要求量を確認し、過剰にリソース要求しているようなPodがあるかどうかや、requestsやlimitsの指定がないPodがないかどうかを確認します。個々のPodのリソース使用量は、上述したようにPrometheusメトリクスからでも確認できますし、「kube-capacity」のようなサードパーティーのコマンドでも確認できます。

 次の例では、「Krew」という「kubectl」のプラグイン管理ツールを利用して、kubectlのサブコマンド「resource-capacity」として「kube-capacity」をインストールして利用しています。なおKrewを利用するには、Installingページを参照し、インストールしてPATHを追加してください。完了すると、kubectl krewコマンドを利用することができます。

$ kubectl krew install resource-capacity
Updated the local copy of plugin index.
Installing plugin: resource-capacity
Installed plugin: resource-capacity
\
 | Use this plugin:
 |      kubectl resource-capacity
 | Documentation:
 |      https://github.com/robscott/kube-capacity
/
WARNING: You installed plugin "resource-capacity" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.
   
$ kubectl resource-capacity --pods
NODE              NAMESPACE     POD                   CPU REQUESTS    CPU LIMITS    MEMORY REQUESTS    MEMORY LIMITS
*                 *             *                     560m (28%)      780m (38%)    572Mi (9%)         770Mi (13%)
example-node-1    *             *                     220m (22%)      320m (32%)    192Mi (6%)         360Mi (12%)
example-node-1    kube-system   metrics-server-lwc6z  100m (10%)      200m (20%)    100Mi (3%)         200Mi (7%)
example-node-1    kube-system   coredns-7b5bcb98f8    120m (12%)      120m (12%)    92Mi (3%)          160Mi (5%)
example-node-2    *             *                     340m (34%)      460m (46%)    380Mi (13%)        410Mi (14%)
example-node-2    kube-system   kube-proxy-3ki7       200m (20%)      280m (28%)    210Mi (7%)         210Mi (7%)
example-node-2    tiller        tiller-deploy         140m (14%)      180m (18%)    170Mi (5%)         200Mi (7%)

 確認の結果、全てのPodが必要なリソース量ならば、Resource Quotaが変更できるかどうかを検討します。併せて、使用リソース量の大きいPodについては「Podのリソース不足」で確認したような、リソース使用量に関するチューニングも検討するといいでしょう。

F. Eviction

 Nodeのメモリあるいはディスクの容量やPID数がしきい値(Eviction thresholds)より下回ると、EvictionによってPodの削除が開始されます。

 Evictionには「Soft Eviction」「Hard Eviction」が存在し、それぞれでしきい値をkubeletの引数として設定できます。

 Soft Evictionでは「kubectl describe nodes」コマンドで確認できるNodeのCondition(メモリ、ディスク、PID)フラグが有効化されて一定の猶予期間後にPodが削除されます。Hard Evictionでは猶予期間なくPodが削除されます。

 Eviction時に削除されるPodには削除順序が存在し、requestsやlimitsから判定されるQoS(Quality of Service)とリソース使用量、優先度(PriorityClass)に基づいて次のように順序が決められます。

  • QoSが「BestEffort」または「Burstable」で使用量がrequestsを上回るPod
    →優先度と使用量がどれだけrequestsを上回っているかによって順序付けられる
  • QoSが「Guaranteed」のPod、「Burstable」で使用量がrequestsより少ないPod
    →優先度に基づいて最後に削除される

※QoSは「kubectl describe pod」コマンドで確認できます。

$ kubectl describe pod test | grep QoS
QoS Class:       BestEffort

 従って、まずはEvictionが発生した際には、まずはNodeに十分な(稼働させるPodの合計リソース量を補えるような)リソースが確保されているかどうかを確認するのが重要です。

 その上で、サービスを稼働させる上で重要なPodについてはQoSを「Guaranteed」にしたり、requestsは余裕を持った設定にしたりすることで、Evictionの削除順序を後ろに回す工夫をするといいでしょう。併せて、Resource Quotaを設定しておくことで想定以上にPodが立ち上がってリソースを奪うことを避けることができるので、Resource Quotaを活用したNamespaceごとのリソース設計もしておくといいでしょう。

 細かいEvictionの挙動については、「Node-pressure Eviction | Kubernetes」を参照してください。

G. コンテナイメージPullエラー

 コンテナイメージのPullエラーが出ている場合、まずは、そもそもイメージのパスやタグが誤っていないかどうかを確認します。

 認証が必要なレジストリを利用している場合は、imagePullSecretsとそこで利用されているSecretが正しく設定されているかどうかや、パブリッククラウドのレジストリ(ECR、GCR、ACR)であればPullエラーを起こしているNodeやPodに割り当たっている権限(IAMなど)が十分かどうかを確認しましょう。

H. Liveness Probe失敗によるコンテナ再起動

 KubernetesにはProbeとして「Liveness Probe」「Readiness Probe」「Startup Probe」があります。Readiness Probe は、しきい値まで失敗するとServiceから切り離されます。Liveness Probe、Startup Probeでは、しきい値まで失敗するとコンテナが再起動します。

 従って、Liveness Probe、Startup Probeの設定がうまくいっていなかったり、Probeで指定されたコマンドやHTTPリクエストが成功できないようにアプリが修正されてしまったりすると、アプリの動作とは関係なくコンテナが再起動してしまいます。

 Liveness Probeによってコンテナが再起動を繰り返している場合は、まずは「何をProbeしているか」の設定をPodのリソース定義(YAML)から確認しましょう。

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5
Pod内でのLiveness Probe定義

 例えばこちらの場合、「cat」コマンドで特定のパスにファイルが存在しているかどうかを確認しています。

 次に、コンテナに対してProbeで利用されるコマンドやHTTPリクエストを実行してみて、成功するかどうかを確認し、失敗するようならアプリ自体の作り、設定、Probeの見直しを検討しましょう。

※Probeを確認する際には、「kubectl exec」コマンドや「kubectl port-forward」コマンド、エフェメラルコンテナを活用できます。

 見直しにおける重要な点は、失敗すると再起動が必要となるような挙動(例えばデッドロック)でのみLiveness Probeを設定することです。単純にトラフィックの受け付けを待たせたいだけならReadiness Probeが適切です。

 細かいProbeの挙動については、「Liveness Probe、Readiness ProbeおよびStartup Probeを使用する | Kubernetes」を参照してください。

I. ミドルウェア、アプリのエラー

 ログから確認できたエラーの内容をベースに、個々のミドルウェア、アプリに合わせてさらなる切り分けや対応を実施します。エラーの再現が難しい場合など必要に応じて、「kubectl exec」コマンドやエフェメラルコンテナによって、稼働しているコンテナのシェルに入って、詳細にデバッグします。

 Javaアプリのトラブルシューティングについては、記事「Webアプリの問題点を「見える化」する7つ道具」などを参考にしてください。

まとめ

 本稿ではKubernetesを前提としたクラウドネイティブシステムにおけるトラブルシューティングフローを紹介しました。

 クラウドネイティブなシステムでは、多数のコンポーネントから問題を特定していく必要があり、情報が大量にあるので、やみくもに情報をあさることはあまり得策ではありません。

 紹介したような3段階のレイヤー(「ぼんやり特定レイヤー」→「しっかり特定レイヤー」→「かっちり判明レイヤー」)で、少しずつ被疑個所と原因を絞り込むことで迅速に問題発生個所とその根本原因を特定するのがいいと考えています。

 フローの中では、前回紹介したObservabilityの3シグナルであるメトリクス、ログ、トレースを活用して切り分けを進めました。あくまで代表的な問題をベースに作成したものなので、必ずしもこのフローで全ての問題が解決するわけではありませんが、切り分けの進め方、確認すべき情報など、参考になれば幸いです。

 今回は一足飛びにObservabilityシグナルを活用したトラブルシューティングを題材にしましたが、ここからはそれぞれのObservabilityシグナルにフォーカスした内容を連載していきます。

 次回の記事はメトリクスについてです。Prometheus、GrafanaといったデファクトなOSSを使ってどのようにメトリクスを実装するのか、気を付ける点や工夫できる点は何かといった観点で記事を執筆する予定です。次回もお楽しみに!

Copyright © ITmedia, Inc. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る