AWS Containers Advent Calendar 2021 の19日目の記事です。
マネージドな Prometheus である、 Amazon Managed Service for Prometheus (AMP) が今年の 9/29 にGAしています。
本記事では、AMP について触れたいと思います。
触ってみた的な記事は既にいくつかあるので、こちらではもう少し細かく、Container Insights との比較と、メトリクスを AMP に入れるまでの方式3パターンにフォーカスしたいと思います。
CloudWatch Conatainer Insights と Prometheus の使い分け
そもそも AMP っていつ使うのか?何ができるのか?を整理したいと思います。
Kubernetes を使っている方は、普段から Prometheus を利用されているケースが多いかと思います。
これは、以下のようなメリットがある OSS ということで、Kubernetes を使う上でのデファクトなツールとなっているのかと思っています。
- Kubernetes API を活用したサービスディスカバリ(node, endpoints, service, pod, ingress)がサポートされていること
- kubeletが
/metrics/
エンドポイントで、Prometheus コンテナリソース情報を Prometheus メトリクスとして公開していること(cAdvisor が kubelet に組み込まれているため。) - エクスポーターが豊富にあり、監視したいメトリクスに合わせて選べる(kube-state-metrics, node-exporter)
一方で、AWS で EKS を使う場合、マネージドな監視方法も用意されているはずですが、それだとどうでしょうか?
EKS を監視できる AWS マネージドなサービスと言えば、 CloudWatch Container Insights です。
CloudWatch Container Insights で、監視できるメトリクスは以下の通りですが、
ざっくり以下のメトリクスが監視できます。
- cluster レベルのメトリクス(node 総数、問題のある node 数、pod 数)
- node レベルのメトリクス
- pod レベルのメトリクス(CPU/メモリ/NWリソース状況、コンテナリスタート数)
そして、監視方法ですが、
CloudWatch エージェントまたは AWS Distro for OpenTelemetry をクラスター上の DaemonSet としてセットアップして、メトリクスを CloudWatch に送信します。
Container Insights は、エージェントを DaemonSet としてセットアップする必要があるので、 Fargate だと対応していません。
AWS の containers-roadmap にも Issue として上がっています。
👍イイねお願いします。
※今のところの議論を見ると、 ステートレスな prometheus の pod を1つデプロイして、クラスタ外にデプロイされるメインの Prometheus とフェデレーションさせる構成が検討されている様です。
2022/2/18 追記
ADOT の Containter Insights サポートがリリースされました。
これにより、EKS Fargate のメトリクスを Conatiner Insights で見れるようになりました。
CloudWatch Insights ではカバーできない点
ここまで見てくると、Container Insights ではカバーしきれないシチュエーションが見えてきそうです。
具体的には以下2点かなと思います。
1. EKS Fargate
上述したとおり、 DaemonSet が使えないと Container Insights が利用できないので、Fargateだと現状サポートされません。
2022/2/18 追記 上述の通り、EKS Fargate で ADOT Collectoer を Statefulset としてデプロイすることで、Container Insights にメトリクスを送ることが可能になっています。(プレビュー)
したがって、現状 EKS Fargate のクラスタを利用している場合、 Prometheus の利用が現実的です。
ちなみに、Container Insights を使って、 EKS Fargate を監視する別案として Fargate クラスタ上にデプロイした Prometheus に必要なメトリクスは収集させ、その Prometheus からメトリクスのスクレイプを CloudWatch Agent の Deployment で実施する方法もあります。
この方式におけるメリットは、Prometheus や Grafana の管理(Config、ストレージ、アクセス制御)から解放される点です。
以下が参考になるので、見てみてください。
- ブログ:https://aws.amazon.com/jp/blogs/news/using-prometheus-metrics-in-amazon-cloudwatch/
- ドキュメント:https://docs.aws.amazon.com/prometheus/latest/userguide/AMP-onboard-ingest-metrics-existing-Prometheus.html
2. 監視したいメトリクスが多い
Conatainer Insights でも最低限必要なメトリクスが cluster / node / pod レベルでサポートされていますが、より詳しく見たい、他のコンポーネントのメトリクスも見たいといったシチュエーションがあるかと思います。
例えば以下が個人的には見たいメトリクスです。
- コントロールプレーンのメトリクス
- より詳細な cluster メトリクス(細かい Pod のステータス状況、Deployment や DaemonSet の数)
- より詳細な node レベルのメトリクス(pod 数、ディスク系)
- より詳細な pod(コンテナのステータス、CPUスロットル)
- MW(Nginx、JMX ,etc)やメッシュのメトリクス
昔に、Prometheus で取りたいメトリクスを性能試験をやっている立場からざっと整理したものがあります。よければどうぞ。
ということで、Prometheus を EKS で使いたシチュエーションがわかりました。
そして、Prometheus を自前で運用するよりも可用性やスケール性のサポートがあり、きめ細かいセキュリティの設定が可能で、アップグレード等の運用コストも削減できるという観点で、マネージドなサービスを使いませんか?というのが Amazon Managed Service for Prometheus (AMP) ですね。
AMP にメトリクスを送るパターン
AMP におけるメトリクスの収集は、通常の Prometheus のようにクラスタ内のリソースのメトリクスエンドポイントに対して直接メトリクスをスクレイプするのではなく、AMP に対して remote write の仕組みを使って送ります。
そして、 remote write を使ったメトリクス送信方法はいくつかあります。
こちらの図にあるように現状、以下の収集メカニズムをサポートしています。
- Kubernetes クラスタ上の Prometheus サーバーからの取り込み
- AWS Distro for OpenTelemetry(プレビュー)
- Grafana Agent
それぞれ見ていきましょう。
Kubernetes クラスタ上の Prometheus サーバーからの取り込み
まずは、シンプルに監視対象の EKS クラスタ上に Prometheus をデプロイして、Prometheus→AMPでメトリクスを送信するパターンです。
以下の AMP の Getting Started 記事では、この方法を使った説明が記載されています。
Prometheus には remote write という別の Prometheus にメトリクスを送信する機能があるので、それを利用します。
クラスタ上に Prometheus をインストールする際に、remote write 先として AMP を指定することで、APM のワークスペースにメトリクスを送信します。
helm install prometheus-for-amp prometheus-community/prometheus -n prometheus -f ./amp_ingest_override_values.yaml \ --set serviceAccounts.server.annotations."eks\.amazonaws\.com/role-arn"="${IAM_PROXY_PROMETHEUS_ROLE_ARN}" \ --set server.remoteWrite[0].url="https://aps-workspaces.${AWS_REGION}.amazonaws.com/workspaces/${WORKSPACE_ID}/api/v1/remote_write" 🌟\ --set server.remoteWrite[0].sigv4.region=${AWS_REGION}
AWS Distro for OpenTelemetry(プレビュー)
※ADOT のメトリクス収集機能はプレビューである点に注意してください。
続いて、CNCF プロジェクトである OpenTelemetry の AWS サポートのディストリビューションである AWS Distro for OpenTeletry のコレクターを利用する方式です。
OpenTelemetry はテレメトリデータの収集方法やバックエンドへの送信方法を標準化するオープンな仕様を作成しており、コレクターの実装も公開しています。 OpenTelemetry や ADOT については、また別日で書きたいと思います。
ADOT のコレクターは Prometheus と同じ形式のスクレイプの設定を記述することができ、Prometheus 等のバックエンドにメトリクスを送信できます。
エージェントのようにクラスタ上にデプロイしてますが、利用するメリットは以下のように、 Prometheus 以外の In/Out が期待できる点になるのかなと思っています。
- Prometheus だけでなく Jaeger 等複数のテレメトリデータフォーマットに対応(それにより単一のコレクタでテレメトリデータ収集が可能)
- Prometheus だけでなく 複数のOSS/商用のバックエンドに送信することができる(CloudWatch, NewRelic, Splunk, DataDog etc)
上記の図のように ADOT コレクターは以下の3つのコンポーネントで構成されています。
- Receiver : メトリクスをサービスディスカバリを利用してスクレイプする。Prometheus の scrape_config を書ける。
- Processor : 取得したメトリクスを整形する。
- Exporter : 取得したトリクスを送信する。
ADOT でメトリクスを取って AMP に送ってみる
とりあえず触ってみないと良くわからないので、 このあたりを参考に ADOT を使って AMP にメトリクスを送るところを触ってみます。
- ADOT 側ドキュメント:https://aws-otel.github.io/docs/getting-started/prometheus-remote-write-exporter/eks
- AWS 側ドキュメント:https://docs.aws.amazon.com/ja_jp/prometheus/latest/userguide/AMP-onboard-ingest-metrics-OpenTelemetry.html
EKS クラスターからメトリクスを取り込むためのIAM Role作成
後ほど、クラスタに ADOT の Deployment をデプロイするのですが、そいつに AMP に対してメトリクスを remote write できる権限を与える必要があります。
以下のドキュメントを参考に、IAM Role for Service Accoutn (IRSA) の設定をします。
以下のシェルを実行します。
シェルでは、Service Account に紐づける用の IAM Role(amp-iamproxy-ingest-role
) と OIDC 登録が実施される模様。
Roleには、以下の AMP への remote write に必要なアクションを持ったポリシーが付与されるようです。
#!/bin/bash -e CLUSTER_NAME=<my_amazon_eks_clustername> SERVICE_ACCOUNT_NAMESPACE=<my_prometheus_namespace> AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) OIDC_PROVIDER=$(aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///") SERVICE_ACCOUNT_AMP_INGEST_NAME=amp-iamproxy-ingest-service-account SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE=amp-iamproxy-ingest-role SERVICE_ACCOUNT_IAM_AMP_INGEST_POLICY=AMPIngestPolicy # # Set up a trust policy designed for a specific combination of K8s service account and namespace to sign in from a Kubernetes cluster which hosts the OIDC Idp. # cat <<EOF > TrustPolicy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OIDC_PROVIDER}:sub": "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_AMP_INGEST_NAME}" } } } ] } EOF # # Set up the permission policy that grants ingest (remote write) permissions for all AMP workspaces # cat <<EOF > PermissionPolicyIngest.json { "Version": "2012-10-17", "Statement": [ {"Effect": "Allow", "Action": [ "aps:RemoteWrite", "aps:GetSeries", "aps:GetLabels", "aps:GetMetricMetadata" ], "Resource": "*" } ] } EOF function getRoleArn() { OUTPUT=$(aws iam get-role --role-name $1 --query 'Role.Arn' --output text 2>&1) # Check for an expected exception if [[ $? -eq 0 ]]; then echo $OUTPUT elif [[ -n $(grep "NoSuchEntity" <<< $OUTPUT) ]]; then echo "" else >&2 echo $OUTPUT return 1 fi } # # Create the IAM Role for ingest with the above trust policy # SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE_ARN=$(getRoleArn $SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE) if [ "$SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE_ARN" = "" ]; then # # Create the IAM role for service account # SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE_ARN=$(aws iam create-role \ --role-name $SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE \ --assume-role-policy-document file://TrustPolicy.json \ --query "Role.Arn" --output text) # # Create an IAM permission policy # SERVICE_ACCOUNT_IAM_AMP_INGEST_ARN=$(aws iam create-policy --policy-name $SERVICE_ACCOUNT_IAM_AMP_INGEST_POLICY \ --policy-document file://PermissionPolicyIngest.json \ --query 'Policy.Arn' --output text) # # Attach the required IAM policies to the IAM role created above # aws iam attach-role-policy \ --role-name $SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE \ --policy-arn $SERVICE_ACCOUNT_IAM_AMP_INGEST_ARN else echo "$SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE_ARN IAM role for ingest already exists" fi echo $SERVICE_ACCOUNT_IAM_AMP_INGEST_ROLE_ARN # # EKS cluster hosts an OIDC provider with a public discovery endpoint. # Associate this IdP with AWS IAM so that the latter can validate and accept the OIDC tokens issued by Kubernetes to service accounts. # Doing this with eksctl is the easier and best approach. # eksctl utils associate-iam-oidc-provider --cluster $CLUSTER_NAME --approve
信頼ポリシーの設定
先ほど作った IAM Role amp-iamproxy-ingest-role
の信頼ポリシーを編集します。
... "Condition": { "StringEquals": { "oidc.eks.ap-northeast-1.amazonaws.com/id/xxxxxxxxxxxxxxxxxxxx:sub": "system:serviceaccount:<your_prometheus_workspace>:amp-iamproxy-ingest-service-account" } ...
↓
Service Account の namespace を adot-col
に変更します。
... "Condition": { "StringEquals": { "oidc.eks.ap-northeast-1.amazonaws.com/id/xxxxxxxxxxxxxxxxxxxx:sub": "system:serviceaccount:adot-col:amp-iamproxy-ingest-service-account" } ...
[Fargate用手順] fargateprofile 作成
ADOT のコレクターで Deployment が adot-col
namespace に作成されるため、この namespace 用の fargateprofile を作成しないと、いつまで経っても pod がデプロイされません。
したがって、fargateprofileを追加します。
合わせてサンプルアプリ用の namespace 分も作成します。 (aoc-prometheus-pipeline-demo
)
$ eksctl create fargateprofile --namespace adot-col --cluster eks-fargate-eksctl --name fp-adot-col $ eksctl create fargateprofile --namespace aoc-prometheus-pipeline-demo --cluster eks-fargate-eksctl --name fp-aoc-prometheus-pipeline-demo
作成した fargateprofile は以下コマンドで確認可能。
namespace 内に pod がデプロイされる際に node もデプロイされるように見える模様。
$ eksctl get fargateprofile --cluster eks-fargate-eksctl NAME SELECTOR_NAMESPACE SELECTOR_LABELS POD_EXECUTION_ROLE_ARN SUBNETS TAGS STATUS fp-adot-col adot-col <none> arn:aws:iam::xxxxxxxxxxx:role/eksctl-eks-fargate-eksctl-FargatePodExecutionRole-xxxxx subnet-xxxx,subnet-xxxx,subnet-xxxx <none> ACTIVE
※後の手順で pod をデプロイすると、、
node が1個増えるのが確認できました。
$ kubectl get no NAME STATUS ROLES AGE VERSION fargate-ip-192-168-104-82.ap-northeast-1.compute.internal Ready <none> 167m v1.19.13-eks-f39f26 fargate-ip-192-168-106-143.ap-northeast-1.compute.internal Ready <none> 168m v1.19.13-eks-f39f26 🌟fargate-ip-192-168-152-66.ap-northeast-1.compute.internal Ready <none> 8m37s v1.19.13-eks-f39f26
サンプルアプリのデプロイ
送るメトリクスがないとつまらないので、ドキュメントでも言及があるように、こちらのサンプアプリをメトリクスの生成に利用します。
手元でコンテナをビルドして、適当に ECR とかに置いて、以下yamlのimageを置き換えてデプロイします。
$ kubectl apply -f prometheus-sample-app.yaml $ kubectl get po kubectl get po -n aoc-prometheus-pipeline-demo NAME READY STATUS RESTARTS AGE prometheus-sample-app-b98ffb86d-nhqnm 1/1 Running 0 75m
ちなみに、このサンプルアプリは、:8080/metrics
でサンプルのメトリクスを公開しています。
kubectl run --image=busybox -n aoc-prometheus-pipeline-demo buxybox -i --tty -- /bin/sh / wget -qO - http://prometheus-sample-app-service:8080/metrics # HELP test_counter0 This is my counter # TYPE test_counter0 counter test_counter0{datapoint_id="0",foo_0="bar_0"} 2203.890083851957 # HELP test_counter1 This is my counter # TYPE test_counter1 counter test_counter1{datapoint_id="0",foo_0="bar_0"} 2177.682858602927 # HELP test_counter2 This is my counter # TYPE test_counter2 counter test_counter2{datapoint_id="0",foo_0="bar_0"} 2142.814262384007 # HELP test_counter3 This is my counter # TYPE test_counter3 counter test_counter3{datapoint_id="0",foo_0="bar_0"} 2156.358509991158 # HELP test_counter4 This is my counter # TYPE test_counter4 counter test_counter4{datapoint_id="0",foo_0="bar_0"} 2188.928156802071 ...
そして、Service の annotations にて、 scrape: "true"
を付けることにより、後に ADOT のスクレイプ対象となる感じです。
apiVersion: v1 kind: Service metadata: name: prometheus-sample-app-service namespace: aoc-prometheus-pipeline-demo labels: app: prometheus-sample-app annotations: scrape: "true" 🌟 spec: ports: - name: web port: 8080 targetPort: 8080 protocol: TCP selector: app: prometheus-sample-app
ADOT コレクターのセットアップ
さて、やっと ADOT のコレクターをセットアップします。
$ curl https://raw.githubusercontent.com/aws-observability/aws-otel-collector/main/examples/eks/prometheus-pipeline/eks-prometheus-daemonset.yaml -o eks-prometheus-daemonset.yaml
ってADOT側のドキュメントには書いてあるけど、yamlがリンク切れです。。
AWS 側のドキュメントには以下のyamlを使えあったので、こっちでやってみます。
namespace, configmap, rbac関連, daemonset のリソースが定義されています。
※今回 Fargate にデプロイしたいので、DaemonSet を Deployment に変更しました。(kind
を書き換えるだけ)
$ curl https://raw.githubusercontent.com/aws-observability/aws-otel-collector/main/examples/eks/aws-prometheus/prometheus-daemonset.yaml -o eks-prometheus-daemonset.yaml
yaml の項目を AMP のエンドポイント等一部、自環境情報にリプレースします。
exporters: awsprometheusremotewrite: # replace this with your endpoint endpoint: "https://aps-workspaces.ap-northeast-1.amazonaws.com/workspaces/ws-xxxxxxxxxxxxxxxxxx/api/v1/remote_write" # replace this with your region aws_auth: region: "ap-northeast-1" service: "aps" namespace: "adot"
$ kubectl apply -f eks-prometheus-daemonset.yaml namespace/adot-col created configmap/adot-collector-conf created serviceaccount/amp-iamproxy-ingest-service-account created clusterrole.rbac.authorization.k8s.io/adotcol-admin-role created clusterrolebinding.rbac.authorization.k8s.io/adotcol-admin-role-binding created service/adot-collector created deployment.apps/adot-collector created $ kubectl get po -n adot-col NAME READY STATUS RESTARTS AGE adot-collector-5cc7c55f7f-gtz97 1/1 Running 0 47m
ADOT の pod のログを見てみます。
上記サンプルアプリで確認しメトリクスがログ上に見えますね。
$ kubectl logs -n adot-col -lapp=aws-adot -f Resource labels: -> service.name: STRING(kubernetes-service-endpoints) -> host.name: STRING(192.168.163.176) -> job: STRING(kubernetes-service-endpoints) -> instance: STRING(192.168.163.176:8080) -> port: STRING(8080) -> scheme: STRING(http) InstrumentationLibraryMetrics #0 InstrumentationLibrary Metric #0 Descriptor: -> Name: test_counter0 🌟 -> Description: This is my counter -> Unit: -> DataType: Sum -> IsMonotonic: true -> AggregationTemporality: AGGREGATION_TEMPORALITY_CUMULATIVE NumberDataPoints #0 Data point attributes: -> datapoint_id: STRING(0) -> foo_0: STRING(bar_0) StartTimestamp: 2021-12-20 00:12:16.5 +0000 UTC Timestamp: 2021-12-20 00:50:46.5 +0000 UTC Value: 1173.409474 ...
ちなみに、configmap adot-collector-conf
の中の receivers
で、スクレイプの設定を書き、 exporters
の中でメトリクスを送信先のバックエンド(今回は AMP)の設定を書く感じです。
apiVersion: v1 kind: ConfigMap metadata: name: adot-collector-conf namespace: adot-col labels: app: aws-adot component: adot-collector-conf data: adot-collector-config: | receivers: prometheus: config: global: scrape_interval: 15s scrape_timeout: 10s scrape_configs: - job_name: 'kubernetes-service-endpoints' ...
AMP にメトリクスが送られていることを確認
AMP に対して awscurl
を使うことで、直接 PromQL が叩けるようなのですが、
めんどくさいので、Amazon Managed Grafana (AMG) と AMP を連携させてメトリクスが連携されているか確認することにします。
※AMG の本名って Amazon Managed Service for Grafana だと思ってたけど、Amazon Managed Grafana なんすね。。
AMG のセットアップ周りは割愛します。
(SSO設定して、AMPとの接続設定するくらいで簡単です。)
Explore にて、AMP のワークスペースから、メトリクスの一覧が見れます。
ちゃんと来ていそうです。
試しに1メトリクスのを可視化してみます。
ちゃんと時系列で値が来ていそうです!
長くなっちゃいましたが、ADOT を使ってスクレイプしたものを AMP に remote write することの確認ができました。
本当は cadvisor
とか他の情報もスクレイプしたかったんですが、力尽きてしまいました。
別日で ADOT の話を書きたい気持ちなので、その時に合わせて触れたらとか思っています。。
Grafana Agent
さて、もうここまで書いてきて早くも体力が限界ですが、
最後の連携方法である、Grafana Agent です。
こちらに Grafana Agent についての説明があります。
- 完全なPrometheusサーバーを実行するための軽量な代替手段
- ※ストレージ、クエリ、アラートエンジンなどの Prometheus のサブコンポーネントがないため軽量
- Prometheusエクスポーターを検出してスクレイピングし、メトリックをバックエンド(今回の場合 AMP )に送信
これは、、
完全に OpenTelemetry と同じ感じに見えますね。
何が違うのかしら、、
こちらのブログに現時点での理解が記載されていました。
Projects like the OpenTelemetry Collector are simultaneously supporting a similar Prometheus Agent use case. Based on this, you might be wondering if it makes sense for two similar solutions to exist in parallel. Asynchronous collaboration allows projects to narrow in on solving a problem from multiple perspectives, allowing for faster iteration towards a more mature approach. Right now, Grafana Agent and OpenTelemetry Collector take different trade-offs, and it’s too early to know if there’s a future where they converge. As long as they exist in parallel, the two solutions can iterate in parallel and learn lessons from the other solution.
要は同じような方向性をしているというのは正しい認識のようで、それぞれ OSS として突き詰める中でコラボレーションする可能性もあるだろうということですね。
※Cortex と Thanos がよぎりました。
grafana.com
ちょっとぱっとPros/Cons整理するところまで今回の記事では無理そうですが、良さげな Issue を見つけました。
ここから読み取るに以下のような感じでしょうか。
- Grafana Agent
- 安定したプロトコルで高負荷下での安定性に優れる
- Prometheus エコシステムにフォーカス(Prometheus, Cortex,, Grafana Loki, Grafana Tempo)
- 11個の Promeheus エクスポーターが付属
- OpenTelemetry
- テレメトリデータの In/Out と変換において幅広いスコープを持つ
- バックエンドを任意に切り替えることができる
- 一方で、互換性を保つことが難しい場合がある
- Prometheus 適合性ェックの remote write の結果が低く(41%)、鋭意取組中
- ここはまだ OpenTelemetry Metrics は GA 前で、今後に期待という部分か、、
ここは継続で調査とさせてください。
最後に
AMP を触れる Workshop があるので、ぜひ試してみてください!
(今は、 Prometheus → AMP と ADOT → AMP が試せます)