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

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

問題は起こり続ける

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は、企業が簡単にクラウド移行を実現できるよう支援します。

推薦する

海外サーバー:安くて信頼できる商人が推奨

専用サーバーは、どのように構成しても、リソースの面で VPS よりも信頼性が高く、独立した制御性も高...

既存顧客へのマーケティングの秘訣で売上を2倍に!

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています販売の世界...

企業の本当の嘘を最適化する

検索エンジンマーケティング業界には、ある程度、嘘や誤解を招く情報が溢れています。これらの誤解を招く情...

フォーラムの外部リンクの時代は終わりました。ウェブマスターは何をすべきでしょうか?

2013年、百度は「ユーザーエクスペリエンスのウェブサイトの最適化を奨励し、不正行為のウェブサイトを...

VMware Kaniz Mahdi: グリッドでインターネットを再構築し、世界規模の自動化を推進

技術の進歩により、人間がコンテンツを消費する方法に変化が起こりました。リアルタイムの没入型コラボレー...

ステーションBでの正確なトラフィック迂回の秘密:アカウントは1つだけで十分

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス注: この記事は共有価値...

tmhhost: US cn2 gia + China Unicom AS9929 独立サーバー、高い防御保護、最低 700 元/月、e3-1230v5/16gDDR4/1T ハードディスク/30M 帯域幅/5 IP、

tmhhost は、米国で独自の独立サーバーを推進しています。ロサンゼルスのデータセンターに位置し、...

アクティブ防御: fail2ban でネットワークのスヌーピングをブロックする

Fail2ban は、自分のサーバーを保護する必要がある人のための、珍しい無料ツールです。何人かの愚...

ウェブマスターネットワークレポート:Sohu、Youkuなどが著作権侵害対策同盟を結成、Renrenはワイヤレスインターネットに後れを取る

1. 捜狐、優酷などが著作権侵害対策同盟を結成し、百度を相手取り3億元の損害賠償を求める中国オンライ...

「壊れた」クラウド サービスは良いことでしょうか、それとも悪いことでしょうか?

クラウド プロバイダーが特定の場所からクラウド サービスを提供することがますます一般的になっています...

SEO ガイド: シンプルな SEO プランをすばやく作成する方法!

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますスタートア...

どのようなユーザーエクスペリエンスが検索エンジンのニーズを満たすのでしょうか? (三つ)

前回の記事では、主にユーザーのニーズの分析とその内容を紹介しました。この記事では、主にほとんどのユー...

高性能で軽量な分散メモリキューシステム - beanstalk

Beanstalk は、高性能、軽量、分散型のインメモリ メッセージ キュー システムです。当初の設...

おすすめ: Vultr-10か月/無料$20/プロモーションコード

クラウド ホスティング プロバイダーの vultr.com が新しい割引コードをリリースしました。こ...

エッジコンピューティングは「黄金の10年」に突入

Internet of Everything と業界インテリジェンスの二重環境によって推進されるクラ...