Kafka ZooKeeper接続問題の深掘り

設定、ネットワーク、タイムアウト、ログ、ブローカーの負荷に関する実用的なチェックで、Kafka ZooKeeper接続障害をトラブルシューティングします。

Kafka ZooKeeper接続問題の深掘り

Kafka ZooKeeper接続問題は、主に古いKafkaクラスターやKRaftモードに移行していないクラスターに影響します。新しいKafkaデプロイメントはZooKeeperなしで動作可能ですが、多くの本番システムは依然としてZooKeeperに依存しています。ブローカーがserver.propertieszookeeper.connectを使用している場合、ZooKeeperは依然としてコントロールプレーンの一部であり、Kafka自体と同様の注意が必要です。

KafkaブローカーがZooKeeperセッションを維持できない場合、症状は単なる接続問題よりも大きく見えることがあります。ブローカーが再起動する場合があります。コントローラーの選出が繰り返される場合があります。パーティションが利用不可になる場合があります。ログには、セッション期限切れ、コントローラーの辞任、または繰り返される再接続試行が表示される場合があります。プロデューサーとコンシューマーは、メタデータエラー、タイムアウト、または不安定なリーダーといった下流の影響のみを目にする可能性があります。

ZooKeeperの役割を理解することから始めましょう。ZooKeeperベースのKafkaクラスターでは、ブローカーは自身をZooKeeperに登録し、コントローラーの選出はZooKeeperに依存し、クラスターメタデータの調整はZooKeeperを介して行われます。ブローカーがZooKeeperセッションを失い、セッションが期限切れになるほど長く続くと、Kafkaはそのブローカーをクラスターから削除されたものとして扱います。ブローカープロセスがまだ実行中であっても、クラスターはリーダーシップをそのブローカーから移動させる可能性があります。

最初のチェックは退屈ですが、しばしば問題を捉えます:すべてのブローカーでzookeeper.connectを確認します。

zookeeper.connect=zk01.example.com:2181,zk02.example.com:2181,zk03.example.com:2181/kafka
zookeeper.connection.timeout.ms=18000
zookeeper.session.timeout.ms=18000

接続文字列には、Kafkaが到達できるアンサンブルメンバーをリストする必要があります。/kafkaのようなchrootパスを使用する場合は、すべてのブローカーで一貫して含めてください。ブローカーの半分を/kafkaで設定し、残りの半分を設定しないでください。それらは異なるKafkaクラスターと通信しているように振る舞います。chrootを使用する場合は、最初にそれを作成するか、ZooKeeperツールで存在を確認してください。

設定のテキストだけでなく、DNSも確認してください。ラップトップから正しく解決されるホスト名が、ブローカーサブネットからは失敗する場合があります。Kafkaブローカーホストからチェックを実行し、バスティオンからは実行しないでください。ただし、バスティオンが同じネットワークパスを持っている場合を除きます。

getent hosts zk01.example.com
nc -vz zk01.example.com 2181
nc -vz zk02.example.com 2181
nc -vz zk03.example.com 2181

成功したTCP接続はZooKeeperが正常であることを証明しませんが、失敗した接続はファイアウォール、セキュリティグループ、ルーティング、DNS、またはリスナー設定をさらに調査する十分な理由になります。すべてのKafkaブローカーからすべてのZooKeeperノードに対してテストしてください。部分的な接続性は完全な障害よりも悪いです。なぜなら、ブローカーが特定のアンサンブルメンバーに接続しようとしたときにのみ障害が現れる可能性があるからです。

ZooKeeperの4文字コマンドは、有効になっている場合に役立ちます。多くのインストールではこれらが制限されているため、機能することを前提としないでください。許可されている場合、ruokimokを返すべきであり、mntrは有用なサーバーステータスを表示できます。

echo ruok | nc zk01.example.com 2181
echo mntr | nc zk01.example.com 2181

これらのコマンドが無効な場合は、代わりにサポートされている管理ツールまたは監視スタックを使用してください。重要なのは、ZooKeeperがリッスンしており、アンサンブルに参加し、迅速に応答しているかという単純な質問に答えることです。

次に、ZooKeeperアンサンブルの健全性を検査します。3ノードのアンサンブルは、1つのZooKeeperノードがダウンしても耐えられます。2つは耐えられません。5ノードのアンサンブルは2つまで耐えられます。偶数サイズのアンサンブルは避けてください。コストが増加する一方で、人々が期待するようなクォーラムの改善にはならないからです。3と5が一般的な選択肢です。

ZooKeeper側では、zoo.cfgを確認してください。clientPorttickTimeinitLimitsyncLimit、およびサーバー行を確認します。アドバタイズされたサーバーホスト名が、Kafkaブローカーからだけでなく、ZooKeeperノード間でも到達可能であることを確認してください。ZooKeeperピアは独自のクォーラムとリーダー選出ポートを必要とします。Kafkaブローカーは2181に到達できるかもしれませんが、ピアトラフィックがブロックされているためにZooKeeperアンサンブル自体が正常でない可能性があります。

セッションタイムアウトの調整も、混乱の一般的な原因です。KafkaはZooKeeperにセッションタイムアウトを要求しますが、ZooKeeperは自身の設定に基づいて制限を強制します。ZooKeeperでは、最小セッションタイムアウトは通常2 * tickTimeであり、最大は通常20 * tickTimeです(特定のサーバー設定で上書きされない限り)。つまり、許可範囲外のKafkaタイムアウト値は、ZooKeeperによって調整される可能性があります。

tickTime=2000の場合、通常の許可セッション範囲は約4秒から40秒です。zookeeper.session.timeout.ms=18000のようなKafka設定はその範囲内に収まります。非常に低いタイムアウトは、短いネットワークポーズやガベージコレクションポーズ中に誤った障害を引き起こす可能性があります。非常に高いタイムアウトは、実際のブローカー障害の検出に時間がかかる可能性があります。感度と安定性の間で選択していることになります。

tickTimeを軽々しく変更しないでください。これはKafkaだけでなく、ZooKeeperアンサンブル全体に影響します。ブローカーのポーズに対する許容度を高める必要がある場合は、ZooKeeperのタイミングを変更する前に、まずKafkaのzookeeper.session.timeout.ms、ブローカーのJVM動作、およびネットワークの健全性を確認する方が良いことがよくあります。

ログは、タイムスタンプで並べると通常は物語を語ります。Kafkaブローカーでは、ZooKeeperの切断とセッション期限切れに関するメッセージを検索します:

rg -i "zookeeper|session|expired|controller|reconnect" /var/log/kafka/server.log

パターンは単一行よりも重要です。計画されたZooKeeper再起動中の1回限りの再接続は無害かもしれません。数分ごとに繰り返される期限切れは不安定性を示します。ガベージコレクション中の期限切れは、JVMポーズまたはブローカーの過負荷を示します。多くのブローカーで同時に期限切れが発生する場合は、ZooKeeper、ネットワーク、または共有インフラストラクチャイベントを示します。

ZooKeeperノードでは、リーダー変更、fsync警告、接続スロットリング、および長いリクエストレイテンシを確認してください。ZooKeeperはトランザクションログを書き込むため、ディスクレイテンシに敏感です。遅いディスクは、サービスが到達可能に見えても、安定したセッションのために十分迅速に応答できない可能性があります。

ネットワークレイテンシとパケット損失は、ZooKeeperにとって生の帯域幅よりも重要です。KafkaブローカーはZooKeeperに大きなスループットを必要としませんが、信頼性が高く低レイテンシの通信が必要です。ブローカーとZooKeeperが遠くのネットワークに分割されている場合、問題が予想されます。それらを近くに保ってください。クラウド環境では、ブローカーからZooKeeperへのトラフィックを不要なNAT、過負荷のファイアウォール、またはクロスリージョンパスを経由してルーティングしないようにしてください。

Kafkaブローカー上のリソース競合は、ZooKeeperの問題とまったく同じように見えることがあります。JVMが長時間のガベージコレクションポーズでワールドを停止すると、ブローカーはハートビートを見逃す可能性があります。CPUが飽和している場合、ハートビート処理が遅延する可能性があります。ホストが高いI/O待機でスタックしている場合、Kafkaは調整作業に追いつけない可能性があります。ZooKeeper切断と同じタイムスタンプでブローカーメトリクスを確認してください。

有用なブローカー側の質問には次のものがあります:切断前にヒープ使用量が増加したか、GCポーズ時間が急増したか、ディスクI/O待機が高かったか、ネットワーク再送信が増加したか、同時に大規模なパーティション再割り当てやリーダー移動があったか。負荷で溺れているブローカーは、より少ないパーティションリーダー、より良いディスク、JVMチューニング、またはトラフィックシフトを必要とするかもしれません。ZooKeeperタイムアウトを増やすことは、原因を修正せずに症状を隠す可能性があります。

設定の一貫性は見落とされがちです。同じクラスター内のすべてのKafkaブローカーは、同じZooKeeper接続文字列とchrootを使用する必要があります。また、一意のbroker.id値を持つ必要があります。重複したブローカーIDは、2つのプロセスが同じブローカーを表現しようとしているため、混乱を招く登録動作を引き起こす可能性があります。

最近ZooKeeperホスト名、証明書、ファイアウォールルール、またはKafkaブローカー設定を変更した場合は、動作しているブローカーと障害が発生しているブローカーを比較してください。小さな違いは一般的です:古いDNSサフィックス、欠落したchrootパス、2つのブローカーに接続されているが3つ目には接続されていないセキュリティグループ、または1つのsystemd環境ファイルのタイプミス。

回復は何が壊れたかに依存します。ファイアウォールルールが欠落していた場合は、それを修正し、影響を受けたブローカーが正常に再接続しない場合は再起動します。ZooKeeperがクォーラムを失った場合は、Kafkaブローカーをバウンスする前にまずクォーラムを復元します。ブローカーが過負荷のために期限切れになった場合、再起動は一時的に戻すかもしれませんが、圧力を取り除かない限り問題は再発します。

ローリング再起動を使用してください。ZooKeeperが不安定だったためにすべてのKafkaブローカーを一度に再起動すると、部分的な障害が完全な障害に変わる可能性があります。ZooKeeperの健全性を回復し、その後、コントローラーの安定性とパーティションリーダーシップを監視しながら、ブローカーを1つずつ再起動または回復します。

長期的な安定性のために、両側を監視してください。ZooKeeperでは、リクエストレイテンシ、未処理リクエスト、リーダー変更、フォロワー同期ステータス、ディスク容量、およびプロセス再起動を監視します。Kafkaでは、コントローラー変更、オフラインパーティション、アンダーレプリケーションパーティション、ブローカー再起動、およびZooKeeperセッション期限切れに言及するログを監視します。プロセス全体の死亡だけでなく、繰り返されるパターンにアラートを設定します。

より大規模なアップグレードを計画しているチームにとって、最もクリーンな修正は、ZooKeeperからKafkaのKRaftモードへの移行かもしれません。それはプロジェクトであり、インシデント対応のステップではありません。バージョン計画、互換性チェック、および慎重な移行作業が必要です。それまでは、ZooKeeperを本番インフラストラクチャとして扱ってください。小さく、Kafkaの近くに、一貫して設定され、監視され、退屈な状態に保ってください。

実用的なランブックパターンの1つは、インシデント中に小さなマトリックスを構築することです。Kafkaブローカーを一方の軸に、ZooKeeperノードをもう一方の軸に配置します。各セルにnc -vz host 2181の結果と、利用可能な場合は簡単なZooKeeper健全性チェックを記入します。これにより、漠然とした「KafkaがZooKeeperに到達できない」という報告が目に見えるパターンに変わります。すべてのブローカーがzk02に到達できない場合は、zk02またはそのネットワークパスを調査します。broker-4だけがすべてのZooKeeperノードに到達できない場合は、そのブローカーのホスト、ルートテーブル、DNS、またはファイアウォールを調査します。

時刻同期も重要になることがあります。ZooKeeperセッションのメカニズムは、すべての操作に完全に同一の壁時計を必要としませんが、大きくずれた時計はログの解釈を難しくし、周囲の自動化、証明書、および監視を壊す可能性があります。KafkaおよびZooKeeperノードでNTPまたはchronyを正常に保ってください。障害中にタイムスタンプが一致しない場合、人々は間違ったイベントの順序を追跡して時間を無駄にします。

コンテナ化またはオーケストレーションされたZooKeeperデプロイメントには注意してください。ZooKeeperはディスクにIDとデータを保存します。ポッドが移動して永続的なIDを失ったり、サービスディスカバリが準備ができていないノードをクライアントに指したりすると、Kafkaは不安定な接続動作を見る可能性があります。StatefulSetスタイルのID、永続ボリューム、安定したDNS、および準備チェックが重要です。ZooKeeperアンサンブルは、使い捨てのステートレスなWebポッドのセットのように振る舞うべきではありません。

セキュリティ設定は別のレイヤーを追加します。SASL、TLS、またはネットワークポリシーが最近導入された場合、接続障害は最初は単純な到達可能性の問題のように見えるかもしれません。Kafkaログに、TCPタイムアウトではなく、認証失敗、ハンドシェイク失敗、または認可エラーが表示されるかどうかを確認してください。ブローカーがZooKeeperに認証できないため、ポートが開いていてもセッションが失敗する可能性があります。

インシデント後、正確な症状、根本原因、および修正の短い記録を保持してください。ZooKeeperの問題は、元の修復が局所的であったために繰り返されることがよくあります:1つのファイアウォールルール、1つのブローカー再起動、1つのタイムアウト増加。優れたインシデント後のメモには、クラスターにクォーラムがあったかどうか、どのブローカーがセッションを失ったか、ISRが縮小したかどうか、コントローラーが変更されたかどうか、および次回それを早期にキャッチするための監視が何であるかを示す必要があります。

Kubernetesまたは他のスケジューラーからトラブルシューティングしている場合は、KafkaおよびZooKeeperワークロードがどこに配置されたかも確認してください。ノードレベルのネットワーク問題、ディスク問題、またはCPU枯渇イベントは、そこにスケジュールされたポッドにのみ影響する可能性があります。ポッドを移動すると問題が修正されたように見えるかもしれませんが、実際の問題はホストである可能性があります。アプリケーションが修復されたと宣言する前に、イベントとノードメトリクスを比較してください。

バックアップとスナップショットには注意が必要です。ZooKeeperデータディレクトリは、バックアップ方法がそれ用に設計されていない限り、プロセスがアクティブな間に軽々しくスナップショットすべきではありません。Kafkaメタデータの場合、損傷したまたは古くなったZooKeeper状態は非常に破壊的になる可能性があります。ZooKeeperがサポートするバックアッププラクティスに従い、本番環境から離れて復元手順をテストしてください。誰も復元したことのないバックアップは、希望に満ちたファイルにすぎません。

最善の予防策は、ZooKeeperを退屈に保つことです。可能であれば、重いKafkaブローカーと同じ場所に配置しないでください。ディスクを信頼性の高いものに保ってください。ヒープサイズを控えめにし、監視してください。アンサンブルメンバーシップを変更できる人を制限してください。私が見たZooKeeperインシデントのほとんどは、エキゾチックなバグによって引き起こされたものではありません。それらは、誰もが重要であることを忘れていた小さなサービスの周りの通常のインフラストラクチャのドリフトから生じました。