Kubernetesアプリケーションのトレーシング

2021年11月25日(木)
深見 圭介

はじめに

前回はオブザーバビリティの概要について述べ、メトリクスとログモニタリングを設定してどのような情報が取得できるかを見てきました。

今回は、分散トレーシングについて説明し、実際にトレースデータを取得してみます。

分散トレーシングの目的

前回で「オブザーバビリティ」の特徴として「アプリケーションの動作まで踏み込んで何が起こっているのかを知る」という点を挙げました。

メトリクスやログのモニタリングも重要ですが、これらは基本的に個々のアプリケーションに閉じた事象をモニタリングしていました。しかしKubernetesを利用する典型的なシステムでは、複数のアプリケーションが通信により相互に連携して利用者にサービスを提供します。こういったシステムでは、複数のアプリケーションの組み合わせが原因で問題が発生することが往々にしてあります。

とあるアプリケーションにおいて処理が繰り返し失敗したとします。では、エラーが発生したアプリケーションを調べることで問題が修正できるでしょうか。もし失敗の原因が誤った入力が原因だったとき、失敗の原因になった入力はどのシステムから送られたものか簡単にわかるでしょうか。また、複数のアプリケーションが連携している状況で、エンドユーザからのリクエストがいくつか遅延を発生させているとき、その遅延の原因はどのアプリケーションでしょうか。応答時間の長いアプリケーションが1つ見つかったとしても、その原因はさらに別のアプリケーションからの応答が遅いことかもしれません。

このように例を挙げると、開発者の方は「それくらいのことはシステム全体の構成を把握していれば簡単なのでは」と思われるかもしれません。しかし、マイクロサービスアーキテクチャなど、分散アーキテクチャを選択するとシステムが柔軟になる一方で、アプリケーション間の連携はより複雑になる傾向があるとされます。

例としてTwittter社の事例を見てみると、2013年のブログに以下の画像が最初に挙げられています(Gory G Watchyourbackson 2013「Observability at Twitter」)。

この図ではTwitter社が運用しているアプリケーションがドーナツ状に並んでおり、円の中の曲線がそのアプリケーション間の通信を、円の外側に向かって伸びている点線のようなものが個々のアプリケーションの名前を示しています*1。アプリケーションの数とその連携の複雑さは、その全体を1人の人間が把握するには複雑すぎることがよくわかる例でしょう。

*1 この図はZipkinというTwitter社が開発した分散トレーシングシステムを使っています。今回使用しているJaegerは元々Uber社で開発されましたが、このZipkinの影響も受けているとされています。

これは少し極端な例かもしれませんが、分散アーキテクチャが成功すればするほど、アプリケーション間連携を人間が管理することが難しくなる、というのは何となく想像できるのではないでしょうか。

分散トレーシングはこういった課題に対して「システムで何が起こっているのか」を知るための1つの手段です。特に、個々のアプリケーションの詳細ではなく、1つのリクエストに注目して複数のアプリケーションがどのように連携したのかを知ることができます。

分散トレーシングの仕組み

分散トレーシングの仕組みについて簡単に説明しますが、その前にいくつか、特有の用語を紹介します。

スパン(Span)
アプリケーションの論理的な作業単位。「APIの呼び出しから応答まで」や「関数の呼び出しから終了まで」などです。スパンは作業単位の呼び出しごとに作成され、作業の開始と終了時刻などを含みます。あるスパンが別のスパンからの呼び出しで開始されたとき、呼び出された側のスパンは呼び出し元のスパンへの参照を持ち、スパンどうしの参照は有向非巡回グラフを構成します。

トレース(Trace)
一連の処理にかかわるスパン全体のあつまり。外部から直接呼び出される処理に対応するスパンを根とし、スパンをノード、呼び出し関係を辺とする有向非巡回グラフとしても表されます。

分散トレーシングはアプリケーションプロファイラに似ています。プロファイラは単一のアプリケーション内のメソッドや関数の呼び出し関係を追跡しますが、分散トレーシングではアプリケーション間の呼び出し関係を追跡します。

分散トレーシングシステムの主な構成要素は、アプリケーションに組み込まれるクライアントライブラリ、トレース情報の保管先としてのストレージ、さらに収集したトレース情報の検索や可視化を行うUIなどからなります。また、この後実際に操作するJaegerではAgentやCollectorといったこのほかに追加の構成要素もありますが、詳細は後述します。

クライアントライブラリは、そのアプリケーション内のスパン情報をストレージに送信するだけでなく、外部アプリケーションを呼び出す際はその通信プロトコルに応じた方法で呼び出し元のスパンの情報を伝え、呼び出し先ではその情報を展開して自身のスパン情報に含めてストレージに送信します。これにより、異なるアプリケーション間でスパンの呼び出し関係を引き継ぎ、分散システム全体にわたるトレース情報を構成します。

分散トレーシングシステム
Jaegerのインストール

ここからは、実際にサンプルアプリケーションのトレースを取得することで、どのような情報が得られるのかを見ていきます。今回は分散トレーシングシステムとしてJaegerを利用します。JaegerはCNCF(Cloud Native Computing Foundation)のプロジェクトとして開発されています。

まず、Jaegerを起動するために、公式サイトの手順を参考にjaeger-operatorをインストールします。

kubectl create namespace observability
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/crds/jaegertracing.io_jaegers_crd.yaml
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/service_account.yaml
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/role.yaml
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/role_binding.yaml
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/operator.yaml

ここで、任意のネームスペースでJaegerカスタムリソースが作成できるようにしたいので、最後に適用したマニフェストで定義されているjaeger-operatorのDeploymentリソースを修正します。jaeger-operatorコンテナのWATCH_NAMESPACE環境変数を空文字列('')に設定します。手順のように適用するマニフェストを修正してもよいですが、ここでは修正内容をファイルとして作成してkubectl patchコマンドで既にあるリソースを修正します。以

以下の内容のpatch-jaeger-operator.yamlファイルを作成します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jaeger-operator
  namespace: observability
spec:
  template:
    spec:
      containers:
      - name: jaeger-operator
        env:
        - name: WATCH_NAMESPACE
          value: ''
          valueFrom: null

以下のコマンドで今作成したファイルをパッチとして適用してネームスペースの指定を変更します。

kubectl -n observability patch deployment jaeger-operator --patch-file=patch-jaeger-operator.yaml

さらにクラスタレベルのロールも付与します。

kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/cluster_role.yaml
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/v1.27.0/deploy/cluster_role_binding.yaml

次に、Jaegerインスタンスを作成します。Jaegerは高可用性と永続ストレージを利用するためのいろいろな構成が可能ですが、今回は分散トレーシングの利用と理解を目的とするため、簡易的なall-in-oneと呼ばれる構成を利用します。この構成は、インストールが容易な反面、トレース情報がメモリに保存されるなど実用的な環境での利用を想定しない構成になっています。

Jaegerカスタムリソースのマニフェストを、以下の内容でsimplest.yamlというファイルに作成します。

apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: simplest

以下のコマンドでマニフェストを適用してJaegerインスタンスを作成します。

kubectl -n default apply -f simplest.yaml

デプロイされたJaegerインスタンスに対応するPodが稼働していることを以下のコマンドで確認します。

kubectl get po -l app.kubernetes.io/instance=simplest -w

Podが稼働し始めたらUIを表示して動作を確認しましょう。以下のコマンドで作成されたServiceにport-forwardを実行します。

kubectl port-forward service/simplest-query 16686:16686

ブラウザからhttp://localhost:16686/にアクセスし、以下の画面が表示されれば正常です。

日立ソリューションズ・クリエイト
日立ソリューションズ・クリエイトに所属し、Webアプリケーション開発に従事したのち、 2019年1月からクリエーションライン株式会社と製造業向けにコンテナ/Kubernetesを活用したデータ分析基盤やIoTアプリケーション基盤の開発に従事。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています