Telemetry APIで信号を設計する
目標
プロキシが作ってくれるシグナルを、設定で整える方法を身につけます。メッシュ全体に既定のログを敷き、失敗だけを残す条件をかけ、ワークロード1つにサンプリングとタグを付け、カーディナリティを爆発させるタグをデプロイ前に弾くところまで進みます。
なぜ重要なのか
メッシュが提供するのは、均一さです。計装のないサービスにも、同じ名前と同じラベルのメトリクスができ、その均一さがダッシュボードを比較可能にします。ところが、既定値のままにしておくと、2つの問題が生じます。アクセスログはリクエストごとに1行なので、トラフィックの多いサービスでは保存コストがすぐに膨らみますし、カスタムタグを誤って入れると、時系列がラベル値の種類の数だけ増えて、ストレージとクエリがともに崩れます。
Telemetry APIは、その調整を宣言として行えるようにしてくれます。範囲が3つの層になっている点が核心です。インストール先のネームスペースにセレクターなしで置けばメッシュ全体、一般のネームスペースにセレクターなしで置けばそのネームスペース、selectorを付ければそのワークロードです。そして、セレクターのないものは、ネームスペースごとに1つだけでなければなりません。2つ以上あると、どれが優先されるのかが定義されないからです。
環境
このPodには、本物のistiodもサイドカーもありません。リクエストが流れないので、メトリクスもログも作られません。その代わり、Telemetry CRDが登録された本物のapiserverとistioctlがあるので、設定が有効かと範囲が正しいかは、そのまま検証されます。この2つは、現場で人がつまずく場所でもあります。作業ディレクトリは/root/istio-obsで、その下のk8s/・reject/・bin/・out/を使います。
ステップ
mesh-obsを作成して、istio-systemにメッシュの既定アクセスログを敷きます。mesh-obsに、失敗だけを残すログフィルターをかけます。app=reviewsにサンプリング10%とリテラルタグをかけ、範囲外の値が拒否されることを確認します。app=ratingsにメトリクスの調整をかけます。bin/tag-lint.shで、危険なタグをデプロイ前に弾きます。- ワークロードを2つ作成して、
istioctl analyzeで範囲のルールを確認します。 out/effective.txtに、1つのPodに実際にかかるものを計算して書きます。out/summary.txtに、6行でまとめます。
参考
- セレクターのないTelemetryは、ネームスペースごとに1つだけです。ステップ3とステップ4には、必ずセレクターを付けてください。付け忘れると、ステップ6で表面化します。
- セレクターが見るのは、Deploymentの名前ではなくPodのラベルです。
- ステップ3の拒否されるファイルは、
k8s/ではなくreject/に置いてください。最後のステップで、k8s/のファイルがすべて有効である必要があります。 - タグの値はCEL式です。定数の文字列を入れるには、シングルクォートで囲む必要があります。
メッシュ全体に既定のアクセスログを敷く
mesh-obsネームスペースを作成してistio-injection=enabledラベルを付け、/root/istio-obs/k8s/mesh-default.yamlでistio-systemにTelemetry mesh-defaultを作成してください。セレクターなしで、accessLoggingのプロバイダーだけを指定します。
Telemetryの範囲は3つの層です。istio-system(インストール先のネームスペース)にセレクターなしで置けばメッシュ全体、一般のネームスペースにセレクターなしで置けばそのネームスペース、selectorを付ければそのワークロードです。セレクターがないという事実そのものが範囲を決める、という点がこのAPIの核心です。プロバイダー名は、メッシュ設定に登録された名前を指す文字列なので、ここでは実際に存在していなくても、形式は有効です。
失敗したリクエストだけをログに残す
/root/istio-obs/k8s/failures-only.yamlで、mesh-obsにTelemetry failures-onlyを作成してください。セレクターは置かず、accessLoggingに、レスポンスコードが400以上のときだけ残すfilter.expressionをかけてください。
全件のログ記録は高くつきます。プロキシがリクエストごとに1行を残すので、トラフィックの多いサービスでは、保存コストがすぐに膨らみます。フィルターはCEL式で、アクセスログのコンテキストではresponse.codeを使えます。このTelemetryはネームスペース全体にかけるものなので、セレクターを付けてはいけません。そして、セレクターのないTelemetryは、ネームスペースごとに1つだけでなければならないので、あとのステップのものには、必ずセレクターを付けてください。
サンプリング率とカスタムタグをワークロードにかける
/root/istio-obs/k8s/trace-sampling.yamlで、mesh-obsにTelemetry trace-samplingを作成してください。selector.matchLabels.appはreviews、tracing[0].randomSamplingPercentageは10で、customTagsにリテラルの値を持つタグを1つ置いてください。そのあと、/root/istio-obs/reject/bad-sampling.yamlに範囲外のサンプリング値を書いて適用を試し、拒否メッセージを/root/istio-obs/out/rejected.txtに保存してください。
サンプリングの既定値は1%です。最初のプロキシがサンプリングするかどうかを決めてヘッダーで伝播するので、後ろのホップはその決定に従います。デバッグのときだけ上げて、常時100%にはしません。カスタムタグには、値の種類が有限なものだけを入れる必要があるので、ここではリテラルを使います。範囲外の値はCRDスキーマが拒否するので、オブジェクトがそもそも作られません。メッセージは標準エラー出力へ出るので、2>&1で受けてください。
標準メトリクスをオフにしてタグを加える
/root/istio-obs/k8s/metrics-tuning.yamlで、mesh-obsにTelemetry metrics-tuningを作成してください。selector.matchLabels.appはratingsで、プロバイダーはprometheusです。overridesでREQUEST_SIZEを、modeを指定してオフにし、別の項目でtagOverridesにより、リテラルの値を持つタグをもう1つ加えてください。
メトリクスは、オフにすることも設定です。使わないヒストグラムをオフにすると、時系列が減って、保存もクエリも軽くなります。modeはCLIENT・SERVER・CLIENT_AND_SERVERの中から選びますが、同じリクエストが両側のプロキシでそれぞれ記録されるので、どちら側のことを言っているのかを決める必要があります。タグの値はCEL式なので、定数の文字列を入れるには、シングルクォートで囲む必要があります。
カーディナリティを爆発させるタグをデプロイ前に弾く
/root/istio-obs/bin/tag-lint.sh <Telemetry파일>を作成してください(プレースホルダーはTelemetryファイルです)。tagOverridesの値が、リクエストのパス・リクエストの識別子・ユーザーの識別子・リクエストヘッダーのように、値の種類が無限に近いものを指していたら、HIGH_CARDINALITY=<태그이름>=<값>を出力して(プレースホルダーはタグ名と値です)、終了コード1で終わります。定数の文字列だけを使うファイルは、OKを出力して0で終わります。
時系列の数は、ラベル値の組み合わせの積で増えていきます。ユーザーの識別子のように、値が無限に近いものをタグに使うと、ストレージとクエリがともに崩れ、プロキシが自動でハッシュしたりサンプリングを下げたりしてくれる保護の仕組みはありません。CEL値がシングルクォートで囲んだ定数なら安全で、request.url_pathやrequest.headers[...]のようなものを指していれば危険です。採点では、安全なファイルと危険なファイルでこの検査ツールを実際に動かし、ステップ4で作成したファイルにも引っかからないことを、一緒に確認します。
範囲のルールを静的分析で確認する
/root/istio-obs/k8s/workloads.yamlで、mesh-obsにDeployment reviewsとratingsを作成してください。Podラベルのappが、それぞれ名前と同じである必要があります。そのあと、istioctl analyze -n mesh-obsがエラーなしで終わるかを確認してください。
セレクターが見るのは、Deploymentの名前ではなくPodのラベルです。そのため、Podテンプレートのラベルを正確に合わせる必要があります。そして、セレクターのないTelemetryが1つのネームスペースに2つ以上あると、どれが優先されるのかが定義されないので、istioctl analyzeがエラーとして検出します。前のステップでセレクターを付け忘れていたら、ここで表面化します。このPodには本物のサイドカーがないので、注入に関する警告は残りますが、エラーがなければ合格です。
1つのPodに実際にかかるものを計算する
app=reviewsのPodに、どのTelemetryがかかるかを計算して、/root/istio-obs/out/effective.txtに5行で書いてください。MESH、NAMESPACE、WORKLOAD、TRACING_PERCENT、METRICS_APPLIESです。
3つの層が重なってかかります。メッシュの範囲が1つ、ネームスペースの範囲が1つ、そしてそのPodを選ぶワークロードの範囲が1つです。最初の3つには名前を書き、TRACING_PERCENTには、ワークロードの範囲に書かれたサンプリング値を書きます。最後の行は、ステップ4のメトリクス設定がこのPodにもかかるのかという問いへの答えを、yesかnoで書いてください。セレクターがどのアプリを選んでいたかを、もう一度見直せばわかります。名前は推測せず、kubectl get telemetry -Aで確認して書いてください。
作ったものを数えて、すべて有効であることを確認する
/root/istio-obs/k8sのファイルがすべてistioctl validateを通ることを確認し、/root/istio-obs/out/summary.txtに6行でまとめてください。TELEMETRY_TOTAL、MESH_SCOPED、NAMESPACE_SCOPED、WORKLOAD_SCOPED、SAMPLING_PERCENT、TRACE_HEADER_PROPAGATIONです。
前の5行は、クラスターで数えて書きます。最後の行は、このコースで最も頻繁に人がつまずく事実についての答えです。プロキシは、スパンを作り、タイミングを測り、コレクターへ送るところまでやってくれますが、インバウンドリクエストのトレースヘッダーをアウトバウンドリクエストにコピーする作業は、誰が行う必要があるのかを、1語で書いてください。それをしないと、サービスごとに別々のトレースが作られて、呼び出しの連鎖が途切れます。