Kubernetesオブザーバビリティチュートリアル:Elastic APMを使ったアプリケーションパフォーマンス監視編
この記事は、Kubernetesで実行されるアプリケーションをあらゆる側面から監視する方法を解説する“Kubernetesオブザーバビリティチュートリアルシリーズブログ”の第3回です。
- ログのインジェストと分析編
- パフォーマンスとヘルスに関するメトリックの収集編
- Elastic APMを使ったアプリケーションパフォーマンス監視編
今回は、Elasticオブザーバビリティを活用し、Elastic APMを使ってアプリケーションパフォーマンス監視(APM)を実行する方法をご紹介します。
| はじめるまえに:このチュートリアル記事は、セットアップ済みのKubernetes環境が用意されていることを前提としています。以降のアクティビティを実行できるように、デモ用アプリケーションを入れたシングルノードのMinikube環境をセットアップする手順を解説したブログ記事もありますので、併せてご活用ください。 |
アプリケーションパフォーマンスを監視する
APMは、リクエストやレスポンスのレイテンシー、ユーザーに生じるエラーなど、ユーザー側における主要なサービルレベルインディケーター(SLI)を自動計測に重点を置いています。このような指標を効果的に監視することで、パフォーマンスの低下やエラーの増加といった問題の原因となるコンポーネント(あるいはコードブロック)を速やかに特定できます。つまりAPMは、問題の根本原因をピンポイントで特定し、平均対応時間(MTTR)の短縮に貢献するすぐれた一次トリアージツールとなります。
Elastic APMを使った分散トレーシング
Elastic APMは分散トレーシングをサポートし、同じユーザーリクエストの処理に携わるさまざまな分散アプリケーションコンポーネントのリクエストやレスポンスのレイテンシーをエンドツーエンドに測定することができます。ElasticのAPMアプリはトレース期間全体を表示するだけでなく、分散されたトレースに関与するコンポーネント関連のレイテンシーの詳細も表示します。さっそくKibanaのAPMアプリを開いて、トレースとトランザクションを確認してみましょう。補足:はじめにPetclinicアプリを何度か操作して、一定のユーザートラフィックを生成する必要があります。
ログとメトリックでコンテクストをトレースする
Kubernetesにデプロイしたアプリケーションに分散トレーシングとAPMを使うと問題をすばやくトリアージできるだけでなく、ログやメトリックなど、オブザーバビリティのパズルを構成する他のピースを捕捉し、相互参照することも可能になります。このようなピースを使用可能にしておくことで、たとえばレイテンシーのスパイクのトラブルシューティングを開始するときも、はじめにAPMを使って調査範囲を1つのコンポーネントに絞り込むことができます。さらにCPUとメモリの使用状況メトリックや特定のKubernetes podのエラーログエントリと簡単にリンクでき、一連の作業をKibanaだけで完了させることができます。
BeatsのプロセッサーとAPMエージェントが収集したメタデータのおかげで、Kibana内のあらゆるオブザーバビリティデータは相互に参照されています。はじめにAPMトレースを調べ、トレース処理の一環としてpodのメトリックを確認し、トレースが処理された際に同時に稼働していたコンポーネントの背後にあるログを調べる、といった作業も、1つのインターフェースで完結します。
Elastic APMエージェントのデプロイ
APMエージェントは、監視するアプリケーションのコンポーネントと共同でデプロイされています。Kubernetes環境で、アプリケーションのコンポーネントはpodで実行されるコードの一部として作成されています。このチュートリアルでは、APM Real User Monitoring(RUM)JavaScript Agentと、APM Java Agentという2つのエージェントを使用します。
Elastic APM Java Agent
以下は、petclinic podデプロイ記述子$HOME/k8s-o11y-workshop/petclinic/petclinic.yml内で、APM Java Agentを初期化している部分です。
env:
- name: ELASTIC_APM_SERVER_URLS
valueFrom:
secretKeyRef:
name: apm-secret
key: apm-url
- name: ELASTIC_APM_SECRET_TOKEN
valueFrom:
secretKeyRef:
name: apm-secret
key: apm-token
- name: ELASTIC_APM_SERVICE_NAME
value: spring-petclinic-monolith
- name: ELASTIC_APM_APPLICATION_PACKAGES
value: org.springframework.samples
このAPMエージェントは、Petclinicアプリケーションのpom.xmlファイル$HOME/k8s-o11y-workshop/docker/petclinic/pom.xmlの依存関係に含まれています。
<!-- Elastic APM dependencies -->
<dependency>
<groupId>co.elastic.apm</groupId>
<artifactId>apm-agent-attach</artifactId>
<version>${elastic-apm.version}</version>
</dependency>
<dependency>
<groupId>co.elastic.apm</groupId>
<artifactId>apm-agent-api</artifactId>
<version>${elastic-apm.version}</version>
</dependency>
<dependency>
<groupId>co.elastic.apm</groupId>
<artifactId>elastic-apm-agent</artifactId>
<version>${elastic-apm.version}</version>
</dependency>
<!-- End of Elastic APM dependencies -->
この方法のデプロイは、Java Agent内にMaven依存関係としてベイクされます。Java Agentを使ったランタイムアタッチメントなど、APMエージェントのデプロイ方法は他にも存在します。詳しくは、Elastic APM Java Agentドキュメントをご覧ください。
Elastic APM Real User Monitoring(RUM)Agent
RUM Agentは、ユーザーブラウザーアプリケーションの一部として実行され、ユーザー側のすべてのメトリックをブラウザーから直接収集します。このチュートリアルではその目的での使用に加え、分散トレーシングの起点としてもRUM Agentを活用します。以下は、Javascriptユーザーサイドコード$HOME/k8s-o11y-workshop/docker/petclinic/src/main/resources/templates/fragments/layout.htmlでAPMエージェントがインスタンス化されている部分です。
<script th:inline="javascript">
...
var serverUrl = [[${apmServer}]];
elasticApm.init({
serviceName: 'petclinic-frontend',
serverUrl: serverUrl,
distributedTracingOrigins: [],
pageLoadTransactionName: pageName,
active: true,
pageLoadTraceId: [[${transaction.traceId}]],
pageLoadSpanId: [[${transaction.ensureParentId()}]],
pageLoadSampled: [[${transaction.sampled}]],
distributedTracing: true,
})
...
</script>
Petclinicはサーバーサイドでレンダリングされるアプリケーションで、ThymeleafはSpring Bootで使用されるテンプレートレンダリングフレームワークです。ThymeleafはコントローラーのJavaコード内で、フロントエンドに送られるいくつかの値をポピュレートします。以下は、transaction model attributeが$HOME/k8s-o11y-workshop/docker/petclinic/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.javaにポピュレートされている例です。
@ModelAttribute("transaction")
public Transaction transaction() {
return ElasticApm.currentTransaction();
}
エラーを監視する
APMエージェントは、未処理の例外も捕捉します。KibanaのAPMアプリで[Error](エラー)メニューをクリックし、throwされた例外の詳細を確認してみましょう。
以下は、原因となったJavaコードです。この例ではほぼそのままの内容ですが、アプリケーションによってthrowされた未処理の例外がどのようにAPMエージェントに捕捉されたかが示されています。$HOME/k8s-o11y-workshop/docker/petclinic/src/main/java/org/springframework/samples/petclinic/system/CrashController.java:
@GetMapping("/oups")
public String triggerException() {
throw new RuntimeException("Expected: controller used to showcase what "
+ "happens when an exception is thrown");
}
ランタイムメトリックの監視
各種エージェントは、ランタイムにアプリケーションコンポーネント共にデプロイされ、ランタイムメトリックを収集します。たとえばJava Agentはコードやconfigを用意しなくても、デプロイするだけで、メトリック収集の有効化に加えてランタイムメトリックの収集を実行します。
サービスマップ
Elastic APM 7.7より、APMトレースを関係性のグラフィック表現として描写する“サービスマップ”がベータバージョンで登場しました。サービスマップビューは、APMアプリに表示されるトレースに関与したコンポーネントを示します。今回はサンプルとしてJavaのブラウザークライアントとMySQLのバックエンドで作成されたシンプルなアプリケーションを入れたので、表示されるマップも非常にシンプルになっています。
Elastic APMエージェントでJMXメトリックを収集する
Java Agentを設定し、アプリケーションがエクスポーズしたJMXメトリックを収集することができます。このチュートリアルのPetclinicコンポーネントは、以下のメトリックを収集するよう設定されています。
$HOME/k8s-o11y-workshop/petclinic/petclinic.yml:
- name: ELASTIC_APM_CAPTURE_JMX_METRICS
value: >-
object_name[java.lang:type=GarbageCollector,name=*]
attribute[CollectionCount:metric_name=collection_count]
attribute[CollectionTime:metric_name=collection_time],
object_name[java.lang:type=Memory] attribute[HeapMemoryUsage:metric_name=heap]
Lensでカスタムメトリックを可視化する
APMアプリにすべてのメトリックが表示されるわけではありません。上の例では、JMXメトリックがこのアプリに特に固有であるため、APMアプリが可視化していません。また他の可視化の一部で使うべきメトリックや、価値を引き出すには別の方法で可視化する必要があるメトリック、というものもあります。Kibanaの可視化やダッシュボードはそのまま使うだけでメトリックを可視化できますが、ここではさらにすばやく、面白い手法をご紹介します。
Lensはより直感的に使えるアナリスト主導の可視化ツールとして、比較的最近Kibanaに登場しました。今回は、Lensの活用例としてエージェントが収集したカスタムJMXメトリックを可視化してみます。
Lensの活用例
結果を面白くするため、scale-outコマンドを実行してPetclinicコンポーネントのpod数を増やし、稼働するJVMごとに複数行のメトリックが生成されるようにしておきます。
$ kubectl scale --replicas=3 deployment petclinic-deployment #スケールアウトが期待通り行われたか検証します $ kubectl get pods
以下の結果を確認できます。
NAME READY STATUS RESTARTS AGE mysql-deployment-7ffc9c5897-grnft 1/1 Running 0 37m nginx-7ff654f859-xjqgn 1/1 Running 0 28m petclinic-deployment-86b666567c-5m9xb 1/1 Running 0 9s petclinic-deployment-86b666567c-76pv7 1/1 Running 0 9s petclinic-deployment-86b666567c-gncsw 1/1 Running 0 30m theia-86d9888954-867kk 1/1 Running 0 43m
ここで、Lensが力を発揮します。
検索といえば、やっぱり検索バー
言うまでもなく、Elastic APMアプリには検索バーが備わっています。アクセスする問題の範囲を絞り込み、干し草の山で1本の針を探そうとするとき、この検索バーが非常に役立ちます。以下は、APM UIビューで特定のブラウザータイプに絞り込む操作の例です。
APMトレースとログの相関付け
部分的な検索だけでなく、全体像の把握に役立つ活用例として、APMトレースをトレースされたコードが生成したログエントリと相関させる使い方もあります。APMトレースは、trace.id<code>フィールドでログエントリにリンクさせることができます。このような操作も、いくつかのシンプルな設定を行うだけで簡単に行うことができます。はじめに、相関付けの手順例を説明します。
あるトレースに着目します。
このトレースは、さまざまなコード片がどのように実行されたかをタイムラインビューで示しています。ここでトレースされたコードが、どのようなログを生成していたか確認してみましょう。[Actions](アクション)メニューで[Trace logs](ログをトレース)を選択します。Logs UI画面が開き、トレースデータが捕捉された時間が指定されています。trace.idを使って同じ trace.idの注釈がついているログデータを絞り込んでみましょう。
trace.idはそのAPMトレースに固有のものですが、このログデータがトレースIDでエンリッチされているのはなぜでしょうか。まず今回の例では、Elastic APM Java Agentを使っており、このエージェントが実装するログの相関付け機能が使われています。この機能は基本的に、Javaのロギングフレームワーク(Spring Bootの場合はログバック)のMDCコンテクストマップ内でtrace.idおよびtransaction.idフィールドをポピュレートします。必要な作業は、petclinic ConfigMappetclinic/petclinic.ymlの環境変数を指定することだけです。
- name:ELASTIC_APM_ENABLE_LOG_CORRELATION
value: "true"
これで、petclinicアプリのロギングをECSロギングに設定できました。この設定は、ログエントリをlog.levelやtransaction.idなどElastic Common Schemaに対応する付加的なフィールドでエンリッチしてElastic Stackに送信します。この設定を有効化するため、Petclinicアプリケーションのpom.xmlに以下の依存関係を含めてあります。
<dependency>
<groupId>co.elastic.logging</groupId>
<artifactId>logback-ecs-encoder</artifactId>
<version>${ecs-logging-java.version}</version>
</dependency>
また、docker/petclinic/src/main/resources/logback-spring.xmlファイルにもECSロギングを設定し、stdoutにJSONとして書き出せるようにします。
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="co.elastic.logging.logback.EcsEncoder">
<serviceName>petclinic</serviceName>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
最後はBeatsの自動検知を活用したPetclinicのConfigMap(petclinic/petclinic.yml)上の注釈で、FilebeatにPetclinicから出力されるJSONをパースするよう指示します。
container:
annotations:
...
co.elastic.logs/type: "log"
co.elastic.logs/json.keys_under_root: "true"
co.elastic.logs/json.overwrite_keys: "true"
つまりこの相関付けを動作させるには、Java Agentと、トレースとの相関をロギングするロギングフレームワークの双方を有効化する必要があります。これで、オブザーバビリティデータの2つのピースをリンクすることが可能になります。
本シリーズのまとめ
ログとメトリックとAPM…すべて達成することができました。このブログシリーズではアプリケーションのインストルメンテーションを行い、Elastic StackのFilebeat、Metricbeat、APMを使ってログ、メトリック、APMトレースを収集する手順を解説しました。またこのチュートリアルで、同じコンポーネントを使ってKubernetesのログとメトリックを収集する方法も実演しました。Elasticのエコシステムにはこの他にも複数のコンポーネントがあり、付加的な情報を入手してお使いのKubernetes環境のオブザーバビリティを強化できます。
- Heartbeat - アプリケーションおよびKubernetes環境全体のアップタイムと応答性を計測するすぐれた手段となります。Heartbeatはクラスター外部の、エンドユーザーに近い場所にデプロイできます。Heartbeatを使うと、ネットワークタイプや観測されるレスポンスのレイテンシー、生じたエラーの数など、エンドユーザー側におけるアプリケーションの動作状況を把握できます。
- Packetbeat - Kubernetes内部のクラスターネットワークトラフィック、TLS証明書のハンドシェイク、DNSルックアップなどのビューを取得できます。
- Jupyter notebookのサンプルデプロイも作成し、Elasticsearchに格納された生のオブザーバビリティデータへのアクセスを入手する方法を解説したサンプルのnotebookを追加しました。自信をもってデータサイエンスを実践できるはずです。
k8s-o11y-workshop/jupyter/scripts/example.ipynb
ElasticはGithubレポへのコントリビューターを歓迎しています。また、何かうまく動作しないという場合はGithubにissueを投稿してみてください。
お使いのシステムやインフラストラクチャーの監視は、今日からはじめることができます。Elastic CloudでElasticsearch Serviceの無料トライルに登録して、あるいはElastic Stackをダウンロードして、ご自身でホストして導入してみましょう。セットアップを完了したら、Elastic Uptimeでホストのアベイラビリティを監視したり、Elastic APMを使ってホストで稼働するアプリケーションのインストルメンテーションを行うことができます。新たにメトリックのクラスターを完全統合して、包括的なオブザーバビリティを備えたシステムの確立を目指してみましょう。お困りのことや、ご質問がおありの場合はディスカッションフォーラムをご活用ください。活発なコミュニティが待っています。