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
で使用する
- apiServiceで定義した、
- resourceRules
- hpaデフォルトで使用できる
recouces
を置き換えるものなのではないか、と思っている - ドキュメントに情報がないので今回これは放置で
- hpaデフォルトで使用できる
- externalRules
- apiServiceで定義した、
external.metrics.k8s.io
で使用する - hpaでは
type: External
で使用する
- apiServiceで定義した、
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
でlabelcontainer
が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
を使用する -
Value
とAverageValue
どっちを使っていいのか迷ったけど、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
こんな感じでデバッグできます
以上
ちょっとまとまりがなさすぎる感じになってしまいました
いやはや難しい