Attribute Java Instrumentation Guide
Introduction
Attribute Sensor は eBPF を使用してカーネルレベルでトラフィックデータを収集し、コード変更を行わずにアプリケーションのトラフィックを可視化します。Java アプリケーションは、この方法だけでは十分でない唯一のケースです。
Java アプリケーションが TLS を使用する場合、暗号化は共有システムライブラリではなく Java プラットフォーム自体によって実行されます。センサーが監視できるライブラリ境界が存在しないため、暗号化された Java 接続は暗号文としてしか見えません。Attribute は JVM の内部で接続を監視し、そのトラフィックをアトリビューションのためにセンサーから見えるようにする Attribute Java agent によって、このギャップを解消します。
この影響を受けるのは TLS で暗号化された Java トラフィックのみです。平文接続で通信する Java アプリケーションのアトリビューションは、Java agent なしで動作し、設定も不要です。
Java agent を配布する方法は 2 つあり、同時に有効にできるのは必ずどちらか一方のみです。
-
Dynamic instrumentation: センサーがノード上で稼働中の JVM に agent をアタッチします。アプリケーションの変更や再デプロイは不要です。
-
Admission-time injection: Attribute Operator が pod 作成時に agent を追加するため、JVM の起動時点から agent が存在します。
本ガイドでは Kubernetes デプロイのみを対象としています。他の環境における Java instrumentation は対象外です。必要な場合は Attribute サポートまでお問い合わせください。
About the Attribute Java Agent
この agent が行う処理の範囲は、意図的に絞り込まれています。Java プラットフォーム内部で、アプリケーションのトラフィックが暗号化・復号されるポイントを監視し、その内容を、すでにノード上で実行中の Attribute Sensor から利用できるようにします。アプリケーションが使用する Java の TLS クラスのみをインスツルメンテーションし、アプリケーション自身のコードには一切手を加えません。
agent は TLS 自体の処理 には参加しません。鍵や証明書を読み取らず、セキュリティプロバイダーを登録せず、アプリケーションがネゴシエートするプロトコルバージョンや暗号スイートにも影響を与えません。
また、チューニングではなく設計として、小さく作られています。
-
バックグラウンド処理なし: agent はスレッドを起動せず、自身のネットワーク接続を開かず、ポーリングも行いません。アプリケーションがすでにデータを送受信しているときにのみ動作します。
-
固定されたメモリフットプリント: バッファは一度だけ割り当てられ、各操作ごとではなく再利用されるため、インスツルメンテーションによって継続的なガーベジコレクション負荷は増加しません。オフヒープ使用量は、トラフィックを扱うアプリケーションスレッド 1 本あたりおおよそ 8 KB です。
-
操作あたりサブマイクロ秒のコスト: 読み取りまたは書き込みに追加される作業は、バッファコピーとローカルハンドオフであり、ペイロードサイズに応じて数十〜数百ナノ秒程度です。
-
設定や状態なし: agent は環境変数、システムプロパティ、設定ファイルを一切読み取らず、自身の状態も保持しません。
最も重要なのは、フェイルセーフとなるよう設計されていることです。すべてのインスツルメンテーションポイントはラップされており、agent 内部でエラーが発生してもその中に閉じ込められます。インスツルメンテーションのみが停止し、アプリケーションは影響を受けずに動作を続けます。センサーが実行されていない場合も同様で、agent は単に何も出力しません。データはアプリケーション自身の read/write が完了した後にのみ読み取られるため、agent がトラフィックの内容、順序、タイミングを変更することはありません。
Requirements
- バージョン 1.28 以上 で稼働する Kubernetes クラスタ。
- Attribute Helm chart 0.0.96 以上、かつ operator 0.0.30 以上、sensor 0.0.284 以上。
- Helm 3.8.0 以上。
- Linux
x86-64またはarm64上で動作する Java 8 以上。 - HotSpot ベースの JVM(OpenJDK、Eclipse Temurin、Amazon Corretto、Azul Zulu、または JIT モードの GraalVM)。OpenJ9 はサポートされません。
- Dynamic instrumentation のみ: アプリケーションコンテナ内に書き込み可能な
/tmpディレクトリ。
これらの条件外にあるアプリケーションには一切手を加えません。アプリケーションを失敗させるのではなく、インスツルメンテーションをスキップします。
Choosing an Instrumentation Mode
どちらのモードも同じ Java agent を提供し、同じアトリビューションデータを生成します。両者が異なるのは、agent が JVM に到達する方法であり、それには実運用上の 違いがあります。
| Dynamic Instrumentation | Admission-Time Injection | |
|---|---|---|
| 適用対象 | すべてのノード上の、サポート対象のすべての JVM | オプトインしたワークロードのみ |
| 再デプロイの必要性 | なし | あり — pod を再作成する必要あり |
| 接続のカバレッジ | agent アタッチ後に開かれた接続 | アプリケーション起動時からのすべての接続 |
| Kubernetes バージョン | 1.28 以上 | 1.28 以上 |
Dynamic instrumentation はアプリケーションの変更や再デプロイを必要としないため、運用がより簡単なモードです。アプリケーション起動時点からすべての接続を完全にカバーする必要がある場合、またはインスツルメンテーションを適用するワークロードを厳密に制御したい場合は、Admission-time injection を選択してください。
Attribute が提供する values ファイルによって、デプロイで使用するモードが決まります。いずれかの設定を変更する前に必ず内容を確認し、どのモードが有効か不明な場合は Attribute サポートにお問い合わせください。
2 つのモードを同時に実行してはいけません。そうするとアプリケーションが二重にインスツルメンテーションされてしまいます。そのため Admission-time injection を有効にする前に、Dynamic instrumentation を明示的に無効化する必要があります。無効化されていない場合、Helm chart はインストールを拒否します。
Dynamic Instrumentation with the Sensor
How It Works
センサーはノード上で起動する Java プロセスを監視し、センサー自身が起動した時点ですでに実行中のプロセスも対象に含めます。Java プロセスを検出すると、JVM の初期化完了を待ってから、agent ファイルをアプリケーションコンテナに配置し、実行中の JVM に agent をロードします。
初期化完了を待つのは意図的な挙動です。起動中の JVM にアタッチすると、一部の Java バージョンでは JVM が不安定になる場合があります。そのためセンサーは、JVM が準備完了であることを確認してから処理を行います。実運用では、典型的な JVM は起動からおよそ 0.5 秒以内にインスツルメンテーションされます。
アプリケーションは再起動も再デプロイもされません。agent はアプリケーション起動後に到着するため、その時点以降に開かれた接続 のみを監視します。すでに確立済みの接続はインスツルメンテーションされません。
Configuration
Dynamic instrumentation は単一のフラグで制御されます。無効化されていない場合、センサーは検出したサポート対象のすべての JVM を、ワークロード単位の設定なしでインスツルメンテーションします。
これをオフにするには(Admission-time injection を有効化する前に必ず行う必要があります)、次を設定してください。
sensorDisableAutoJavaInstrumentation: true
Limitations
- agent アタッチ後に開かれた接続のみがインスツルメンテーションされます。アプリケーション起動時に作成される長寿命のコネクションプールについては、その接続を対象に含めるにはアプリケーションを再起動するか、代わりに Admission-time injection を使用してください。
- ノード上のサポート対象のすべての JVM がインスツルメンテーションされます。ワークロード単位のオプトインはありません。
- アプリケーションコンテナには書き込み可能な
/tmpが必要です。読み取り専用の場合、そのコンテナへのインスツルメンテーションはスキップされます。 -XX:+DisableAttachMechanismで起動された JVM はインスツルメンテーションできません。-XX:-UsePerfDataで起動された JVM は、起動直後ではなく、起動からおよそ 1 分後にインスツルメンテーションされます。Cassandra など一部のデータベースイメージでは、これがデフォルトで設定されています。
Admission-Time Injection
How It Works
pod が作成されるとき、operator は次の 3 つの要素を追加します。
-
共有ボリュームと、そのボリュームに Java agent をコピーする init コンテナ。
-
pod 内のすべてのコンテナに対する、そのボリュームの読み取り専用マウント。
-
各コンテナの
JAVA_TOOL_OPTIONS環境変数にマージされる agent の-javaagentオプション。アプリケーションがすでに設定している値は維持されます。
この結果、JVM が起動する前に agent が存在し、アプリケーションが開くすべての接続がインスツルメンテーションされます。
Injection は pod が_作成_されるときにのみ発生します。すでに存在する pod は変更されないため、有効化後にワークロードをロールアウトしてください。また、Injection は冪等です。すでに agent を含む pod はそのまま保持されます。
Workload Opt-In Annotation
オプトインしない限り、どのワークロードにも Injection は行われません。pod にこのアノテーションを true で設定するか、namespace に設定して、その namespace 内のすべての pod をオプトインさせてください。
instrumentation.attrb.io/inject-java: "true"
Deployment では、このアノテーションは Deployment 本体ではなく、pod テンプレートに設定する必要があります。
spec:
template:
metadata:
annotations:
instrumentation.attrb.io/inject-java: "true"
Enabling Injection
sensorDisableAutoJavaInstrumentation: true
javaAutoInject:
enabled: true
mode: auto
Selecting a Backend
2 つの互換性のあるバックエンドが、同一の Injection を実行します。mode でどちらを使用するかを選択し、デフォルトの auto はクラスタに適したものを自動的に選びます。
mode | Backend | クラスタ要件 |
|---|---|---|
auto | Kubernetes 1.36 以上では policy を、それ以外では webhook を選択 | サポート対象の任意のバージョン |
policy | Kubernetes API server 内で実行 | Kubernetes 1.36 以上 |
webhook | chart によってデプロイされる小さな admission サービス | Kubernetes 1.28 以上 |
特定のバックエンドを固定する明確な理由がない限り、auto のままにしておいてください。クラスタがサポートできないバックエンドを設定した場合、動作しないものをインストールするのではなく、説明付きメッセージとともにインストールが失敗します。
policy バックエンドは API server 自身によって評価されるため、クラスタに追加のワークロードを課しません。webhook バックエンドは、小さな高可用な Deployment として実行され、pod 作成時に API server から呼び出されます。chart は、このバックエンドと、それに必要な証明書・サービス・権限をすべてプロビジョニングし、追加のセットアップは不要です。
構成リファレンス
| 値 | デフォルト | 説明 |
|---|---|---|
javaAutoInject.enabled | values ファイルで設定 | アドミッション時のインジェクションを有効化します。 |
javaAutoInject.mode | auto | バックエンドの選択: auto、policy、webhook のいずれか。 |
javaAutoInject.failurePolicy | Ignore | Ignore は、インジェクションに失敗した場合に、インストルメンテーションなしで pod を起動させます。Fail は代わりに pod の作成をブロックします。 |
javaAutoInject.injectAnnotation | instrumentation.attrb.io/inject-java | オプトイン用のアノテーション。 |
javaAutoInject.image.repository | quay.io/attribute/java-agent | エージェントを含むイメージ。 |
javaAutoInject.image.tag | 0.0.11 | エージェントイメージのタグ。 |
javaAutoInject.mountPath | /attrb-jagent | エージェントのボリュームがコンテナ内にマウントされるパス。 |
javaAutoInject.resourceName | attrb-jagent | インジェクトされるボリュームおよび init コンテナの名前。 |
本番環境では failurePolicy を Ignore のままにしておいてください。これは、インジェクションに問題があってもアプリケーションの起動自体は妨げず、インストルメンテーションなしで動作させることを意味します。
高度な Webhook チューニング
これらは webhook バックエンドにのみ適用され、デフォルトのままでも構いません。最も有用なのは namespaceSelector です。
| 値 | デフォルト | 説明 |
|---|---|---|
javaAutoInject.webhook.namespaceSelector | {} (すべてのネームスペース) | どのネームスペースを webhook に送るかを制限します。大規模クラスタで推奨されます。 |
javaAutoInject.webhook.replicas | 3 | webhook Deployment のレプリカ数。 |
javaAutoInject.webhook.timeoutSeconds | 2 | API server が webhook を待機する時間。タイムアウト後に failurePolicy が適用されます。 |
javaAutoInject.webhook.antiAffinity | preferred | レプリカをノード間に分散させます。required は分散を保証しますが、ノードが不足している場合レプリカが Pending のままになります。 |
javaAutoInject.webhook.resources | 50m / 64Mi requests | webhook pod のリクエストおよびリミット。 |
選択されたネームスペースで作成されるすべての pod は webhook に送られるため、大規模クラスタでは、実際にインジェクションを使用するネームスペースにのみ namespaceSelector を絞り込むことが、最も効果的なチューニングとなります。
javaAutoInject:
webhook:
namespaceSelector:
matchLabels:
attrb.io/inject-java: "true"
Attribute のリリースに使用しているネームスペースと kube-system は、ここで何を設定しても常に除外されます。
networkPolicy.enabled=true でチャートを実行している場合、webhook バックエンドは API server から到達できるように自身のポートを開きます。他の Attribute コンポーネントは、引き続き default-deny ポリシーの対象のままです。
インストール後の検証
動的インストルメンテーションでは、エージェントが Java アプリケーションコンテナに到達し、JVM によってロードされたことを確認します。最初のファイルはセンサーがエージェントを配布したことを示し、2 つ目のファイルはエージェント自身が起動時に書き出すものなので、JVM がエージェントを受け入れたことを確認できます。
kubectl exec <java-pod> -- ls /tmp/jagent.jar /tmp/attrb_agent.jar
アドミッション時インジェクションでは、新しく作成された pod にエージェントが含まれていることを確認します。両方のコマンドが値を返すはずです。
kubectl get pod <java-pod> -o jsonpath='{.spec.initContainers[*].name}'
kubectl get pod <java-pod> -o jsonpath='{.spec.containers[0].env[?(@.name=="JAVA_TOOL_OPTIONS")].value}'
webhook モードでは、webhook 自体が利用可能であることを確認します。
kubectl get pods -n attribute -l app=attrb-inject-java-webhook
トラブルシューティング
インストルメンテーションは静かに失敗するよう設計されています。何かによってインストルメンテーションが妨げられても、アプリケーションはインストルメンテーションなしで通常どおり起動・実行されます。これは安全な動作ですが、エージェン トが欠落していても明確な症状が現れないことを意味します。以下の表に沿って確認してください。
| 症状 | 想定される原因 | 対処方法 |
|---|---|---|
| インストルメンテーションなし、アドミッション時インジェクション | pod が構成変更前に作成されている | ワークロードをロールアウトしてください。インジェクションされるのは新規作成された pod のみです。 |
| インストルメンテーションなし、アドミッション時インジェクション | オプトインアノテーションがない | アノテーションが pod テンプレート(Deployment 自体ではなく)またはネームスペースに付与されていることを確認してください。 |
| インストルメンテーションなし、アドミッション時インジェクション | failurePolicy: Ignore の下でインジェクションが静かに失敗している | 一時的に javaAutoInject.failurePolicy=Fail を設定してください。pod 作成時に根本原因のエラーが報告されます。確認後は元に戻してください。 |
| インストルメンテーションなし、動的モード | アプリケーションコンテナの /tmp が読み取り専用 | このワークロードにはアドミッション時インジェクションを使用してください。 |
| インストルメンテーションなし、動的モード | JVM が -XX:+DisableAttachMechanism で起動している | このフラグを削除するか、アドミッション時インジェクションを使用してください。 |
| インストルメンテーションが約 1 分遅れて現れる、動的モード | JVM が -XX:-UsePerfData で起動している | 想定どおりの挙動です。起動直後からのカバレッジが必要な場合はアドミッション時インジェクションを使用してください。 |
| 一部の接続のみがアトリビュートされる、動的モード | エージェントがアタッチされる前に開かれた接続がある | アプリケーションを再起動するか、アドミッション時インジェクションを使用してください。 |
| 何も起きない、任意のモード | 非対応の JVM・Java バージョン・アーキテクチャ | 上記の要件に照らしてアプリケーションを確認してください。 |
インストールが sensorDisableAutoJavaInstrumentation に関するメッセージで失敗する | 両方のモードが同時に有効になっている | アドミッション時インジェクションを使用する場合は sensorDisableAutoJavaInstrumentation: true を設定してください。 |
動的モードで詳しく調査するには、センサーでデバッグログを有効にし、そのログで Java のアタッチ活動を確認してください。アタッチの失敗は常にログに記録され、成功したアタッチはデバッグレベルでのみ記録されます。出力内容の解釈に支援が必要な場合は Attribute サポートにお問い合わせください。
コンテナに追加されるファイル
両方のモードで、アプリケーションコンテナ内にファイルが配置されます。以下の点を想定してく ださい。
動的インストルメンテーションは、エージェントと小さなアタッチヘルパーをアプリケーションコンテナの /tmp ディレクトリに書き込み、その後、実行中の JVM にエージェントをアタッチします。これらのファイルはコンテナの書き込み可能レイヤー内にコンテナのライフタイム中残り、コンテナが置き換えられると削除されます。きめ細かく監視している場合は、インストルメントされたコンテナごとに数十 MB 程度の一時ストレージ使用量を見込んでください。
アドミッション時インジェクションは、オペレーターが pod に追加する専用のボリューム内にエージェントを配置します。コンテナイメージやその書き込み可能レイヤーには何も書き込まれず、pod の削除とともにすべて削除されます。
いずれの場合も、エージェントはアプリケーションの接続を観測するためにアプリケーションの JVM 内で動作します。エージェントが追加されるのは実行中のアプリケーションのみであり、コンテナイメージが変更されることは決してありません。
商標: Java および GraalVM は Oracle および/またはその関連会社の登録商標です。Kubernetes は The Linux Foundation の登録商標です。Amazon Corretto は Amazon.com, Inc. またはその関連会社の商標です。Eclipse および Temurin は Eclipse Foundation の商標です。Azul および Zulu は Azul Systems, Inc. の商標です。その他すべての商標はそれぞれの所有者に帰属します。