マイクロサービスにおけるAWS X-Rayを利用した分散トレーシングをEKSで試す

マイクロサービス時代に課題になりがちなサービス間リクエストの可視化と分析を後押しするAWS X-RayをEKS上でサンプルアプリケーションと共に試してみた様子をお届けします。
2019.06.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

「ま、丸見えやないか!!」

以前から興味があったAWS X-Ray。ごっつ便利そうながら、導入の敷居が高そうで触るのを躊躇してました。

そんな悶々とした日々を送っていた矢先、いつもお世話になっているEKS Workshopにおいて、X-Rayをサンプルアプリケーションと共に簡単に試すことができるチュートリアルがあったので、渡りに船とばかり触ってみました。

インフラ側を設定しサンプルアプリケーションを展開することで、各マイクロサービス間のリクエスト内容とリクエストタイム、その分布が丸裸になるという、なんとも興奮する結果が得られました。

実際のアプリケーションに導入するにはひと手間かかりますが、まずはEKSにおいてAWS X-Rayがどのような役割をはたすのかその感触を掴むには最適なので、マイクロサービスな分散トレーシングに興味がある方は、一度触ってみることをオススメいたします。

(祭) ∧ ∧
 Y  ( ゚Д゚)
 Φ[_ソ__y_l〉     X-Rayマツリダワッショイ
    |_|_|
    し'´J

AWS X-Rayによる分散トレーシングとは

いわゆるAPM(Application Performance Management)に属するサービスです。

近年の分散システムの進化により、そのシステムの監視やデバッグは困難となっています。Kubernetesのようなコンテナオーケストレーションプラットフォームはそれらの多くの問題を解決しますが、サービス間の相互作用やそれぞれの待機時間を把握するのは依然として難しい問題です。

AWS X-Rayは、X-RayエージェントをDaemonSetとして各Workerノードに展開し、アプリケーション内にX-Ray SDKを導入することで、分散トレーシングを可能とします。

今回は、Amazon EKS Workshop内のTracing with X-Rayを利用して、サンプリアプリケーションを利用しながら、AWS X-Rayの挙動を確認してみます。

ほな、いってみよ。

EKS環境の確認

既にEKSのクラスターが用意されている前提で進めていきます。この記事では、EKSクラスターのバージョンは1.12.6です。

$ kubectl version --short
Client Version: v1.10.3
Server Version: v1.12.6-eks-d69f1b

AWS X-Rayのセットアップ

アプリケーションへのAWS X-Ray SDKの組み込み

今回はサンプルアプリケーションを利用するのでこの手順は不要です。

自分のアプリケーション内にX-Ray SDKを組み込む場合は、下記公式ドキュメントを参考に、各言語で必要なSDKの組み込みを実施しておきます。

What Is AWS X-Ray? - AWS X-Ray

WorkerノードへのIAMポリシー付与

最初にWorkerノードがX-Rayに対してトレーシング結果を送信できるように、WorkerノードのIAMロールにarn:aws:iam::aws:policy/AWSXRayDaemonWriteAccessポリシーが必要です。

WorkerノードのEC2インスタンスに付与されているIAMロール名を確認し、以下のコマンドでポリシーを付与します。

$ aws iam attach-role-policy --role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess

X-RayエージェントをDaemonSetとして展開

各WorkerノードにX-Rayエージェントを展開するため、エージェントをDaemonSetとして展開します。

eksworkshopで用意されている、マニフェストファイルをダウンロードします。

$ curl -O https://eksworkshop.com/x-ray/daemonset.files/xray-k8s-daemonset.yaml

マニフェストファイルの内容です。ここでは、DeamonSetのポートに2000を利用しています。

xray-k8s-daemonset.yaml

# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# or in the "license" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app: xray-daemon
  name: xray-daemon
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: xray-daemon
  labels:
    app: xray-daemon
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: xray-daemon
  namespace: default
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: xray-daemon
spec:
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: xray-daemon
    spec:
      volumes:
      - name: config-volume
        configMap:
          name: xray-config
      hostNetwork: true
      containers:
      - name: xray-daemon
        image: rnzdocker1/eks-workshop-x-ray-daemon:dbada4c77e6ae10ecf5a7b1c5864aa6522d9fb02
        imagePullPolicy: Always
        command: [ "/usr/bin/xray", "-c", "/aws/xray/config.yaml" ]
        resources:
          limits:
            memory: 24Mi
        ports:
        - name: xray-ingest
          containerPort: 2000
          hostPort: 2000
          protocol: UDP
        volumeMounts:
        - name: config-volume
          mountPath: /aws/xray
          readOnly: true
---
# Configuration for AWS X-Ray daemon
apiVersion: v1
kind: ConfigMap
metadata:
  name: xray-config
data:
  config.yaml: |-
    # Maximum buffer size in MB (minimum 3). Choose 0 to use 1% of host memory.
    TotalBufferSizeMB: 0
    # Maximum number of concurrent calls to AWS X-Ray to upload segment documents.
    Concurrency: 8
    # Send segments to AWS X-Ray service in a specific region
    Region: ""
    # Change the X-Ray service endpoint to which the daemon sends segment documents.
    Endpoint: ""
    Socket:
      # Change the address and port on which the daemon listens for UDP packets containing segment documents.
      # Make sure we listen on all IP's by default for the k8s setup
      UDPAddress: 0.0.0.0:2000
    Logging:
      LogRotation: true
      # Change the log level, from most verbose to least: dev, debug, info, warn, error, prod (default).
      LogLevel: prod
      # Output logs to the specified file path.
      LogPath: ""
    # Turn on local mode to skip EC2 instance metadata check.
    LocalMode: false
    # Amazon Resource Name (ARN) of the AWS resource running the daemon.
    ResourceARN: ""
    # Assume an IAM role to upload segments to a different account.
    RoleARN: ""
    # Disable TLS certificate verification.
    NoVerifySSL: false
    # Upload segments to AWS X-Ray through a proxy.
    ProxyAddress: ""
    # Daemon configuration file format version.
    Version: 1
---
# k8s service definition for AWS X-Ray daemon headless service
apiVersion: v1
kind: Service
metadata:
  name: xray-service
spec:
  selector:
    app: xray-daemon
  clusterIP: None
  ports:
  - name: incoming
    port: 2000
    protocol: UDP

このマニフェストファイルをクラスタに適用します。

$ kubectl create -f xray-k8s-daemonset.yaml
serviceaccount "xray-daemon" created
clusterrolebinding.rbac.authorization.k8s.io "xray-daemon" created
daemonset.extensions "xray-daemon" created
configmap "xray-config" created
service "xray-service" created

デーモンセットとして正常に展開されているか確認します。Pods Status3 Runningになっていれば、きちんとPodとして展開されています。

$ kubectl describe daemonset xray-daemon
Name:           xray-daemon
Selector:       app=xray-daemon
Node-Selector:  <none>
Labels:         app=xray-daemon
Annotations:    <none>
Desired Number of Nodes Scheduled: 3
Current Number of Nodes Scheduled: 3
Number of Nodes Scheduled with Up-to-date Pods: 3
Number of Nodes Scheduled with Available Pods: 3
Number of Nodes Misscheduled: 0
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  app=xray-daemon
  Containers:
   xray-daemon:
    Image:      rnzdocker1/eks-workshop-x-ray-daemon:dbada4c77e6ae10ecf5a7b1c5864aa6522d9fb02
    Port:       2000/UDP
    Host Port:  2000/UDP
    Command:
      /usr/bin/xray
      -c
      /aws/xray/config.yaml
    Limits:
      memory:     24Mi
    Environment:  <none>
    Mounts:
      /aws/xray from config-volume (ro)
  Volumes:
   config-volume:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      xray-config
    Optional:  false
Events:
  Type    Reason            Age   From                  Message
  ----    ------            ----  ----                  -------
  Normal  SuccessfulCreate  1m    daemonset-controller  Created pod: xray-daemon-kzzzr
  Normal  SuccessfulCreate  1m    daemonset-controller  Created pod: xray-daemon-bgmzs
  Normal  SuccessfulCreate  1m    daemonset-controller  Created pod: xray-daemon-lvlxd

Podの展開も確認。

$ kubectl get pod --selector app=xray-daemon
NAME                READY     STATUS    RESTARTS   AGE
xray-daemon-bgmzs   1/1       Running   0          2m
xray-daemon-kzzzr   1/1       Running   0          2m
xray-daemon-lvlxd   1/1       Running   0          2m

こんな感じでログが出力されていればOKです。

$ kubectl logs -l app=xray-daemon
2019-06-01T06:24:37Z [Info] Initializing AWS X-Ray daemon 3.0.0
2019-06-01T06:24:37Z [Info] Using buffer memory limit of 38 MB
2019-06-01T06:24:37Z [Info] 608 segment buffers allocated
2019-06-01T06:24:37Z [Info] Using region: ap-northeast-1
2019-06-01T06:24:37Z [Info] Starting proxy http server on 127.0.0.1:2000
2019-06-01T06:24:34Z [Info] Initializing AWS X-Ray daemon 3.0.0
2019-06-01T06:24:34Z [Info] Using buffer memory limit of 38 MB
2019-06-01T06:24:34Z [Info] 608 segment buffers allocated
2019-06-01T06:24:34Z [Info] Using region: ap-northeast-1
2019-06-01T06:24:34Z [Info] Starting proxy http server on 127.0.0.1:2000
2019-06-01T06:24:34Z [Info] Initializing AWS X-Ray daemon 3.0.0
2019-06-01T06:24:34Z [Info] Using buffer memory limit of 38 MB
2019-06-01T06:24:34Z [Info] 608 segment buffers allocated
2019-06-01T06:24:34Z [Info] Using region: ap-northeast-1
2019-06-01T06:24:34Z [Info] Starting proxy http server on 127.0.0.1:2000

サンプルアプリケーションのデプロイ

eksworkshopには、AWS X-RayのSDKを事前に組み込んだサンプルアプリケーションが用意されているので、それらをデプロイします。

$ kubectl apply -f https://eksworkshop.com/x-ray/sample-front.files/x-ray-sample-front-k8s.yml
$ kubectl apply -f https://eksworkshop.com/x-ray/sample-back.files/x-ray-sample-back-k8s.yml

しばらくまったら、デプロイメントを確認し、正常に展開されているか確認します。

$ kubectl describe deployments x-ray-sample-front-k8s x-ray-sample-back-k8s

サービスステータスを確認します。

$ kubectl describe services x-ray-sample-front-k8s x-ray-sample-back-k8s

フロントエンドサービスが正常にデプロイされていることを確認したら、CLBでフロントエンドサービスを公開します。

$ kubectl get service x-ray-sample-front-k8s -o wide
NAME                     TYPE           CLUSTER-IP     EXTERNAL-IP                                                                    PORT(S)        AGE       SELECTOR
x-ray-sample-front-k8s   LoadBalancer   10.100.X.YYY   abcdefg0123456789.ap-northeast-1.elb.amazonaws.com   80:31229/TCP   4m        app=x-ray-sample-front-k8s,tier=frontend

ロードバランサーがデプロイされた後、実行結果に表示されているEXTERNAL-IPにブラウザでアクセスし、サンプルアプリケーションのフロント画面が表示されたOKです。

むっちゃ怪しい感じスネ… 分散トレーシングのサンプルとするため、フロントから1秒ごとに自動的にサーバーにリクエストが飛んでいます。

AWS X-Rayコンソールを確認する

ここまでで事前準備はOK。いよいよAWS X-Rayコンソールで、分散トレーシングの内容を確認していきます。

Service Map

無事、X-Rayにデータが転送されていれば、こんな感じでService Mapが表示されています。ここでは、各Pod間のリクエストの依存関係とPod名を確認できます。

各リソースは、同じコンテキスト空間でX-Rayにデータ転送され、グラフとして表示されます。このサンプルサプリケーションでは、x-ray-sample-fornt-k8sサービスが、1分間あたり43回のりクエストをなげていて、平均レイテンシーが0.6msとなります。x-ray-sample-back-k8sは、平均トランザクションが0.08msになっていることがわかります。

各サービスをクリックすると、計測範囲時間におけるレスポンスの分布を確認できます。

Traces

左側メニューのTracesをクリックすると、各リクエストにおけるセグメント単位のリクエストの実行結果をトレースできます。初期表示では、URLをELBのエンドポイントでグループ化したトレース結果が表示されています。

リストのトレースリストをクリックすると、各リクエスト内全ての実行時間を確認できます。

Rawデータも確認ができます。

このGolangのサンプルでは、the main segmentにおいて、xray.Handler helperを初期化しており、全てのhttpリクエスト内の全ての必要な情報を構造化して送信しています。

アナリティクス

左側メニューのアナリティクスをクリックすると、トレースデータの理解に役立つ、サービス性能の全体的な分析結果を確認できます。最近でた機能らしいですが、なんかすげぇな…

応答時間の分布状況を確認できたり

応答時間でフィルタしたなかでの、HTTPステータスコードの分布、リクエストURI、ユーザーエージェント、HTTPメソッドの分布、レスポンスタイム、そして、そこに含まれる詳細トレースを確認することができます。

サンプルアプリケーションなので、全レスポンスが200だったりあまり意味のあるグラフには見えないのですが、実アプリケーションであれば、応答時間が非常にかかっている部分のリクエストに絞って、そのエラー率や実際のトレース内容を把握するのに威力を発揮しそうです。

AWS X-Rayの料金

料金- AWS X-Ray | AWS

無料利用枠が用意されています。とりあえず試してみるにはありがたい。

  • 期限なしの無料利用枠
    • 毎月、トレースの記録は 10 万回まで無料です。
    • 毎月、トレースの取得とスキャンは合わせて 100 万回まで無料です。
  • 追加料金
    • トレースの記録の無料利用枠を超えた分の料金は、トレースの記録 100 万件あたり 5 USD (トレース 1 件あたり 0.000005 USD) です。
    • トレースの取得とスキャンの無料利用枠を超えた分の料金は、トレースの取得とスキャンを合わせて 100 万件あたり 0.50 USD (トレース 1 件あたり 0.0000005 USD) です。

サンプルアプリケーションの後片付け

サンプルアプリケーションは、このままだと延々ログを吐き出してしまうので、検証が終わったらさくっと片付けてしまいましょう。

サンプルアプリケーションの削除はこちら。

$ kubectl delete deployments x-ray-sample-front-k8s x-ray-sample-back-k8s
$ kubectl delete services x-ray-sample-front-k8s x-ray-sample-back-k8s

X-Rayデーモンセットの削除はこちら。

$ kubectl delete -f https://eksworkshop.com/x-ray/daemonset.files/xray-k8s-daemonset.yaml

アプリケーションへのSDK組み込みは必須だが、分散トレーシングとして非常にパワフル

サンプルアプリケーションがあるおかげで、Pod間のリクエストのビジュアライズから、各リクエストのトレースと分析結果を垣間見ることができました。むっちゃ面白かったです。

アプリケーションへのX-Ray SDKの組み込みと設定が必須になっている部分は最初の導入で敷居が高いですが、マイクロサービスにおける各サービス間のトレーシングや分析には非常に役立つ機能かと思います。

実際に活きたアプリケーションに導入しないとその威力がわかりにくいと思いますが、サンプルアプリケーションの導入と展開自体はこの記事で書いたように非常に簡単なので、まずはどのようなデータがでるか感触を掴んでいただき、「これ、ごっつええやん(^q^)」と興奮した人であれば、皆さんの運用アプリケーションに組み込んでみてはいかがでしょうか。

それでは、今日はこのへんで。濱田(@hamako9999)でした。