コレクターは200を返したのにスパンはどこにもなかった
目標
バックエンドが停止したりリクエストを拒否したりするとき、コレクターのキューとリトライの設定によって、呼び出し元が受け取るレスポンスと内部テレメトリのメトリクスがどう変わるかを実際に計測し、短い障害を損失なしに乗り切る設定を設計します。
なぜ重要なのか
アプリケーションが200を受け取ったという事実は、スパンがストレージに届いたという意味ではありません。キューは、呼び出し元を障害から切り離す代わりに損失をコレクターの中へ移し、リトライは一時的な失敗だけを救い、上限を過ぎるとデータはログ1行とメトリクス1つだけを残して消えます。otelcol_receiver_accepted_spans・otelcol_exporter_sent_spans・send_failed・enqueue_failedを並べて読めてはじめて、パイプラインの損失を診断できます。
用意されている環境
python3 /opt/fixtures/otca_backpressure_lab.py initが、/root/otca-export/にbroken.yamlとspans.json(1回のリクエストにスパン2個)を置きます。python3 /opt/fixtures/otca_backpressure_lab.py run 설정.yaml [--backend down|ok|reject] [--backend-after 초] [--requests N] [--wait 초](プレースホルダーは設定ファイル名と秒数です)は、lab-k8sのotelcol-contrib 0.116.0を実際に起動してリクエストを0.2秒間隔で送り、待ったあとでコレクターの内部テレメトリのotelcol_*メトリクスを読んで要約します。otlphttpエクスポーターのendpointは、実験用の受信器に差し替えます(downは誰も待ち受けていないポート、okは200、rejectは400)。採点は、同じ条件で皆さんの設定を再実行し、書かれた数字と照合します。
ステップ
python3 /opt/fixtures/otca_backpressure_lab.py initで材料を作成してから、otelcol-contrib validate --config /root/otca-export/broken.yamlを実行してください。/root/otca-export/01-validate.txtにmissing_component=(エラーが指している存在しないコンポーネント)を書き、パイプラインが定義済みのotlphttp/backendへ出力するように直した/root/otca-export/fixed.yamlを作成してください。/root/otca-export/no-queue.yamlをfixed.yamlから作成し、otlphttp/backendにsending_queue.enabled: falseとretry_on_failure.enabled: falseを入れてください。python3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/no-queue.yaml(バックエンドdown、リクエスト5回×スパン2個)の結果を、/root/otca-export/02-no-queue.txtにcodes=、accepted_spans=、refused_spans=、send_failed_spans=として書き写してください。/root/otca-export/queue.yamlで、sending_queueとretry_on_failureを有効にしてください(ほかの値はデフォルトです)。同じ方法(バックエンドdown)で実行して、/root/otca-export/03-queue.txtにcodes=、accepted_spans=、sent_spans=、send_failed_spans=を書いてください。/root/otca-export/small-queue.yamlに、sending_queue: {enabled: true, queue_size: 2, num_consumers: 1}と、retry_on_failure: {enabled: true, initial_interval: 1s, max_interval: 1s}を置いてください。バックエンドdownで実行して、/root/otca-export/04-full.txtにcodes=、accepted_spans=、refused_spans=、enqueue_failed_spans=、queue_size=を書いてください。- 同じsmall-queue.yamlを、
--backend ok --backend-after 2.5 --wait 6で実行してください(2.5秒後に受信器が起動します)。/root/otca-export/05-recover.txtにaccepted_spans=、sent_spans=、send_failed_spans=を書いてください。 - queue.yamlを、
--backend reject(受信器がHTTP 400を返します)で実行してください。/root/otca-export/06-permanent.txtに、codes=、sent_spans=、send_failed_spans=、dropping_logged=(ログにDropping dataがあったかどうかをtrue/false)、retried=(受信器が受け取ったリクエスト数backend_requestsが、リクエスト5回より多かったかどうかをtrue/false)を書いてください。 /root/otca-export/short-retry.yamlに、retry_on_failureをinitial_interval: 500ms、max_interval: 500ms、max_elapsed_time: 2sにして置いてください(キューはデフォルトです)。バックエンドdown、--wait 6で実行して、/root/otca-export/07-give-up.txtにaccepted_spans=、sent_spans=、send_failed_spans=、dropping_logged=を書いてください。/root/otca-export/resilient.yamlを設計してください。採点では、--backend ok --backend-after 4 --requests 8 --wait 8の条件で実行し、呼び出し元への拒否が0件で、8秒以内にスパン16個がすべて送信され、失敗が0件であることを確認します。まずpython3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/resilient.yaml --backend ok --backend-after 4 --requests 8 --wait 8で確認してください。
参考
- 受理(accepted)・拒否(refused)はレシーバーの、送信(sent)・送信失敗(send_failed)・キュー投入失敗(enqueue_failed)はエクスポーターのメトリクスです。
- よくある間違い: 200のレスポンスを配信完了と読むこと、キューを大きくすれば損失がなくなると考えること(リトライの上限・メモリ・再起動)、400もリトライされると期待することです。
- このラボのキューはメモリキューなので、コレクターが再起動すると空になります。永続キュー(storage拡張)は扱いません。数字は、このバージョンと短い実験条件で計測した値です。
- Exporter helper・Internal telemetry
起動前に捕まえられたエラー
python3 /opt/fixtures/otca_backpressure_lab.py initで材料を作成してから、otelcol-contrib validate --config /root/otca-export/broken.yamlを実行してください。/root/otca-export/01-validate.txtにmissing_component=(エラーが指している存在しないコンポーネント)を書き、パイプラインが定義済みのotlphttp/backendへ出力するように直した/root/otca-export/fixed.yamlを作成してください。
validateは、コレクターを起動せずに、設定の参照関係を検査します。パイプラインが呼び出している名前と、exportersに定義した名前を比べてください。
キューもリトライもないとき
/root/otca-export/no-queue.yamlをfixed.yamlから作成し、otlphttp/backendにsending_queue.enabled: falseとretry_on_failure.enabled: falseを入れてください。python3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/no-queue.yaml(バックエンドdown、リクエスト5回×スパン2個)の結果を、/root/otca-export/02-no-queue.txtにcodes=、accepted_spans=、refused_spans=、send_failed_spans=として書き写してください。
キューがないと、エクスポーターの呼び出しがレシーバーのリクエストの中で同期的に行われます。失敗がどこまでさかのぼって伝わるかを、レスポンスコードで確認してください。
200を受け取ったのに送信は0
/root/otca-export/queue.yamlで、sending_queueとretry_on_failureを有効にしてください(ほかの値はデフォルトです)。同じ方法(バックエンドdown)で実行して、/root/otca-export/03-queue.txtにcodes=、accepted_spans=、sent_spans=、send_failed_spans=を書いてください。
キューがあると、レシーバーはキューに入れた瞬間に成功を返します。受理と配信は、別のメトリクスです。
キューが満杯になると拒否が返る
/root/otca-export/small-queue.yamlに、sending_queue: {enabled: true, queue_size: 2, num_consumers: 1}と、retry_on_failure: {enabled: true, initial_interval: 1s, max_interval: 1s}を置いてください。バックエンドdownで実行して、/root/otca-export/04-full.txtにcodes=、accepted_spans=、refused_spans=、enqueue_failed_spans=、queue_size=を書いてください。
コンシューマー1つが最初のバッチを抱えてリトライしている間、キューには2スロットしかありません。リクエストは0.2秒間隔で入ってきます。
バックエンドが戻ったとき、何が生き残るか
同じsmall-queue.yamlを、--backend ok --backend-after 2.5 --wait 6で実行してください(2.5秒後に受信器が起動します)。/root/otca-export/05-recover.txtにaccepted_spans=、sent_spans=、send_failed_spans=を書いてください。
キューに入ったものはリトライで生き残りますが、キューに入れずに拒否されたものは、コレクターが保持していません。再送は呼び出し元の役目です。
400は再送しない
queue.yamlを、--backend reject(受信器がHTTP 400を返します)で実行してください。/root/otca-export/06-permanent.txtに、codes=、sent_spans=、send_failed_spans=、dropping_logged=(ログにDropping dataがあったかどうかをtrue/false)、retried=(受信器が受け取ったリクエスト数backend_requestsが、リクエスト5回より多かったかどうかをtrue/false)を書いてください。
リトライは、一時的な失敗(接続拒否、503など)のための仕組みです。形式が間違っているというレスポンスは、何度送っても同じです。
リトライにも終わりがある
/root/otca-export/short-retry.yamlに、retry_on_failureをinitial_interval: 500ms、max_interval: 500ms、max_elapsed_time: 2sにして置いてください(キューはデフォルトです)。バックエンドdown、--wait 6で実行して、/root/otca-export/07-give-up.txtにaccepted_spans=、sent_spans=、send_failed_spans=、dropping_logged=を書いてください。
max_elapsed_timeが過ぎると、リトライをやめて、そのバッチを捨てます。呼び出し元はすでに200を受け取っているため、損失はコレクターのログと内部メトリクスにしか残りません。
4秒の障害に耐える設定
/root/otca-export/resilient.yamlを設計してください。採点では、--backend ok --backend-after 4 --requests 8 --wait 8の条件で実行し、呼び出し元への拒否が0件で、8秒以内にスパン16個がすべて送信され、失敗が0件であることを確認します。まずpython3 /opt/fixtures/otca_backpressure_lab.py run /root/otca-export/resilient.yaml --backend ok --backend-after 4 --requests 8 --wait 8で確認してください。
必要なものは2つです。障害の間に入ってきたバッチをすべて収められる場所(リトライ中のバッチはコンシューマーが抱えているため、場所はqueue_sizeとnum_consumersを合わせて見ます)と、バックエンドが戻ったあとの待ち時間の内に最初のリトライが来る間隔です。デフォルトの最初のリトライ間隔を、ドキュメントで確認してください。