Jaegerを使った分散トレーシングの検証 on Kubernetes (1)

08 Feb 2021, 17:04 By mosuke5

もーすけです。
過去にDatadogでAPMを少し検証したり、トレーニングの中でJaegerを触ったりしましたが、正直ちゃんとわかっていなかったので、改めてJaegerというか分散トレーシングについて検証してみたいと思います。

今回はKubernetes上にJaegerを構築するので、Jaeger Operatorをインストールして利用します。 今回はOpenShiftにて検証していますが、他のKubernetesディストリビューションでも同じように利用可能です。ockubectl と置き換えて読んでください。 検証環境は下記のとおりです。

  • OpenShift: 4.4
  • Jaeger: 1.17

AllInOne deployment

まずは AllInOne というデプロイメントでJaegerのとっかかりを掴みます。 名前の通りなのですが、1つのPodのなかにJaegerが必要とするコンポーネントを全部詰め込んだものです。 データはインメモリに保存するので、揮発性であり永続性はありません。 完全にテスト用でもちいるデプロイメント方式です。

以下のJaeger CRでデプロイしてみます。
.specは何も記載していないので、すべてデフォルト値で、AllInOneでインストールします。

apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: jaeger-all-in-one-inmemory
  namespace: default

生成されたものを確認してみます。 PodはAllInOneということだけあってひとつです。全部入り。

% oc get pod
NAME                                         READY   STATUS      RESTARTS   AGE
jaeger-all-in-one-inmemory-5b8dd55fc-lv4kh   2/2     Running     0          172m

Serviceは、大きくagent, collector, queryと作成されています。
AllInOneは検証用に特化しているので、agentも含めています。アプリケーションサイドでclientだけ用意できればすぐに利用できるようになっています。後述するproductionのデプロイメント方式だとagentは作成されないので注目してみていきましょう。 それぞれがどんな役割を担っているかは、Jaegerのアーキテクチャ図と照らし合わせてみてみましょう。

% oc get service
NAME                                            TYPE           CLUSTER-IP       EXTERNAL-IP                            PORT(S)                                  AGE
jaeger-all-in-one-inmemory-agent                ClusterIP      None             <none>                                 5775/UDP,5778/TCP,6831/UDP,6832/UDP      172m
jaeger-all-in-one-inmemory-collector            ClusterIP      172.30.81.141    <none>                                 9411/TCP,14250/TCP,14267/TCP,14268/TCP   172m
jaeger-all-in-one-inmemory-collector-headless   ClusterIP      None             <none>                                 9411/TCP,14250/TCP,14267/TCP,14268/TCP   172m
jaeger-all-in-one-inmemory-query                ClusterIP      172.30.187.60    <none>                                 443/TCP                                  172m

Jaegerの全体像
jaeger-architecture

JaegerのUIにアクセスしてみます。OpenShiftではない環境の方はIngressなどからアクセスしてください。jaeger-queryのサービスにアクセスできればUI画面にたどり着けます。

oc get route -n default
NAME                         HOST/PORT                                                                         PATH   SERVICES                           PORT    TERMINATION   WILDCARD
jaeger-all-in-one-inmemory   jaeger-all-in-one-inmemory-default.apps.my-k8s-cluster.com          jaeger-all-in-one-inmemory-query   <all>   reencrypt     None

jaeger-query

Production deployment

AllInOneでは、とにかく簡単にJaegerを検証できることがわかりました。 実際の運用を見据えて、production用のデプロイメントを確認してみます。 まず、大きな違いとしては、データストアです。AllInOneでは、インメモリに保存していましたが、当然永続化、可用性の面から外出しされ、永続ボリュームを持ちます。 Jaegerでは、DBにCassandraかElasticSearchをサポートしています。OpenShift環境では、ElasticSearchがサポートされています。 その他の違いとしては、コンポーネントが分離されている点です。collector, query, databaseとPod毎に分離されています。

Production用のデプロイをするために、はじめにElasticSearch Operatorをインストールします。 バックエンドのデータストアにElasticSearchを利用しますが、Jaeger OperatorがJaegerを構築する際にElasticSearch Operatorを利用するためです。

この検証では以下のJaeger CRを使ってみました。 ElasticSearchは3台でクラスタリング、各ElasticSearchに50GBのボリュームをつけて構築です。 その他、ElasticSearchのデータ削除の設定などを付与。

apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: jaeger-production
spec:
  strategy: production
  ingress:
    security: oauth-proxy
  storage:
    type: elasticsearch
    elasticsearch:
      nodeCount: 3
      redundancyPolicy: SingleRedundancy
      storage: 
        storageClassName: gp2
        size: 50Gi
    esIndexCleaner:
      enabled: true
      numberOfDays: 7
      schedule: 55 23 * * *
    esRollover:
      schedule: '*/30 * * * *'

Podをみてみると、AllInOneのときと違ってコンポーネント毎にPodがあることを確認できます。 また、上にもすこし書きましたがJager agentは含まれていません。Jaeger agentの準備は別で書きます。

% oc get pod
NAME                                                              READY   STATUS    RESTARTS   AGE
elasticsearch-cdm-jaegerproductionjaegerproduction-1-75d6cwghmg   2/2     Running   0          9m34s
elasticsearch-cdm-jaegerproductionjaegerproduction-2-78958d46ml   2/2     Running   0          9m30s
elasticsearch-cdm-jaegerproductionjaegerproduction-3-5fb4dr9k4l   2/2     Running   0          9m27s
jaeger-production-collector-cff65bd5d-j4mfh                       1/1     Running   0          7m36s
jaeger-production-query-5dcbb9b79f-kzqkb                          3/3     Running   0          7m36s

参考程度ですが、PVもできていると。

% oc get pvc
NAME                                                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
elasticsearch-elasticsearch-cdm-jaegerproductionjaegerproduction-1   Bound    pvc-b2e0b560-3b29-42a3-93a4-59adc6dca872   50Gi       RWO            gp2            15m
elasticsearch-elasticsearch-cdm-jaegerproductionjaegerproduction-2   Bound    pvc-5b59197d-59b7-469a-800c-ecf7e9f70097   50Gi       RWO            gp2            15m
elasticsearch-elasticsearch-cdm-jaegerproductionjaegerproduction-3   Bound    pvc-a59e50fb-4eb4-4c34-9320-4fbe37c034a9   50Gi       RWO            gp2            15m

Serviceも同様です。jaeger agentのserviceは同様にありません。

% oc get service
NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                  AGE
elasticsearch                          ClusterIP   172.30.141.84    <none>        9200/TCP                                 10m
elasticsearch-cluster                  ClusterIP   172.30.251.197   <none>        9300/TCP                                 10m
elasticsearch-metrics                  ClusterIP   172.30.221.210   <none>        60000/TCP                                10m
jaeger-production-collector            ClusterIP   172.30.196.153   <none>        9411/TCP,14250/TCP,14267/TCP,14268/TCP   8m2s
jaeger-production-collector-headless   ClusterIP   None             <none>        9411/TCP,14250/TCP,14267/TCP,14268/TCP   8m2s
jaeger-production-query                ClusterIP   172.30.209.29    <none>        443/TCP                                  8m2s

Jaeger agentのサイドカーとしての起動

Production deploymentで、Jaeger agentが含まれていないことを書きました。 実運用では、スケーリングなどの観点からアプリケーションPodのサイドカーとしてJaeger agentを起動することが望ましいです。Jaeger Operatorには、サイドカーでJager agentを埋め込む仕組み(Auto-injecting Jaeger Agent Sidecars)を持ちます。

試しにやってみましょう。NginxのDeploymentに特定のannotationを追加します。 Jaager Operatorは、このannotationを検知して、サイドカーを注入します。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  annotations:
    sidecar.jaegertracing.io/inject: "jaeger-production" # このannotationを追加。値はJagerの名前
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginxinc/nginx-unprivileged
        name: nginx-unprivileged
        resources: {}

Nginx podを確認するとコンテナがふたつ起動していることがわかります。 また、Deploymentも書き換わっています。 あとは、アプリケーション側で、Jaeger angetに向けてデータを出力すればJaeger本体に記録されるわけです。

% oc get pod | grep nginx
nginx-557fc655c-zhprq      2/2     Running   0      28m

% oc get deploy nginx -o yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "4"
    prometheus.io/port: "14271"
    prometheus.io/scrape: "true"
    sidecar.jaegertracing.io/inject: jaeger-production
  creationTimestamp: "2021-02-08T08:38:21Z"
  generation: 6
  labels:
    app: nginx
    sidecar.jaegertracing.io/injected: jaeger-production
  name: nginx
  namespace: jaeger-production
  resourceVersion: "207651"
  selfLink: /apis/apps/v1/namespaces/jaeger-production/deployments/nginx
  uid: 75c63e00-0396-4d63-a5db-183084e826a7
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - env:
        - name: JAEGER_SERVICE_NAME
          value: nginx.jaeger-production
        - name: JAEGER_PROPAGATION
          value: jaeger,b3
        image: nginxinc/nginx-unprivileged
        imagePullPolicy: Always
        name: nginx-unprivileged
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      - args:
        - --jaeger.tags=cluster=undefined,deployment.name=nginx,pod.namespace=jaeger-production,pod.name=${POD_NAME:},host.ip=${HOST_IP:},container.name=nginx-unprivileged
        - --reporter.grpc.host-port=dns:///jaeger-production-collector-headless.jaeger-production.svc:14250
        - --reporter.grpc.tls.ca=/etc/pki/ca-trust/source/service-ca/service-ca.crt
        - --reporter.grpc.tls.enabled=true
        - --reporter.type=grpc
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: HOST_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.hostIP
        image: registry.redhat.io/distributed-tracing/[email protected]:ff3f61601ca4f799958f26be0525383339c1fa6ac29e16eec9d2c32dbe59407e
        imagePullPolicy: IfNotPresent
        name: jaeger-agent
        ports:
        - containerPort: 5775
          name: zk-compact-trft
          protocol: UDP
        - containerPort: 5778
          name: config-rest
          protocol: TCP
        - containerPort: 6831
          name: jg-compact-trft
          protocol: UDP
        - containerPort: 6832
          name: jg-binary-trft
          protocol: UDP
        - containerPort: 14271
          name: admin-http
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /etc/pki/ca-trust/extracted/pem
          name: jaeger-production-trusted-ca
          readOnly: true
        - mountPath: /etc/pki/ca-trust/source/service-ca
          name: jaeger-production-service-ca
          readOnly: true
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          defaultMode: 420
          items:
          - key: ca-bundle.crt
            path: tls-ca-bundle.pem
          name: jaeger-production-trusted-ca
        name: jaeger-production-trusted-ca
      - configMap:
          defaultMode: 420
          items:
          - key: service-ca.crt
            path: service-ca.crt
          name: jaeger-production-service-ca
        name: jaeger-production-service-ca
...

サンプルアプリでトレーシング体験

JaegerのインストールはAllInOneを用いてテスト用であれば、正直なにも考える必要なくできます。 トレーシングがどのようなものか理解するには、なにはともあれ確認してみないとですよね。 Hot R.O.D. - Rides on Demandというちょうどいいサンプルがあるので取っ掛かりとしてはオススメです。

こちらのREADMEには、Kubernetesへのデプロイは書かれていませんがコンテナイメージが用意されているので簡単にデプロイできます。以下サンプルです。 せっかくなので、sidecar.jaegertracing.io/inject: "jaeger-production"のannotationをつかってJaeger agentを起動します。 envJAEGER_AGENT_HOSTはこの場合、サイドカーで起動しているコンテナになるのでlocalhostでOKです。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: hotrod
  name: hotrod
  annotations:
    sidecar.jaegertracing.io/inject: "jaeger-production"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hotrod
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: hotrod
    spec:
      containers:
      - image: jaegertracing/example-hotrod:latest
        name: example-hotrod
        env:
          - name: JAEGER_AGENT_HOST
            value: "localhost"
          - name: JAEGER_AGENT_PORT
            value: "6831"
        ports:
          - containerPort: 8080
        resources: {}

起動した、Podに対して接続し、アプリケーションを操作すれば、Jaegerにトレーシング情報が保存されていることを確認できました。今回はここまでとして、次回もう少し細かいところを見ていこうと思います。

jaeger-hotrod

関連情報

記事へのフィードバック

本記事に対して、フィードバックあればこちらのフォームからご記入ください。フォードバックしてみる

このエントリーをはてなブックマークに追加