やりたいこと
Fargate で Tomcat の Java/JMX を取得し CloudWatch メトリクスとして表示する。
また、Container Insights に表示する。
参考にしたサイト
先にハマったところ
CloudWatch メトリクスまで表示させるのであれば、2 つ目の記事で良いが、Container Insights まで表示させるのであれば、1 つ目の AWS のブログの記事を参考にした方が良い。
CloudWatch エージェントの設定をパラメータストアに保存(CWAgentConfigSSMParameter)するのですが、この中で CloudWatch の名前空間を 2 つ目の記事では"metric_namespace": "CWAgent","ECS/ContainerInsights",
にしている。この場合、CloudWatch メトリクスまでは表示されたが、Container Insights には表示されなかった。
1 つ目の AWS のブログでは"metric_namespace"
を明示的にしてしていないため、名前空間には、ECS/ContainerInsights/Prometheus
として出来上がる。これで Container Insights に表示された。
Java/JMX の Prometheus メトリクス
CloudWatch エージェントにより収集される Java/JMX の Prometheus メトリクスは以下の通りです。
手順
Container Insights を有効にする
下記にサイトを参考に ECS クラスターで Container Insights が有効になっていない場合は有効にしておきます。
セキュリティグループ
セキュリティグループの受信ルールで、Prometheus メトリクスをプライベート IP でスクレイピングするために、CloudWatch エージェントへの Prometheus ポートを開く必要がある点に留意が必要です。
ここでは、9404
番ポートを開放しておきます。
CWA の設定
下記のサイトにあるawsvpc ネットワークモードでの Fargate クラスターの CloudFormation スタックの作成 よりデプロイしますが、CloudFormation テンプレート(cwagent-ecs-prometheus-metric-for-awsvpc.yaml)内の CWAgentConfigSSMParameter
リソースを次項の「CloudWatch エージェント設定」の内容に置き換えてデプロイするだけです。
ここで、CloudWatch エージェント設定と Prometheus スクレイプ設定について説明しておきます。
Prometheus モニタリングを使用した CloudWatch エージェントは、Prometheus メトリクスをスクレイプするために 2 つの設定が必要です。
これらは、タスク定義のシークレットによって Parameter Store と統合されます。
設定ファイル | タスク定義のシークレット |
---|---|
CloudWatch エージェント設定用 | CW_CONFIG_CONTENT |
Prometheus スクレイプ設定用 | PROMETHEUS_CONFIG_CONTENT |
CloudWatch エージェント設定
各フィールドの説明はドキュメントを参考にすれば良いので割愛します。
{
"agent": {
"debug": true
},
"logs": {
"metrics_collected": {
"prometheus": {
"prometheus_config_path": "env:PROMETHEUS_CONFIG_CONTENT",
"ecs_service_discovery": {
"sd_frequency": "1m",
"sd_result_file": "/tmp/cwagent_ecs_auto_sd.yaml",
"docker_label": {
"sd_port_label": "ECS_PROMETHEUS_EXPORTER_PORT",
"sd_job_name_label": "ECS_PROMETHEUS_JOB_NAME"
},
"task_definition_list": [
{
"sd_job_name": "bugbash-workload-java-ec2-awsvpc-task-def-sd",
"sd_metrics_ports": "9404;9406",
"sd_task_definition_arn_pattern": ".*:task-definition/tomcat*"
}
]
},
"emf_processor": {
"metric_declaration_dedup": true,
"metric_declaration": [
{
"source_labels": ["Java_EMF_Metrics"],
"label_matcher": "^true$",
"dimensions": [["ClusterName","TaskDefinitionFamily"]],
"metric_selectors": [
"^jvm_threads_(current|daemon)$",
"^jvm_classes_loaded$",
"^java_lang_operatingsystem_(freephysicalmemorysize|totalphysicalmemorysize|freeswapspacesize|totalswapspacesize|systemcpuload|processcpuload|availableprocessors|openfiledescriptorcount)$",
"^catalina_manager_(rejectedsessions|activesessions)$",
"^jvm_gc_collection_seconds_(count|sum)$",
"^catalina_globalrequestprocessor_(bytesreceived|bytessent|requestcount|errorcount|processingtime)$"
]
},
{
"source_labels": ["Java_EMF_Metrics"],
"label_matcher": "^true$",
"dimensions": [["ClusterName","TaskDefinitionFamily","pool"]],
"metric_selectors": [
"^jvm_memory_pool_bytes_used$"
]
},
{
"source_labels": ["Java_EMF_Metrics"],
"label_matcher": "^true$",
"dimensions": [["ClusterName","TaskDefinitionFamily","area"]],
"metric_selectors": [
"^jvm_memory_bytes_used$"
]
}
]
}
}
},
"force_flush_interval": 5
}
}
Prometheus スクレイプ設定
こちらも各フィールドの説明はドキュメントを参考にすれば良いので割愛します。
global:
scrape_interval: 1m
scrape_timeout: 10s
scrape_configs:
- job_name: cwagent-ecs-file-sd-config
sample_limit: 10000
file_sd_configs:
- files: [ "/tmp/cwagent_ecs_auto_sd.yaml" ]
タスクロール
タスクロールに必要な権限の説明については以下の通りです。
ECS Prometheus サービス検出を有効にするとき、CloudWatch エージェントは定期的に ECS および EC2 フロントエンドに対して次の API コールを行い、ターゲット ECS クラスターで実行中の ECS タスクのメタデータを取得するために下記のECSServiceDiscoveryInlinePolicy
が必要ということになります。
- CloudWatchAgentServerPolicy
- ECSServiceDiscoveryInlinePolicy
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"ArnEquals": {
"ecs:cluster": "arn:aws:ecs:ap-northeast-1:123456789012:cluster/tomcat-ecs-cluster"
}
},
"Action": [
"ecs:DescribeTasks",
"ecs:ListTasks",
"ecs:DescribeContainerInstances",
"ecs:DescribeServices",
"ecs:ListServices"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"ec2:DescribeInstances",
"ecs:DescribeTaskDefinition"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
タスク実行ロール
タスク実行ロールに必要な権限の説明については以下の通りです。
- AmazonECSTaskExecutionRolePolicy
- CloudWatchAgentServerPolicy
- ECSSSMInlinePolicy
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ssm:GetParameters"
],
"Resource": "arn:aws:ssm:*:*:parameter/AmazonCloudWatch-*",
"Effect": "Allow"
}
]
}
タスク定義
ここでのポイントは先に説明した通り、Prometheus スクレイプ設定と CloudWatch エージェント設定を secrets として渡している点です。
{
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/cwagent-prometheus-tomcat-ecs-cluster-FARGATE-awsvpc:1",
"containerDefinitions": [
{
"name": "cloudwatch-agent-prometheus",
"image": "public.ecr.aws/cloudwatch-agent/cloudwatch-agent:1.300039.0b612",
"cpu": 0,
"links": [],
"portMappings": [],
"essential": true,
"entryPoint": [],
"command": [],
"environment": [],
"environmentFiles": [],
"mountPoints": [],
"volumesFrom": [],
"secrets": [
{
"name": "PROMETHEUS_CONFIG_CONTENT",
"valueFrom": "AmazonCloudWatch-PrometheusConfigName-tomcat-ecs-cluster-FARGATE-awsvpc"
},
{
"name": "CW_CONFIG_CONTENT",
"valueFrom": "AmazonCloudWatch-CWAgentConfig-tomcat-ecs-cluster-FARGATE-awsvpc"
}
],
"dnsServers": [],
"dnsSearchDomains": [],
"extraHosts": [],
"dockerSecurityOptions": [],
"dockerLabels": {},
"ulimits": [],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-create-group": "True",
"awslogs-group": "/ecs/ecs-cwagent-prometheus",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs-FARGATE-awsvpc"
},
"secretOptions": []
},
"systemControls": [],
"credentialSpecs": []
}
],
"family": "cwagent-prometheus-tomcat-ecs-cluster-FARGATE-awsvpc",
"taskRoleArn": "arn:aws:iam::123456789012:role/tomcat-ecs-task-role",
"executionRoleArn": "arn:aws:iam::123456789012:role/tomcat-ecs-execution-role",
"networkMode": "awsvpc",
"revision": 8,
"volumes": [],
"status": "ACTIVE",
"requiresAttributes": [
{
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"name": "ecs.capability.execution-role-awslogs"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.17"
},
{
"name": "com.amazonaws.ecs.capability.task-iam-role"
},
{
"name": "ecs.capability.secrets.ssm.environment-variables"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"name": "ecs.capability.task-eni"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
}
],
"placementConstraints": [],
"compatibilities": [
"EC2",
"FARGATE"
],
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "512",
"memory": "1024",
"registeredAt": "2024-05-21T01:34:22.288Z",
"registeredBy": "arn:aws:iam::123456789012:user/vscode",
"tags": []
}
サービス
サービスでは特別な設定はありませんので、次の通りタスクが起動したことを確認しておきます。
tomcat の設定
基本的に下記のページを参考に作成していきます。
jmx_prometheus_javaagent
prometheus/jmx_exporter から最新バージョンのもの(投稿時点では、jmx_prometheus_javaagent-0.20.0.jar)をダウンロードします。
config.yaml
config.yaml
は JMX Exporter 設定ファイルです。
lowercaseOutputName: true
lowercaseOutputLabelNames: true
rules:
- pattern: 'java.lang<type=OperatingSystem><>(FreePhysicalMemorySize|TotalPhysicalMemorySize|FreeSwapSpaceSize|TotalSwapSpaceSize|SystemCpuLoad|ProcessCpuLoad|OpenFileDescriptorCount|AvailableProcessors)'
name: java_lang_OperatingSystem_$1
type: GAUGE
- pattern: 'java.lang<type=Threading><>(TotalStartedThreadCount|ThreadCount)'
name: java_lang_threading_$1
type: GAUGE
- pattern: 'Catalina<type=GlobalRequestProcessor, name=\"(\w+-\w+)-(\d+)\"><>(\w+)'
name: catalina_globalrequestprocessor_$3_total
labels:
port: "$2"
protocol: "$1"
help: Catalina global $3
type: COUNTER
- pattern: 'Catalina<j2eeType=Servlet, WebModule=//([-a-zA-Z0-9+&@#/%?=~_|!:.,;]*[-a-zA-Z0-9+&@#/%=~_|]), name=([-a-zA-Z0-9+/$%~_-|!.]*), J2EEApplication=none, J2EEServer=none><>(requestCount|maxTime|processingTime|errorCount)'
name: catalina_servlet_$3_total
labels:
module: "$1"
servlet: "$2"
help: Catalina servlet $3 total
type: COUNTER
- pattern: 'Catalina<type=ThreadPool, name="(\w+-\w+)-(\d+)"><>(currentThreadCount|currentThreadsBusy|keepAliveCount|pollerThreadCount|connectionCount)'
name: catalina_threadpool_$3
labels:
port: "$2"
protocol: "$1"
help: Catalina threadpool $3
type: GAUGE
- pattern: 'Catalina<type=Manager, host=([-a-zA-Z0-9+&@#/%?=~_|!:.,;]*[-a-zA-Z0-9+&@#/%=~_|]), context=([-a-zA-Z0-9+/$%~_-|!.]*)><>(processingTime|sessionCounter|rejectedSessions|expiredSessions)'
name: catalina_session_$3_total
labels:
context: "$2"
host: "$1"
help: Catalina session $3 total
type: COUNTER
- pattern: ".*"
setenv.sh
Tomcat とともに JMX Exporter を起動し、localhost のポート 9404
で Prometheus メトリクスを公開する Tomcat 起動スクリプトです。また、config.yaml ファイルパスを JMX Exporter に提供します。
export JAVA_OPTS="-javaagent:/opt/jmx_exporter/jmx_prometheus_javaagent-0.20.0.jar=9404:/opt/jmx_exporter/config.yaml $JAVA_OPTS"
sample.war
適当に以下のアプリケーションを利用しました。
Dockerfile
ここまでの内容を Dockerfile に記述します。
FROM tomcat:9.0-jdk8-openjdk
RUN mkdir -p /opt/jmx_exporter
COPY ./jmx_prometheus_javaagent-0.20.0.jar /opt/jmx_exporter
COPY ./config.yaml /opt/jmx_exporter
COPY ./setenv.sh /usr/local/tomcat/bin
COPY sample.war /usr/local/tomcat/webapps/
RUN chmod o+x /usr/local/tomcat/bin/setenv.sh
ENTRYPOINT ["catalina.sh", "run"]
Docker イメージの作成
ここまでの手順で以下の通り必要なファイルが出来上がっていると思います。
.
├── Dockerfile
├── config.yaml
├── jmx_prometheus_javaagent-0.20.0.jar
├── sample.war
└── setenv.sh
Build 後、CodeCommit に Push します。
docker build --platform linux/x86_64 -t tomcat .
docker tag tomcat:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/tomcat:latest
docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/tomcat:latest
タスク定義
ここでのポイントは、dockerLabels を指定する点です。
ECS_PROMETHEUS_EXPORTER_PORT
ラベル付きのコンテナは、Prometheus スクレイピング用に指定されたコンテナポートに基づいて自動検出されます。
Java_EMF_Metrics=“true”
ラベル付きのコンテナの場合、埋め込みメトリクス形式が生成されます。
Key | Value |
---|---|
ECS_PROMETHEUS_EXPORTER_PORT | 9404 |
Java_EMF_Metrics | true |
{
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/tomcat-tf:8",
"containerDefinitions": [
{
"name": "tomcat",
"image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/tomcat:latest",
"cpu": 0,
"portMappings": [
{
"name": "tomcat-8080-tcp",
"containerPort": 8080,
"hostPort": 8080,
"protocol": "tcp",
"appProtocol": "http"
},
{
"name": "tomcat-9404-tcp",
"containerPort": 9404,
"hostPort": 9404,
"protocol": "tcp"
}
],
"essential": true,
"environment": [],
"environmentFiles": [],
"mountPoints": [],
"volumesFrom": [],
"dockerLabels": {
"ECS_PROMETHEUS_EXPORTER_PORT": "9404",
"Java_EMF_Metrics": "true"
},
"ulimits": [],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-create-group": "true",
"awslogs-group": "/ecs/tomcat-tf",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
},
"secretOptions": []
},
"systemControls": []
}
],
"family": "tomcat-tf",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"networkMode": "awsvpc",
"revision": 8,
"volumes": [],
"status": "ACTIVE",
"requiresAttributes": [
{
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"name": "ecs.capability.execution-role-awslogs"
},
{
"name": "com.amazonaws.ecs.capability.ecr-auth"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
},
{
"name": "ecs.capability.execution-role-ecr-pull"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"name": "ecs.capability.task-eni"
},
{
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
}
],
"placementConstraints": [],
"compatibilities": [
"EC2",
"FARGATE"
],
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "1024",
"memory": "3072",
"runtimePlatform": {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX"
},
"registeredAt": "2024-05-21T13:37:02.633Z",
"registeredBy": "arn:aws:iam::123456789012:user/leomaro7",
"tags": []
}
サービス
サービスでは特別な設定はありませんので、次の通りタスクが起動したことを確認しておきます。
動作確認
JMX のメトリクスが出力されることの確認
次の通り、9404
番ポートにアクセスし、JMX のメトリクスが出力されることを確認しておきます。
curl X.X.X.X:9404/metrics
# HELP jmx_config_reload_success_total Number of times configuration have successfully been reloaded.
# TYPE jmx_config_reload_success_total counter
jmx_config_reload_success_total 0.0
# HELP jmx_exporter_build_info A metric with a constant '1' value labeled with the version of the JMX exporter.
# TYPE jmx_exporter_build_info gauge
jmx_exporter_build_info{version="0.20.0",name="jmx_prometheus_javaagent",} 1.0
# HELP jvm_buffer_pool_used_bytes Used bytes of a given JVM buffer pool.
# TYPE jvm_buffer_pool_used_bytes gauge
jvm_buffer_pool_used_bytes{pool="direct",} 98303.0
jvm_buffer_pool_used_bytes{pool="mapped",} 0.0
アプリケーションの動作確認
一応、8080
番ポートにアクセスし、アプリケーションの動作も確認しておきます。
curl X.X.X.X:8080/sample
CloudWatch ログ
/aws/ecs/containerinsights/cluster-name/Prometheus
に記録されたことが確認できました。
CloudWatch メトリクス
名前空間ECS/ContainerInsights/Prometheus
に表示されたことを確認できました。
CloudWatch Logs に送信された Prometheus メトリクスが表示されない場合などは以下のサイトを参考にトラブルシュートすることをお勧めします。