6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ひとりでkubernetesを中心にAdvent Calendar 2021

Day 12

prometheus-adapterの設定を完全理解したい

Last updated at Posted at 2021-12-12

kubernetesのオートスケールはデフォルトでhorizontal pod autoscalerが用意されています
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

cpuやメモリの使用率でスケールさせたい場合は大体これで事足りる(もちろん足りない場合もあるが)のですが、リクエスト数やキュー数などでスケールさせたい、という場面が出てくると思います
そういう場合は、外部メトリクスをHPAの指標にすることができます
そのためのアダプターはいくつかありますが、私はこちらを使って、prometheusのメトリクスをHPAの指標に変換しています

が、設定が少し難しくてちゃんと理解せず雰囲気で使っていたので、今回理解を目指します
なかなかうまく言葉にできませんでした

資料

prometehus-adapterのドキュメント
https://github.com/kubernetes-sigs/prometheus-adapter/tree/master/docs
ここに全部書いてあるので、それを解読するだけの記事になります

HPAについて
https://kubernetes.io/ja/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
typeの種類、Pods Object External が今回関連してくるものなので押さえておきます

種類

大きな分類で3種類あります

  • rules
    • apiServiceで定義した、custom.metrics.k8s.ioで使用する
    • hpaでは type: Pods type: Object で使用する
  • resourceRules
    • hpaデフォルトで使用できるrecoucesを置き換えるものなのではないか、と思っている
    • ドキュメントに情報がないので今回これは放置で
  • externalRules
    • apiServiceで定義した、external.metrics.k8s.ioで使用する
    • hpaでは type: External で使用する

rules

設定例

rules:
- seriesQuery: '{__name__=~"^container_.*",container!="POD",namespace!="",pod!=""}'
  seriesFilters:
    - isNot: "^container_fs_.+"
  resources:
    overrides:
      namespace:
        resource: namespace
      pod:
        resource: pod
  name:
    matches: ^container_(.*)_seconds_total$
    as: ""
  metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[1m])) by (<<.GroupBy>>)

設定項目を上から順に見ていきます

seriesQuery

prometheusのどのメトリクスを変換するかを決めます

  • 変換したいメトリクスがひとつだけの場合は、単純にメトリクス名を直接書くだけでよい
  • 複数のメトリクスを変換したい場合は、クエリ形式でまとめて取得することもできる
    • その場合は例のように__name__を使用してごそっと取得
    • その他のlabelのフィルタも必要に応じて設定する
  • ここではあくまで、どのメトリクス(series)を変換対象にするか、を決める

seriesFilters

seriesQueryで取得したメトリクスを更に正規表現でフィルタリングします
例えば、__name__で一回で絞り込みきれなかった場合などに使用します

  • is で一致したものを残す
  • isNot で一致したものを除外

resources

メトリクスとkubernetesのresourceを繋ぐ設定です
ここが設定のキモなんですが、文章で説明するのが難しい部分です

例えばpodの場合だと、

resources:
  overrides:
    <<メトリクスのnamespaceのlabel名>>:
      resource: namespace
    <<メトリクスのpodのlable名>>:
      resource: pod

という形になります
もう少し具体的な例を挙げると

some_metrics{kubernetes_namespace="samplenamespace",kubernetes_pod_name="samplepodname"}
というメトリクスを変換したい場合、seriesQueryでsome_metricsを指定した上で

resources:
  overrides:
    kubernetes_namespace:
      resource: namespace
    kubernetes_pod_name:
      resource: pod

という形になります
podだけでなく、全てのresourceの変換が可能です
実はapi-groupを指定する必要があるのですが、podはcore apiなので指定不要になっています
例えばingressの場合だと

resources:
  overrides:
    <<メトリクスのnamespaceのlabel名>>:
      resource: namespace
    <<メトリクスのingressのlable名>>:
      group: networking.k8s.io
      resource: ingress

となります、意外と定型文な部分が多いです

以下のように対応するlabel名が同じになっていて、一見設定不要な形になっている場合も定義を書く必要があります
some_metrics{namespace="mynamespace",pod="mypodname"}

resources:
  overrides:
    namespace:
      resource: namespace
    pod:
      resource: pod

また、置き換えパターンが決まっている場合、テンプレートで一括変換可能です
例えば

resources:
  overrides:
    kubernetes_pod_name:
      resource: "pod"
    kubernetes_namespace_name:
      resource: "namespace"

この設定は、以下のようにも設定可能です

resources:
  template: "kubernetes_<<.Resource>>_name"

変数になっている部分は、<<.Group>><<.Resource>>が使用可能みたいですが、Groupは推測されるので必須ではないとかなんとか(ここちょっと追いきれてないです、使わなさそうなので…)

name

HPAを設定する時に使用する名前を決められます

matchesで正規表現を使って元のメトリクス名からキャプチャして
asでそのキャプチャ結果を使用して名前を決められます

name:
  matches: "^(.*)_total$"
  as: "${1}_per_second"

この設定の場合、container_requests_totalという名前のメトリクスはcontainer_requests_per_secondという名前でHPAで使用できます

matchesの指定がない場合、デフォルトは .* になり、matchesにキャプチャ(括弧)が使用されていない場合、asのデフォルトは$0となります
何も指定しないとメトリクス名がそのまま使用されるということですかね

matchesでキャプチャが使われていると、asのデフォルトは$1になります

(このあたり軽く謎仕様の雰囲気を感じます)

metricsQuery

対象になるseriesに対して、メトリクスとなる値を算出するためのクエリを定義します
以下の変数(テンプレート?)を使用しています

  • <<.Series>>
    • メトリクス名
    • seriesQueryで__name__で正規表現を使わずに単一のメトリクスを設定する場合は、これを使わず直接メトリクス名を書くのでも問題ない
  • <<.LabelMatchers>>
    • resourcesでの設定を使用して作られたlabel matcher
    • 上のresourceでの例だとこんな値になる
      • kubenetes_pod_name=~"samplepodname",kubernetes_namespace=~"samplenamespace"
    • 複数のpodのメトリクスを取得する場合は、正規表現で並ぶ pod=~"mypod1|mypod2"
    • 他にlabelを指定したい場合は、前後にコンマで繋げてlabelを追加できる
      • {<<.LabelMatchers>>,container!="POD"} {container!="POD",<<.LabelMatchers>>}
  • <<.GroupBy>>
    • .LabelMatchersで使用されているのと同じlabelのkeyがコンマ区切りで入る

基本は以上の3つで事足りますが、以下も用意されています

  • <<.LabelValuesByName>>
    • .GroupByとは逆で、こっちは値のリスト
    • 複数labelのを全部混ぜるんだろうか?namespaceとpodとか…
    • |で区切り
  • <<.GroupBySlice>>
    • GroupByのスライス…よくわからなかった…

externalRules

podなどのリソースと紐付けにくいメトリクスを指標に変換したい場合はこちらのexternalRulesを使用します
worker処理待ちのキュー数などがこれになります

使用例

externalRules:
- seriesQuery: '{__name__=~"^.*_queue_(length|size)$",namespace!=""}'
  resources:
    overrides:
      namespace:
        resource: namespace
  name:
    matches: ^.*_queue_(length|size)$
    as: "$0"
  metricsQuery: max(<<.Series>>{<<.LabelMatchers>>})

基本的にはrulesと同じ書式なのですが、resourcesでkubernetesのリソースと紐付ける部分が減っています
v0.8以前ではnamespaceが必要になっているので、hpaがあるnamespaceと同じnamespaceのlabelをメトリクスに付与しておく必要があります(key名は変換できるので同じ値のlabelがあればなんとかなる)
v0.9以降ではnamespaceを指定しない方法もできていますが、v0.8以降はkubernetes-external-secretと衝突する問題があるので私は使えていません…

実演

全体的にうまいこと説明できなかったので軽く実演して誤魔化すことにします
以下の設定でprometheus-adapterを起動します
ちなみにバージョンはv0.7

rules:
- seriesQuery: 'container_threads'
  seriesFilters: []
  resources:
    overrides:
      namespace:
        resource: namespace
      pod:
        resource: pod
  name:
    matches: ""
    as: pod_test
  metricsQuery: 'sum(<<.Series>>{<<.LabelMatchers>>,container!="POD",container!=""}) by (<<.GroupBy>>)'
- seriesQuery: 'kube_service_created'
  seriesFilters: []
  resources:
    overrides:
      namespace:
        resource: namespace
      service:
        resource: service
  name:
    matches: ""
    as: service_test
  metricsQuery: 'time() - sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)'
externalRules:
- seriesQuery: container_fs_inodes_total
  seriesFilters: []
  resources:
    overrides:
      namespace:
        resource: namespace
  name:
    matches: ""
    as: "external_test"
  metricsQuery: 'sum(<<.Series>>{<<.LabelMatchers>>})'

使ってるメトリクスはcAdvisorとkube-state-metricsのメトリクスを取得していればあると思います、たぶん

  • 1番目
    • メトリクスcontainer_threadsを変換してpod_testとして使用
      • podで使えるメトリクスを適当に選んだ
    • resourceはpodで、hpaではPodsで使用する
    • metricsQueryでlabel container がPODのものと空のものを除外する
  • 2番目
    • メトリクスkube_service_createdを変換してservice_testとして使用
    • resourceはserviceで、hpaではObjectで使用する
    • metricsQueryでは、serviceが作られてからの経過時間を計算している
  • 3番目
    • externalRules
    • メトリクスcontainer_fs_inodes_totalを変換してexternal_testとして使用
      • 普通ならpodsで使うようなメトリクスだけど、デフォルトで出るメトリクスのうちexternalで使える良さそうなのが見当たらなかったので、externalとして使用する
    • metricsQueryでnamespace内にある全てのコンテナのcontainer_fs_inodes_totalを合計する
      • 値を出すためなので特に意味はない

この設定に対して、テスト用のdeploymentとserviceをapplyして

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80

そこにhpaをapply

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx
  minReplicas: 2
  maxReplicas: 5
  metrics:
    - type: Pods
      pods:
        metric:
          name: pod_test
        target:
          type: AverageValue
          averageValue: "10000"
    - type: Object
      object:
        describedObject:
          kind: service
          name: nginx
        metric:
          name: service_test
        target:
          type: AverageValue
          averageValue: "10000"
    - type: External
      external:
        metric:
          name: external_test
        target:
          type: Value
          value: "10000"

prometheus-adapterに設定した3種類をそれぞれ使用するようにHPAを設定します

  • 共通
    • type: Valueは取得した値をそのまま使う
      • 値がすでにpodの数で割られてるならこっちを使用
    • type: AverageValueは取得した値を現在のpodの数で割って使う
  • 1番目
    • type: Pods
    • prometheus-adapterで設定したresourceがpodの場合はtype: Podsを使用する
    • ValueAverageValueどっちを使っていいのか迷ったけど、Valueで設定しようとするとエラーになるのでAverageValueを使っておけばよさそう
  • 2番目
    • type: Object
    • resourceがpod以外のrulesはtype: Objectを使用して、describedObjectで対象リソース詳細を設定する
  • 3番目
    • type: External
    • 意外と語ることがない

HPAをdescrieしてみます

$ kubectl describe hpa nginx
Name:                                                      nginx
Namespace:                                                 default
Labels:                                                    <none>
Annotations:                                               <none>
CreationTimestamp:                                         Sun, 12 Dec 2021 23:28:54 +0900
Reference:                                                 Deployment/nginx
Metrics:                                                   ( current / target )
  "pod_test" on pods:                                      3 / 10k
  "service_test" on service/nginx (target average value):  85760m / 10k
  "external_test" (target value):                          74 / 10k
Min replicas:                                              2
Max replicas:                                              5
Deployment pods:                                           2 current / 2 desired

HPAでちゃんと使えてますね

デバッグ

ちなみにapiを直接叩いて設定できてるかの確認も可能です

rules
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2
externalRules
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1

メトリクス詳細
rules
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/<<namespace名>>/<<リソース種(podsなど)>>/<<pod名(全podの場合は*)>>/<<メトリクス名>>"
externalRules
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/mynamespace/<<namespace名>>/<<メトリクス名>>"

今回の場合だと

$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/pod_test
$ kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/service/*/service_test
$ kubectl get --raw /apis/external.metrics.k8s.io/v1beta1/namespaces/default/external_test

こんな感じでデバッグできます

以上

ちょっとまとまりがなさすぎる感じになってしまいました
いやはや難しい

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?