EKS で CloudWatch Application Signals を有効化してみた
こんにちは。クラウド事業本部の枡川です。
今回は EKS で CloudWatch Application Signals(以降 Application Signals) を有効化してみました。
Application Signals とは
OpenTelemetry 互換のアプリケーションパフォーマンスモニタリングを実現するためのサービスです。
アプリケーションの自動計測をセットアップして、各種メトリクスを分析するためのダッシュボードを用意して、各種メトリクスから SLO を定義してアラームを設定して、といった一連の流れをベストプラクティスに沿った形で比較的簡単に行うことができます。
EKS で利用する際と ECS で利用する際の違い
ECS on Fargate を利用している場合、都度サイドカーの追加や環境変数の設定などが必要になります。
環境変数としては下記のようなものを指定します。
環境変数名 | 内容 |
---|---|
OTEL_RESOURCE_ATTRIBUTES | サービス名や環境名など |
OTEL_AWS_APPLICATION_SIGNALS_ENABLED | X-Ray トレースと CloudWatch メトリクスを Application Signals に送信するかどうか |
OTEL_METRICS_EXPORTER | 他のメトリクスエクスポーターを利用するかどうか |
OTEL_LOGS_EXPORTER | 他のログエクスポーターを利用するかどうか |
OTEL_EXPORTER_OTLP_PROTOCOL | OTLP トランスポートプロトコルで Application Signals では http/protobuf を選択する |
OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT | メトリクスの送信先で http://localhost:4316/v1/metrics を指定する |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | メトリクスの送信先で http://localhost:4316/v1/traces を指定する |
OTEL_TRACES_SAMPLER | サンプラーで X-Ray のサンプリングルールを利用するなら xray を指定 |
OTEL_PROPAGATORS | プロパゲーターで xray を指定 |
JAVA_TOOL_OPTIONS | Java の自動計装エージェントのパス |
また、Java の場合は自動計装エージェントを Java エージェントとしてコンテナ内に仕込む必要があります。
ある程度定型化された作業を行うだけではありますが、それなりに手間です。
EKS で CloudWatch Observability アドオンを利用している場合、この辺りのインスツルメントは自動で行ってくれます。
EKS の場合の自動インスツルメント
マネジメントコンソールから設定する方法と、Kubernetes のマニフェストファイルにアノテーションをつける方法があります。
マネジメントコンソールからの設定も便利ではありますが、どのサービスで Application Signals を有効化するかをコード管理しようと思った場合、マニフェストファイル側で管理した方がやりやすいと思うので、今回はアノテーションをつける方法を試してみます。
※ マネジメントコンソールの場合、アプリケーションをデプロイした後に名前空間や deployment を選択して有効化する形になります。
EKS クラスターの作成
各種リソースは Terraform の AWS EKS Terraform module で作ります。
EKS Auto Mode を利用します。
また、Observability アドオンも EKS Pod Identity に対応しているので、こちらで権限を付与します。
権限としては CloudWatchAgentServerPolicy と AWSXRayWriteOnlyAccess を付与します。
locals {
cluster_name = "test-cluster"
k8s_service_account_namespace = "amazon-cloudwatch"
k8s_service_account_name = "cloudwatch-agent"
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.36.0"
cluster_name = local.cluster_name
cluster_version = "1.32"
cluster_endpoint_public_access = true
cluster_endpoint_private_access = true
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
enable_cluster_creator_admin_permissions = true
cluster_compute_config = {
enabled = true
node_pools = ["general-purpose"]
}
bootstrap_self_managed_addons = false
cluster_addons = {
"observability" = {
name = "amazon-cloudwatch-observability"
version = "v4.0.0"
pod_identity_role_arn = {
role_arn = aws_iam_role.observability_addon.arn
service_account = local.k8s_service_account_name
}
}
}
}
data "aws_iam_policy_document" "allow_pod_identity" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["pods.eks.amazonaws.com"]
}
actions = [
"sts:AssumeRole",
"sts:TagSession"
]
}
}
resource "aws_iam_role" "observability_addon" {
name = "AmazonCloudWatchObservabilityAddonRole"
assume_role_policy = data.aws_iam_policy_document.allow_pod_identity.json
}
resource "aws_iam_role_policy_attachment" "observability_addon_cloudwatch" {
role = aws_iam_role.observability_addon.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}
resource "aws_iam_role_policy_attachment" "observability_addon_xray" {
role = aws_iam_role.observability_addon.name
policy_arn = "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess"
}
resource "aws_eks_pod_identity_association" "observability_addon" {
cluster_name = module.eks.cluster_name
namespace = local.k8s_service_account_namespace
service_account = local.k8s_service_account_name
role_arn = aws_iam_role.observability_addon.arn
}
Application Signals を有効化
Application Signals はマネジメントコンソールから有効化していきます。
「Application Signals を有効にする」をクリックします。
プラットフォームとして EKS を選択します。
「マニフェストフィアルに注釈を付ける」を選択します。
プログラミング言語ごとの注釈の付与方法が表示されます。
「完了」をクリックして、クラスターで Application Signals を有効化します。
アプリケーションのデプロイ
アノテーション (注釈) を付与してアプリケーションをデプロイします。
Spring Boot のアプリケーションを作成して、下記 DockerFile でコンテナ化しました。
FROM public.ecr.aws/amazoncorretto/amazoncorretto:17aws-opentelemetry-agent.jar
COPY ./build/libs/sample-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
ECS の際は ADD https://github.com/aws-observability/aws-otel-java-instrumentation/releases/download/v1.32.6/aws-opentelemetry-agent.jar ./aws-opentelemetry-agent.jar
といった行を入れて自動計装エージェントを追加しましたが、今回は不要です。
下記マニフェストファイルでデプロイします。
特筆すべき点は instrumentation.opentelemetry.io/inject-java: "true"
のアノテーションをつけるくらいで環境変数の指定などは不要です。
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: spring-boot
spec:
selector:
matchLabels:
app.kubernetes.io/name: spring-boot
replicas: 1
template:
metadata:
labels:
app.kubernetes.io/name: spring-boot
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- image: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-app:v3
imagePullPolicy: Always
name: spring-boot
ports:
- containerPort: 80
resources:
requests:
cpu: "0.5"
env:
- name: DATABASE_HOST
value: "sample-aurora-postgres-cluster.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
- name: DATABASE_NAME
value: "postgres"
- name: DATABASE_USER
value: "postgres"
- name: DATABASE_PASSWORD
value: "password"
起動後された Pod を確認していみると、裏側で良い感じにインスツルメントしてくれていることがわかります。
Init コンテナ経由で Java の自動計装エージェントを追加しつつ、各種環境変数が設定されています。
% kubectl describe pod spring-boot-84cd88dd7-5dwck
Name: spring-boot-84cd88dd7-5dwck
Namespace: default
Priority: 0
Service Account: default
Node: i-02d6699d4ada43805/10.0.100.54
Start Time: Fri, 23 May 2025 21:14:38 +0900
Labels: app.kubernetes.io/name=spring-boot
pod-template-hash=84cd88dd7
Annotations: instrumentation.opentelemetry.io/inject-java: true
Status: Running
IP: 10.0.100.209
IPs:
IP: 10.0.100.209
Controlled By: ReplicaSet/spring-boot-84cd88dd7
Init Containers:
opentelemetry-auto-instrumentation-java:
Container ID: containerd://8e5c79d379875bc9ed3052f54889bd955fcca3e7e0e636127ac765466a977cb6
Image: 602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/observability/adot-autoinstrumentation-java:v2.10.0
Image ID: 602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/observability/adot-autoinstrumentation-java@sha256:d16db829c68a6826c2ae28cba0feb063b48dd9c7ff434a8a4ecf4753d6d30dea
Port: <none>
Host Port: <none>
Command:
cp
/javaagent.jar
/otel-auto-instrumentation-java/javaagent.jar
State: Terminated
Reason: Completed
Exit Code: 0
Started: Fri, 23 May 2025 21:14:38 +0900
Finished: Fri, 23 May 2025 21:14:38 +0900
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 64Mi
Requests:
cpu: 50m
memory: 64Mi
Environment: <none>
Mounts:
/otel-auto-instrumentation-java from opentelemetry-auto-instrumentation-java (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-mnsvn (ro)
Containers:
spring-boot:
Container ID: containerd://f1752825f80b834603beaa6d2aeca4377fae551c7c6f5662c6435b602c7a1723
Image: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-app:v3
Image ID: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-app@sha256:6b8391a294eaab0fe8f421caa53026121f2d8cd2918004429e341fe33693855e
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 23 May 2025 21:14:43 +0900
Ready: True
Restart Count: 0
Requests:
cpu: 500m
Environment:
DATABASE_HOST: sample-aurora-postgres-cluster.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com
DATABASE_NAME: postgres
DATABASE_USER: postgres
DATABASE_PASSWORD: password
OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf
OTEL_METRICS_EXPORTER: none
OTEL_LOGS_EXPORTER: none
OTEL_AWS_APP_SIGNALS_ENABLED: true
OTEL_AWS_APPLICATION_SIGNALS_ENABLED: true
OTEL_TRACES_SAMPLER_ARG: endpoint=http://cloudwatch-agent.amazon-cloudwatch:2000
OTEL_TRACES_SAMPLER: xray
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://cloudwatch-agent.amazon-cloudwatch:4316/v1/traces
OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT: http://cloudwatch-agent.amazon-cloudwatch:4316/v1/metrics
OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT: http://cloudwatch-agent.amazon-cloudwatch:4316/v1/metrics
OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED: true
JAVA_TOOL_OPTIONS: -javaagent:/otel-auto-instrumentation-java/javaagent.jar
OTEL_SERVICE_NAME: spring-boot
OTEL_RESOURCE_ATTRIBUTES_POD_NAME: spring-boot-84cd88dd7-5dwck (v1:metadata.name)
OTEL_RESOURCE_ATTRIBUTES_NODE_NAME: (v1:spec.nodeName)
OTEL_PROPAGATORS: tracecontext,baggage,b3,xray
OTEL_RESOURCE_ATTRIBUTES: com.amazonaws.cloudwatch.entity.internal.service.name.source=K8sWorkload,k8s.container.name=spring-boot,k8s.deployment.name=spring-boot,k8s.namespace.name=default,k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME),k8s.replicaset.name=spring-boot-84cd88dd7,service.version=v3
Mounts:
/otel-auto-instrumentation-java from opentelemetry-auto-instrumentation-java (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-mnsvn (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-mnsvn:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
opentelemetry-auto-instrumentation-java:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: 200Mi
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 90s default-scheduler Successfully assigned default/spring-boot-84cd88dd7-5dwck to i-02d6699d4ada43805
Normal Pulled 90s kubelet Container image "602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/eks/observability/adot-autoinstrumentation-java:v2.10.0" already present on machine
Normal Created 90s kubelet Created container: opentelemetry-auto-instrumentation-java
Normal Started 90s kubelet Started container opentelemetry-auto-instrumentation-java
Normal Pulling 89s kubelet Pulling image "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-app:v3"
Normal Pulled 85s kubelet Successfully pulled image "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-app:v3" in 4.7s (4.7s including waiting). Image size: 236889469 bytes.
Normal Created 85s kubelet Created container: spring-boot
Normal Started 85s kubelet Started container spring-boot
動作確認
Application Signals のサービスページでも確認してみます。
無事、サービスとして追加されていました。
Application Signals のセットアップ画面に行くと、コンソールから追加していないものの監視対象のサービスとして表示されていることを確認できます。
SLO 管理を行うこともできました。
トレース情報も確認できました。
Java のランタイムメトリクスも見れます。
Observability アドオンをセットアップしているので、当然 Container Insights のダッシュボードも見れます。
最後に
EKS で Application Signals を有効化してみました。
以前 ECS で Application Signals を利用した時と比較すると、設定が非常に楽に感じました。
Container Insights のダッシュボードや、Observability アドオンに含まれた Fluent Bit も合わせて、初期設定の簡単さは目を見張るものがあります。
Prometheus + Grafana などの OSS 系や、Datadog などの SaaS 系も良いですが、AWS 純正サービスも充実してきているので、是非使っていきたいです。
後は Go とかに対応してくれたら良いなと思っています。