この記事を読めば、クラスタノードはオフラインになりません

この記事を読めば、クラスタノードはオフラインになりません

問題は起こり続ける

1. まだ準備ができていない

Alibaba Cloud には独自の Kubernetes コンテナ クラスター製品があります。 Kubernetes クラスターの出荷量が劇的に増加したことにより、オンライン ユーザーは、クラスターのノードが NotReady になる可能性が非常に低いことに散発的に気付きました。

私たちの観察によれば、ほぼ毎月 1 ~ 2 人の顧客がこの問題に遭遇しています。ノードが NotReady になると、クラスター マスターは、新しい Pod の発行や、ノードで実行されている Pod に関するリアルタイム情報の取得など、ノードに対する制御を実行できなくなります。

2. Kubernetesについて知っておくべきこと

ここで、Kubernetes クラスターに関する基本的な知識をいくつか追加したいと思います。 Kubernetes クラスターの「ハードウェア基盤」は、スタンドアロン マシンの形で存在するクラスター ノードです。これらのノードは物理マシンまたは仮想マシンにすることができます。クラスター ノードは、マスター ノードとワーカー ノードに分かれています。

  • マスターノードは主に、スケジューラやコントローラなどのクラスター管理コンポーネントをロードするために使用されます。
  • ワーカーノードは主にビジネスを実行するために使用されます。 Kubelet は各ノード上で実行されるエージェントです。制御コンポーネントと通信し、制御コンポーネントの指示に従ってワーカー ノードを直接管理する役割を担います。

クラスターノードが NotReady 状態になった場合、最初に行う必要があるのは、ノード上で実行されている kubelet が正常かどうかを確認することです。この問題が発生した場合は、systemctl コマンドを使用して kubelet のステータスを確認し、systemd によって管理されるデーモンとして正常に実行されていることを確認します。 journalctl を使用して kubelet ログを確認すると、次のエラーが見つかりました。

3. PLEGとは

このエラーは、コンテナ ランタイムが動作しておらず、PLEG が正常ではないことを明確に示しています。ここでのコンテナ ランタイムは Docker デーモンを指します。 Kubelet は Docker デーモンを直接操作してコンテナのライフサイクルを制御します。

ここでの PLEG は、ポッド ライフサイクル イベント ジェネレーターを指します。

PLEG は、kubelet がコンテナのランタイムをチェックするために使用するヘルスチェック メカニズムです。これは、ポーリングを使用して kubelet によって実行できた可能性があります。しかし、投票にはコスト面での不利があるため、PLEG が誕生しました。 PLEG は、実際にはポーリングと「割り込み」メカニズムの両方を使用しますが、「割り込み」の形式でコンテナ ランタイムのヘルス チェックを実装しようとします。

基本的に、上記のエラーを見ると、コンテナのランタイムに問題があることが確認できます。問題のあるノードで、docker コマンドを使用して新しいコンテナを実行しようとすると、コマンドは応答しません。これは、上記で報告されたエラーが正しいことを示しています。

コンテナランタイム

1. Dockerデーモンのコールスタック分析

Alibaba Cloud Kubernetes クラスターで使用されるコンテナ ランタイムである Docker は、OCI 標準に適応するためにバージョン 1.11 以降、複数のコンポーネントに分割されました。

分割後、docker デーモン、containerd、containerd-shim、runC が含まれます。コンポーネント containerd は、クラスター ノード上のコンテナーのライフサイクル管理を担当し、docker デーモンに gRPC インターフェイスを提供します。

この問題では、PLEG はコンテナ操作に問題があると判断しているため、docker デーモン プロセスから開始する必要があります。 kill -USR1 コマンドを使用して、USR1 シグナルを docker デーモンに送信できます。シグナルを受信すると、docker デーモンはすべてのスレッド呼び出しスタックを /var/run/docker フォルダーに出力します。

Docker デーモン プロセスのコール スタックは比較的簡単に分析できます。少し注意してみると、ほとんどのコールスタックは次の図のようになることがわかります。

スタック上の各関数の名前と、関数が配置されているファイル (モジュール) の名前を観察すると、呼び出しスタックの下半分は http 要求を受信して​​要求をルーティングするプロセスであることがわかります。上半分は実際の処理機能に入ります。最後に、処理関数は待機状態に入り、ミューテックス インスタンスを待機します。

この時点で、ContainerInspectCurrent 関数の実装を確認する必要があります。最も重要なことは、この関数の最初のパラメーターがミューテックス ポインターであることを理解できることです。このポインターを使用してコールスタック ファイル全体を検索すると、このミューテックスを待機しているすべてのスレッドが見つかります。

同時に、以下のスレッドも見ることができます。

このスレッドでは、関数 ContainerExecStart は特定の要求を処理するときに mutex パラメータも受け取ります。ただし、違いは、ContainerExecStart はミューテックスを待機しているのではなく、すでにミューテックスの所有権を取得しており、実行ロジックを containerd 呼び出しに転送していることです。コードを使用してこれを確認できます。

前述したように、containerd は gRPC を介して docker デーモンへのインターフェースを提供します。このコールスタックの上部にある内容は、gRPC リクエストを通じて containerd を呼び出す docker デーモンです。

2. Containerd コールスタック分析

docker デーモンのコールスタックを出力するのと同様に、kill -SIGUSR1 コマンドを使用して containerd のコールスタックを出力できます。違いは、今回はコールスタックがメッセージ ログに直接出力されることです。

gRPC サーバーとして、containerd は docker デーモンからリモート要求を受信した後、要求を処理するための新しいスレッドを作成します。ここでは gRPC の詳細にあまり注意を払う必要はありません。

このリクエストのクライアント呼び出しスタックを見ると、この呼び出しのコア機能がプロセスの開始であることがわかります。 containerd コールスタックで Start、Process、process.go などのフィールドを検索すると、次のスレッドが簡単に見つかります。

このスレッドの中心的なタスクは、runC を利用してコンテナ プロセスを作成することです。コンテナが起動すると、runC プロセスは終了します。したがって、次のステップは、runC がタスクを正常に完了したかどうかを確認することです。

プロセス リストを見ると、システム内の一部の runC プロセスがまだ実行中であることがわかりますが、これは予期された動作ではありません。コンテナとプロセスの起動にかかる時間はほぼ同じになるはずです。システム内で runC プロセスが実行されている場合、runC はコンテナを正常に起動できないことを意味します。

Dbusとは

1. RunCがDbusを要求する

コンテナ ランタイムの runC コマンドは、libcontainer の単純なラッパーです。このツールは、コンテナの作成や削除など、単一のコンテナを管理するために使用できます。前のセクションの最後で、runC がコンテナの作成タスクを完了できなかったことがわかりました。

対応するプロセスを強制終了し、コマンドラインで同じコマンドを使用してコンテナを起動し、strace を使用してプロセス全体を追跡することができます。

分析により、runC は org.free フィールドを使用して dbus にデータを書き込む時点で停止することが示されています。では、dbus とは何でしょうか? Linux では、dbus はプロセス間のメッセージ通信のメカニズムです。

2. 理由はDbusではない

busctl コマンドを使用して、システム内の既存のバスをすべて一覧表示できます。下図に示すように、問題が発生したとき、顧客クラスターノード名の数が非常に多かったことがわかりました。したがって、この問題は Name などの dbus 関連のデータ構造の枯渇によって発生したと考える傾向があります。

Dbus メカニズムの実装は、dbus-daemon と呼ばれるコンポーネントに依存します。 dbus 関連のデータ構造が本当に使い果たされている場合は、デーモンを再起動すると問題を解決できるはずです。しかし残念なことに、問題はそれほど単純ではありません。 dbus-daemon を再起動しても、問題は依然として存在します。

上記の strace を使用して runC を実行しているスクリーンショットでは、runC が org.free フィールドを使用してバスにデータを書き込むときにスタックしたことを述べました。 busctl によって出力されるバス リストでは、このフィールドを持つバスがすべて systemd によって使用されていることは明らかです。この時点で、systemctl daemon-reexec を使用して systemd を再起動すると、問題は解消されました。

したがって、基本的には、問題は systemd に関連している可能性があるという結論を下すことができます。

Systemdは難しい問題だ

Systemd は、特に私のように関連する開発作業を一切行ったことがない人にとっては、非常に複雑なコンポーネントです。基本的に、systemd の問題をトラブルシューティングするために次の 4 つの方法を使用しました。

  • (デバッグレベル) ログ
  • コアダンプ
  • コード分​​析
  • ライブデバッグ

1 番目、3 番目、4 番目の方法を組み合わせることで、数日間の懸命な作業の末、問題の原因を見つけることができました。しかし、ここでは「役に立たない」コアダンプから始まります。

1. 役に立たないコアダンプ

systemd を再起動すると問題は解決しましたが、問題自体は、dbus を使用して systemd と通信するときに runC が応答しなかったため、最初に確認する必要があるのは、systemd にロックされている重要なスレッドがあるかどうかです。

コアダンプ内のすべてのスレッドを見ると、次のスレッドだけがロックされていません。応答するために dbus イベントを待機しています。

2. 散らばった情報

他にできることがないので、いろいろなテストや試行をすることしかできません。 busctl tree コマンドを使用して、バス上の公開されているすべてのインターフェースを出力します。出力結果から、org.freedesktop.systemd1 バスはインターフェース クエリ要求に応答できないようです。

org.freedesktop.systemd1 で受信したすべてのリクエストを監視するには、次のコマンドを使用します。通常のシステムでは、ユニットの作成と削除のメッセージが多数ありますが、問題のあるシステムでは、このバスにメッセージがまったく存在しないことがわかります。

 gdbus monitor --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1

問題発生前後のシステム ログを分析すると、runC は libcontainer_%d_systemd_test_default.slice テストを繰り返し実行していました。このテストは非常に頻繁に行われていましたが、問題が発生すると、このテストは停止しました。

したがって、私の直感では、この問題はこのテストと大きく関係している可能性があると思います。

さらに、systemd-analyze コマンドを使用して systemd デバッグ ログを開いたところ、systemd に「操作はサポートされていません」というエラー メッセージが表示されていました。

上記の散在した知識に基づいて、大まかな結論しか出せません。大量のユニットが作成および削除された後、バス org.freedesktop.systemd1 は応答しなくなります。

これらの頻繁なユニット作成および削除テストは、systemd の特定の機能が使用可能かどうかをテストするために使用される UseSystemd 関数を上書きする runC のチェックインによって発生します。 UseSystemd 関数は、コンテナの作成やコンテナのパフォーマンスのチェックなど、さまざまな場所で呼び出されます。

3. コード分析

この問題は、すべてのオンライン Kubernetes クラスターで月に 2 回程度発生します。この問題は引き続き発生しており、発生後に systemd を再起動することによってのみ修正できますが、これは非常に危険です。

我々はそれぞれ systemd と runC のコミュニティにバグレポートを提出しましたが、非常に現実的な問題として、Alibaba Cloud のようなオンライン環境がないため、この問題を再現できる可能性はほぼゼロであり、コミュニティがこの問題を解決することは期待できません。固い骨は自分で噛まなければなりません。

前のセクションの最後で、問題が発生すると、systemd が Operation not supported エラーを出力することを説明しました。このエラーは問題自体とは無関係のようですが、私の直感ではこれが問題に最も近い場所かもしれないので、まずはこのエラーの原因を突き止めることにしました。

Systemd には大量のコードが含まれており、このエラーは多くの場所で報告されています。多くのコード分析 (ここでは千語は省略) を通じて、疑わしい箇所をいくつか見つけました。こうした怪しい場所では、次にすべきことは待つことです。 3 週間待った後、オンライン クラスターで問題がようやく再発しました。

4. ライブデバッグ

顧客の同意を得た後、systemd デバッグ シンボルをダウンロードし、systemd に gdb をマウントし、疑わしい関数にブレークポイントを設定して実行を続行します。複数回の検証の結果、systemd が最終的に sd_bus_message_seal 関数で EOPNOTSUPP エラーに遭遇したことが判明しました。

このエラーの原因は、systemd が変数 Cookie を使用して、処理するすべての dbus メッセージを追跡することです。新しいメッセージが追加されるたびに、systemd はまず Cookie の値を 1 増やし、次に Cookie の値を新しいメッセージにコピーします。

gdb を使用して dbus->cookie の値を印刷すると、値が 0xffffffff を超えていることが明確にわかります。したがって、この問題は、systemd が大量のメッセージをシールした後に Cookie 値が 32 ビット オーバーフローし、新しいメッセージをシールできないことが原因で発生するようです。

さらに、通常のシステムでは、gdb を使用して bus->cookie の値を 0xffffffff に近くなるように変更し、cookie がオーバーフローするとすぐに問題が発生することを観察すると、結論が証明されます。

5. クラスターノードの NotReady がこの問題によって発生したかどうかを判断する方法

まず、問題のノードに gdb と systemd debuginfo をインストールし、コマンド gdb / usr/lib / systemd / systemd 1 を使用して gdb を systemd に接続し、関数 sd_bus_send にブレークポイントを設定して、実行を続行する必要があります。

systemd がブレークポイントに到達したら、 p /x bus->cookie を使用して対応する Cookie 値を表示します。この値が 0xffffffff を超えると、Cookie がオーバーフローし、必然的にノード NotReady 問題が発生します。確認後、quit を使用してデバッガーをデタッチできます。

バグ修正

この問題の解決はそれほど簡単ではありません。理由の 1 つは、systemd が dbus1 および dbus2 との互換性を保つために同じ Cookie 変数を使用していることです。

dbus1 の場合、Cookie は 32 ビットであり、systemd が 3 ~ 5 か月間にユニットを頻繁に作成および削除すると、この値は確実にオーバーフローします。

dbus2 クッキーは 64 ビットなので、時間の終わりにオーバーフローしない可能性があります。

もう 1 つの理由は、オーバーフローの問題を解決するために Cookie を単純にラップすることはできないことです。これにより、systemd が同じ Cookie を使用して異なるメッセージを封印することになり、悲惨な結果を招く可能性があります。

最終的な修正は、32 ビットの Cookie を使用して dbus1 と dbus2 の両方のケースを平等に処理することでした。同時に、クッキーが 0xffffffff に達した後の次のクッキーは、最上位ビットを使用してクッキーがオーバーフローしたことをマークし、0x80000000 を返します。クッキーがこの状態にあることが検出された場合、クッキーの競合を避けるために、次のクッキーが他のメッセージによって使用されているかどうかを確認する必要があります。

追記

この問題の根本的な原因は systemd にあるはずですが、runC 関数 UseSystemd は systemd の機能をテストするためにあまり美しくない方法を使用しています。この機能は、コンテナのライフサイクル管理プロセス全体を通じて頻繁にトリガーされるため、この低確率の問題が発生する可能性があります。

systemd の修正は Red Hat によって承認されており、近い将来、systemd をアップグレードすることでこの問題を根本的に解決できると予想されます。

<<:  ByteDance Spark Shuffle 大規模クラウドネイティブ進化の実践

>>:  スムーズで信頼性が高く、安全なF5は、企業が簡単にクラウド移行を実現できるよう支援します。

推薦する

ウェンタオ:衛星テレビ設置会社の SEO 事例

これは私が最近担当したクライアントです。クライアントが要求するキーワード競争は百度で55万で、それほ...

NetEase Games、26の新製品+「神のような」ゲームソーシャルプラットフォームをリリース

2018年はNetEaseにとって重要な年です。財務報告によると、NetEaseの第1四半期のオンラ...

ウェブマスターネットワークからの毎日のレポート:Bingのシェアが低下、Qihoo 360が包括的な検索をテスト

1. 莫口傑Cの資金調達評価額は2億、Alexaランキングは半年間で5回ゼロにリセットMogujie...

ホストオンはどうですか?ロサンゼルスの AMD Ryzen+NVMe シリーズ VPS のレビュー

現在、hosteons の AMD Ryzen+NVMe シリーズ VPS は 3 つのデータセンタ...

アウトバウンドリンクを構築するための実践ガイド

私は a5 ウェブマスターの Web サイトに記事を送信しましたが、多くのウェブマスターが記事を収集...

inceptionhosting - 50% 割引コード、512M メモリ、KVM 仮想 VPS、年間 15 ユーロ

2011年に運営を開始したVPS業者のinceptionhostingは、海外の格安VPS業界では一...

レッドハット、マット・ヒックスを社長兼CEOに任命

米国時間7月12日、Red Hatはマット・ヒックス氏をRed Hatの社長兼CEOに任命したことを...

ネットワークマーケティングの永遠の伝説

ソフト商品プロモーションの現状百度が7月1日にリリースした「青大根アルゴリズム2.0」は、宣伝サイト...

Baidu アルゴリズムアップグレードに関する考察: SEO 業界は現実的であるべき

以前は、大量のコンテンツを収集し、投稿マシンを使用して多数の外部リンクを投稿することで、Web サイ...

マイクロソフト: メタバースの無限の可能性を探り、デジタル技術で業界のアップグレードを強化

[51CTO.com からのオリジナル記事] 今最もホットな技術用語は何かと言えば、それは「メタバー...

Nvidia CTO: クラウドからメタバースへ

仮想世界でロボットを訓練して、現実世界でより優れたロボットにしましょう。メタバース(またはオムニバー...

ショッピングモール運営には4Pマーケティング理論を学び、適用する必要がある

ウェブサイトの運営は、結局のところ、少ないエネルギーを投入して巨大なマーケティング効果を達成すること...

ユーザーエクスペリエンス: 印象派のホームページ再設計の視覚的側面に関する簡単な説明

。 。。少し前、私は Impression Pie のサイト全体の再設計に取り組んでいました。これに...