はじめに
Fargate上で動かすTomcatコンテナ内のJava Management Extensions (以下 JMX)の情報を取得するために、jmx_exporter + CloudWatchAgent の構成で検証しました。
jmx_exporter( https://github.com/prometheus/jmx_exporter )とは、Prometheus向けのJavaのエクスポーターで、JMXの情報をPrometheus用に出力してくれます。この情報をCloudWatchAgent(以下CWA)で取得し、CloudWatchLogsに出力します。また、CloudWatchLogsに出力された情報を基にカスタムメトリクスを作成することで、JMXの情報の可視化や監視を行うことが可能になります。
jmx_exporterはPrometheus用のエクスポーターなので、Prometheusを構築する必要があるのでは?と思いましたが、CWAがPrometheus用の情報も取り扱えるので、不要とのことです。嬉しいですね。
構成図
今回作成した構成のイメージは以下のようになっています。
大まかな流れとしては、
- CWAからECSに対してクラスタ内で起動しているサービスを取得する。
- CWAコンテナからTomcatコンテナのPrometheusエンドポイント(に指定したポート)に対してコールを行い、JMXメトリクスを収集する。
- CWAでCloudWatchLogsに収集したログを出力する。
となっています。
手順
Tomcatコンテナ
1. jmx_exporterの構築
Dockerfile
Tomcatを動かすコンテナのDockerfileに、jmx_exporterをインストールするように記述します。
jmx_exporterは https://github.com/prometheus/jmx_exporter から取得します。
RUN mkdir -p /opt/jmx_exporter
COPY ./jmx_prometheus_javaagent-0.20.0.jar /opt/jmx_exporter
COPY ./config.yaml /opt/jmx_exporter
config.yaml
jmx_exporterの設定ファイルであるconfig.yaml
ですが、今回は特に設定していません。
rules:
- pattern: ".*"
詳細を知りたい方は公式ドキュメントを参照してください。
https://github.com/prometheus/jmx_exporter?tab=readme-ov-file#configuration
サンプルケースも多く用意されています。
https://github.com/prometheus/jmx_exporter/tree/main/example_configs
setenv.sh
Tomcatのsetenv.sh
にjmx_exporterをJavaエージェントとして動かすように設定します。
export JAVA_OPTS="-javaagent:/opt/jmx_exporter/jmx_prometheus_javaagent-0.20.0.jar=9404:/opt/jmx_exporter/config.yaml $JAVA_OPTS"
jmx_prometheus_javaagent-0.20.0.jar=9404:
の部分でどのポートをPrometheusエンドポイントとするかを指定しています。コンテナを立ち上げ、localhost:9404/metricsにアクセスすると、JMXのメトリクスが出力されることが確認できます。
$ curl localhost:9404/metrics
# HELP jvm_memory_objects_pending_finalization The number of objects waiting in the finalizer queue.
# TYPE jvm_memory_objects_pending_finalization gauge
jvm_memory_objects_pending_finalization 0.0
# HELP jvm_memory_bytes_used Used bytes of a given JVM memory area.
# TYPE jvm_memory_bytes_used gauge
jvm_memory_bytes_used{area="heap",} 3.1449648E7
jvm_memory_bytes_used{area="nonheap",} 3.1373688E7
2. タスク定義の作成
Prometheusエンドポイントに指定したポートにアクセスできるように、ポートマッピングで9404を開放しておきます。
CWAコンテナ
1. コンテナイメージの作成
CWAのコンテナイメージはAWS公式のパブリックイメージがあるので、それを使えばOKです。
ただし今回はコンテナをプライベートサブネットで立ち上げるので、あらかじめプライベートリポジトリにプッシュしました。
2. 環境変数の作成
CWAのタスク定義で渡す環境変数を用意します。
これらの環境変数はAWS Systems Manager パラメータストア(以下、SSMパラメータストア)に保存します。
PROMETHEUS_CONFIG_CONTENT
Prometheus用の環境変数であるPROMETHEUS_CONFIG_CONTENT
を以下のようにYAML形式で定義します。
global:
scrape_interval: 1m # エンドポイントをスクレイピングする間隔
scrape_timeout: 10s # スクレイピングするときのタイムアウト設定
scrape_configs:
- job_name: tomcat
sample_limit: 10000 # 収集するメトリクスの最大数
file_sd_configs:
- files: ["/tmp/cwagent_ecs_auto_sd.yaml"]
スクレイピング(Scrayping)とは、特定の情報だけを抽出・取集する手法のことです。
files: ["/tmp/cwagent_ecs_auto_sd.yaml"]
は、CWAがどのターゲットをスクレイピングするかの情報が記述されるファイルなので、特に変更する必要はありません。
CWAGENT_CONFIG
CWA用の環境変数であるCWAGENT_CONFIG
を以下のようにJSON形式で記述します。
{
"agent": {
"debug": false
},
"logs": {
"metrics_collected": {
"prometheus": {
"log_group_name": "/ecs/cloudwatch-agent",
"prometheus_config_path": "env:PROMETHEUS_CONFIG_CONTENT",
"ecs_service_discovery": {
"sd_frequency": "1m",
"sd_result_file": "/tmp/cwagent_ecs_auto_sd.yaml",
"service_name_list_for_tasks": [
{
"sd_metrics_ports": "9404",
"sd_service_name_pattern": ".*",
"sd_metrics_path": "/metrics"
}
]
},
"emf_processor": {
"metric_namespace": "CWAgent",
"metric_declaration": [
{
"source_labels": ["container_name"],
"label_matcher": "^tomcat$",
"dimensions": [["ClusterName", "TaskId", "area"]],
"metric_selectors": [
"^jvm_memory_bytes_used$",
"^jvm_memory_bytes_committed$"
]
}
]
}
}
},
"force_flush_interval": 5
}
}
設定したパラメータについて私なりの理解で解説します。
-
log_group_name
: JMXメトリクスのログを出力する、ロググループを指定。 -
prometheus_config_path
: Prometheusのconfigを指定したパスから読み込む。 -
ecs_service_discovery
: CWAがクラスタ内でスクレイピングを行うECSサービスを探すための設定。-
sd_frequency
: ECSにサービス一覧を取得しにいく間隔。 -
sd_result_file
: CWAがどのターゲットをスクレイピングするかの情報が記述されるファイル。 -
service_name_list_for_tasks
: メトリクスを取得するサービス/タスクを指定する。-
sd_metrics_ports
: Prometheusエンドポイントに指定したポート。 -
sd_service_name_pattern
: 正規表現でスクレピングを行うサービスのパターンを指定。 -
sd_metrics_path
: メトリクスを取得する際にアクセスするパスを指定。今回だとprivate_ip:9404/metrics
となる。
-
-
-
emf_processor
: CloudWatchメトリクスにカスタムメトリクスとして出力する設定。-
metric_namespace
: カスタムメトリクスの名前。 -
metric_declaration
: どのJMXメトリクスをCloudWatchのカスタムメトリクスにとするかの設定。-
source_labels
: ログを絞り込むためのラベルを指定。(container_name
やTaskDefinitionFamily
などを指定可能) -
label_matcher
:source_labels
の値が、一致するものだけをCloudWatchメトリクスに飛ばす。今回はコンテナ名がTomcatであるものだけに絞り込む。 -
dimensions
: カスタムメトリクスのディメンジョンを指定する。 -
metric_selectors
: カスタムメトリクスとして取り込む、JMXメトリクスを指定する。
-
-
-
force_flush_interval
: ログがサーバーに送信されるまでにメモリバッファ内に残留する最大時間を指定。
今回はクラスタ内のECS サービス検出の設定であるecs_service_discovery
では、service_name_list_for_tasks
でサービスの検出を行いましたが、docker_label
を使ってサービスの検知を行うこともできます。
詳しくは以下のドキュメントを参照してください。
https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/ContainerInsights-Prometheus-Setup-autodiscovery-ecs.html
また、メトリクスの設定であるsource_labels
, dimensions
, metric_selectors
あたりは一度動かしてみて、CloudWatchLogsに出力されるログを見て設定すると良いと思います。
詳細については以下のドキュメントを参照してください。
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights-Prometheus-Setup-configure-ECS.html
3. タスク定義の作成
イメージ
作成したCWAイメージを指定します。
環境変数
CWAコンテナの環境変数でPROMETHEUS_CONFIG_CONTENTとCWAGENT_CONFIGをSSMパラメータストアから持ってくるように設定します。
タスクロールの設定
以下のポリシーを付与したタスクロールを作成し、アタッチします。
ここでCWAからECSに対して、サービスやタスクのリストを取得するために、EcsCloudwatchagentDiscoveryServicePolicyというカスタムポリシーを作成しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"ecs:ListTasks"
"ecs:ListServices"
"ecs:DescribeContainerInstances"
"ecs:DescribeServices"
"ecs:DescribeTasks"
"ecs:DescribeTaskDefinition"
],
"Resource": [
"*"
]
}
]
}
(必要であれば) VPCエンドポイントの作成
CWAコンテナをプライベートサブネットに配置する場合、ECSに対してサービスやタスクのリストを取得するために、com.amazonaws.region.ecs
のVPCエンドポイントが必要になります。
サービスの起動
クラスター内に、作成したタスク定義を基にtomcatサービスとCWAサービスを起動します。
結果の確認
CloudWatchLogsのロググループで、TomcatコンテナのJMXメトリクスを取ることができていることが確認できます。
またCloudWatchMetricsで、CWagentというカスタムメトリクスが作成されており、指定したJMXの情報を可視化できることが確認できます。
終わりに
Fargate上で動かすTomcatコンテナ内のJMXの情報を jmx_exporter + CloudWatchAgent の構成で取得することが確認できました。コンテナであっても気軽にJMXの情報を得られるのは嬉しいですね。
今回はCWAをサービスとして独立で動かしましたが、サイドカーコンテナとして動かすこともできるみたいです。
https://zenn.dev/wadahiro/articles/1c3c1fb5f030ec
ただし、タスクIDなどを自動で付与できず(ECSにサービスの情報を取得しにいかないため)、ログからどのコンテナの情報なのかを判断するのが難しいのでは?と思うため、サービスとして動かす方が安牌なような気がします。
※本ブログに記載した内容は個人の見解であり、所属する会社、組織とは関係ありません。