EKS on FargateでPodのメトリクスをCloudWatchに収集する

Container InsightsはまだEKS on Fargateをサポートしていないが、Prometheusメトリクスモニタリングを使用してFargate上のPodのメトリクスを取得してみるメモ。

クラスターの作成

1.21でクラスターを作成する。ノードなしで作成する。

cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: container-insights-fargate
  region: ap-northeast-1
  version: "1.21"
vpc:
  cidr: "10.0.0.0/16"

availabilityZones:
  - ap-northeast-1a
  - ap-northeast-1c

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true

fargateProfiles:
  - name: fp-default
    selectors:
      - namespace: default
      - namespace: kube-system
      - namespace: amazon-cloudwatch
EOF
eksctl create cluster -f cluster.yaml

ロギング

Fargateでのマネージドなロギングを設定する。

まずNamespaceを作成する。

cat << EOF > aws-observability-namespace.yaml
kind: Namespace
apiVersion: v1
metadata:
  name: aws-observability
  labels:
    aws-observability: enabled
EOF
kubectl apply -f aws-observability-namespace.yaml

ConfigMapを作成する。

cat << EOF > aws-logging-cloudwatch-configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
  name: aws-logging
  namespace: aws-observability
  labels:
data:
  output.conf: |
    [OUTPUT]
        Name cloudwatch_logs
        Match   *
        region ap-northeast-1
        log_group_name fluent-bit-cloudwatch
        log_stream_prefix from-fluent-bit-
        auto_create_group true

  parsers.conf: |
    [PARSER]
        Name crio
        Format Regex
        Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>P|F) (?<log>.*)$
        Time_Key    time
        Time_Format %Y-%m-%dT%H:%M:%S.%L%z

  filters.conf: |
     [FILTER]
        Name parser
        Match *
        Key_name log
        Parser crio
        Reserve_Data On
        Preserve_Key On
EOF
kubectl apply -f aws-logging-cloudwatch-configmap.yaml

IAMポリシーをダウンロードする。

curl -o permissions.json https://raw.githubusercontent.com/aws-samples/amazon-eks-fluent-logging-examples/mainline/examples/fargate/cloudwatchlogs/permissions.json

IAMポリシーを作成する。

aws iam create-policy --policy-name eks-fargate-logging-policy --policy-document file://permissions.json

IAMポリシーをPod実行ロールにアタッチする。

aws iam list-roles | jq -r '.Roles[].RoleName | select( . | contains("FargatePodExecutionRole") )'
ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
aws iam attach-role-policy \
  --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/eks-fargate-logging-policy \
  --role-name eksctl-container-insights-FargatePodExecutionRole-1M61R5UHYEUL

サンプルPodをデプロイする。

cat << EOF > sample-app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hey-yo
  template:
    metadata:
      labels:
        app: hey-yo
    spec:
      containers:
        - name: hey-yo
          image: public.ecr.aws/toricls/everlasting-hey-yo:latest
EOF
kubectl apply -f sample-app-deployment.yaml

メトリクス

Container InsightsのPrometheusメトリクスのモニタリングを設定する。

IAMロールを作成する。

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

Fargateプロファイルは作成済みなのでスキップ。

Deploymentのマニフェストをダウンロードして、クラスター名とリージョン名を書き換える。

wget https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/service/cwagent-prometheus/prometheus-eks-fargate.yaml
sed -i -e "s/{{cluster_name}}/container-insights-fargate/;s/{{region_name}}/ap-northeast-1/" prometheus-eks-fargate.yaml

例のコメントにあるような設定を追加する。

cwagentconfig.jsonのmetric_declarationの下に以下を追加する。

                {
                  "source_labels": ["job"],
                  "label_matcher": "^kubernetes-nodes-cadvisor$",
                  "dimensions": [["ClusterName","namespace","pod","container"]],
                  "metric_selectors": [
                    "^container_cpu_usage_seconds_total$",
                    "^container_memory_usage_bytes$"
                  ]
                },
                {
                  "source_labels": ["job"],
                  "label_matcher": "^kubernetes-nodes-cadvisor$",
                  "dimensions": [["ClusterName","namespace","pod"]],
                  "metric_selectors": [
                    "^container_network_(receive|transmit)_bytes_total$"
                  ]
                },

prometheus.yamlのscrape_configsの下に下記を追加する。

    - job_name: kubernetes-nodes-cadvisor
      sample_limit: 10000
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - replacement: kubernetes.default.svc:443
        target_label: __address__
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
        target_label: __metrics_path__
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      metric_relabel_configs:
      - source_labels: [namespace]
        action: drop
        regex: ^(amazon-cloudwatch|kube-system)$
      - source_labels: [pod]
        regex: (.+)(-\w+-\w+)|(.+)(-\w+)|(.+)
        replacement: ${1}${3}${5}
        target_label: pod
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        insecure_skip_verify: true

Deploymentを作成する。

kubectl apply -f prometheus-eks-fargate.yaml

Podを確認する。

$ k get po -A
NAMESPACE           NAME                                  READY   STATUS    RESTARTS   AGE
amazon-cloudwatch   cwagent-prometheus-79db458586-lq9q6   1/1     Running   0          3m50s
default             sample-app-55947f9c75-bmddr           1/1     Running   0          26m
default             sample-app-55947f9c75-fqcgx           1/1     Running   0          26m
default             sample-app-55947f9c75-t88nm           1/1     Running   0          26m
kube-system         coredns-9f6f89c76-9ccp4               1/1     Running   0          51m
kube-system         coredns-9f6f89c76-lgkqs               1/1     Running   0          51m

CloudWatch Logsにログが収集できていることを確認する。

f:id:sotoiwa:20210820174321p:plain

EMFを含まないログが多いが、たまにEMFを含むログが送られてきている。

f:id:sotoiwa:20210820174344p:plain

こんな感じでEMFのログ取得できている。

{
    "CloudWatchMetrics": [
        {
            "Metrics": [
                {
                    "Name": "container_memory_usage_bytes"
                }
            ],
            "Dimensions": [
                [
                    "ClusterName",
                    "container",
                    "namespace",
                    "pod"
                ]
            ],
            "Namespace": "ContainerInsights/Prometheus"
        }
    ],
    "ClusterName": "container-insights-fargate",
    "Timestamp": "1629448193180",
    "Version": "0",
    "beta_kubernetes_io_arch": "amd64",
    "beta_kubernetes_io_os": "linux",
    "container": "hey-yo",
    "container_cpu_load_average_10s": 0,
    "container_file_descriptors": 7,
    "container_last_seen": 1629448195,
    "container_memory_cache": 0,
    "container_memory_mapped_file": 0,
    "container_memory_max_usage_bytes": 7573504,
    "container_memory_rss": 122880,
    "container_memory_swap": 0,
    "container_memory_usage_bytes": 1069056,
    "container_memory_working_set_bytes": 1069056,
    "container_processes": 2,
    "container_sockets": 0,
    "container_spec_cpu_period": 100000,
    "container_spec_cpu_shares": 2,
    "container_spec_memory_limit_bytes": 0,
    "container_spec_memory_reservation_limit_bytes": 0,
    "container_spec_memory_swap_limit_bytes": 0,
    "container_start_time_seconds": 1629446563,
    "container_threads": 2,
    "container_threads_max": 0,
    "eks_amazonaws_com_compute_type": "fargate",
    "failure_domain_beta_kubernetes_io_region": "ap-northeast-1",
    "failure_domain_beta_kubernetes_io_zone": "ap-northeast-1c",
    "id": "/kubepods/besteffort/podd568a774-833c-4bd0-bd94-9d9e5adaba4e/e078e206de637e5da80f357f49949739a5be1d10d758d381250b94017adf4860",
    "image": "public.ecr.aws/toricls/everlasting-hey-yo@sha256:00e3b7602c020a2a367dc0f75732c718a962898d80b02ca6ca3b30bce45c794c",
    "instance": "fargate-ip-10-0-126-158.ap-northeast-1.compute.internal",
    "job": "kubernetes-nodes-cadvisor",
    "kubernetes_io_arch": "amd64",
    "kubernetes_io_hostname": "ip-10-0-126-158.ap-northeast-1.compute.internal",
    "kubernetes_io_os": "linux",
    "name": "e078e206de637e5da80f357f49949739a5be1d10d758d381250b94017adf4860",
    "namespace": "default",
    "pod": "sample-app",
    "prom_metric_type": "gauge",
    "topology_kubernetes_io_region": "ap-northeast-1",
    "topology_kubernetes_io_zone": "ap-northeast-1c"
}

この方法では、Container InsightsのダッシュボードにPodのメトリクスが出るわけではない。今回のクラスターが選択肢に出てこない。

f:id:sotoiwa:20210820174407p:plain

メトリクスとしてはとれている。

f:id:sotoiwa:20210820174431p:plain

f:id:sotoiwa:20210820174445p:plain

f:id:sotoiwa:20210820174507p:plain

ただし、container_cpu_usage_seconds_totalとかなので、そのままでは使いにくい値になっているし、個別のPodやコンテナではなく集約された値になっているので、用途が限られそう。

追記

Prometheus メトリクスの変換についてユーザーガイドに記載があった。

カウンタータイプのメトリクスは、前回のスクレイプからのデルタが送信される仕様になっている。スクレイプ間隔は1分なので、container_cpu_usage_seconds_totalを60で割るとCPU使用率になると思われる。

Metric Mathで計算したところ、それっぽい値になっている。

f:id:sotoiwa:20210825190420p:plain

ここで表示しているのはnginxコンテナのメトリクスで、nginxのPodを起動して中でstressコマンドを実行して上限までCPUを使っている状態。topコマンドの結果とだいたい一致している。

$ k top pod -n default
NAME                          CPU(cores)   MEMORY(bytes)
nginx                         228m         51Mi
sample-app-55947f9c75-bmddr   1m           0Mi
sample-app-55947f9c75-fqcgx   1m           1Mi
sample-app-55947f9c75-t88nm   1m           1Mi