Kubernetesネイティブな監視基盤への移行(#15)

はじめに

#5では、研究室サーバーの監視基盤としてPrometheus、Grafana、Alertmanagerを導入しました。この基盤で半年ほど問題なく運用できており、現在Grafanaダッシュボードは研究室の他のメンバーにも利用されています。

当時は引き継ぎやバージョンアップのコストを考慮して、各コンポーネントをバイナリインストール(Linuxサービス起動)ではなく、Dockerコンテナとして起動する意思決定を行いました。しかし、この方法でも依然として手動でのバージョンアップが必要であり、決してメンテナンス性が高いとは言えません。

一方、#14では研究室サーバーでオンプレミスのKuberentesクラスタを構築しました。本記事では、Prometheus Operatorなどを利用したKubernetesネイティブな監視基盤に移行する方法とそのメリットを紹介します。

1. Prometheus Operatorとは

Prometheus Operatorを利用することで、PrometheusやAlertmanagerなどをKubernetesネイティブなOperator patternでデプロイ・管理することができます。

github.com

目的として「KubernetesクラスタにおけるPrometheusベースの監視スタックの設定を簡素化し、自動化すること」を掲げていて、主に以下の3つの機能を提供しています。

  1. カスタムリソースの提供
  2. シンプルなデプロイ設定
  3. Prometheusターゲット設定

1つ目について、デプロイされるPrometheusAlertmanagerだけでなく、(prometheus.yamlで指定されるような)ProbeScrapeConfigPrometheusRuleなどといった設定用のカスタムリソースも定義されています。また、Serviceに関連付けられたPodを間接的に指定するServiceMonitorと、Podを直接指定するPodMonitorも特徴的です。

例えばPrometheusというCRDは、scrapeConfigSelectorserviceMonitorSelectorなどのフィールドによって、使用するScrapeConfigServiceMonitorを特定できます。詳細は以下の公式ドキュメントが図解付きで分かりやすいです。

prometheus-operator.dev

2つ目は冒頭で述べた課題を解決するもので、KubernetesのリソースとしてPrometheusのバージョンやレプリカ数などを管理できます。3つ目については、Kubernetesのラベルから監視ターゲットの設定を生成できるため、Prometheusの従来のYAML設定を必要としません。どちらも運用上のメリットが大きいです。

2. Prometheus Operatorのインストール

prometheus-operator.dev

さて、Prometheus Operatorのインストール方法ですが、公式ドキュメントでは3通りの方法が紹介されていました。Prometheus OperatorとCRDのみインストールする方法もこのうちの1つですが、より簡単にデプロイできる方法を検討します。

kube-prometheusはPrometheus Operatorと同じOrganizationによってメンテナンスされています。しかし今回はHelmを利用してインストールしたかったため、kube-prometheus-stackを利用します。どちらもPrometheus Operatorを利用したGrafanaダッシュボード付きのマニフェストです。

kubeadmを利用している場合、いくつか事前準備が必要なので公式ドキュメントをご参照ください。

既にArgo CDを導入してGitOpsを実践しているため、以下のようなマニフェストを用意しました。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: kube-prometheus-stack
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 70.4.2
    helm:
      valuesObject:
        grafana:
          grafana.ini:
            server:
              root_url: "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
              serve_from_sub_path: true
        prometheus:
          prometheusSpec:
            externalUrl: "http://<NodeIP>:<NodePort>/prometheus"
            routePrefix: /prometheus
        alertmanager:
          alertmanagerSpec:
            externalUrl: "http://<NodeIP>:<NodePort>/alertmanager"
            routePrefix: /alertmanager
  destination:
    server: https://kubernetes.default.svc
    namespace: kube-prometheus-stack
  syncPolicy:
    automated: {}
    syncOptions:
    - CreateNamespace=true
    - ServerSideApply=true

<NodeIP><NodePort>は各自で置き換えてください。

ブラウザからGrafana / Prometheus / Alertmanagerにアクセスするとリダイレクトが発生するため、私たちのように/grafana / /prometheus / /alertmanagerでアクセスさせたい場合はvalues.yamlの値を上書きしてサブパスを指定する必要があります。

公式ドキュメントではPrometheusやAlertmanagerを公開する方法として、NodePortやKuberenetes APIIngressを利用する方法が紹介されていましたが、今回は引き続きIstioのIngress Gatewayを利用することにしました。

Gatewayのマニフェスト

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: http-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

VirtualServiceのマニフェスト

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: kube-prometheus-stack-virtualservice
  namespace: kube-prometheus-stack
spec:
  hosts:
  - "*"
  gateways:
  - istio-system/http-gateway
  http:
  - match:
    - uri:
        prefix: /grafana
    route:
    - destination:
        host: kube-prometheus-stack-grafana
        port:
          number: 80
  - match:
    - uri:
        prefix: /prometheus
    route:
    - destination:
        host: kube-prometheus-stack-prometheus
        port:
          number: 9090
  - match:
    - uri:
        prefix: /alertmanager
    route:
    - destination:
        host: kube-prometheus-stack-alertmanager
        port:
          number: 9093

この設定によってhttp://<NodeIP>:<NodePort>/{grafana,prometheus,alertmanager}のようなパスベースのルーティングを行うことができ、<NodeIP>:<NodePort>さえ分かれば簡単にそれぞれのWeb UIにアクセスできます。

<NodeIP>:<NodePort>/grafanaでGrafanaのWeb UIにアクセス

<NodeIP>:<NodePort>/promethuesでPrometheusのWeb UIにアクセス

<NodeIP>:<NodePort>/alertmanagerでAlertmanagerのWeb UIにアクセス

そうは言ってもやはりドメイン名でアクセスしたいので、近いうちにMetalLBExternalDNSを導入する予定です。

3. NVIDIA GPU Operatorのインストール

お馴染みのNVIDIA/dcgm-exporterを利用してKubernetes上でもGPUメトリクスを監視する予定でしたが、READMEには以下のように書かれていました。

Note: Consider using the NVIDIA GPU Operator rather than DCGM-Exporter directly.

今度はNVIDIA/gpu-operatorのREADMEを確認すると以下のように紹介されています。

These components include the NVIDIA drivers (to enable CUDA), Kubernetes device plugin for GPUs, the NVIDIA Container Runtime, automatic node labelling, DCGM based monitoring and others.

DCGMだけでなく、#14でコンテナからGPUを利用するために導入したNVIDIA/k8s-device-pluginも含まれているようです。さらに、今まで手動でメンテナンスしていたNVIDIA GPU DriverやNVIDIA Container Toolkitも含まれているとのことで(インストール済みなので今回は無効化しますが)恩恵が大きいと感じました。

早速#14で導入したk8s-device-pluginは削除して、gpu-operatorを以下の公式ドキュメントに従ってインストールします。

docs.nvidia.com

注意点として、以下のような差分を一度にSyncしないようにしてください。k8s-device-pluginにもNode Feature Discovery(NFD)が含まれているため、一旦無効化してからgpu-operatorでNFDをインストールする必要があります(公式ドキュメント参照)。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
-  name: nvidia-device-plugin
+  name: gpu-operator
  namespace: argocd
spec:
  project: default
  source:
-    repoURL: https://nvidia.github.io/k8s-device-plugin
-    chart: nvidia-device-plugin
-    targetRevision: 0.17.1
+    repoURL: https://helm.ngc.nvidia.com/nvidia
+    chart: gpu-operator
+    targetRevision: v25.3.0
    helm:
      parameters:
-        - name: gfd.enabled
-          value: "true"
+        - name: driver.enabled
+          value: "false"
+        - name: toolkit.enabled
+          value: "false"
  destination:
    server: https://kubernetes.default.svc
-    namespace: nvidia-device-plugin
+    namespace: gpu-operator
  syncPolicy:
    automated: {}
    syncOptions:

nvidia-device-plugin-***nvidia-dcgm-exporter-***というPodが起動していることを確認しましょう。

続いて、GPUメトリクスに関してPrometheusとGrafanaのセットアップも行います。

docs.nvidia.com

上記の公式ドキュメントに従うのが最も手っ取り早いです。2章のマニフェストで変更が必要なPrometheus部分だけを示します。

        prometheus:
          prometheusSpec:
            externalUrl: "http://<NodeIP>:<NodePort>/prometheus"
            routePrefix: /prometheus
            serviceMonitorSelectorNilUsesHelmValues: false
            additionalScrapeConfigs:
            - job_name: gpu-metrics
              scrape_interval: 15s
              metrics_path: /metrics
              scheme: http
              kubernetes_sd_configs:
              - role: endpoints
                namespaces:
                  names:
                  - gpu-operator
              relabel_configs:
              - source_labels: [__meta_kubernetes_endpoints_name]
                action: drop
                regex: .*-node-feature-discovery-master
              - source_labels: [__meta_kubernetes_pod_node_name]
                action: replace
                target_label: kubernetes_node
              - source_labels: [__address__,__meta_kubernetes_endpoint_node_name]
                action: replace
                separator: ,
                target_label: instance

relabel_configs:の最後のアイテムは独自に付け加えました。理由はGrafanaダッシュボード上でホスト名による変数フィルタリングを行うためです。NVIDIA DCGM Exporterという人気のあるダッシュボードを利用する場合、フィルタリング変数はinstance(デフォルトは__address__つまり192.168.1.xxx:9400のようなフォーマット)となり、ダッシュボード利用者がワーカーノードのIPアドレスを暗記しなければなりません。

__address____meta_kubernetes_endpoint_node_name(ホスト名)をカンマ区切りで結合した文字列でinstanceラベルを置換することで、さらにダッシュボード側で正規表現を利用して表示用のtextとクエリ用のvalueを抽出できます。

/(?<value>[^,]+),(?<text>.+)/

ただし、これらの変更によって元のinstanceラベルも変更されてしまったのでダッシュボード設定のJSON Modelタブで以下のような書き換えも行う必要があります。

s/instance=~\"${instance}\"/instance=~\"${instance}.*\"/g

お馴染みのホスト名で変数フィルタリングを行う様子

Grafanaダッシュボードは管理者だけでなく研究室メンバーにも普及しているので可能な限り認知負荷を下げるようにダッシュボードをセットアップしました。

まとめ

今回は、Prometheus Operatorなどを利用してKubernetesネイティブな監視基盤に移行する方法を紹介しました。Kubernetes上でメトリクスを収集できましたが、古い監視基盤のAlertmanagerの設定はまだ移行できていないので徐々に進めていきたいです。

一般的な研究室ではワーカーノードが数台程度なので古い監視基盤でも問題にならない規模ですが、GitOpsで監視の設定を一元管理できる点と各コンポーネントのアップデートが現実的になる点で、Kubernetesネイティブな監視基盤に移行するメリットは大きいと考えました。

ただし、Kubernetesクラスタがある時点で逸般的な研究室である可能性は高いので、ファーストステップとして監視基盤を構築したい方は依然として#5の方法をオススメします。