Amazon Web Services ブログ

Amazon CloudWatch での Prometheus メトリクスの使用

Imaya Kumar Jagannathan、Justin Gu、Marc Chéné、および Michael Hausenblas

今週の初めに、AWS は CloudWatch Container Insights での Prometheus メトリクスモニタリングの公開ベータ版サポートを発表しました。この記事では、ユーザーがプロビジョニングする AWS クラスター上の Amazon Elastic Kubernetes Service (EKS) および Kubernetes で、コンテナ化されたワークロードに新しい Amazon CloudWatch 機能を使用する方法をご紹介します。

Prometheus は Cloud Native Compute Foundation (CNCF) の卒業プロジェクトで、アクティブで大きなプラクティショナーコミュニティを持つ人気のオープンソースモニタリングツールです。Amazon CloudWatch Container Insights は、コンテナ化されたアプリケーションからの Prometheus メトリクスの検出と収集を自動化します。CloudWatch Container Insights は、カスタム CloudWatch メトリクスを自動的に収集し、フィルタリングして、AWS App Mesh、NGINX、Java/JMX、Memcached、および HAProxy などのワークロード用のダッシュボードで視覚化された集約メトリクスを作成します。デフォルトで、事前に選択されたサービスが 60 秒ごとにスクレイピングおよび事前集約され、クラスターおよびポッドの名前などのメタデータで自動的にリッチ化されます。

AWS では、OpenMetrics との互換性があるすべての Prometheus エクスポーターをサポートして、ユーザーが 150 を超えるオープンソースのサードパーティーエクスポーターのひとつを使ってコンテナ化されたあらゆるワークロードをスクレイプできるようにすることを目指しています。

これは次のような仕組みになっています。 まず、Kubernetes クラスターで CloudWatch エージェントを実行する必要があります。Prometheus の設定、検出、およびメトリクスのプル型収集をサポートするようになったこのエージェントは、忠実度の高い Prometheus メトリクスとメタデータのすべてをリッチ化し、埋め込みメトリックフォーマット (EMF) として CloudWatch Logs に発行します。各イベントは、完全に設定可能なキュレーションされた一連のメトリクスディメンションのための CloudWatch カスタムメトリクスとしてメトリクスデータポイントを作成します。集約された Prometheus メトリクスの CloudWatch カスタムメトリクス統計としての発行は、パフォーマンス問題と障害のモニタリング、アラーム発行、およびトラブルシューティングに必要なメトリクスの数を低減させます。また、CloudWatch Logs Insights クエリ構文を使って忠実度の高い Prometheus メトリクスを分析して、コンテナ化された環境の正常性とパフォーマンスに影響を及ぼしている特定のポッドとラベルを特定することもできます。

これらを踏まえて、2 つのセットアップでの CloudWatch Container Insights Prometheus メトリクスの使用方法を説明する実践的な部分に進みましょう。まず NGINX スクレイピングのシンプルな例から初めて、次に ASP.NET Core アプリを計装することによってカスタムメトリクスを使用する方法を見ていきます。

NGINX からの追加設定不要のメトリクス

最初の例では、EKS クラスターをランタイム環境として使用し、メトリクスを EMF イベントとして CloudWatch に取り込むために CW Prometheus エージェントをデプロイします。スクレイピングターゲットであり、専用アプリがトラフィックを生成する NGINX をイングレスコントローラとして使用します。全体的なセットアップは以下のようになります。

EKS クラスターには 3 つの名前空間があります。これらは、CW Prometheus エージェントをホストする amazon-cloudwatch、NGINX Ingress コントローラを実行する nginx-ingress-sample、およびトラフィックジェネレータを含めたサンプルアプリをホストする nginx-sample-traffic です。

手順を同時に進めたい場合は、EKS クラスターをプロビジョニングするための eksctl、およびアプリケーションインストールのための Helm 3 をインストールしておく必要があります。

EKS クラスターには、以下のクラスター設定を使っています (clusterconfig.yaml として保存。リージョンは地理的に近いリージョンに変更するとよいかもしれません)。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: cw-prom
  region: eu-west-1

iam:
  withOIDC: true

managedNodeGroups:
  - name: defaultng
    minSize: 1
    maxSize: 4
    desiredCapacity: 2
    labels: {role: mngworker}
    iam:
      withAddonPolicies:
        externalDNS: true
        certManager: true
        ebs: true
        albIngress: true
        cloudWatch: true
        appMesh: true

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

この後、以下のコマンドを使って EKS クラスターをプロビジョニングできます。

eksctl create cluster -f clusterconfig.yaml

eksctl は見えないところで CloudFormation を使用するので、そのコンソールで進行状況を確認できます。プロビジョニングは、開始から完了まで 15 分ほどかかることを見込んでください。

次に、Helm を使って、専用の Kubernetes 名前空間 nginx-ingress-sample に NGINX Ingress コントローラをインストールします。

kubectl create namespace nginx-ingress-sample

helm repo add stable https://kubernetes-charts.storage.googleapis.com/

helm install stable/nginx-ingress --generate-name --version 1.33.5 \
--namespace nginx-ingress-sample \
--set controller.metrics.enabled=true \
--set controller.metrics.service.annotations."prometheus\.io/port"="10254" \
--set controller.metrics.service.annotations."prometheus\.io/scrape"="true"

NGINX Ingress コントローラによって管理されるロードバランサーをトラフィックジェネレータのターゲットとするには、次のようにそのパブリック IP アドレスをクエリする必要があります。

$ kubectl -n nginx-ingress-sample get svc 
NAME                                          TYPE           CLUSTER-IP      EXTERNAL-IP                                                               PORT(S)                      AGE
nginx-ingress-1588245517-controller           LoadBalancer   10.100.245.88   ac8cebb58959a4627a573fa5e5bd0937-2083146415.eu-west-1.elb.amazonaws.com   80:31881/TCP,443:32010/TCP   72s
nginx-ingress-1588245517-controller-metrics   ClusterIP      10.100.32.79    <none>                                                                    9913/TCP                     72s
nginx-ingress-1588245517-default-backend      ClusterIP      10.100.75.112   <none>                                                                    80/TCP                       72s

これで、nginx-sample-traffic 名前空間にサンプルアプリとトラフィックジェネレータをセットアップする準備が整いました (EXTERNAL_IP には、前のステップで確認した独自の IP を使用するようにしてください)。

SAMPLE_TRAFFIC_NAMESPACE=nginx-sample-traffic

EXTERNAL_IP=ac8cebb58959a4627a573fa5e5bd0937-2083146415.eu-west-1.elb.amazonaws.com

curl https://cloudwatch-agent-k8s-yamls.s3.amazonaws.com/quick-start/nginx-traffic-sample.yaml | \
sed "s/{{external_ip}}/$EXTERNAL_IP/g" | \
sed "s/{{namespace}}/$SAMPLE_TRAFFIC_NAMESPACE/g" | \
kubectl apply -f -

最後に、以下を使って amazon-cloudwatch 名前空間に CW エージェントをインストールします。

kubectl create namespace amazon-cloudwatch
kubectl apply -f https://cloudwatch-agent-k8s-yamls.s3.amazonaws.com/quick-start/prometheus-eks.yaml

完成まであと一歩ですが、もうひとつやることがあります。CloudWatch にメトリクスを書き込む許可を CW エージェントに提供することです。これにはサービスアカウントの IAM ロール (IRSA) を使用します。これは、最小権限のアクセスコントロールを許可する EKS 機能で、CW エージェントを実行するポッドへの直接的な CloudWatchAgentServerPolicy を通じて CW へのアクセスを事実上制限します。

eksctl create iamserviceaccount \
           --name cwagent-prometheus \
           --namespace amazon-cloudwatch \
           --cluster cw-prom \
           --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
           --override-existing-serviceaccounts \
           --approve

これで、セットアップを検証できるようになりました。まず、CW エージェントデプロイメントによって使用されるサービスアカウントが適切にアノテートされているかどうかをチェックします (ここでは eks.amazonaws.com/role-arn キーのアノテーションを確認してください)。

$ kubectl -n amazon-cloudwatch get sa cwagent-prometheus -o yaml |\
  grep eks.amazon
eks.amazonaws.com/role-arn (http://eks.amazonaws.com/role-arn): arn:aws:iam::148658015984:role/eksctl-cw-prom-addon-iamserviceaccount-amazo-Role1-69WKQE6D9CG3

また、kubectl -n amazon-cloudwatch get pod で CWAgent が適切に実行されていることも検証してください。これは、Running 状態になっているはずです。

すべてがデプロイおよび実行されたところで、以下のように CLI からメトリクスをクエリできるようになります。

aws logs start-query \
       --log-group-name /aws/containerinsights/cw-prom/prometheus \
       --start-time `date -v-1H +%s` \
       --end-time `date +%s` \
       --query-string "fields @timestamp, Service, CloudWatchMetrics.0.Metrics.0.Name as PrometheusMetricName, @message | sort @timestamp desc | limit 50 | filter CloudWatchMetrics.0.Namespace='ContainerInsights/Prometheus'"

aws logs get-query-results \
        --query-id e69f2544-add0-4d14-98ff-0fadb54f27f1

上記の aws logs コマンドの出力は、以下のようになります (最後の value フィールドでエンコードされている Prometheus メトリクスに注意してください)。

{
    "results": [
        [
            {
                "field": "@timestamp",
                "value": "2020-04-30 11:40:38.230"
            },
            {
                "field": "Service",
                "value": "nginx-ingress-1588245517-controller-metrics"
            },
            {
                "field": "PrometheusMetricName",
                "value": "nginx_ingress_controller_nginx_process_connections"
            },
            {
                "field": "@message",
                "value": "{\"CloudWatchMetrics\":[{\"Metrics\":[{\"Name\":\"nginx_ingress_controller_nginx_process_connections\"}],\"Dimensions\":[[\"ClusterName\",\"Namespace\",\"Service\"]],\"Namespace\":\"ContainerInsights/Prometheus\"}],\"ClusterName\":\"cw-prom\",\"Namespace\":\"nginx-ingress-sample\",\"Service\":\"nginx-ingress-1588245517-controller-metrics\",\"Timestamp\":\"1588246838202\",\"Version\":\"0\",\"app\":\"nginx-ingress\",\"chart\":\"nginx-ingress-1.33.5\",\"component\":\"controller\",\"container_name\":\"nginx-ingress-controller\",\"controller_class\":\"nginx\",\"controller_namespace\":\"nginx-ingress-sample\",\"controller_pod\":\"nginx-ingress-1588245517-controller-56d5d786cd-xqwc2\",\"heritage\":\"Helm\",\"instance\":\"192.168.89.24:10254\",\"job\":\"kubernetes-service-endpoints\",\"kubernetes_node\":\"ip-192-168-73-163.eu-west-1.compute.internal\",\"nginx_ingress_controller_nginx_process_connections\":1,\"pod_name\":\"nginx-ingress-1588245517-controller-56d5d786cd-xqwc2\",\"prom_metric_type\":\"gauge\",\"release\":\"nginx-ingress-1588245517\",\"state\":\"active\"}"
            },
 ...       

NGINX からの例で Prometheus メトリクスをスクレイプして追加設定なしで使用する方法を確認したところで、カスタムメトリクスを使用する方法に進みましょう。

ASP.NET Core アプリからのカスタムメトリクス

以下のセットアップでは、Prometheus クライアントライブラリを使用して ASP.NET Core アプリケーションを計装します。ここでの目標は、カスタムメトリクスを公開して、これらのメトリクスを CloudWatch に取り込むことです。これは、カスタム設定された CloudWatch Prometheus エージェントを使って実行します。

カスタムメトリクスを公開するためのアプリの計装

まず、aws-samples/amazon-cloudwatch-prometheus-metrics-sample からサンプルアプリケーションをクローンし、HomeController.cs ファイルを検証します。

// Prometheus metrics:
private static readonly Counter HomePageHitCounter = Metrics.CreateCounter("PrometheusDemo_HomePage_Hit_Count", "Count the number of hits to Home Page");

private static readonly Gauge SiteVisitorsCounter = Metrics.CreateGauge("PrometheusDemo_SiteVisitors_Gauge", "Site Visitors Gauge");

public IActionResult Index() {
            HomePageHitCounter.Inc();
            SiteVisitorsCounter.Set(rn.Next(1, 15));
            return View();
}

ProductsController.cs ファイルも検証します。

// Prometheus metric:
private static readonly Counter ProductsPageHitCounter = Metrics.CreateCounter("PrometheusDemo_ProductsPage_Hit_Count", "Count the number of hits to Products Page");

public IActionResult Index(){
            ProductsPageHitCounter.Inc();
            return View();
}

上記のコードスニペットは、オープンソースの Prometheus クライアントライブラリを使って、各ページへの訪問者数と全体的な訪問者数を追跡するための 3 つの異なるメトリクスを計装します。

次に、ローカルでのテストとプレビューのため、Dockerfile があるディレクトリに移動します。以下のコマンドを使ってコンテナイメージを構築し、実行します。

docker build . -t prometheusdemo
docker run -p 80:80 prometheusdemo

localhost に移動します。ここでは、以下のような画面が表示されるはずです。[Home] および [Products] リンクを数回クリックして、トラフィックを生成します。

 

次に、http://localhost/metrics に移動します。ここには、/metrics エンドポイントを通じてアプリが公開しているすべての Prometheus メトリクスが表示されます。

Prometheus メトリクス検出のための CloudWatch エージェントの設定

リポジトリにある /kubernetes フォルダの prometheus-eks.yaml ファイルを開きます。YAML ファイルの cwagentconfig.json セクションにある以下の設定は、CloudWatch エージェントがアプリケーションからスクレイプするメトリクスを示しています。

{
 "source_labels": ["job"],
 "label_matcher": "prometheusdemo-dotnet",
 "dimensions": [["ClusterName","Namespace"]],
 "metric_selectors": [
 "^process_cpu_seconds_total$",
 "^process_open_handles$",
 "^process_virtual_memory_bytes$",
 "^process_start_time_seconds$",
 "^process_private_memory_bytes$",
 "^process_working_set_bytes$",
 "^process_num_threads$"
 ]
 },
 {
 "source_labels": ["job"],
 "label_matcher": "^prometheusdemo-dotnet$",
 "dimensions": [["ClusterName","Namespace"]],
 "metric_selectors": [
 "^dotnet_total_memory_bytes$",
 "^dotnet_collection_count_total$",
 "^dotnet_gc_finalization_queue_length$",
 "^dotnet_jit_method_seconds_total$",
 "^dotnet_jit_method_total$",
 "^dotnet_threadpool_adjustments_total$",
 "^dotnet_threadpool_io_num_threads$",
 "^dotnet_threadpool_num_threads$",
 "^dotnet_gc_pinned_objects$"
 ]
 },
 {
 "source_labels": ["job"],
 "label_matcher": "^prometheusdemo-dotnet$",
 "dimensions": [["ClusterName","Namespace","gc_heap"]],
 "metric_selectors": [
 "^dotnet_gc_allocated_bytes_total$"
 ]
 },
 {
 "source_labels": ["job"],
 "label_matcher": "prometheusdemo-dotnet",
 "dimensions": [["ClusterName","Namespace","app"]],
 "metric_selectors": [
 "^PrometheusDemo_HomePage_Hit_Count$",
 "^PrometheusDemo_SiteVisitors_Gauge$",
 "^PrometheusDemo_ProductsPage_Hit_Count$"
 ]
 }

prometheus.yaml には、標準の Prometheus 設定を使って、Prometheus メトリクスエンドポイントの詳細を CloudWatch エージェントに指示するセクションがあります。address ソースラベルの regex を変更して、サンプルアプリケーションがメトリクスを公開しているエンドポイントとポート番号に一致させる必要があることに注意してください。

- job_name: 'prometheusdemo-dotnet'
      sample_limit: 10000
      metrics_path: /metrics
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - source_labels: [__address__]
        action: keep
        regex: '.*:80$'
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: Namespace
      - source_labels: [__meta_kubernetes_pod_name]
        action: replace
        target_label: pod_name
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container_name
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_controller_name
        target_label: pod_controller_name
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_controller_kind
        target_label: pod_controller_kind
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_phase
        target_label: pod_phase

アプリと CloudWatch Prometheus エージェントのデプロイメント

まず、先ほど構築した ASP.NET Core アプリの Docker コンテナイメージを、独自に選択するコンテナレジストリにプッシュします。次に、deployment.yaml ファイルの <YOUR_CONTAINER_REPO> を、イメージを発行したコンテナリポジトリの値に変更します。

すべてが設定されたところで、以下のコマンドを使ってサンプルアプリケーションと CloudWatch Prometheus エージェントを Kubernetes クラスターにデプロイします。

kubectl apply -f kubernetes/

この時点で、オプションとして Kubernetes クラスターで CloudWatch Container Insights を有効化できることに注意してください。これは、AWS マネジメントコンソールでのインフラストラクチャマップと自動ダッシュボードの表示を可能にします。

セットアップは、次のように検証できます (すべてのポッドが Running 状態になっているはずです)。

$ kubectl -n amazon-cloudwatch get pods 
NAME                                  READY   STATUS    RESTARTS   AGE
cloudwatch-agent-785zq                1/1     Running   0          26h
cloudwatch-agent-tjxcj                1/1     Running   0          26h
cwagent-prometheus-75dfcd47d7-gtx58   1/1     Running   0          120m
fluentd-cloudwatch-7ttck              1/1     Running   0          26h
fluentd-cloudwatch-n2jvm              1/1     Running   0          26h

カスタムダッシュボードの作成

us-east-2 AWS リージョンで PrometheusDotNetApp という名前のカスタムダッシュボードを作成するには、次を実行します。

dashboardjson=$(<cloudwatch_dashboard.json)

aws cloudwatch put-dashboard \
        --dashboard-name PrometheusDotNetApp \
        --dashboard-body  $dashboardjson

別のリージョンにダッシュボードを作成したい場合は、JSON config ファイルの us-east-2 を希望する値に置き換えてください。

これで、作成した CloudWatch ダッシュボードに移動できるようになります。ダッシュボードは以下のようになるはずです。

CloudWatch Container Insights は、Kubernetes クラスターからのパフォーマンスメトリクスに基づいて自動ダッシュボードをパブリッシュします。CloudWatch Container Insights のパフォーマンスモニタリングページに移動して、Container Insights によって作成された自動ダッシュボードを表示してください。

CloudWatch Container Insights が有効化されているので、[リソース] から、コンポーネントを含めた Kubernetes クラスタートポロジを表示するマップビューにアクセスできます。

カスタムメトリクス例のシナリオはこれで終了です。では、今後について簡単に見てみましょう。

次のステップ

私たちは、Prometheus メトリクスに対する CloudWatch Container Insights サポートの公開ベータを開始できることをとてもうれしく思っています。皆さんがこの新しい機能をどのように使用し、将来何を期待しておられるかについて、ぜひお聞かせください。たとえば、PromQL サポート、または Prometheus のヒストグラムやサマリーメトリクスのネイティブサポートなどがあります。ご提案と経験の共有には、containerinsightsfeedback@amazon.com をご利用ください。AWS では、皆さんのフィードバックに基づいて機能の改善と追加を絶えず行っているため、今後も AWS でのコンテナ分野に引き続きご注目ください。

Imaya Kumar Jagannathan

Imaya Kumar Jagannathan

Imaya は Amazon CloudWatch と AWS X-Ray に焦点を当てるシニアソリューションアーキテクトです。モニタリングとオブザーバビリティに情熱を傾ける Imaya は、アプリケーション開発とアーキテクチャに関する優れた経歴を持っており、分散型システムでの作業と、マイクロサービスアーキテクチャ設計について話すことが好きです。C# でのプログラミングに愛着を持ち、コンテナとサーバーレステクノロジーに取り組んでいます。

Justin Gu

Justin Gu

Justin Gu は、カナダのバンクーバーを拠点とする Amazon CloudWatch のシニアソフトウェア開発エンジニアです。Justin は、大規模なメトリクス収集、分散型システム/クラウドコンピューティング、データの視覚化、ログ処理、および分析をサポートするモニタリングソリューションの設計と開発を楽しんでいます。

Marc Chéné

Marc Chéné

Marc は、モダンアプリケーション環境のためのマイクロサービスとコンテナのモニタリングに焦点を当てるプリンシパルプロダクトマネージャーで、理解を深め、信頼を築き、俊敏な方法で最高のユーザーエクスペリエンスを提供するためにお客様と協力しています。現在は、CloudWatch と、Grafana および Prometheus といったオープンソースツールを使ったメトリクス、ログ、および分散トレーシングなどの時系列データ全体での最高のオブザーバビリティエクスペリエンスの実現に着目しています。