zaki work log

作業ログやら生活ログやらなんやら

[K3s / Prometheus] リモートのPrometheusサーバーを使う構成でkubectl topするためのK3sメトリクスサーバー構築

Prometheus Adapterの設定にPrometheusサーバー本体のアドレスを設定する箇所があったのは覚えてたので、ここにリモートで動作するサーバーを指定すれば、クラスター内にPrometheus本体や溜めたデータのストレージを持つ必要がなくなるはずと見込んで、ラズパイKubernetesクラスターみたいな軽量さが欲しいところにちょうどいいと思い試してみた。

zaki-hmkc.hatenablog.com

構成としては以下の通りで、Prometheus本体はK8sクラスターの外部、K8sクラスター内にはPrometheusサーバー本体無しでPrometheus AdapterとNode Exporterをデプロイする。
(矢印の向きはデータの流れ)

ちなみに、原理はあまりわかってなくて「動いてる環境からどの設定をコピーしたら動くかをトライアルアンドエラーした」結果がこうなった、というものの調査結果。

デフォルトのメトリクスサーバーのオフ

事前準備として/etc/systemd/system/k3s.serviceにあるsystemdユニットファイル末尾にある起動引数に以下を追記してサービスを再起動。

--disable metrics-server

systemctl daemon-reload 
systemctl restart k3s.service 

ちなみにメトリクスサーバーが動いたままだとPrometheus Adapterのインストールに失敗する。

Error: INSTALLATION FAILED: Unable to continue with install: APIService "v1beta1.metrics.k8s.io" in namespace "" exists and cannot be imported into the current release: invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "prometheus-adapter"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "monitoring"

始めからメトリクスサーバーをオフでK3sをデプロイしてれば不要。

zaki-hmkc.hatenablog.com

Prometheus

作業の順序は最終的に不問だが、「データを取りに行ける順」で記述。
(「データを取られるための設定」をまず入れてから、「データを取りに行く設定」を入れている、ということ。逆順でも作業中にデータが取れないだけで最終的にはあるべき状態になるのでそんな気にしなくてよい)

K3sクラスターにおいてリソースのデプロイ先は、以下すべてmonitoringネームスペースとする。

Node Exporter (on K3s)

Prometheusサーバーがノード情報を参照するためのもの。kubectl top nodeで使用するメトリクス情報で必要。
Node Exporterはデフォルトの設定で特に問題は無く余計なものも入らない(多分)ためパラメタの指定は無し。

helm upgrade --install prometheus-node-exporter -n monitoring --create-namespace prometheus-community/prometheus-node-exporter

Prometheusアクセス用トークン (on K3s)

外部にあるPrometheusサーバーがKubernetesAPIエンドポイントにアクセスするためのServiceAccountとトークンを作成する。
また、作成したServiceAccountにはメトリクス情報を参照するための権限を付与する。

権限の内容についてはこれといった決め手となる情報がなかったため、「Prometheus本体をKubernetesにデプロイした際に設定されるClusterRole」をHelmチャートから拝借して、同じ権限にしている。

github.com

まとめると、作成するリソースは以下の通り。

  • serviceaccount/monitoring-user
  • clusterrole/monitoring-metrics-clusterrole
  • clusterrolebinding/monitoring-metrics-clusterrole
  • secret/monitoring-token
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitoring-user
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-metrics-clusterrole
rules:
  - apiGroups:
      - ""
    resources:
      - nodes
      - nodes/proxy
      - nodes/metrics
      - services
      - endpoints
      - pods
      - ingresses
      - configmaps
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "discovery.k8s.io"
    resources:
      - endpointslices
    verbs:
      - get
      - list
      - watch
  - nonResourceURLs:
      - "/metrics"
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: monitoring-metrics-clusterrole
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: monitoring-metrics-clusterrole
subjects:
- kind: ServiceAccount
  name: monitoring-user
  namespace: monitoring
---
apiVersion: v1
kind: Secret
metadata:
  name: monitoring-token
  namespace: monitoring
  annotations:
    kubernetes.io/service-account.name: "monitoring-user"
type: kubernetes.io/service-account-token

リソースを作成したらSecretからトークンを取得し、トークンファイルをPrometheusサーバーにコピーする。

kubectl get secret -n monitoring monitoring-token -o jsonpath='{.data.token}' | base64 -d > token
scp token prometheus-server:/path/to/token

Prometheusサーバー本体 (on リモート)

設定内容のベースは以下(「リモートで動いているPrometheus」なので本エントリにおいてはこれがK8sで動いてるのかDockerなのかVMに直接入れてるのかは特に影響しない)

zaki-hmkc.hatenablog.com

既存のPrometheusサーバーに、Service Discoverを使ったKubernetes情報取得の設定を追加する。
接続の際に前述の定義で作成したトークンファイルを指定する。

kubectl topを最低限動作させるために必要なprometheus.ymlの設定ポイントは以下の通り。
job_nameは設定名のため重複しなければなんでもよい。以下はデフォルト設定に-k3sを付与。複数のクラスターを管理するなら、クラスター名とかをsuffixとかにするのが良さそう。
トークンファイルはbearer_token_fileで指定し、監視対象のK3sクラスターはkubernetes_sd_configs[*].api_serverAPIサーバーのURLを指定する。
cadvisorの定義はbearer_token_filetls_configが同じ内容で複数個所あるが、どちらにも書かないと動作しなかった(詳細は不明)。また、relabel_configstarget_label: __address__の次の行のreplacementにも監視対象のIPアドレスとポートを指定する。

設定自体はPrometheusのHelmチャートの内容から拝借。

  - job_name: 'kubernetes-nodes-cadvisor-k3s'
    # kubectl top podが有効になる
    scheme: https
    tls_config:
      insecure_skip_verify: true
    bearer_token_file: /var/run/token
    kubernetes_sd_configs:
      - role: node
        api_server: https://192.168.0.77:6443
        tls_config:
          insecure_skip_verify: true
        bearer_token_file: /var/run/token
    relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - target_label: __address__
        replacement: 192.168.0.77:6443
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        target_label: __metrics_path__
        replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor

  - job_name: 'kubernetes-service-endpoints-k3s'
    # kubectl top nodeが有効になる(要Node Exporter)
    honor_labels: true
    kubernetes_sd_configs:
      - role: endpoints
        api_server: https://192.168.0.77:6443
        tls_config:
          insecure_skip_verify: true
        bearer_token_file: /var/run/token
    relabel_configs:
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape_slow]
        action: drop
        regex: true
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
        action: replace
        target_label: __scheme__
        regex: (https?)
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: (.+?)(?::\d+)?;(\d+)
        replacement: $1:$2
      - action: labelmap
        regex: __meta_kubernetes_service_annotation_prometheus_io_param_(.+)
        replacement: __param_$1
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: namespace
      - source_labels: [__meta_kubernetes_service_name]
        action: replace
        target_label: service
      - source_labels: [__meta_kubernetes_pod_node_name]
        action: replace
        target_label: node

Prometheus Adapter (on K3s)

メトリクスサーバーとして動作。
以前はクラスター内のPrometheusサーバーを参照するように設定してデプロイしたが、今回はここにリモートのPrometheusサーバーを指定する。

zaki-hmkc.hatenablog.com

values.yamlの内容は以下の通り、prometheus.urlにリモートのPrometheusサーバーのアドレスをセットする。
また、rules.resourcekubectl topを使ってCPU/Memoryのメトリクスを取得するのに必要な定義があるため、コメントアウトされてる箇所を有効にする。

# Url to access prometheus
prometheus:
  # Value is templated
  url: http://192.168.0.22
  port: 9090
  path: ""

rules:
  resource:
    cpu:
      containerQuery: |
        sum by (<<.GroupBy>>) (
          rate(container_cpu_usage_seconds_total{container!="",<<.LabelMatchers>>}[3m])
        )
      nodeQuery: |
        sum  by (<<.GroupBy>>) (
          rate(node_cpu_seconds_total{mode!="idle",mode!="iowait",mode!="steal",<<.LabelMatchers>>}[3m])
        )
      resources:
        overrides:
          node:
            resource: node
          namespace:
            resource: namespace
          pod:
            resource: pod
      containerLabel: container
    memory:
      containerQuery: |
        sum by (<<.GroupBy>>) (
          avg_over_time(container_memory_working_set_bytes{container!="",<<.LabelMatchers>>}[3m])
        )
      nodeQuery: |
        sum by (<<.GroupBy>>) (
          avg_over_time(node_memory_MemTotal_bytes{<<.LabelMatchers>>}[3m])
          -
          avg_over_time(node_memory_MemAvailable_bytes{<<.LabelMatchers>>}[3m])
        )
      resources:
        overrides:
          node:
            resource: node
          namespace:
            resource: namespace
          pod:
            resource: pod
      containerLabel: container
    window: 3m

これを指定してデプロイすればOK

helm upgrade --install prometheus-adapter -n monitoring --create-namespace prometheus-community/prometheus-adapter -f values.yaml

これで、ノードのメトリクス情報を集めるために必要なNode ExporterとリモートのPrometheusサーバーからデータを引っ張ってきて処理するメトリクスサーバーがK3sノードに、対象K3sへメトリクス値を取りに行くPrometheusサーバー設定が投入されたので、クラスター内にPrometheusを必要とせずにkubectl topやHPAが使用可能になる。

K3s用カスタムリソースHelmChartにまとめると

ここまで手動の構築手順を説明してきたけど、K3sには標準でHelmによるデプロイを宣言的に行うカスタムリソースHelmChartが用意されている。

zaki-hmkc.hatenablog.com

helmCLIツールを使ったリポジトリ管理やパラメタ指定をすべてマニフェストへ記述できるので、手動あるいはHelmで生成される前述の全リソースは以下のリソース1発でデプロイできる。 (デプロイしたあとのトークン取得とリモートのPrometheusサーバーへの設定投入は別途必要だけど)

ここまでの全マニフェストを繋げただけだけど、以下の通り。
(マニフェストファイル利用時はリモートのPrometheusサーバーのアドレスは読み替えること)

github.com

注意点

PrometheusのService DiscoverでデフォルトではCoreDNSのメトリクスも取得する設定が投入されるが、クラスターの外からのIPリーチが無いためメトリクス情報が取れない。
外部Prometheusサーバーありきの構成でCoreDNSのメトリクスも必要であれば、従来通りクラスター内にPrometheusサーバーも立ててメトリクスを収集し、フェデレーション機能で外部へ集約する方法がある。あるいは、本エントリの構成にしつつ外部からのメトリクス情報を参照するためにLoadBalancer ServiceとかでCoreDNSのメトリクスを参照するURLを外部公開し、外部Prometheusからそれを参照するようにするとか、かな。(前者は検索すれば情報たくさんあると思う)

環境とバージョン

  • K3s (v1.31.6+k3s1)
  • Prometheus Adapter (helm chart 4.13.0)
  • Prometheus Node Exporter (helm chart 4.45.0)
  • Prometheus (3.2.1 / Ubuntu 24.04上にDockerで動作)