AWS Managed Prometheus+GrafanaでEKS監視基盤をラクラク構築

去年発表されたAmazon Managed Service for PrometheusとAmazon Managed Service for GrafanaをEKSと組み合わせてマネージドなモニタリング環境を作ります。
多少IAM系の準備が必要でしたが、以下のようなダッシュボードを簡単に作ることができました。

k8sクラスタ(EKS)がすでに稼働している環境があったので、そのメトリックの監視ダッシュボードをPrometheusとGrafanaで構築します。

全体像はこんな感じです。

Amazon Managed Service for Prometheus(AMP)

完全マネージド型の Prometheus と互換性のあるモニタリングサービスで、書き込みや読み取りの量によって自動的にスケールし、マルチ AZ 配置になっているのでPrometheusをインフラの運用をあまり考慮することなく気軽に使うことができます。
他のAWSサービスと同様にIAM統合されているのでアクセス管理も今までの感覚で行うことができます。

ワークスペース作成

適当な名前でワークスペースを作成します。

ここで入力した名前は後々使うので覚えておきます。

IAMロール作成

EKSからPrometheusにアクセスするためのIAMロールを作成する必要があります。
下記にガイドがありますが、私が使っているAWSがPermission Boundaryを設定しないとIAMロールを作れない制約があるので少しカスタマイズします。
https://docs.aws.amazon.com/prometheus/latest/userguide/set-up-irsa.html#set-up-irsa-ingest

なぜそんな制約をつけているかは以下参照。

下記2つのファイル(createIRSA-AMPIngest.sh, createIRSA-AMPQuery.sh)を作成し、実行権限を付け実行します。

CLUSTER_NAMEのところにAMPにメトリックを送信したいEKSのクラスタ名をいれてください。
SERVICE_ACCOUNT_NAMESPACEには先程作成したprometheusのワークスペース名を入れます。
PERMISSION_BOUNDARY_POLICYにはPermission Boundaryとして指定したいIAMポリシーのARN(arn:aws:iam::999999999999:policy/pb-policyのようなフォーマット)を入れれます。

#!/bin/bash -e
# 
# This is a shell script that create proper iam role for managed prometheus
CLUSTER_NAME=<EKSクラスタ名>
SERVICE_ACCOUNT_NAMESPACE=<prometheusのワークスペース名>
PERMISSION_BOUNDARY_POLICY=<permission boundaryに設定するIAMポリシーのARN>
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 \
  --permissions-boundary $PERMISSION_BOUNDARY_POLICY \
  --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
#!/bin/bash -e
CLUSTER_NAME=<EKSクラスタ名>
SERVICE_ACCOUNT_NAMESPACE=<prometheusのワークスペース名>
PERMISSION_BOUNDARY_POLICY=<permission boundaryに設定するIAMポリシーのARN>
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_QUERY_NAME=amp-iamproxy-query-service-account
SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE=amp-iamproxy-query-role
SERVICE_ACCOUNT_IAM_AMP_QUERY_POLICY=AMPQueryPolicy
#
# Setup 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_QUERY_NAME}"
        }
      }
    }
  ]
}
EOF
#
# Set up the permission policy that grants query permissions for all AMP workspaces
#
cat <<EOF > PermissionPolicyQuery.json
{
  "Version": "2012-10-17",
   "Statement": [
       {"Effect": "Allow",
        "Action": [
           "aps:QueryMetrics",
           "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 query with the above trust policy
#
SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE_ARN=$(getRoleArn $SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE)
if [ "$SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE_ARN" = "" ]; 
then
  #
  # Create the IAM role for service account
  #
  SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE_ARN=$(aws iam create-role \
  --role-name $SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE \
  --assume-role-policy-document file://TrustPolicy.json \
  --permissions-boundary $PERMISSION_BOUNDARY_POLICY \
  --query "Role.Arn" --output text)
  #
  # Create an IAM permission policy
  #
  SERVICE_ACCOUNT_IAM_AMP_QUERY_ARN=$(aws iam create-policy --policy-name $SERVICE_ACCOUNT_IAM_AMP_QUERY_POLICY \
  --policy-document file://PermissionPolicyQuery.json \
  --query 'Policy.Arn' --output text)
  #
  # Attach the required IAM policies to the IAM role create above
  #
  aws iam attach-role-policy \
  --role-name $SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE \
  --policy-arn $SERVICE_ACCOUNT_IAM_AMP_QUERY_ARN  
else
    echo "$SERVICE_ACCOUNT_IAM_AMP_QUERY_ROLE_ARN IAM role for query already exists"
fi
echo $SERVICE_ACCOUNT_IAM_AMP_QUERY_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

実行権限を付け、実行。

chmod +x createIRSA-AMPQuery.sh createIRSA-AMPIngest.sh
./createIRSA-AMPIngest.sh
./createIRSA-AMPQuery.sh

EKSにprometheusインストール

AMPにメトリクスを送信するためにEKSにもprometheusのインストールが必要です。
今回はEKSにPrometheusをまだ入れていなかったので、AMPを利用したHelmの構成ファイルを作ってインストールします。

prometheus-communityを今回使います。レポジトリを追加し、設定ファイルを作ります。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm inspect values prometheus-community/prometheus > prometheus.yaml;

構成ファイルを編集します。ここでAMPの画面に戻って設定値をゲットします。

取り込み/収集のタブにある「Helm チャートを使用して Prometheus サーバーからのリモート書き込みを設定するには」に記載されている文字列をコピーします。

取り込み/収集のタブを選択

先程作成したprometheus.yamlを開き、ServiceAccountsとserverのところの値を書き換えます。

ServiceAccounts部分書き換え
serverの中のremoteWrite部分書き換え

編集したらprometheus.yamlを保存。

次のコマンドでEKSにデプロイ。–namespaceはご自身のEKSにあるものをお使いください。

helm install --namespace prometheus-test -f prometheus.yaml prometheus-community/prometheus

これでEKSのメトリックがAMPに送信されます。

Amazon Managed Service for Grafana(AMG)

Amazon Managed Service for Grafana (AMG) は、スケーラビリティ、安全性、および高可用性を備えたフルマネージド型の Grafana サービスです。AMG を使用すると、複数のデータソースにわたるメトリクス、ログ、トレースを分析、モニタリング、アラーム通知できます。

AWS SSOを使ったユーザアクセス制御が可能です。
また、AWS IoT SiteWise、AWS X-Ray、Amazon CloudWatch、Amazon Elasticsearch Service、Amazon Managed Service for Prometheus、Amazon TimeStreamをデータソースとするプラグインがデフォルトで有効になっており、適切にIAMロールを設定することで非常に簡単にこれらをデータソースとして利用することができます。

ワークスペース作成

ワークスペース名は適当

AWS SSOを選びます。

Permission Boundaryの制約があるので、ここでもカスタマーマネージドを選択します。

Permission Boundaryの設定がいらない人はサービスマネージドを選択してOK。

IAMロール作成

今回はAMPとCloudWatch, SNSを使うように設定します。
下記のとおり、CreateIAMRole-AMG.shというファイルを作成します。
CLUSTER_NAMEに先程も指定したEKSのクラスタ名、
PERMISSION_BOUNDARY_POLICYにはPermission Boundaryとして指定したいIAMポリシーのARN(arn:aws:iam::999999999999:policy/pb-policyのようなフォーマット)を、
SERVICE_ACCOUNT_IAM_GRAFANA_ROLEに作成するIAMロール名、SERVICE_ACCOUNT_IAM_GRAFANA_POLICYに作成するポリシー名を指定します。

#!/bin/bash -e
CLUSTER_NAME=soltech-eks
PERMISSION_BOUNDARY_POLICY=<permission boundaryに設定するIAMポリシーのARN>
SERVICE_ACCOUNT_IAM_GRAFANA_ROLE=amg-role
SERVICE_ACCOUNT_IAM_GRAFANA_POLICY=AMGPolicy

cat <<EOF > TrustPolicy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "grafana.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

cat <<EOF > PermissionPolicyQuery.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "cloudwatchandaps",
            "Effect": "Allow",
            "Action": [
                "cloudwatch:DescribeInsightRules",
                "cloudwatch:GetDashboard",
                "cloudwatch:GetInsightRuleReport",
                "cloudwatch:GetMetricData",
                "aps:GetLabels",
                "cloudwatch:GetMetricStatistics",
                "cloudwatch:ListMetrics",
                "cloudwatch:DescribeAnomalyDetectors",
                "aps:QueryMetrics",
                "cloudwatch:DescribeAlarmHistory",
                "aps:GetMetricMetadata",
                "aps:DescribeWorkspace",
                "cloudwatch:DescribeAlarmsForMetric",
                "cloudwatch:ListDashboards",
                "cloudwatch:ListTagsForResource",
                "cloudwatch:DescribeAlarms",
                "aps:ListWorkspaces",
                "cloudwatch:GetMetricWidgetImage",
                "aps:GetSeries"
            ],
            "Resource": "*"
        },
        {
            "Sid": "sns",
            "Effect": "Allow",
            "Action": [
                "sns:Publish"
            ],
            "Resource": [
                "arn:aws:sns:*:326447680198:grafana*"
            ]
        }
    ]
}
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 query with the above trust policy
#
SERVICE_ACCOUNT_IAM_GRAFANA_ROLE_ARN=$(getRoleArn $SERVICE_ACCOUNT_IAM_GRAFANA_ROLE)
if [ "$SERVICE_ACCOUNT_IAM_GRAFANA_ROLE_ARN" = "" ]; 
then
  #
  # Create the IAM role for service account
  #
  SERVICE_ACCOUNT_IAM_GRAFANA_ROLE_ARN=$(aws iam create-role \
  --role-name $SERVICE_ACCOUNT_IAM_GRAFANA_ROLE \
  --assume-role-policy-document file://TrustPolicy.json \
  --permissions-boundary $PERMISSION_BOUNDARY_POLICY \
  --query "Role.Arn" --output text)
  #
  # Create an IAM permission policy
  #
  SERVICE_ACCOUNT_IAM_AMP_QUERY_ARN=$(aws iam create-policy --policy-name $SERVICE_ACCOUNT_IAM_GRAFANA_POLICY \
  --policy-document file://PermissionPolicyQuery.json \
  --query 'Policy.Arn' --output text)
  #
  # Attach the required IAM policies to the IAM role create above
  #
  aws iam attach-role-policy \
  --role-name $SERVICE_ACCOUNT_IAM_GRAFANA_ROLE \
  --policy-arn $SERVICE_ACCOUNT_IAM_AMP_QUERY_ARN  
else
    echo "$SERVICE_ACCOUNT_IAM_GRAFANA_ROLE_ARN IAM role for query already exists"
fi
echo $SERVICE_ACCOUNT_IAM_GRAFANA_ROLE_ARN

実行権限をつけて実行します。

chmod +x CreateIAMRole-AMG.sh
./CreateIAMRole-AMG.sh

IAMロールを作成したら、さきほどのカスタマーマネージドのロールに作成したIAMロールを指定します。

その後は特に選ぶものもなく次へを押しているだけでワークスペース作成できます。

AWS SSO

今回はAWS SSOをGrafanaの認証方法に選んだので、SSOの設定をします。
AMGをデプロイしたあとにAWS SSOを確認するとGrafanaがアプリケーションに追加されていることが確認できます。

また、AWS SSOに有効なユーザが登録されていることも確認しておきます。ADなどと連携させることも可能ですが、今回はAWS SSOに直接ユーザを作成して使います。

Grafanaでユーザ権限設定

デプロイ後、AWS SSOのユーザがGrafanaに割り当てられている状態にします。

Grafanaダッシュボードの変更をするには管理者に設定しておく必要があります。

Grafanaへアクセス

Grafana ワークスペース URLをクリックすると、AWS SSOの画面が出てくるので先程選択したユーザでログインするとGrafanaにリダイレクトされます。


Grafanaにログインできたら、サイドバーにAWSのマークがあるのでマウスオーバーして、Data Sourcesをクリックします。

そうしたら、ServiceのところでPrometheus、リージョンにAMPを作ったところを選ぶと…先程作ったAMPが出てくるので、選択してデータソースに加えます。この簡単さ、すごく便利ですよね。

次にダッシュボードを作ります。

今回は簡単化のため、コミュニティで作成されたダッシュボードを利用します。
サイドバーのプラスアイコン->importをクリックします。

ここでは11074と入力して、Loadします。

VictoriaMetricsに先程追加したPrometheusを選択し、Importします。

EKSの状態を可視化することができました。

他のダッシュボードは以下で探すことができますし、自分で定義することもできます。
https://grafana.com/grafana/dashboards

価格

AMG

月ごとにアクティブユーザ毎に課金される。
管理者ユーザ: 9$/Month/人
閲覧者ユーザ: 5$/Month/人

これとは別にEnterpriseライセンスもある。
https://docs.aws.amazon.com/grafana/latest/userguide/upgrade-to-Grafana-Enterprise.html

AMP

3種類課金がある。
メトリクスの数(サンプル数): ingest時に1回のみ課金
データの容量: store時に1回のみ課金
クエリの計算量: クエリを処理するのに使ったプロセッサの稼働時間(秒単位)に応じて課金

Metrics Ingested TiersCost ($/10,000 samples)
First 2 billion samples$0.002
Next 50 billion samples$0.0015
Over 52 billion samples$0.0010
Metric storage$0.03/GB
Query Processing Minute (QPM)$0.142/QPM

まとめ

今回はAMPとAMGを利用してEKSの状態をモニタリング+可視化してみました。
監視インフラをマネージドにでき、AWSサービスとの連携も簡単なので運用や構築の手間を減らすことができると思われます。

コメントする

メールアドレスが公開されることはありません。