TT Lab
はじめる
学ぶ 学習パス コース

OTCA — OpenTelemetry認定アソシエイト

別の注文のタグが付いてきた

TT Labで続きを見る

目標

実際のPython関数7個を直して、実行時点のコンテキストを引き渡し、正常・例外・キャンセルのあとで元のコンテキストを復元します。続けて、外部baggageの抽出と、宛先ごとの伝播ポリシーを分離します。

なぜ重要なのか

リクエストの値が間違った処理に付くと、データが収集されても、原因を誤って解釈することになります。このラボは、Python 3.12とOpenTelemetry SDK 1.44.0がインストールされた専用環境で、実際のasyncio・ThreadPoolExecutor・W3Cのbaggageプロパゲーターを実行します。依存関係はイメージにあらかじめインストールされているため、インターネットからのダウンロードやAPIキーは必要ありません。

外部のHTTP・DNS・Collector・認証サーバーは実行しません。warehouse.internalは、関数に渡す論理的な宛先の文字列です。課題の合格は、実際のネットワーク宛先の検証、認証・認可、外部ストレージへの格納、包括的な個人情報保護の検証を意味しません。8つのステップのうち、baggageがスパン属性に自動で記録されるかどうかは、前の理論のSDK実験と公式の説明を根拠に判定します。残りのコード検査は、Context関数の実行の観測です。

準備と実行

作業ディレクトリは/root/otca-contextです。startersには、文法は合っているものの、動作が間違っている開始ファイル8個だけが用意されます。該当するファイルを作業ディレクトリにコピーして、直してください。

cd /root/otca-context
cp starters/scope.py scope.py
/opt/otel-lab/bin/python /opt/app/otca_sdk/runner.py --track context run 1

runのあとの数字を変えると、該当するステップのobservationsとchecksが出ます。現在の観測と期待値を比べてください。SDKや採点を変更する課題ではなく、同じ意味の別のコードでも、実際の動作が合っていれば合格します。答案のコピーを、制限された子プロセスで実行し、元のファイルは変更しません。個別のコードは最大8秒、総合の実行は全体で50秒の予算で、無限ループ・出力の暴走は失敗です。この実行制限を、悪意のあるコードに対する別個のセキュリティ上の分離と解釈しないでください。分離は、ラボのPodが担当します。

ステップ

  1. /root/otca-context/scope.py: scope.pyのwith_context(value, callback)を直します。コールバックを1回実行する間、baggageのrequestがvalueである必要があります。返り値と元の例外をそのまま伝え、正常・例外のどちらでも、呼び出し側の以前のコンテキストを復元してください。
  2. /root/otca-context/tasks.py: tasks.pyのstart_task(coro)は、現在のリクエストのコンテキストでコルーチンを予約し、呼び出し側がawaitするオブジェクトを返します。呼び出し側があとでコンテキストを変えても、処理は予約時点の値を読む必要があり、呼び出し側の新しい値は保持してください。
  3. /root/otca-context/threads.py: threads.pyのstart_thread(function)は、同期関数をスレッドで実行するように予約し、awaitするオブジェクトを返します。あとでawaitした時点ではなく、この関数を呼び出した時点のリクエストの値を、ワーカーが読むようにしてください。
  4. /root/otca-context/executor.py: executor.pyのsubmit(executor, function)は、与えられたThreadPoolExecutorに関数を提出し、Futureを返します。提出時点のリクエストの値が引き継がれ、同じワーカーの次の通常の処理には残らないようにします。呼び出し側のコンテキストも保持してください。
  5. /root/otca-context/requests.py: requests.pyのasync handle(value, callback)は、baggageのrequestをvalueとして適用して、非同期コールバックを1回awaitします。2つのリクエストが重なっても値を分離し、正常な返り値・例外・Task.cancelのいずれでも、以前のコンテキストを復元してください。返り値・例外・キャンセルは、呼び出し側にそのまま伝えます。
  6. /root/otca-context/incoming.py: incoming.pyのinbound(carrier)は、W3Cのbaggageヘッダーのdictを、空のOTel Contextを基準に抽出して、OTel Contextを返します。既存のローカルのbaggageを混ぜず、元のdictと呼び出し側のコンテキストを保持します。role=adminも、このステップでは抽出しますが、認証の成功とは解釈しません。
  7. /root/otca-context/outgoing.py: outgoing.pyのoutbound(source, destination)は、destinationがwarehouse.internalと完全に同じときにだけ、regionのtest-east/test-west、channelのweb/batchという文字列を許可します。新しいContextに選択して入れ、W3Cのbaggageヘッダーのdictを返してください。ほかのキー・値・宛先は除外し、元は保持します。残る値がなければ、空のdictです。
  8. /root/otca-context/report.json: report.jsonの8つの仮説を、JSONのブール値で判定します。前の7つのコードもすべて動作する必要があります。Taskの生成時点、遅延されたto_thread、通常のexecutor、キャンセル時の復元、baggageとスパン属性、空のinbound、認可、宛先の影響を区別してください。

参考

コールバックのあとで元のリクエストに戻る

/root/otca-context/scope.py: scope.pyのwith_context(value, callback)を直します。コールバックを1回実行する間、baggageのrequestがvalueである必要があります。返り値と元の例外をそのまま伝え、正常・例外のどちらでも、呼び出し側の以前のコンテキストを復元してください。

set_baggageの返り値と、現在のコンテキストに適用する動作は別です。コールバックのあとの復元が、例外の経路でも実行されるかを確認してください。

Task予約時点のリクエストを保持する

/root/otca-context/tasks.py: tasks.pyのstart_task(coro)は、現在のリクエストのコンテキストでコルーチンを予約し、呼び出し側がawaitするオブジェクトを返します。呼び出し側があとでコンテキストを変えても、処理は予約時点の値を読む必要があり、呼び出し側の新しい値は保持してください。

コルーチンオブジェクトを返すことと、現在のContextを持つTaskを作ることの違いを確認してください。

遅延されたスレッド処理の時点を直す

/root/otca-context/threads.py: threads.pyのstart_thread(function)は、同期関数をスレッドで実行するように予約し、awaitするオブジェクトを返します。あとでawaitした時点ではなく、この関数を呼び出した時点のリクエストの値を、ワーカーが読むようにしてください。

to_threadが返したコルーチンは、いつ実行されるでしょうか。現在のコンテキストで実行を予約する段階を考えてみてください。

再利用されるワーカーにコンテキストを残さない

/root/otca-context/executor.py: executor.pyのsubmit(executor, function)は、与えられたThreadPoolExecutorに関数を提出し、Futureを返します。提出時点のリクエストの値が引き継がれ、同じワーカーの次の通常の処理には残らないようにします。呼び出し側のコンテキストも保持してください。

コピーをワーカーの中で行うと、すでに手遅れです。提出ごとに別のコピーを作り、その中で実行する方法を探してみてください。

重なったリクエストと実際のキャンセルを後始末する

/root/otca-context/requests.py: requests.pyのasync handle(value, callback)は、baggageのrequestをvalueとして適用して、非同期コールバックを1回awaitします。2つのリクエストが重なっても値を分離し、正常な返り値・例外・Task.cancelのいずれでも、以前のコンテキストを復元してください。返り値・例外・キャンセルは、呼び出し側にそのまま伝えます。

正常な返りのあとにだけdetachがあると、キャンセルがその行を飛ばします。復元とキャンセルの隠蔽を区別してください。

外部ヘッダーとローカルのリクエストを分離する

/root/otca-context/incoming.py: incoming.pyのinbound(carrier)は、W3Cのbaggageヘッダーのdictを、空のOTel Contextを基準に抽出して、OTel Contextを返します。既存のローカルのbaggageを混ぜず、元のdictと呼び出し側のコンテキストを保持します。role=adminも、このステップでは抽出しますが、認証の成功とは解釈しません。

デフォルトのextractと、明示的に空のContextを指定したextractを比べてください。contextvars.ContextとOTel Contextを区別します。

宛先・キー・値をあわせて制限する

/root/otca-context/outgoing.py: outgoing.pyのoutbound(source, destination)は、destinationがwarehouse.internalと完全に同じときにだけ、regionのtest-east/test-west、channelのweb/batchという文字列を許可します。新しいContextに選択して入れ、W3Cのbaggageヘッダーのdictを返してください。ほかのキー・値・宛先は除外し、元は保持します。残る値がなければ、空のdictです。

キーだけを許可すると、値に別の情報が入ることがあります。宛先を接頭辞で比べると、似たホストを許可してしまわないかも確認してください。

動作と仮説をあわせて検証する

/root/otca-context/report.json: report.jsonの8つの仮説を、JSONのブール値で判定します。前の7つのコードもすべて動作する必要があります。Taskの生成時点、遅延されたto_thread、通常のexecutor、キャンセル時の復元、baggageとスパン属性、空のinbound、認可、宛先の影響を区別してください。

レポートだけ合っていても十分ではありません。実際の観測と公式の契約を区別して読み、各関数の失敗の原因もあわせて直してください。