k8sクラスタの外にあるPrometheusからk8sクラスタ内のPodのメトリクスを収集する
これは Kubernetes3 Advent Calendar の 8日目の記事です。
はじめに
k8sのモニタリングの手段はいくつかあると思いますが、Prometheusにもk8sサービスディスカバリ機能があるのでk8sのモニタリングを行う事が出来ます。
Prometheusをk8sクラスタ内にデプロイしてモニタリングするには、Prometheusの設定をk8sのConfigMapに書き、Volume経由でPrometheusのPodに渡して使うと良いでしょう。
k8sクラスタの外にあるPrometheusからk8sクラスタ内のPodのメトリクスを収集したい
ここからがこの記事の本題です。
動機
僕は普段Prometheusを使って自分たちのサービスをモニタリングしていますが、Prometheusサーバは専任のエンジニアが運用をやってくれています。
僕たちのサービスで収集しているメトリクスはかなりの量で、k8sクラスタ毎にPrometheusサーバを配置すると運用が大変になるので避けたいと思っています。
- サービス開発側でPrometheusサーバの運用する事は避けたい
(監視対象のサーバやメトリクスが大量になるサービスの場合、Prometheusサーバの運用は大変です) - k8sクラスタ毎にPrometheusサーバを配置してPrometheus担当エンジニアに運用してもらう事も避けたい
(運用コストの面から、Prometheusサーバ の台数を増やしたくない)
以下のようなイメージです。
Prometheusをクラスタ内で動かす場合
こうしたい
考えられる選択肢
以上の理由から、k8sクラスタの中にPrometheusは配置せずに、クラスタ外のPrometheusサーバからモニタリング出来ないかを考えてみました。
パッと思いつくのは、以下の選択肢です。
- PushProxを使う
- Prometheus開発者のBrian Brazilさんが開発したPrometheus用のProxyですが、現在は活発に開発はされていないようです
- 日本語で記事を書かれている方がいらっしゃいます → PrometheusでNATを越えよう!!
- 現在の開発状況や、上記の紹介記事を読むと、あんまり使いたくない印象を受けました
- Reverse proxyアプリをクラスタにデプロイして、Service経由で外部のPrometheusサーバからメトリクスを収集する
※なお、「クラスタ内にPrometheusを配置したくない」という今回の目的から外れるので詳しくは書きませんが、クラスタ内にPrometheusを配置しつつクラスタ外のPrometheusと連携させるという方法もあります。
- クラスタ内のPrometheusから、クラスタ外のPrometheusに対してRemote writeを行う
- Prometheus公式ドキュメント - Storate
- Prometheus公式ドキュメント - Remote write tuning
- クラスタ外のPrometheusから、クラスタ内のPrometheusに対してRemote readを行う
- クラスタ外のPrometheusから、クラスタ内のPrometheusに対してFederationを行う
- Prometheus公式ドキュメント - Federation
Reverse proxyアプリを書いてみた
Reverse proxyアプリを書くのは難しくないので自分で書いてみました。
https://github.com/matsumana/psystrike
※Podのメトリクス収集のリクエストをproxyするだけならNginxとかを使っても実現できる気がします。Podのメトリクス収集以外にも実装したい機能が他にもあるので今回は自分でアプリを書きました。
使い方
軽く動作確認してみたところ、うまく動いているようなので使い方を紹介します。
Reverse proxyアプリをクラスタにデプロイする
Docker imageをDocker Hubにアップしてあります。
https://hub.docker.com/r/matsumana/psystrike
Reverse proxyアプリをデプロイするためのManifestファイルは以下のような感じで。
※この例ではServiceのnodePort(31000)でクラスタ外から接続できるようにしています。
例)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: psystrike
labels:
app: psystrike
spec:
replicas: 1
selector:
matchLabels:
app: psystrike
template:
metadata:
labels:
app: psystrike
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: /metrics
spec:
containers:
- name: psystrike
image: matsumana/psystrike:latest
ports:
- containerPort: 8080 # Application
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 60 # Need to wait until the app starts up
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # Need to wait until the app is ready
periodSeconds: 10
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"] # Wait a few seconds to service-out from the Service before the SIGTERM signal is sent
terminationGracePeriodSeconds: 120 # Need to wait until the app shuts down
---
kind: Service
apiVersion: v1
metadata:
name: psystrike
spec:
type: NodePort
selector:
app: psystrike
ports:
- name: app
protocol: TCP
targetPort: 8080
port: 8080
nodePort: 31000
Prometheusの設定ファイルを変更する
PrometheusからはReverse proxy Podに接続してメトリクスを収集するので、prometheus.ymlのrelabel設定でメトリクスのlabelを変更する必要があります。
そうしないと正しくメトリクスが収集できません。
変更箇所:
instance
- 収集されたメトリクスに付加される
instance
ラベルには、__address__
の値が自動的にセットされますが、以下で__address__
を書き換えてしまうので明示的に設定する必要があります
- 収集されたメトリクスに付加される
__metrics_path__
- /pods/{podのip}/{podのport}/{Podのメトリクスendpoint}`という形式に書き換える
- ※Reverse proxyアプリ側のリクエストハンドラは以下のようになっています
@Get("regex:^/pods/(?<host>.*?)/(?<port>.*?)/(?<actualUri>.*)$")
__address__
- デフォルトだと、Podのipとportに対してPrometheusがメトリクス収集に行くのでProxy経由に変更する必要があります
see also:
実際のManifestファイルは以下のようになります。
※Pod用jobの部分だけ抜粋
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
api_server: https://your_k8s_api_server
bearer_token: your_k8s_api_server_token
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)(?::\d+);(\d+);(.*)
replacement: /pods/$1/$2$3
- source_labels: [__address__]
target_label: instance
- target_label: __address__
replacement: your_proxy_app_ip:your_proxy_app_port
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
- source_labels: [__meta_kubernetes_pod_node_name]
action: replace
target_label: kubernetes_pod_node_name
元のManifestファイルとの差分。
$ diff prometheus_before.yml prometheus_after.yml
14c14
< - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
---
> - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_annotation_prometheus_io_path]
17,22c17,22
< regex: (.+)
< - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
< action: replace
< target_label: __address__
< regex: (.+)(?::\d+);(\d+)
< replacement: $1:$2
---
> regex: (.+)(?::\d+);(\d+);(.*)
> replacement: /pods/$1/$2$3
> - source_labels: [__address__]
> target_label: instance
> - target_label: __address__
> replacement: your_proxy_app_ip:your_proxy_app_port
Prometheusのk8sディスカバリのしくみ
最後に、ご参考までにPrometheusがどうやってk8sからディスカバリをしているかを説明しておきます。
- Prometheusの起動直後、PodやNodeのリストを取得します
- その後は変更された差分(追加,変更,削除)だけを取得します
- 差分の取得はlong pollingです
- long pollingのtimeoutは5分〜10分でランダムに決定されます
# 最初のリスト取得
GET /api/v1/pods?limit=500&resourceVersion=0
# long pollingによる変更差分取得
GET /api/v1/pods?resourceVersion=886756&timeout=8m2s&timeoutSeconds=482&watch=true
long pollingのソースはこの辺りです。
k8sのclientライブラリで実装されていて、Pormetheusはそれを使っています。
- https://github.com/prometheus/prometheus/blob/v2.14.0/vendor/k8s.io/client-go/tools/cache/reflector.go#L262-L305
- https://github.com/prometheus/prometheus/blob/v2.14.0/vendor/k8s.io/client-go/tools/cache/reflector.go#L318-L387
概要図
ディスカバリ関連のメトリクス
prometheus_sd_kubernetes_
で始まる名前のメトリクスがPrometheusのk8sディスカバリ関連メトリクスです。
メトリクスはいくつかありますが、prometheus_sd_kubernetes_events_total
がPodなどが増減した時に変化するメトリクスです。
long polling以外に、Prometheusが持っているリストを定期的にリフレッシュする処理が動いているようです。
まとめ
- k8sクラスタの外に配置したPrometheusからクラスタ内のPodのメトリクスを取れるようにReverse proxyを書いてみました
- 今回は試してませんが、他の選択肢もあります
- PushProx
- PrometheusのRemote read/write
- PrometheusのFederation