kube-state-metrics を Container Insights に送る

kube-state-metrics を Container Insights に送る方法を確認したメモ。

クラスターの作成

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

CLUSTER_NAME="mycluster"
cat << EOF > cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1
  version: "1.24"
vpc:
  cidr: "10.0.0.0/16"

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

cloudWatch:
  clusterLogging:
    enableTypes: ["*"]

iam:
  withOIDC: true
EOF
eksctl create cluster -f cluster.yaml

ノードを作成する。

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

metadata:
  name: ${CLUSTER_NAME}
  region: ap-northeast-1

managedNodeGroups:
  - name: m1
    minSize: 2
    maxSize: 2
    desiredCapacity: 2
    privateNetworking: true
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EOF
eksctl create nodegroup -f m1.yaml

Adminロールにも権限をつけておく。

CLUSTER_NAME="mycluster"
USER_NAME="Admin:{{SessionName}}"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/Admin"
eksctl create iamidentitymapping --cluster ${CLUSTER_NAME} --arn ${ROLE_ARN} --username ${USER_NAME} --group system:masters

Prometheus のデプロイ

helmefile.yaml に以下を追加する。

repositories:
- name: prometheus-community
  url: https://prometheus-community.github.io/helm-charts

releases:
- name: prometheus
  namespace: prometheus
  createNamespace: true
  chart: prometheus-community/prometheus
  version: 23.0.0
  values:
  - ./prometheus/values.yaml

prometheus/values.yaml を作成する。

# https://github.com/prometheus-community/helm-charts/blob/main/charts/prometheus/values.yaml
server:
  persistentVolume:
    enabled: false
alertmanager:
  enabled: false
kube-state-metrics:
  enabled: true
prometheus-node-exporter:
  enabled: true
prometheus-pushgateway:
  enabled: false

デプロイする。

helmfile apply

Container Insights のログ収集のデプロイ

Fluent Bit 用の IAM ロールを作成する。

eksctl create iamserviceaccount \
  --cluster=${CLUSTER_NAME} \
  --namespace=amazon-cloudwatch \
  --name=fluent-bit \
  --attach-policy-arn=arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
  --approve \
  --role-only

helmefile.yaml に以下を追加する。

releases:
- name: container-insights
  chart: ./container_insights
  namespace: amazon-cloudwatch
  createNamespace: true

マニフェストをダウンロードして container_insights フォルダに配置する。

curl -O https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/fluent-bit/fluent-bit.yaml

ConfigMap のマニフェストを作成する。

ClusterName=${CLUSTER_NAME}
RegionName=ap-northeast-1
FluentBitHttpPort='2020'
FluentBitReadFromHead='Off'
[[ ${FluentBitReadFromHead} = 'On' ]] && FluentBitReadFromTail='Off'|| FluentBitReadFromTail='On'
[[ -z ${FluentBitHttpPort} ]] && FluentBitHttpServer='Off' || FluentBitHttpServer='On'
kubectl create configmap fluent-bit-cluster-info --dry-run=client -o yaml \
--from-literal=cluster.name=${ClusterName} \
--from-literal=http.server=${FluentBitHttpServer} \
--from-literal=http.port=${FluentBitHttpPort} \
--from-literal=read.head=${FluentBitReadFromHead} \
--from-literal=read.tail=${FluentBitReadFromTail} \
--from-literal=logs.region=${RegionName} -n amazon-cloudwatch > fluent-bit-cluster-info.yaml

fluent-bit.yaml の IRSA 用のアノテーションを修正する。

eksctl get iamserviceaccount --cluster ${CLUSTER_NAME}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: amazon-cloudwatch
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-mycluster-addon-iamserviceaccount-ama-Role1-1OSUU3XOAFAJZ

デプロイする。

helmfile apply

Container Insights のインフラメトリクス収集のデプロイ

ADOT Collector 用の IAM ロールを作成する。

k create ns aws-otel-eks
eksctl create iamserviceaccount \
  --cluster=${CLUSTER_NAME} \
  --namespace=aws-otel-eks \
  --name=aws-otel-sa \
  --attach-policy-arn=arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
  --approve \
  --role-only

helmefile.yaml に以下を追加する。

releases:
- name: adot-collector
  chart: ./adot_collector
  namespace: aws-otel-eks
  createNamespace: true

マニフェストをダウンロードして adot_collector フォルダに配置する。

curl -O https://raw.githubusercontent.com/aws-observability/aws-otel-collector/main/deployment-template/eks/otel-container-insights-infra.yaml

Namespace は作成済みなので削除する。

IRSA のアノテーションを付与する。

---
# create cwagent service account and role binding
apiVersion: v1
kind: ServiceAccount
metadata:
  name: aws-otel-sa
  namespace: aws-otel-eks
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-mycluster-addon-iamserviceaccount-aws-Role1-1SKD8JAEOLGKW

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: aoc-agent-role
rules:
  - apiGroups: [""]
    resources: ["pods", "nodes", "endpoints"]
    verbs: ["list", "watch", "get"]
  - apiGroups: ["apps"]
    resources: ["replicasets"]
    verbs: ["list", "watch", "get"]
  - apiGroups: ["batch"]
    resources: ["jobs"]
    verbs: ["list", "watch"]
  - apiGroups: [""]
    resources: ["nodes/proxy"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["nodes/stats", "configmaps", "events"]
    verbs: ["create", "get"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["update"]
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["otel-container-insight-clusterleader"]
    verbs: ["get","update", "create"]
  - apiGroups: ["coordination.k8s.io"]
    resources: ["leases"]
    verbs: ["create","get", "update"]
  - apiGroups: ["coordination.k8s.io"]
    resources: ["leases"]
    resourceNames: ["otel-container-insight-clusterleader"]
    verbs: ["get","update", "create"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: aoc-agent-role-binding
subjects:
  - kind: ServiceAccount
    name: aws-otel-sa
    namespace: aws-otel-eks
roleRef:
  kind: ClusterRole
  name: aoc-agent-role
  apiGroup: rbac.authorization.k8s.io

---
# create Daemonset
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: aws-otel-eks-ci
  namespace: aws-otel-eks
spec:
  selector:
    matchLabels:
      name: aws-otel-eks-ci
  template:
    metadata:
      labels:
        name: aws-otel-eks-ci
    spec:
      containers:
        - name: aws-otel-collector
          image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          env:
            - name: K8S_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: HOST_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: HOST_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: K8S_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          imagePullPolicy: Always
          command:
            - "/awscollector"
            - "--config=/conf/otel-agent-config.yaml"
          volumeMounts:
            - name: rootfs
              mountPath: /rootfs
              readOnly: true
            - name: dockersock
              mountPath: /var/run/docker.sock
              readOnly: true
            - name: containerdsock
              mountPath: /run/containerd/containerd.sock
            - name: varlibdocker
              mountPath: /var/lib/docker
              readOnly: true
            - name: sys
              mountPath: /sys
              readOnly: true
            - name: devdisk
              mountPath: /dev/disk
              readOnly: true
            - name: otel-agent-config-vol
              mountPath: /conf
          resources:
            limits:
              cpu:  200m
              memory: 200Mi
            requests:
              cpu: 200m
              memory: 200Mi
      volumes:
        - configMap:
            name: otel-agent-conf
            items:
              - key: otel-agent-config
                path: otel-agent-config.yaml
          name: otel-agent-config-vol
        - name: rootfs
          hostPath:
            path: /
        - name: dockersock
          hostPath:
            path: /var/run/docker.sock
        - name: varlibdocker
          hostPath:
            path: /var/lib/docker
        - name: containerdsock
          hostPath:
            path: /run/containerd/containerd.sock
        - name: sys
          hostPath:
            path: /sys
        - name: devdisk
          hostPath:
            path: /dev/disk/
      serviceAccountName: aws-otel-sa

ConfigMap は別ファイルに切り出す。

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-agent-conf
  namespace: aws-otel-eks
  labels:
    app: opentelemetry
    component: otel-agent-conf
data:
  otel-agent-config: |
    extensions:
      health_check:

    receivers:
      awscontainerinsightreceiver:

    processors:
      batch/metrics:
        timeout: 60s

    exporters:
      awsemf:
        namespace: ContainerInsights
        log_group_name: '/aws/containerinsights/{ClusterName}/performance'
        log_stream_name: '{NodeName}'
        resource_to_telemetry_conversion:
          enabled: true
        dimension_rollup_option: NoDimensionRollup
        parse_json_encoded_attr_values: [Sources, kubernetes]
        metric_declarations:
          # node metrics
          - dimensions: [[NodeName, InstanceId, ClusterName]]
            metric_name_selectors:
              - node_cpu_utilization
              - node_memory_utilization
              - node_network_total_bytes
              - node_cpu_reserved_capacity
              - node_memory_reserved_capacity
              - node_number_of_running_pods
              - node_number_of_running_containers
          - dimensions: [[ClusterName]]
            metric_name_selectors:
              - node_cpu_utilization
              - node_memory_utilization
              - node_network_total_bytes
              - node_cpu_reserved_capacity
              - node_memory_reserved_capacity
              - node_number_of_running_pods
              - node_number_of_running_containers
              - node_cpu_usage_total
              - node_cpu_limit
              - node_memory_working_set
              - node_memory_limit

          # pod metrics
          - dimensions: [[PodName, Namespace, ClusterName], [Service, Namespace, ClusterName], [Namespace, ClusterName], [ClusterName]]
            metric_name_selectors:
              - pod_cpu_utilization
              - pod_memory_utilization
              - pod_network_rx_bytes
              - pod_network_tx_bytes
              - pod_cpu_utilization_over_pod_limit
              - pod_memory_utilization_over_pod_limit
          - dimensions: [[PodName, Namespace, ClusterName], [ClusterName]]
            metric_name_selectors:
              - pod_cpu_reserved_capacity
              - pod_memory_reserved_capacity
          - dimensions: [[PodName, Namespace, ClusterName]]
            metric_name_selectors:
              - pod_number_of_container_restarts

          # cluster metrics
          - dimensions: [[ClusterName]]
            metric_name_selectors:
              - cluster_node_count
              - cluster_failed_node_count

          # service metrics
          - dimensions: [[Service, Namespace, ClusterName], [ClusterName]]
            metric_name_selectors:
              - service_number_of_running_pods

          # node fs metrics
          - dimensions: [[NodeName, InstanceId, ClusterName], [ClusterName]]
            metric_name_selectors:
              - node_filesystem_utilization

          # namespace metrics
          - dimensions: [[Namespace, ClusterName], [ClusterName]]
            metric_name_selectors:
              - namespace_number_of_running_pods

    service:
      pipelines:
        metrics:
          receivers: [awscontainerinsightreceiver]
          processors: [batch/metrics]
          exporters: [awsemf]

      extensions: [health_check]

デプロイする。

helmfile apply

Container Insights の Prometheus メトリクス収集のデプロイ

ADOT Collector 用の IAM ロールを作成する。

eksctl create iamserviceaccount \
  --cluster=${CLUSTER_NAME} \
  --namespace=aws-otel-eks \
  --name=aws-otel-collector \
  --attach-policy-arn=arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
  --approve \
  --role-only

マニフェストをダウンロードして adot_collector フォルダに配置する。

curl -O https://raw.githubusercontent.com/aws-observability/aws-otel-collector/main/deployment-template/eks/otel-container-insights-prometheus.yaml

Namespace は作成済みなので削除する。

IRSA のアノテーションを付与する。このマニフェストでは AppMesh/HAProxy/JMX/Memcached/Nginx のメトリクスを収集するための固定の定義が使われるので、カスタマイズした定義を使うため、インフラメトリクス収集用の定義を参考にして、ConfigMap をマウントするようにする。

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: aws-otel-collector
  namespace: aws-otel-eks
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-mycluster-addon-iamserviceaccount-aws-Role1-T5JD5TS9NP53
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: otel-prometheus-role
rules:
  - apiGroups:
      - ""
    resources:
      - nodes
      - nodes/proxy
      - services
      - endpoints
      - pods
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - nonResourceURLs:
      - /metrics
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: otel-prometheus-role-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: otel-prometheus-role
subjects:
  - kind: ServiceAccount
    name: aws-otel-collector
    namespace: aws-otel-eks
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: aws-otel-collector
  namespace: aws-otel-eks
  labels:
    name: aws-otel-collector
spec:
  replicas: 1
  selector:
    matchLabels:
      name: aws-otel-collector
  template:
    metadata:
      labels:
        name: aws-otel-collector
    spec:
      serviceAccountName: aws-otel-collector
      containers:
        - name: aws-otel-collector
          image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          command:
            - "/awscollector"
            - "--config=/conf/otel-agent-config.yaml"
          env:
            - name: AWS_REGION
              value: ap-northeast-1
            - name: OTEL_RESOURCE_ATTRIBUTES
              value: ClusterName=mycluster
          imagePullPolicy: Always
          resources:
            limits:
              cpu: 256m
              memory: 512Mi
            requests:
              cpu: 32m
              memory: 24Mi
          volumeMounts:
            - name: otel-agent-config-vol
              mountPath: /conf
      volumes:
        - name: otel-agent-config-vol
          configMap:
            name: otel-agent-conf-prometheus
            items:
              - key: otel-agent-config
                path: otel-agent-config.yaml

この Issue コメントを参考にして、カスタム定義を入れた ConfigMap を作成する。スクレイプは static_configs で固定の設定なので、kube-state-metrics をデプロした Namespace 名と Service 名に合わせる必要がある。

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-agent-conf-prometheus
  namespace: aws-otel-eks
  labels:
    app: opentelemetry
    component: otel-agent-conf
data:
  otel-agent-config: |
    extensions:
      health_check:

    receivers:
      prometheus:
        config:
          global:
            scrape_interval: 1m
            scrape_timeout: 10s
          scrape_configs:
            - job_name: 'kube-state-metrics'
              static_configs:
                - targets: [ 'prometheus-kube-state-metrics.prometheus.svc.cluster.local:8080' ]

    processors:
      resourcedetection/ec2:
        detectors: [ env ]
        timeout: 2s
        override: false
      resource:
        attributes:
          - key: TaskId
            from_attribute: job
            action: insert
          - key: receiver
            value: "prometheus"
            action: insert

    exporters:
      awsemf:
        namespace: ContainerInsights/Prometheus
        log_group_name: "/aws/containerinsights/{ClusterName}/prometheus"
        log_stream_name: "{TaskId}"
        resource_to_telemetry_conversion:
          enabled: true
        dimension_rollup_option: NoDimensionRollup
        metric_declarations:
          - dimensions: [ [ ClusterName, deployment, namespace ], [ ClusterName, namespace ], [ ClusterName ] ]
            metric_name_selectors:
              - "^kube_deployment_spec_replicas$"
              - "^kube_deployment_status_replicas$"
              - "^kube_deployment_status_replicas_ready$"
              - "^kube_deployment_status_replicas_available$"
              - "^kube_deployment_status_replicas_unavailable$"
            label_matchers:
              - label_names:
                  - service.name
                regex: "^kube-state-metrics$"
          - dimensions: [ [ ClusterName, statefulset, namespace ], [ ClusterName, namespace ], [ ClusterName ] ]
            metric_name_selectors:
              - "^kube_statefulset_replicas$"
              - "^kube_statefulset_status_replicas$"
              - "^kube_statefulset_status_replicas_ready$"
              - "^kube_statefulset_status_replicas_available$"
            label_matchers:
              - label_names:
                  - service.name
                regex: "^kube-state-metrics$"
          - dimensions: [ [ ClusterName, daemonset, namespace ], [ ClusterName, namespace ], [ ClusterName ] ]
            metric_name_selectors:
              - "^kube_daemonset_status_desired_number_scheduled$"
              - "^kube_daemonset_status_number_ready$"
              - "^kube_daemonset_status_number_available$"
              - "^kube_daemonset_status_number_unavailable$"
            label_matchers:
              - label_names:
                  - service.name
                regex: "^kube-state-metrics$"
          - dimensions: [ [ ClusterName, namespace, phase ], [ ClusterName, phase ], [ ClusterName ] ]
            metric_name_selectors:
              - "^kube_pod_status_ready$"
              - "^kube_pod_status_scheduled$"
              - "^kube_pod_status_unschedulable$"
              - "^kube_pod_status_phase$"
            label_matchers:
              - label_names:
                  - service.name
                regex: "^kube-state-metrics$"
          - dimensions: [ [ ClusterName, condition ] ]
            metric_name_selectors:
              - "^kube_node_status_condition$"
            label_matchers:
              - label_names:
                  - service.name
                regex: "^kube-state-metrics$"

    service:
      pipelines:
        metrics:
          receivers: [prometheus]
          processors: [resourcedetection/ec2, resource]
          exporters: [awsemf]

デプロイする。

helmfile apply

確認

/aws/containerinsights/mycluster/prometheus ロググループを確認する。TaskId がとれていないのか、ログストリーム名が設定できていないが、EMF 形式のログが送られている。

{
    "ClusterName": "mycluster",
    "OTelLib": "otelcol/prometheusreceiver",
    "Version": "1",
    "_aws": {
        "CloudWatchMetrics": [
            {
                "Namespace": "ContainerInsights/Prometheus",
                "Dimensions": [
                    [
                        "ClusterName",
                        "daemonset",
                        "namespace"
                    ],
                    [
                        "ClusterName",
                        "namespace"
                    ],
                    [
                        "ClusterName"
                    ]
                ],
                "Metrics": [
                    {
                        "Name": "kube_daemonset_status_number_available"
                    },
                    {
                        "Name": "kube_daemonset_status_number_unavailable"
                    },
                    {
                        "Name": "kube_daemonset_status_desired_number_scheduled"
                    },
                    {
                        "Name": "kube_daemonset_status_number_ready"
                    }
                ]
            }
        ],
        "Timestamp": 1688664351790
    },
    "daemonset": "kube-proxy",
    "http.scheme": "http",
    "kube_daemonset_annotations": 1,
    "kube_daemonset_created": 1688650419,
    "kube_daemonset_labels": 1,
    "kube_daemonset_metadata_generation": 1,
    "kube_daemonset_status_current_number_scheduled": 2,
    "kube_daemonset_status_desired_number_scheduled": 2,
    "kube_daemonset_status_number_available": 2,
    "kube_daemonset_status_number_misscheduled": 0,
    "kube_daemonset_status_number_ready": 2,
    "kube_daemonset_status_number_unavailable": 0,
    "kube_daemonset_status_observed_generation": 1,
    "kube_daemonset_status_updated_number_scheduled": 2,
    "namespace": "kube-system",
    "net.host.name": "prometheus-kube-state-metrics.prometheus.svc.cluster.local",
    "net.host.port": "8080",
    "prom_metric_type": "gauge",
    "receiver": "prometheus",
    "service.instance.id": "prometheus-kube-state-metrics.prometheus.svc.cluster.local:8080",
    "service.name": "kube-state-metrics"
}

どのメトリクスをとるかはもう少し精査が必要だが、メトリクスもとれている。

補足

ADOT Collector ではなく、CloudWatch Prometheus Agent でも同じことが可能。

スクレイプ設定は以下のようになる。

---
# create configmap for prometheus scrape config
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: amazon-cloudwatch
data:
  # prometheus config
  prometheus.yaml: |
    global:
      scrape_interval: 1m
      scrape_timeout: 10s
    scrape_configs:
    - job_name: 'kube-state-metrics'
      static_configs:
      - targets: [ 'kube-state-metrics.kube-system.svc.cluster.local:8080' ]

マッピング設定は以下のようになる。ADOT Collector の場合と微妙に違うので注意。

---
# create configmap for prometheus cwagent config
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-cwagentconfig
  namespace: amazon-cloudwatch
data:
  # cwagent json config
  cwagentconfig.json: |
    {
      "logs": {
        "metrics_collected": {
          "prometheus": {
            "prometheus_config_path": "/etc/prometheusconfig/prometheus.yaml",
            "emf_processor": {
              "metric_declaration": [
                {
                  "source_labels": ["job"],
                  "label_matcher": "^kube-state-metrics$",
                  "dimensions": [
                    [
                      "ClusterName",
                      "deployment",
                      "namespace"
                    ],
                    [
                      "ClusterName",
                      "namespace"
                    ],
                    [
                      "ClusterName"
                    ]
                  ],
                  "metric_selectors": [
                    "^kube_deployment_spec_replicas$",
                    "^kube_deployment_status_replicas$",
                    "^kube_deployment_status_replicas_ready$",
                    "^kube_deployment_status_replicas_available$",
                    "^kube_deployment_status_replicas_unavailable$"
                  ]
                },
                {
                  "source_labels": ["job"],
                  "label_matcher": "^kube-state-metrics$",
                  "dimensions": [
                    [
                      "ClusterName",
                      "statefulset",
                      "namespace"
                    ],
                    [
                      "ClusterName",
                      "namespace"
                    ],
                    [
                      "ClusterName"
                    ]
                  ],
                  "metric_selectors": [
                    "^kube_statefulset_replicas$",
                    "^kube_statefulset_status_replicas$",
                    "^kube_statefulset_status_replicas_ready$",
                    "^kube_statefulset_status_replicas_available$"
                  ]
                },
                {
                  "source_labels": ["job"],
                  "label_matcher": "^kube-state-metrics$",
                  "dimensions": [
                    [
                      "ClusterName",
                      "daemonset",
                      "namespace"
                    ],
                    [
                      "ClusterName",
                      "namespace"
                    ],
                    [
                      "ClusterName"
                    ]
                  ],
                  "metric_selectors": [
                    "^kube_daemonset_status_desired_number_scheduled$",
                    "^kube_daemonset_status_number_ready$",
                    "^kube_daemonset_status_number_available$",
                    "^kube_daemonset_status_number_unavailable$"
                  ]
                },
                {
                  "source_labels": ["job"],
                  "label_matcher": "^kube-state-metrics$",
                  "dimensions": [
                    [
                      "ClusterName",
                      "namespace",
                      "phase"
                    ],
                    [
                      "ClusterName",
                      "phase"
                    ],
                    [
                      "ClusterName"
                    ]
                  ],
                  "metric_selectors": [
                    "^kube_pod_status_ready$",
                    "^kube_pod_status_scheduled$",
                    "^kube_pod_status_unschedulable$",
                    "^kube_pod_status_phase$"
                  ]
                },
                {
                  "source_labels": ["job"],
                  "label_matcher": "^kube-state-metrics$",
                  "dimensions": [
                    [
                      "ClusterName",
                      "condition"
                    ]
                  ],
                  "metric_selectors": [
                    "^kube_node_status_condition$"
                  ]
                }
              ]
            }
          }
        },
        "force_flush_interval": 5
      }
    }

送られた EMF のログは以下のようになっていた。

{
    "CloudWatchMetrics": [
        {
            "Metrics": [
                {
                    "Name": "kube_daemonset_status_number_ready"
                },
                {
                    "Name": "kube_daemonset_status_desired_number_scheduled"
                },
                {
                    "Name": "kube_daemonset_status_number_available"
                },
                {
                    "Name": "kube_daemonset_status_number_unavailable"
                }
            ],
            "Dimensions": [
                [
                    "ClusterName",
                    "daemonset",
                    "namespace"
                ],
                [
                    "ClusterName",
                    "namespace"
                ],
                [
                    "ClusterName"
                ]
            ],
            "Namespace": "ContainerInsights/Prometheus"
        }
    ],
    "ClusterName": "fully-private",
    "Timestamp": "1688665159898",
    "Version": "0",
    "daemonset": "efs-csi-node",
    "instance": "kube-state-metrics.kube-system.svc.cluster.local:8080",
    "job": "kube-state-metrics",
    "kube_daemonset_annotations": 1,
    "kube_daemonset_created": 1687945935,
    "kube_daemonset_labels": 1,
    "kube_daemonset_metadata_generation": 1,
    "kube_daemonset_status_current_number_scheduled": 2,
    "kube_daemonset_status_desired_number_scheduled": 2,
    "kube_daemonset_status_number_available": 2,
    "kube_daemonset_status_number_misscheduled": 0,
    "kube_daemonset_status_number_ready": 2,
    "kube_daemonset_status_number_unavailable": 0,
    "kube_daemonset_status_observed_generation": 1,
    "kube_daemonset_status_updated_number_scheduled": 2,
    "namespace": "kube-system",
    "prom_metric_type": "gauge"
}

2023 年 6 月の読書メモを書いてないけど読んだ本 (3 冊)

2023 年 5 月の読書メモを書いてないけど読んだ本 (2 冊)

読書メモ: 専門医が教える鼻と睡眠の深い関係 鼻スッキリで夜ぐっすり

鼻うがい関連の本を何冊か読もうと思ったが、堀田修先生の本がほとんどだったので、堀田修先生以外の本で探して読んだ本。ちょうど去年の今頃睡眠の本を読んでたころに、目にとまっていたが読まなかった本でもある。

全体的な感想

タイトルの通り、以前読んだ睡眠の本と、鼻うがいの本の両方を合わせた本だった。睡眠と鼻うがいに関しては新しい情報はあまりなかったが、よい睡眠睡眠のためには鼻呼吸ができ、鼻の状態がよいことが大事であるということが書かれている。鼻炎に関連する治療法が一通り書かれていて参考になる。睡眠時無呼吸症候群に関する話題も多い。自分が睡眠時無呼吸症候群ではないかと疑い、10 年くらい前に検査を受けたことを思い出した。そのときは異常判定はされなかった。

3 つに要約

  • 睡眠時無呼吸症候群の治療法である CPAP は、鼻呼吸が前提であり、鼻呼吸ができないと CPAP は使えない。
  • 幼少期に口呼吸の期間が長いと、顎の発達がわるくなり歯並びが悪くなったり、学習能力が悪くなったりするので、子どものいびきや鼻づまりには要注意。
  • 身体のさまざまな不調の原因として上咽頭炎がある可能性があり、EAT による治療は効果がある可能性がある。

気になった部分の引用

ネーザルサイクルを直訳すると「鼻の周期」という意味ですが、左右の鼻が周期を持つ理由として、粘膜が腫れている側の呼吸を休止させることにより、鼻の換気機能を修復しているといわれています。他にも、休止中に免疫物質を運搬し、免疫機能を強化しているのだろうとも考えられています。ネーザルサイクルの周期は人によって異なりますが、昼間は2~3時間ごとに左右交代しているものの、夜間寝ているときはもっとゆったりと遅いサイクルになり、寝返りやレム睡眠に移行したときに左右が入れ替わりやすいようです。

左右の鼻というのは交互に通りやすい方が交代しているらしい。

ステロイド点鼻薬には即効性はないものの、用法通り使用することにより効果が維持されることを期待して使用します。また、用法通りに使用し続けることにより、アレルギー性鼻炎の治療効果が最も高いとされています。一方、市販の点鼻薬は血管収縮薬が含まれているため、即効性があり、楽になることからつい乱用されがちな面があります。この市販の点鼻薬を長期にわたって使用すると、薬が切れたときに鼻づまりが余計にひどくなる、「薬剤性鼻炎」という状態になってしまいます。

最近もらった点鼻薬を見てみたら、血管収縮剤の含まれているタイプだったので、もともとあまり使っていなかったが、乱用はしない方がよいのだと思った。

本来、息をするとき、胸とお腹は一緒に膨らんで、しぼんで……という動きをします。睡眠時にいびきをかく、時々息が止まってしまう、といった子どもでは、睡眠中に胸とお腹の動きが一緒に動かず逆転し、まるでシーソーのような動きの呼吸をすることがあります。これを「呼吸努力」と言います。よく観察してみてください。

子どもが「呼吸努力」していないか確認してみたが、大丈夫そうであったので安心した。

1日1回、舌下に投与し3年を目標に治療を継続しますが、治療効果はスギ花粉で70%前後、ダニアレルギーでは80~90%と高い治療効果が期待されます。また、3年で治療を終了した後もその効果は4~5年経過した後も80%以上持続されていると報告されています(前出「鼻アレルギー診療ガイドライン」より)。長い期間を要する治療法ですが、これにより症状の程度を優位に抑えてくれることは、それこそが大きな意味を有しており、鼻閉にまつわる眠りに関する問題や症状を改善しうる治療だと思います。

自分はハウスダストとスギ花粉のアレルギーがあるので、舌下免疫療法に興味がある。20 年前は舌下免疫療法はなくて、減感作療法をやったことがあるが、ちゃんと続けられなかった。

そのため私は、毎晩のようにトイレ覚醒が多い方には寝る前3時間以降、極力水分を取らないようにとお伝えしています。薬を飲んだり、夏にのどが渇いたりしたときには、のどをうるおす程度の量に、水以外であればノンカフェインのものを飲むように勧めています。

夜トイレに起きてしまうことは多いのだが、水分を控えるのはあまりよくないような気がしてやっていなかったが、多少控えようと思った。

読書メモ: ウイルスを寄せつけない! 痛くない鼻うがい

花粉症なのか体調を崩したのをきっかけに、数年前に買ったっきり放置していた「ハナクリーン EX」を使ってみたところ、快適に鼻うがいができたので、鼻うがいについて勉強してみたくなり読んだ。

本とは関係ないが、昔から鼻うがいはやりたかったが上手くできたことがなく、それが「ハナクリーン EX」を使ったら簡単にできたのは感動的な体験であった。

ちなみに、耳鼻咽喉科でアレルギーのテストをやってもらったところ、ハウスダストが強めのアレルギーがあり、スギ花粉にも弱めのアレルギーがあった。

全体的な感想

鼻うがいの本であるが、この著者としては、EAT という上咽頭炎の治療法を本丸と考えているようであり、鼻うがいは補助的な位置づけと感じた。鼻うがいの効能についての研究がいくつか紹介されているが、エビデンスとしては弱いものが多いようだった。時期的に、鼻うがいに新型コロナウイルスの予防と治療の可能性があることが、科学的エビデンスがないと断った上で述べられている。全般的に、エビデンスに基づいた記載が少なく、逆に、鼻うがいには科学的な根拠はさほどないのかなという印象をもってしまった。

3 つのポイントに要約

  • 風邪の症状の震源地は上咽頭にあるので、鼻うがいによってウイルス等を洗い流すのは効果的。
  • 慢性上咽頭炎はさまざまな体の不調の原因となる。EAT という治療法が効果的。
  • 鼻うがいは 1% 程度の生理食塩水が快適であるが、2%程度の高張食塩水のほうが効果的。予防目的には 1%、治療目的では 2% を使うなど使い分けるとよい。

気になった部分の引用

日本では、鼻うがいの洗浄液として人の体液と同じ濃度の生理食塩水を使うのが一般的ですが、高張食塩水(濃い食塩水)の方が粘膜のむくみや炎症を軽減しやすいとの報告がこれまでに多数あります。鼻炎と副鼻腔炎の症例の報告をまとめて解析したところ、生理食塩水よりも高張食塩水が症状を改善する効果が勝ることが示唆されています。しかしその一方で、高張食塩水の鼻うがいは、炎症を引き起こすヒスタミンやサブスタンスP*hを作り出し、鼻づまり・鼻水・疼痛の原因になりうることも報告されています。簡単にいうと、「鼻うがいの効果は高張食塩水の方が期待できるが、副反応も出やすい」ということになります。ですから、「風邪予防のために行う毎日の『鼻うがい』には約1%の食塩水を使い、風邪かなと思ったら食塩水の濃度を約2%にする」といった具合に、用途に応じて濃度を変えるのが良いかもしれません。

こう書いてあったので、試しに 2% で試してみたところ、鼻うがい自体は少しツンとして不快になったが、その後の鼻の通りはよい感じがする。

私は外来の診療をしていて、「一年中風邪をひいている」「季節の変わり目には必ず風邪をひく」「周りの人の風邪をすぐもらう」と話す患者さんが少なからずいることに気づきました。そういう人は、他人の風邪はすぐもらってしまうのに自分の風邪は人にうつさない、という特徴があります。このような人を調べてみると、風邪をひいていないときも激しい慢性上咽頭炎がみられることが分かりました。つまり、風邪を引き起こしているのはウイルスではないのです。もともと慢性上咽頭炎を起こしていて、寒さや過労などで慢性上咽頭炎が悪化したときに、風邪をひいたと感じているのです。

自分も季節の変わり目や、疲れがたまったときに体調を崩すので、慢性上咽頭炎を持っているのかもしれないと思い、興味がわいた。

口呼吸の習慣があると風邪をひきやすいだけでなく、口の奥の両側にある扁桃腺の炎症を起こしやすくなります。扁桃に慢性的な炎症が起こると、扁桃炎自体の症状は軽くても、IgA腎症をはじめとする腎臓の病気や掌蹠膿疱症という治りにくい皮膚炎など、さまざまな病気を引き起こす原因となります。

体調が悪くなったとき、自覚症状として扁桃腺が腫れる感じがするのと、最近の健康診断で腎臓のスコアがよくないが、これが口呼吸や慢性上咽頭炎に関係があるのかもしれない。

そしてウイルス感染以外で上咽頭炎の原因として比較的多いのが「GERD(胃食道逆流症)」です。これは胃酸を含んだ胃の内容物が、胃から食道に逆流することによって発生する疾患で、食道裂孔ヘルニアがあるとGERDを起こしやすくなります。

これも自分がまさに逆流性食道炎で治療中なので、これも上咽頭炎とつながるのかとびっくりした。EAT による慢性上咽頭炎の治療にがぜん興味がわいたので、機会を見つけて受けてみたい。