LoginSignup
2
1

Fargate で Tomcat の Java/JMX を取得し CloudWatch に表示する

Posted at

やりたいこと

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が必要ということになります。

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"
        }
    ]
}

タスク実行ロール

タスク実行ロールに必要な権限の説明については以下の通りです。

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": []
}

サービス

サービスでは特別な設定はありませんので、次の通りタスクが起動したことを確認しておきます。
スクリーンショット 2024-05-22 14.05.47.png

tomcat の設定

基本的に下記のページを参考に作成していきます。

jmx_prometheus_javaagent

prometheus/jmx_exporter から最新バージョンのもの(投稿時点では、jmx_prometheus_javaagent-0.20.0.jar)をダウンロードします。

config.yaml

config.yamlは JMX Exporter 設定ファイルです。

config.yaml
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 に提供します。

setenv.sh
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 に記述します。

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": []
}

サービス

サービスでは特別な設定はありませんので、次の通りタスクが起動したことを確認しておきます。

スクリーンショット 2024-05-22 14.21.44.png

動作確認

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に記録されたことが確認できました。
スクリーンショット 2024-05-22 15.48.26.png

CloudWatch メトリクス

名前空間ECS/ContainerInsights/Prometheusに表示されたことを確認できました。
スクリーンショット 2024-05-22 15.46.15.png

CloudWatch Logs に送信された Prometheus メトリクスが表示されない場合などは以下のサイトを参考にトラブルシュートすることをお勧めします。

Container Insights

Container Insights に表示されたことも確認できました。
スクリーンショット 2024-05-22 15.54.22.png

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1