AWS Distro for OpenTelemetry を使ってトレースを X-Ray でサクッと可視化する on EKS
この文書は何か
AWS Distro for OpenTelemetry(ADOT) を使ったトレース情報の収集と X-Ray での可視化を、EKS 上にアプリケーションをデプロイし最小構成でハンズオンします。
OpenTelemetry は、テレメトリを収集するための計装ライブラリやエクスポーター、送信プロトコルの標準仕様を定めているオープンソースプロジェクトです。アプリケーションにベンダ依存の API・SDK を入れることなく、テレメトリ収集の計装をすることができるため、プロプライエタリからの解放を期待できます。
※ O11yCon2022 の OpenTelemetryのこれまでとこれから のセッションが大変勉強になります。
ADOT は OpenTelemetry の AWS サポートディストリビューションです。EKS を含む AWS サービス上で OpenTelemetry を使うために便利なコンポーネントがプリインストールされています。
アプリケーションはオープン化(OpenTelemetry 化)しつつ、AWS のモニタリングソリューションは継続的に使いたいというケースは多くあると思うので、ADOT は個人的興味のあるサービスです。
今回やること
EKS の構築から、ADOT 環境の構築、openTelemetry-go
を用いたサンプルアプリの実装、X-Ray でのトレース情報の取得までを最小構成で行います。
- EKS クラスタの構築
- ADOT のインストール
-
opentelemetry-go
を用いたアプリ実装と EKS へのデプロイ - X-Ray の確認
EKS クラスタの構築
EKS クラスタの構築は eksctl
コマンドを用いることで簡易的に実施できます。
-
IAM の準備
-
eksctl
でクラスタ作成では VPC や NAT IP など含んだ様々コンポーネントを作成するため、適切なポリシーを付与したユーザーを使った操作が必要です - 今回は構築目的なので、
AdministratorAccess
ポリシーをアタッチしたユーザーを作成し、その認証情報を使いeksctl
を実行をします - 以下のように config ファイルに認証情報をセットしていることを確認してください
❯ aws configure list Name Value Type Location ---- ----- ---- -------- profile <not set> None None access_key ******************** shared-credentials-file secret_key ******************** shared-credentials-file region ap-northeast-1 config-file ~/.aws/config
-
-
EKS クラスタの構築
以下のコマンドを実行し Kubernets クラスタを作成します。実行には 15 分ほどかかりました。
※ CloudFormation で環境が作られるため、CloudFormation の管理画面で進捗を確認できます構築が終わったら EKS クラスタ への接続(とクラスタ名の変更)を行います。❯ eksctl create cluster \ --name <クラスタ名は適宜変更> \ --region <リージョンは適宜変更 例: ap-northeast-1> \ --version 1.22 \ # ※ 1.19 以降要 --nodegroup-name <ノードグループは適宜変更> \ --node-type t3.small \ --nodes 2 \ --nodes-min 2 \ --nodes-max 3
-
kubeconfig
の更新❯ aws eks --region <リージョン> update-kubeconfig --name <クラスタ名> ❯ kubectl config rename-context <元の名前> <新しい名前> ~ (⎈ |otel-work-eks-zenn:default) # kube-ps1 設定で接続クラスタ表示。上で作ったクラスタに接続 ❯
- Pod リソースの確認
❯ kubectl get pod -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system aws-node-9rmjn 1/1 Running 0 2m46s kube-system aws-node-qw5jb 1/1 Running 0 2m41s kube-system coredns-5b6d4bd6f7-d8h68 1/1 Running 0 12m kube-system coredns-5b6d4bd6f7-gt6qm 1/1 Running 0 12m kube-system kube-proxy-9fhwp 1/1 Running 0 2m41s kube-system kube-proxy-n9v7q 1/1 Running 0 2m46s
-
ADOT のインストール
ADOT 環境を整備するために、ADOT アドオンのインストール、ADOT Collector
のデプロイなどが必要となります。基本的には CLI 操作で完結します。
- ADOT アドオンのインストール
ADOT アドオンは k8s に ADOT 操作機能を提供します。アドオンについてはこちらを参照。- ADOT アドオンをインストールするための RBAC 設定を適用
❯ kubectl apply -f https://amazon-eks.s3.amazonaws.com/docs/addons-otel-permissions.yaml
- cert-manager のインストール。公式 はこちらを参照。
# helm chart への repository 追加 ❯ helm repo add jetstack https://charts.jetstack.io ❯ helm repo update # sample namespace への cert-manager のデプロイ ❯ helm install \ cert-manager jetstack/cert-manager \ --namespace sample \ --create-namespace \ --version v1.5.5. \ # ADOT は 1.6.0 未満をサポート --set installCRDs=true
- ADOT アドオンのインストール
❯ aws eks create-addon --addon-name adot --addon-version v0.45.0-eksbuild.1 --cluster-name <クラスタ名> ❯ aws eks describe-addon --addon-name adot --cluster-name <クラスタ名> | jq .addon.status "ACTIVE" # ADOT アドオンが有効化
- ADOT アドオンをインストールするための RBAC 設定を適用
-
ADOT Collector
のデプロイ設定
ADOT Collector
はアプリケーションからエクスポートされたテレメトリ情報を集約し、X-Ray などのバックエンドに送信するプロキシです。
※ Collector に関しては O11yCon2022 の 入門 OpenTelemetry Collector が参考になります
ADOT アドオンはKubernetes Operator
の実装であり、OpenTelemetryCollector
のカスタムリソースをデプロイすることができます。マニフェストは以下。opentelemetry-collector-sample.yamlapiVersion: opentelemetry.io/v1alpha1 kind: OpenTelemetryCollector metadata: name: sample namespace: sample spec: image: public.ecr.aws/aws-observability/aws-otel-collector:v0.17.0 mode: deployment serviceAccount: sample config: | receivers: otlp: protocols: grpc: endpoint: "0.0.0.0:4318" processors: exporters: awsxray: region: ap-northeast-1 service: pipelines: traces: receivers: [otlp] processors: [] exporters: [awsxray]
-
ServiceAccouunt
を作成し、AWSXRayDaemonWriteAccess
ポリシーを持つロールを紐付ける必要があります❯ kubectl create sa <サービスアカウント名> -n sample # ここの名前を↑のマニフェストに記述します ❯ kubectl annotate sa sample -n sample eks.amazonaws.com/role-arn=arn:aws:iam::<自身のARN>:role/<自身のロール名>
- トレース情報を
otlp
形式で受信し、awsxray
形式で送信する設定をしています
-
-
ADOT Collector
のデプロイ
上記のマニフェストファイルをアプライします❯ kubectl apply -f opentelemetry-collector-sample.yaml -n sample ❯ kubectl get po -n sample | grep collector sample-collector-7cbd4db667-rznzm 1/1 Running 0 29s
- 最後、EKS クラスタに IAM OIDC Provider を設定します。OIDC プロバイダ ID は AWS コンソールの EKS クラスタ概要から確認できる
❯ eksctl utils associate-iam-oidc-provider --cluster <自身のクラスタ名> --approve
OpenID Connect プロバイダー URL
の下 32 文字のランダム値を参照。以下のように上記で作ったロールの信頼関係を編集します。ロールの信頼関係{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" }, ### --- 今回追加する部分 --- ### { "Sid": "Statement2", "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::<自身のARN>:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/<自身のOIDCプロバイダID>" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.ap-northeast-1.amazonaws.com/id/<自身のOIDCプロバイダID>:sub": "system:serviceaccount:<namespace 名 例: sample>:<サービスアカウント名 例: sample" } } } ### --- 今回追加する部分 --- ### ] }
opentelemetry-go
を用いたアプリ実装と EKS へのデプロイ
EKS クラスタ構築と ADOT 環境準備はこれで終わりです。あとはアプリケーションに OpenTelemetry で計装をしていくだけです。今回は opentelemetry-go
の SDK を使ってトレース・スパンを取得・エクスポートする計装をしていきます。
サンプルアプリは go の Web Framework である gin
を用いて作りました。トレースやスパン情報は Context
を介して伝播するため Context Propergation を扱いやすくするためです。
- アプリの実装いくつか解説をしていきます。細かい内容は OpenTelemetry in Go が参考になります
ソースコード全量はこちらです。
main.gopackage main import ( "context" "fmt" "log" "os" "os/signal" "time" "github.com/gin-gonic/gin" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func initProvider() (func(context.Context) error, error) { ctx := context.Background() res, err := resource.New(ctx, resource.WithAttributes( semconv.ServiceNameKey.String("sample"), ), ) if err != nil { return nil, fmt.Errorf("failed to create resource: %w", err) } conn, err := grpc.DialContext(ctx, "sample-collector.sample.svc.cluster.local:4318", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err) } traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) if err != nil { return nil, fmt.Errorf("failed to create trace exporter: %w", err) } bsp := sdktrace.NewBatchSpanProcessor(traceExporter) var tracerProvider *sdktrace.TracerProvider tracerProvider = sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), sdktrace.WithIDGenerator(xray.NewIDGenerator()), ) otel.SetTracerProvider(tracerProvider) otel.SetTextMapPropagator(propagation.TraceContext{}) return tracerProvider.Shutdown, nil } var tracer = otel.Tracer("sample") func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() shutdown, err := initProvider() if err != nil { log.Fatal(err) } defer func() { if err := shutdown(ctx); err != nil { log.Fatal("failed to shutdown TracerProvider: %w", err) } }() r := gin.New() r.Use(otelgin.Middleware("sample")) r.GET("/sample", sample1) r.Run(":8080") } func sample1(c *gin.Context) { _, span := tracer.Start(c.Request.Context(), "sample1") time.Sleep(time.Second * 1) log.Println("sample1 done.") sample2(c) span.End() } func sample2(c *gin.Context) { _, span := tracer.Start(c.Request.Context(), "sample2") time.Sleep(time.Second * 2) log.Println("sample2 done.") sample3(c) span.End() } func sample3(c *gin.Context) { _, span := tracer.Start(c.Request.Context(), "sample3") time.Sleep(time.Second * 3) log.Println("sample3 done.") span.End() }
-
initProvider()
ではトレースプロバイダーを生成しています
上記でデプロイしたADOT Collector
に Kubernetes クラスタ内通信で接続していますconn, err := grpc.DialContext(ctx, "sample-collector.sample.svc.cluster.local:4318", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
-
tracer.Start
で任意の処理でスパンを生成します。
ここでは各 sample メソッド内で処理を実行し、後続のメソッドを呼び出す形に実装しています。処理が終わったらスパンを閉じるため、span.End()
を実行します。_, span := tracer.Start(c.Request.Context(), "sample1") time.Sleep(time.Second * 1) log.Println("sample1 done.") sample2(c) span.End()
-
- アプリのデプロイ
今回は gitlab のレジストリにコンテナイメージを置き、EKS 上にデプロイしました。サンプルアプリの yaml ファイル
sample-app.yamlapiVersion: v1 kind: Pod metadata: name: sample spec: containers: - name: sample image: registry.gitlab.com/keisuke.sakasai/otel-sample-app-zenn:latest
❯ kubectl apply -f sample-app.yaml ❯ kubectl get po | grep sample sample 1/1 Running 0 22m
X-Ray の確認
ここまで正常に設定がされていれば、サンプルアプリから X-Ray にトレース・スパン情報が送信されコンソール画面から確認することができます。
Pod に portforward して、localhost に対してリクエストを送信してみます。
❯ kubectl port-forward sample 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
❯ curl localhost:8080/sample
X-Ray のコンソール画面にいくと、トレースリストに該当のトレース情報が確認できます。以下のように Span も正常に取得できていることがわかります。
最後に
今回は AWS の ADOT を用いてトレース情報を X-Ray に送信する方法について紹介しました。
アプリケーションに一度計装を施してしまえば、あとは AWS の提供するモニタリングソリューションをすぐに使うことができるので、マネージドサービスの強力さを改めて感じました。
アプリケーション側は OpenTelemetry を使って標準化を行い、バックエンドソリューションは用途に応じて簡単に切り替えることができるようにしておくことが良いのかなと思います。
OpenTelemetry を使って標準化の波に乗りましょう。
Discussion