Amazon Web Services ブログ

Amazon Managed Service for Prometheus を使用してサービスメッシュコンテナ環境をモニタリングする

オブザーバビリティは、いかなるアプリケーションにとっても、システムの挙動や性能を理解する上で重要です。性能劣化や障害を検知して修復するには、多くの時間と労力を要します。これは、多くのマイクロサービスが動いていて、リクエストの処理がいくつかのサービスにまたがっているようなマルチテナント環境では、より一層困難となります。サービスメッシュは、マイクロサービス間のネットワーク通信を処理します。

そのような複雑な環境でトラブルシューティングをしていると、途方にくれてしまうこともあるでしょう。アプリケーションのトレースは性能の問題を特定するのに重要な役割を果たしますが、サービスメッシュのコンポーネントのメトリクスも重要です。Prometheus はオープンソースのシステムモニタリング・アラートのソリューションとして広く利用されています。これには、アラートを配信、グループ化、停止するためのアラートマネージャが含まれます。また、メトリクス名とキーバリューペアで識別される時系列データを持つデータモデルと、大量のラベルに渡ってメトリクスを集約する柔軟なクエリ言語を提供します。Prometheus が収集した運用メトリクスを即座にクエリし、相関付け、可視化するために、私たちは広くデプロイされている可視化ツール Grafana を使用します。

Prometheus と Grafana をデプロイすることは容易ですが、複数サーバーにスケールさせ、高可用性、スケーラブル、セキュアな環境を構築するには非常に多くのエンジニアリングの労力を要します。メモリやストレージなどのリソースを最適化し、コストを制御しクエリのレスポンスタイムを向上させるには、より多くの時間を費やさなければなりません。Prometheus サーバーのパッチ適用やアップグレードなど、環境を継続的に維持するのは大変です。Amazon Managed Service for Prometheus (AMP) と Amazon Managed Grafana (AMG) を利用することで、重要なリソースのモニタリング能力に影響を与えることなく、オンデマンドでスケール可能な Prometheus サービスを運用できます。

AMP はコンテナのインフラストラクチャとアプリケーションメトリクスのための Prometheus 互換のモニタリングサービスで、大規模なコンテナ環境を簡単かつ安全にモニタリングできます。AMP を用いることで、ワークロードの増減に応じて運用メトリクスの挿入、保存、クエリを自動でスケールできます。AMG は大規模な運用データの可視化と分析を容易にするフルマネージド型サービスです。

Envoy メトリクス

組織は Amazon Elastic Kubernetes Service (Amazon EKS) や Amazon Elastic Container Service (Amazon ECS) などのオーケストレーションエンジンを利用して、マイクロサービスの活用範囲を拡大しています。このような組織では、異なるチームが所有するアプリケーションが異なる AWS アカウント、異なる VPC 内で実行されることが多く、アプリケーション間の一貫した接続性、オブザーバビリティ、セキュリティを実現するために AWS App Mesh などのサービスメッシュに依存しています。App Mesh は Amazon ECS や Amazon EKS、Amazon Elastic Compute Cloud (Amazon EC2) インスタンス、EC2 上で自己管理される Kubernetes で利用可能なマネージド型のサービスメッシュです。App Mesh は、異なるコンピュートプラットフォーム上で動くアプリケーションを共通のメッシュに容易に接続可能にします。App Mesh は Envoy プロキシを利用してアプリケーション間通信を標準化することで 、エンドツーエンドの可視性を提供し、高可用性の実現に役立ちます。AMP と AMG は、異なるコンピュートプラットフォーム上で動く組織全体に渡るコンテナからメトリクスを取り込む、信頼できる唯一の情報源として動作しますが、Envoy メトリクスを収集することも重要です。Envoy は異なるアプリケーション間のトラフィックのルーティングに役立つ、App Mesh の必要不可欠な要素です。

本ブログ記事では、マイクロサービスの健全性や性能をモニタリングするために、Amazon EKS クラスターから AMP に App Mesh Envoy メトリクスを取り込み、AMG 上でカスタムダッシュボードを作成する手順を紹介します。

このソリューションを実装する中で、私たちは

  • AMP ワークスペースを作成します
  • App Mesh Controller for Kubernetes をインストールし、Pod 内に Envoy コンテナを取り込みます
  • EKS クラスター内に設定した Grafana エージェントを利用して Envoy メトリクスを収集し、AMP に書き込みます
  • AMG ワークスペースを作成し、AMP をデータソースに設定します
  • AMG 上に Grafana ダッシュボードを作成、設定します

前提条件

本ブログ記事の手順を完了するためには、以下が必要です。

  • AWS CLI version 2
  • eksctl
  • kubectl
  • jq
  • helm
  • AWS アカウント内に設定された AMP ワークスペース。手順に関しては、AMP ユーザーガイド内の Create a workspace を参照してください。
  • AWS Single Sign-On (AWS SSO)。手順に関しては、AWS SSO ユーザーガイド内の Enable AWS SSO を参照してください。

EKS クラスターを作成する

まず、サンプルアプリケーションを動かすために、App Mesh を有効にする EKS クラスターを作成します。eks-cluster-config.yaml を使用して、eksctl CLI ツールでクラスターをデプロイします。

export AMP_EKS_CLUSTER=AMP-EKS-CLUSTER
export AMP_ACCOUNT_ID=<Your Account id>
export AWS_REGION=<Your Region>


cat << EOF > eks-cluster-config.yaml
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: $AMP_EKS_CLUSTER
  region: $AWS_REGION
  version: '1.18'
iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: appmesh-controller
      namespace: appmesh-system
      labels: {aws-usage: "application"}
    attachPolicyARNs:
    - "arn:aws:iam::aws:policy/AWSAppMeshFullAccess"
managedNodeGroups:
- name: default-ng
  minSize: 1
  maxSize: 3
  desiredCapacity: 2
  labels: {role: mngworker}
  iam:
    withAddonPolicies:
      certManager: true
      cloudWatch: true
      appMesh: true
cloudWatch:
  clusterLogging:
    enableTypes: ["*"]
EOF

以下のコマンドを実行して、EKS クラスターを作成します。

eksctl create cluster -f eks-cluster-config.yaml

このコマンドは AMP-EKS-CLUSTER という名前の EKS クラスターと、App Mesh controller for Kubernetes によって使われる appmesh-controller という名前のサービスアカウントを作成します。

次に、以下のコマンドを実行して App Mesh controller をインストールします。

まず、カスタムリソース定義 (CRD) を設定します。

helm repo add eks https://aws.github.io/eks-charts
helm upgrade -i appmesh-controller eks/appmesh-controller \
--namespace appmesh-system \
--set region=${AWS_REGION} \
--set serviceAccount.create=false \
--set serviceAccount.name=appmesh-controller

AMP ワークスペースを作成する

AMP ワークスペースは、Envoy から収集された Prometheus メトリクスを取り込むのに利用されます。ワークスペースは、メトリクスなどの Prometheus リソース専用の、論理的かつ隔離された Prometheus サーバーです。ワークスペースは、更新、リスト、記述、削除などの管理アクティビティや、メトリクスの取り込みやクエリの認可のための、きめ細やかなアクセスコントロールをサポートします。

aws amp create-workspace --alias AMP-APPMESH --region $AWS_REGION

次に、任意の手順として、VPC 内にデプロイされたリソースからマネージド型サービスに安全にアクセスするためのインターフェース VPC エンドポイントを作成します。AMP のパブリックエンドポイントを利用することも可能です。この VPC エンドポイントを利用することで、マネージド型サービスに取り込まれるデータを、インターネットを経由せずに AWS ネットワーク内で保護することが可能です。ここに示すように、AWS CLI を用いて作成できます。VPC_IDAWS_REGION、その他の値を自身の値に置き換えてください。

export VPC_ID=<Your EKS Cluster VPC Id>

aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.<$AWS_REGION>.aps-workspaces \
--security-group-ids <SECURITY_GROUP_IDS> \
--vpc-endpoint-type Interface \
--subnet-ids <SUBNET_IDS>

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add kube-state-metrics https://kubernetes.github.io/kube-state-metrics

メトリクスをスクレイピングする

AMP は、Kubernetes クラスター内のコンテナ化されたワークロードから運用メトリクスを直接スクレイピングする訳ではありません。このタスクを実行するためには、Prometheus サーバー、AWS Distro for OpenTelemetry Collector などの OpenTelemetry エージェント、Grafana エージェントのいずれかをデプロイ、管理する必要があります。本ブログ記事では、Grafana エージェントを設定して、Envoy メトリクスを収集し、AMP と AMG を用いて分析する手順を紹介します。

AMP 用の Grafana エージェントを設定する

Grafana エージェントは、Prometheus サーバー全体を動かす代替手段であり、軽量です。なぜなら、Prometheus Exporter を検出、スクレイピングして、メトリクスを Prometheus 互換のバックエンド (この場合は AMP) に送信するのに必要な部分を残し、ストレージやクエリ、アラートエンジンを取り除いているからです。Grafana エージェントは Prometheus メトリクスと 100% 互換であり、Prometheus サービスディスカバリ、スクレイピング、write-ahead ログ、リモート書き込みメカニズムを、Prometheus プロジェクトから利用しています。また Grafana エージェントは、Grafana エージェントの Pod と同じノードで実行されているメトリクスのみを収集することで、Amazon EKS クラスター内のすべてのノードにまたがる基本的なシャーディングをサポートします。これにより、すべての Prometheus メトリクスを収集する 1 つの巨大なマシンと、複数の手動管理の Prometheus によるシャーディングのうち、どちらか一方を選択する必要がなくなります。また Grafana エージェントは、AWS Identity and Access Management (IAM)認証のための AWS 署名バージョン 4 をネイティブにサポートするため、AWS 署名バージョン 4 プロキシをサイドカーで実行する必要がなくなり、複雑さやメモリ、CPU の需要を削減します。

本ブログ記事では、AMP に Prometheus メトリクスを送るための IAM ロールを設定する手順を紹介します。EKS クラスターに Grafana エージェントをインストールし、メトリクスを AMP に転送します。

権限を設定する

Grafana エージェントは Amazon EKS クラスターで動くコンテナ化されたワークロードの運用メトリクスをスクレイピングし、長期的な保存と Grafana などのモニタリングツールによるクエリのために AMP に送信します。AMP に送信されるデータは、マネージド型サービスに対するクライアントリクエストごとに認証・認可を行うため、AWS 署名バージョン 4 アルゴリズムを用いた有効な認証情報で署名しなければなりません。

Grafana エージェントは、Amazon EKS クラスターにデプロイすることで Kubernetes サービスアカウントのアイデンティティで実行できます。サービスアカウントの IAM ロール (IRSA) を利用することで、Kubernetes サービスアカウントと IAM ロールを関連付けることができ、そのサービスアカウントを使用したすべての Pod に IAM 権限を付与できます。Prometheus メトリクスを AMP に取り込むための AWS 署名バージョン 4 を含む Grafana エージェントを IRSA を利用して安全に設定することで、最小権限の原則に従うことができます。

agent-permissions-aks シェルスクリプトを利用して以下のアクションを実行できます。YOUR_EKS_CLUSTER_NAME を自身の Amazon EKS クラスターの名前に置き換えてください。

  • AMP ワークスペースへのリモート書き込み権限を持つ IAM ポリシーがアタッチされた、EKS-GrafanaAgent-AMP-ServiceAccount-Role という名前の IAM ロールを作成します
  • grafana-agent 名前空間に、その IAM ロールに関連づけられた grafana-agent という名前の Kubernetes サービスアカウントを作成します
  • IAM ロールと Amazon EKS クラスター内にホストされた OIDC プロバイダーの間に信頼関係を構築します

このスクリプトを実行するためには、kubectl と eksctl CLI ツールが必要です。これらは Amazon EKS クラスターにアクセスするために利用されます。

kubectl create namespace grafana-agent
export WORKSPACE=$(aws amp list-workspaces | jq -r '.workspaces[] | select(.alias=="AMP-APPMESH").workspaceId')
export ROLE_ARN=$(aws iam get-role --role-name EKS-GrafanaAgent-AMP-ServiceAccount-Role --query Role.Arn --output text)
export REGION=$AWS_REGION
export NAMESPACE="grafana-agent"
export REMOTE_WRITE_URL="https://aps-workspaces.$REGION.amazonaws.com/workspaces/$WORKSPACE/api/v1/remote_write"

そして、grafana-agent.yaml というマニフェストファイルを作成し、Envoy メトリクスを抽出するためのスクレイピングに関する設定を行い、Grafana エージェントをデプロイします。この例では、grafana-agent という名前の DaemonSet と grafana-agent-deployment という名前の (レプリカを 1 つ持つ) Deployment をデプロイします。grafana-agent DaemonSet は、クラスター上の Pod からメトリクスを収集します。grafana-agent-deployment は、Amazon EKS コントロールプレーンなどのクラスター上で動作していないサービスからメトリクスを収集します。本記事執筆時点 (2021年4月20日) では、Fargate データプレーンは DaemonSet をサポートしていないため、このソリューションは動作しません。

cat > grafana-agent.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: ${ROLE_ARN}
  name: grafana-agent
  namespace: ${NAMESPACE}
---
apiVersion: v1
data:
  agent.yml: |
    prometheus:
        configs:
          - host_filter: true
            name: agent
            remote_write:
              - sigv4:
                    enabled: true
                    region: ${REGION}
                url: ${REMOTE_WRITE_URL}
            scrape_configs:
              - job_name: 'appmesh-envoy'
                metrics_path: /stats/prometheus
                kubernetes_sd_configs:
                - role: pod
                relabel_configs:
                - source_labels: [__meta_kubernetes_pod_container_name]
                  action: keep
                  regex: '^envoy$'
                - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
                  action: replace
                  regex: ([^:]+)(?::\d+)?;(\d+)
                  replacement: \${1}:9901
                  target_label: __address__
                - action: labelmap
                  regex: __meta_kubernetes_pod_label_(.+)
                - source_labels: [__meta_kubernetes_namespace]
                  action: replace
                  target_label: namespace
                - source_labels: ['app']
                  action: replace
                  target_label: service      
                - source_labels: [__meta_kubernetes_pod_name]
                  action: replace
                  target_label: kubernetes_pod_name          
              - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                job_name: kubernetes-pods
                kubernetes_sd_configs:
                  - role: pod
                relabel_configs:
                  - action: drop
                    regex: "false"
                    source_labels:
                      - __meta_kubernetes_pod_annotation_prometheus_io_scrape
                  - action: keep
                    regex: .*-metrics
                    source_labels:
                      - __meta_kubernetes_pod_container_port_name
                  - action: replace
                    regex: (https?)
                    replacement: \$1
                    source_labels:
                      - __meta_kubernetes_pod_annotation_prometheus_io_scheme
                    target_label: __scheme__
                  - action: replace
                    regex: (.+)
                    replacement: \$1
                    source_labels:
                      - __meta_kubernetes_pod_annotation_prometheus_io_path
                    target_label: __metrics_path__
                  - action: replace
                    regex: (.+?)(\:\d+)?;(\d+)
                    replacement: \$1:\$3
                    source_labels:
                      - __address__
                      - __meta_kubernetes_pod_annotation_prometheus_io_port
                    target_label: __address__
                  - action: drop
                    regex: ""
                    source_labels:
                      - __meta_kubernetes_pod_label_name
                  - action: replace
                    replacement: \$1
                    separator: /
                    source_labels:
                      - __meta_kubernetes_namespace
                      - __meta_kubernetes_pod_label_name
                    target_label: job
                  - action: replace
                    source_labels:
                      - __meta_kubernetes_namespace
                    target_label: namespace
                  - action: replace
                    source_labels:
                      - __meta_kubernetes_pod_name
                    target_label: pod
                  - action: replace
                    source_labels:
                      - __meta_kubernetes_pod_container_name
                    target_label: container
                  - action: replace
                    separator: ':'
                    source_labels:
                      - __meta_kubernetes_pod_name
                      - __meta_kubernetes_pod_container_name
                      - __meta_kubernetes_pod_container_port_name
                    target_label: instance
                  - action: labelmap
                    regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+)
                    replacement: __param_\$1
                  - action: drop
                    regex: Succeeded|Failed
                    source_labels:
                      - __meta_kubernetes_pod_phase
                tls_config:
                    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                    insecure_skip_verify: false
              - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                job_name: ${NAMESPACE}/kube-state-metrics
                kubernetes_sd_configs:
                  - namespaces:
                        names:
                          - ${NAMESPACE}
                    role: pod
                relabel_configs:
                  - action: keep
                    regex: kube-state-metrics
                    source_labels:
                      - __meta_kubernetes_pod_label_name
                  - action: replace
                    separator: ':'
                    source_labels:
                      - __meta_kubernetes_pod_name
                      - __meta_kubernetes_pod_container_name
                      - __meta_kubernetes_pod_container_port_name
                    target_label: instance
                tls_config:
                    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                    insecure_skip_verify: false
              - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                job_name: ${NAMESPACE}/node-exporter
                kubernetes_sd_configs:
                  - namespaces:
                        names:
                          - ${NAMESPACE}
                    role: pod
                relabel_configs:
                  - action: keep
                    regex: node-exporter
                    source_labels:
                      - __meta_kubernetes_pod_label_name
                  - action: replace
                    source_labels:
                      - __meta_kubernetes_pod_node_name
                    target_label: instance
                  - action: replace
                    source_labels:
                      - __meta_kubernetes_namespace
                    target_label: namespace
                tls_config:
                    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                    insecure_skip_verify: false
              - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                job_name: kube-system/kubelet
                kubernetes_sd_configs:
                  - role: node
                relabel_configs:
                  - replacement: kubernetes.default.svc.cluster.local:443
                    target_label: __address__
                  - replacement: https
                    target_label: __scheme__
                  - regex: (.+)
                    replacement: /api/v1/nodes/\${1}/proxy/metrics
                    source_labels:
                      - __meta_kubernetes_node_name
                    target_label: __metrics_path__
                tls_config:
                    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                    insecure_skip_verify: false
              - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                job_name: kube-system/cadvisor
                kubernetes_sd_configs:
                  - role: node
                metric_relabel_configs:
                  - action: drop
                    regex: container_([a-z_]+);
                    source_labels:
                      - __name__
                      - image
                  - action: drop
                    regex: container_(network_tcp_usage_total|network_udp_usage_total|tasks_state|cpu_load_average_10s)
                    source_labels:
                      - __name__
                relabel_configs:
                  - replacement: kubernetes.default.svc.cluster.local:443
                    target_label: __address__
                  - regex: (.+)
                    replacement: /api/v1/nodes/\${1}/proxy/metrics/cadvisor
                    source_labels:
                      - __meta_kubernetes_node_name
                    target_label: __metrics_path__
                scheme: https
                tls_config:
                    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                    insecure_skip_verify: false
        global:
            scrape_interval: 15s
        wal_directory: /var/lib/agent/data
    server:
        log_level: info
kind: ConfigMap
metadata:
  name: grafana-agent
  namespace: ${NAMESPACE}
---
apiVersion: v1
data:
  agent.yml: |
    prometheus:
        configs:
          - host_filter: false
            name: agent
            remote_write:
              - sigv4:
                    enabled: true
                    region: ${REGION}
                url: ${REMOTE_WRITE_URL}
            scrape_configs:
              - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                job_name: default/kubernetes
                kubernetes_sd_configs:
                  - role: endpoints
                metric_relabel_configs:
                  - action: drop
                    regex: apiserver_admission_controller_admission_latencies_seconds_.*
                    source_labels:
                      - __name__
                  - action: drop
                    regex: apiserver_admission_step_admission_latencies_seconds_.*
                    source_labels:
                      - __name__
                relabel_configs:
                  - action: keep
                    regex: apiserver
                    source_labels:
                      - __meta_kubernetes_service_label_component
                scheme: https
                tls_config:
                    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                    insecure_skip_verify: false
                    server_name: kubernetes
        global:
            scrape_interval: 15s
        wal_directory: /var/lib/agent/data
    server:
        log_level: info
kind: ConfigMap
metadata:
  name: grafana-agent-deployment
  namespace: ${NAMESPACE}
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: grafana-agent
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs:
  - get
  - list
  - watch
- nonResourceURLs:
  - /metrics
  verbs:
  - get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: grafana-agent
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: grafana-agent
subjects:
- kind: ServiceAccount
  name: grafana-agent
  namespace: ${NAMESPACE}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: grafana-agent
  namespace: ${NAMESPACE}
spec:
  minReadySeconds: 10
  selector:
    matchLabels:
      name: grafana-agent
  template:
    metadata:
      labels:
        name: grafana-agent
    spec:
      containers:
      - args:
        - -config.file=/etc/agent/agent.yml
        - -prometheus.wal-directory=/tmp/agent/data
        command:
        - /bin/agent
        env:
        - name: HOSTNAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        image: grafana/agent:v0.11.0
        imagePullPolicy: IfNotPresent
        name: agent
        ports:
        - containerPort: 80
          name: http-metrics
        securityContext:
          privileged: true
          runAsUser: 0
        volumeMounts:
        - mountPath: /etc/agent
          name: grafana-agent
      serviceAccount: grafana-agent
      tolerations:
      - effect: NoSchedule
        operator: Exists
      volumes:
      - configMap:
          name: grafana-agent
        name: grafana-agent
  updateStrategy:
    type: RollingUpdate
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana-agent-deployment
  namespace: ${NAMESPACE}
spec:
  minReadySeconds: 10
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      name: grafana-agent-deployment
  template:
    metadata:
      labels:
        name: grafana-agent-deployment
    spec:
      containers:
      - args:
        - -config.file=/etc/agent/agent.yml
        - -prometheus.wal-directory=/tmp/agent/data
        command:
        - /bin/agent
        env:
        - name: HOSTNAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        image: grafana/agent:v0.11.0
        imagePullPolicy: IfNotPresent
        name: agent
        ports:
        - containerPort: 80
          name: http-metrics
        securityContext:
          privileged: true
          runAsUser: 0
        volumeMounts:
        - mountPath: /etc/agent
          name: grafana-agent-deployment
      serviceAccount: grafana-agent
      volumes:
      - configMap:
          name: grafana-agent-deployment
        name: grafana-agent-deployment
EOF

kubectl apply -f grafana-agent.yaml

grafana-agent がデプロイされると、メトリクスを収集して指定された AMP ワークスペースに取り込みます。さて、Amazon EKS クラスター上にサンプルアプリケーションをデプロイし、メトリクスの分析を始めましょう。

サンプルアプリケーションを EKS クラスター上にデプロイする

アプリケーションをインストールして Envoy コンテナを挿入するには、先ほど作成した App Mesh Controller for Kubernetes を利用します。AWS App Mesh Controller for K8s は、Kubernetes クラスター内の App Mesh リソースを管理します。コントローラーには CRD が付属しており、Deployment や Service などのネイティブな Kubernetes オブジェクトを定義するのと全く同じように、Kubernetes API を用いてメッシュや仮想ノードなどの App Mesh コンポーネントを定義できます。これらのカスタムリソースは、コントローラーが管理する App Mesh API オブジェクトにマッピングされます。コントローラーは、これらのカスタムリソースの変化をモニタリングし、App Mesh API に反映します。

## ベースとなるアプリケーションをインストールする
git clone https://github.com/aws/aws-app-mesh-examples.git
kubectl apply -f aws-app-mesh-examples/examples/apps/djapp/1_base_application
kubectl get all -n prod      ## Pod のステータスが Running であることを確認

NAME                            READY   STATUS    RESTARTS   AGE
pod/dj-cb77484d7-gx9vk          1/1     Running   0          6m8s
pod/jazz-v1-6b6b6dd4fc-xxj9s    1/1     Running   0          6m8s
pod/metal-v1-584b9ccd88-kj7kf   1/1     Running   0          6m8s




## App Mesh controller をインストールし、Deployment をメッシュ化する

kubectl apply -f aws-app-mesh-examples/examples/apps/djapp/2_meshed_application/
kubectl rollout restart deployment -n prod dj jazz-v1 metal-v1
kubectl get all -n prod     ## Pod ごとに 2 つずつコンテナが見えていることを確認
 
NAME                        READY   STATUS    RESTARTS   AGE
dj-7948b69dff-z6djf         2/2     Running   0          57s
jazz-v1-7cdc4fc4fc-wzc5d    2/2     Running   0          57s
metal-v1-7f499bb988-qtx7k   2/2     Running   0          57s


## この後 Grafana で可視化するために、5 分間トラフィックを発生させる
dj_pod=`kubectl get pod -n prod --no-headers -l app=dj -o jsonpath='{.items[*].metadata.name}'`

loop_counter=0
while [ $loop_counter -le 300 ] ; do kubectl exec -n prod -it $dj_pod  -c dj  -- curl jazz.prod.svc.cluster.local:9080 ; echo ; loop_counter=$[$loop_counter+1] ; done

AMG ワークスペースを作成する

AMG ワークスペースの作成は簡単です。Amazon Managed Service for Grafana を始めようのブログ記事の手順に従ってください。ダッシュボードへのユーザーアクセスを付与するためには、AWS SSO を有効化する必要があります。ワークスペースを作成したら、Grafana ワークスペースへのアクセスを個人ユーザーやユーザーグループに割り当てることが可能です。デフォルトでは、ユーザーは閲覧者のユーザータイプになっています。ユーザーの役割に応じてユーザータイプを変更してください。AMP ワークスペースをデータソースに追加して、ダッシュボードの作成を開始しましょう。 この例では、ユーザの名前は grafana-admin でユーザータイプは管理者です。必要なデータソースを選択し、設定を確認し、Create workspace を選択してください。

データソースとカスタムダッシュボードを設定する

AMP をデータソースに設定するには、Data sources のセクションで Configure in Grafana を選択し、ブラウザ上で Grafana ワークスペースを起動してください。Grafana ワークスペースの URL をブラウザに入力することでも起動できます。Amazon Managed Service for Grafana を始めようのブログ記事の指示に従い、データソースに先ほど作成した AMP ワークスペースを指定してください。

データソースを設定した後は、カスタムダッシュボードをインポートし Envoy メトリクスを分析します。(下図に示すように) Import を選択し、ID 11022 を入力してください。Envoy Global ダッシュボードがインポートされ、Envoy メトリクスの分析を開始できます。

スクリーンショットから見て取れるように、ダウンストリームのレイテンシー、接続数、レスポンスコードなどの Envoy メトリクスを可視化できます。フィルターを使って、特定の Envoy メトリクスに掘り下げることもできます。

AMG でアラートを設定する

メトリクスが閾値を上回ったときに Grafana アラートを設定することができます。AMG では、アラートがダッシュボード内でどのくらいの頻度で評価され、通知するかを設定できます。現在、AMG は Amazon SNS、Opsgenie、Slack、PagerDuty、VictorOp の通知タイプをサポートします。アラートルールを作成する前に、通知チャンネルを作成する必要があります。 この例では、通知チャンネルとして Amazon SNS を設定します。トピックへの通知を正しく発行するためには、SNS トピック名は grafana で始まる必要があります。 以下のコマンドを使って grafana-notification という名前の SNS トピックを作成し、email アドレスをサブスクライブします。

aws sns create-topic --name grafana-notification

aws sns subscribe --topic-arn arn:aws:sns:<region>:<account-id>:grafana-notification --protocol email --notification-endpoint <email-id>

Grafana ダッシュボードから新しい通知チャンネルを追加します。

grafana-notification という名前の新しい通知チャンネルを設定します。Type には、ドロップダウンから AWS SNS を選択します。Topic には、今作成した SNS トピックの ARN を使用します。Auth Provider には、AWS SDK Default を選択します。

そして、1 分間でダウンストリームのレイテンシーが 5 ミリ秒を上回った場合にアラートを設定します。ダッシュボード内のドロップダウンから Downstream latency を選択し、Edit を選択します。グラフパネルの Alert タブで、どのくらいの頻度でアラートルールが評価されるべきか、そしてアラートが状態を変更し通知を作成するための条件を設定します。

以下の設定では、ダウンストリームのレイテンシーが閾値を上回った場合にアラートが作成され、grafana-alert-notification チャンネルから SNS トピックに通知が送信されます。

まとめ

本ブログ記事では、EKS 上で動くアプリケーションをデプロイし、アプリケーションを App Mesh と統合し、Grafana エージェントを利用して Prometheus メトリクスをスクレイピングし、AMP に送信し、AMG で可視化しました。AMP と AMG はフルマネージド型かつサーバレスのモニタリングサービスなので、モニタリング環境の管理を AWS に任せて、ビジネスを変革するアプリケーションに時間を費やすことができます。AMP は、Prometheus サーバーや ADOT、Grafana エージェントなどのいかなる Prometheus 互換のコレクターと併せて利用することで、Amazon ECS や AWS 上で自己管理される Kubernetes、オンプレミスのインフラストラクチャなど、他のコンテナ環境からもメトリクスを収集できます。

参考文献

翻訳はソリューションアーキテクト渡辺が担当しました。原文はこちらです。