HPA簡介php
HPA(Horizontal Pod Autoscaler)是kubernetes(如下簡稱k8s)的一種資源對象,可以根據某些指標對在statefulSet、replicaController、replicaSet等集合中的pod數量進行動態伸縮,使運行在上面的服務對指標的變化有必定的自適應能力。node
HPA目前支持四種類型的指標,分別是Resource、Object、External、Pods。其中在穩定版本autoscaling/v1
中只支持對CPU指標的動態伸縮,在測試版本autoscaling/v2beta2
中支持memory和自定義指標的動態伸縮,並以annotation的方式工做在autoscaling/v1
版本中。nginx
HPA在k8s中的結構 正則表達式
首先能夠看一下HPA在k8s中的結構,這裏找了一個k8s官方給出的HPA例子,我在關鍵字段上給出一些註釋方便理解。算法
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: php-apache namespace: default spec: # HPA的伸縮對象描述,HPA會動態修改該對象的pod數量 scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache # HPA的最小pod數量和最大pod數量 minReplicas: 1 maxReplicas: 10 # 監控的指標數組,支持多種類型的指標共存 metrics: # Object類型的指標 - type: Object object: metric: # 指標名稱 name: requests-per-second # 監控指標的對象描述,指標數據來源於該對象 describedObject: apiVersion: networking.k8s.io/v1beta1 kind: Ingress name: main-route # Value類型的目標值,Object類型的指標只支持Value和AverageValue類型的目標值 target: type: Value value: 10k # Resource類型的指標 - type: Resource resource: name: cpu # Utilization類型的目標值,Resource類型的指標只支持Utilization和AverageValue類型的目標值 target: type: Utilization averageUtilization: 50 # Pods類型的指標 - type: Pods pods: metric: name: packets-per-second # AverageValue類型的目標值,Pods指標類型下只支持AverageValue類型的目標值 target: type: AverageValue averageValue: 1k # External類型的指標 - type: External external: metric: name: queue_messages_ready # 該字段與第三方的指標標籤相關聯,(此處官方文檔有問題,正確的寫法以下) selector: matchLabels: env: "stage" app: "myapp" # External指標類型下只支持Value和AverageValue類型的目標值 target: type: AverageValue averageValue: 30
autoscaling/v1
版本將metrics字段放在了annotation中進行處理。
target共有3種類型:Utilization、Value、AverageValue。Utilization表示平均使用率;Value表示裸值;AverageValue表示平均值。apache
metrics中的type字段有四種類型的值:Object、Pods、Resource、External。 api
Resource指的是當前伸縮對象下的pod的cpu和memory指標,只支持Utilization和AverageValue類型的目標值。 數組
Object指的是指定k8s內部對象的指標,數據須要第三方adapter提供,只支持Value和AverageValue類型的目標值。 app
Pods指的是伸縮對象(statefulSet、replicaController、replicaSet)底下的Pods的指標,數據須要第三方的adapter提供,而且只容許AverageValue類型的目標值。 ide
External指的是k8s外部的指標,數據一樣須要第三方的adapter提供,只支持Value和AverageValue類型的目標值。
HPA動態伸縮的原理
HPA在k8s中也由一個controller控制,controller會間隔循環HPA,檢查每一個HPA中監控的指標是否觸發伸縮條件,默認的間隔時間爲15s。一旦觸發伸縮條件,controller會向k8s發送請求,修改伸縮對象(statefulSet、replicaController、replicaSet)子對象scale中控制pod數量的字段。k8s響應請求,修改scale結構體,而後會刷新一次伸縮對象的pod數量。伸縮對象被修改後,天然會經過list/watch
機制增長或減小pod數量,達到動態伸縮的目的。
HPA伸縮過程敘述
HPA的伸縮主要流程以下:
1.判斷當前pod數量是否在HPA設定的pod數量區間中,若是不在,太小返回最小值,過大返回最大值,結束伸縮。
2.判斷指標的類型,並向api server發送對應的請求,拿到設定的監控指標。通常來講指標會根據預先設定的指標從如下三個aggregated APIs
中獲取:metrics.k8s.io
、custom.metrics.k8s.io
、 external.metrics.k8s.io
。其中metrics.k8s.io
通常由k8s自帶的metrics-server來提供,主要是cpu,memory使用率指標,另外兩種須要第三方的adapter來提供。custom.metrics.k8s.io
提供自定義指標數據,通常跟k8s集羣有關,好比跟特定的pod相關。external.metrics.k8s.io
一樣提供自定義指標數據,但通常跟k8s集羣無關。許多知名的第三方監控平臺提供了adapter實現了上述api(如prometheus),能夠將監控和adapter一同部署在k8s集羣中提供服務,甚至可以替換原來的metrics-server來提供上述三類api指標,達到深度定製監控數據的目的。
3.根據得到的指標,應用相應的算法算出一個伸縮係數,並乘以目前pod數量得到指望pod數量。係數是指標的指望值與目前值的比值,若是大於1表示擴容,小於1表示縮容。指標數值有平均值(AverageValue)、平均使用率(Utilization)、裸值(Value)三種類型,每種類型的數值都有對應的算法。如下幾點值得注意:若是係數有小數點,統一進一;係數若是未達到某個容忍值,HPA認爲變化過小,會忽略此次變化,容忍值默認爲0.1。
HPA擴容算法是一個很是保守的算法。若是出現獲取不到指標的狀況,擴容時算最小值,縮容時算最大值;若是須要計算平均值,出現pod沒準備好的狀況,平均數的分母不計入該pod。
一個HPA支持多個指標的監控,HPA會循環獲取全部的指標,並計算指望的pod數量,並從指望結果中得到最大的pod數量做爲最終的伸縮的pod數量。一個伸縮對象在k8s中容許對應多個HPA,可是隻是k8s不會報錯而已,事實上HPA彼此不知道本身監控的是同一個伸縮對象,在這個伸縮對象中的pod會被多個HPA無心義地來回修改pod數量,給系統增長消耗,若是想要指定多個監控指標,能夠如上述所說,在一個HPA中添加多個監控指標。
4.檢查最終的pod數量是否在HPA設定的pod數量範圍的區間,若是超過最大值或不足最小值都會修改成最大值或最小值。而後向k8s發出請求,修改伸縮對象的子對象scale的pod數量,結束一個HPA的檢查,獲取下一個HPA,完成一個伸縮流程。
HPA的應用場景
HPA的特性結合第三方的監控應用,使得部署在HPA伸縮對象(statefulSet、replicaController、replicaSet)上的服務有了很是靈活的自適應能力,可以在必定限度內複製多個副原本應對某個指標的急劇飆升,也能夠在某個指標較小的狀況下刪除副原本讓出計算資源給其餘更須要資源的應用使用,維持整個系統的穩定。很是適合於一些流量波動大,機器資源吃緊,服務數量多的業務場景,如:電商服務、搶票服務、金融服務等。
k8s-prometheus-adapter
前文說到,許多監控系統經過adapter實現了接口,給HPA提供指標數據。在這裏咱們具體介紹一下prometheus監控系統的adapter。
prometheus是一個知名開源監控系統,具備數據維度多,存儲高效,使用便捷等特色。用戶能夠經過豐富的表達式和內置函數,定製本身所須要的監控數據。
prometheus-adapter在prometheus和api-server中起到了適配者的做用。prometheus-adapter接受從HPA中發來,經過apiserver aggregator中轉的指標查詢請求,而後根據內容發送相應的請求給prometheus拿到指標數據,通過處理後返回給HPA使用。prometheus能夠同時實現metrics.k8s.io
、custom.metrics.k8s.io
、 external.metrics.k8s.io
三種api接口,代替k8s本身的matrics-server,提供指標數據服務。
prometheus-adapter部署可否成功的關鍵在於配置文件是否正確。配置文件中能夠設定須要的指標以及對指標的處理方式,如下是一份簡單的配置文件,加上註釋以稍作解釋。
# 指標規則,能夠多個規則共存,上一個規則的結果會傳給下一個規則 rules: # 計算指標數據的表達式 - metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>) # 指標重命名,支持正則表達式。這裏表示刪除指標名字中的"_seconds_total" name: as: "" matches: (.*)_seconds_total$ # 指標與k8s資源經過標籤關聯,這裏將指標經過標籤與k8s的namspace和pod相互關聯 resources: overrides: namespace: resource: namespace pod: resource: pod # 過濾指標條件 seriesFilters: [] # 指標查詢表達式,能夠根據標籤等條件,篩選特定的指標 seriesQuery: '{namespace!="",pod!=""}'
metricsQuery
字段會在k8s請求時執行,其中,「<<」 「>>」是go的模板語法,Series表示指標名稱,LabelMatchers表示指標與k8s對象名稱匹配的標籤鍵值對,GroupBy表示指標數值歸併的標籤名。
不太好理解,這裏截取官方文檔中的一個例子來進行說明,看了就應該明白了:
For instance, suppose we had a series `http_requests_total` (exposed as`http_requests_per_second` in the API) with labels `service`, `pod`,`ingress`, `namespace`, and `verb`. The first four correspond to Kubernetes resources. Then, if someone requested the metric `pods/http_request_per_second` for the pods `pod1` and `pod2` in the `somens` namespace, we’d have: \- `Series: "http_requests_total"` \- `LabelMatchers: "pod=~\"pod1|pod2",namespace="somens"` \- `GroupBy`: `pod`
resources
字段是k8s資源對象與指標關聯的重要字段,本質上是根據指標的標籤值與k8s資源對象名稱進行匹配,resources
字段告訴系統,應該用指標的哪一個標籤的標籤值來匹配k8s資源對象名稱。有兩個方法關聯資源,一種是經過overrides,將特定的標籤與k8s資源對象綁定起來,當k8s請求指標的時候,會將資源對象名稱與這個特定的標籤值進行比較,來分辨指標具體是哪一個對象的;另外一種是template,經過go語言模板語法將k8s資源對象名轉化成標籤名,進行匹配。
第二種方法,不太好理解,一樣截取官方文檔中的一個例子來進行說明:
# any label `kube_<group>_<resource>` becomes <group>.<resource> in Kubernetes resources: template: "kube_<<.Group>>_<<.Resource>>"
部署一個監控自定義指標的HPA
1.部署prometheus應用,使其正常工做。可使用官方的helm包快捷部署,以後的應用也基本以helm包的方式部署,再也不贅述。
2.部署須要伸縮的應用。這裏我選擇了一個簡單的nginx服務,部署爲deployment做爲伸縮對象。
3.在應用的命名空間下,部署提供自定義指標的應用。這裏我選擇了官方的prometheus-node-exporter,並以nodeport的方式暴露數據端口,做爲自定義指標數據的來源。這個應用會以daemonSet的方式運行在k8s集羣的每一個node上,並對外開放在本身node上獲取到的指標。
在prometheus的界面上已經能夠看到node-exporter暴露出來的指標了
4.部署prometheus-adapter應用。在helm包的values中修改其配置文件,配置文件以下
rules: - metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>) name: as: "" matches: (.*)_seconds_total$ resources: template: <<.Resource>> seriesFilters: [] seriesQuery: '{namespace!="",pod!=""}' - metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>) name: as: "" matches: (.*)_total$ resources: template: <<.Resource>> seriesFilters: - isNot: (.*)_seconds_total$ seriesQuery: '{namespace!="",pod!=""}' - metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>) name: as: "" matches: (.*)$ resources: template: <<.Resource>> seriesFilters: - isNot: (.*)_total$ seriesQuery: '{namespace!="",pod!=""}'
上述配置文件將_seconds_total和_total結尾的指標用prometheus的內置函數進行了速率計算,並去掉對應的後綴做爲指標名稱。
咱們在k8s中利用kubectl查看指標是否可以得到到指標
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/
你會看到全部的你可以得到的指標名稱,可是名字和數據已經和原來的指標有所不一樣了,由於咱們在adapter的配置文件中作了必定程度的修改。
而後咱們獲取一下node_cpu
這個指標,看看是否可以正確地顯示出來
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-nginx/pods/*/node_cpu | jq
這個命令可以顯示my-nginx這個namspace下的全部pod的node_cpu
指標的數據,結果以下
{ "kind": "MetricValueList", "apiVersion": "custom.metrics.k8s.io/v1beta1", "metadata": { "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/my-nginx/pods/%2A/node_cpu" }, "items": [ { "describedObject": { "kind": "Pod", "namespace": "my-nginx", "name": "prometheus-node-exporter-b25zl", "apiVersion": "/v1" }, "metricName": "node_cpu", "timestamp": "2019-10-29T03:33:47Z", "value": "3822m" } ] }
ok,到這裏,說明全部的組件工做正常,hpa可以順利地獲得這個指標。須要注意的是HPA和監控對象,伸縮對象必定要部署在同一個命名空間下,否則會獲取不到相應的指標。
5.部署hpa,其yaml文件以下
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: hpa-asdfvs namespace: my-nginx spec: scaleTargetRef: apiVersion: apps/v1beta1 kind: Deployment name: my-nginx minReplicas: 1 maxReplicas: 10 metrics: - type: Object object: metric: name: node_cpu describedObject: apiVersion: v1 kind: Pod name: prometheus-node-exporter-b25zl target: type: Value value: 9805m
咱們將根據prometheus-node-exporter這個pod的node_cpu這個指標來動態伸縮咱們的nginx應用。
咱們來獲取一下這個hpa
kubectl get horizontalPodAutoscaler -n my-nginx hpa-asdfvs -oyaml
apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: annotations: autoscaling.alpha.kubernetes.io/conditions: '[{"type":"AbleToScale","status":"True","lastTransitionTime":"2019-10-29T02:54:50Z","reason":"ReadyForNewScale","message":"recommended size matches current size"},{"type":"ScalingActive","status":"True","lastTransitionTime":"2019-10-29T03:05:24Z","reason":"ValidMetricFound","message":"the HPA was able to successfully calculate a replica count from Pod metric node_cpu"},{"type":"ScalingLimited","status":"False","lastTransitionTime":"2019-10-29T02:54:50Z","reason":"DesiredWithinRange","message":"the desired count is within the acceptable range"}]' autoscaling.alpha.kubernetes.io/current-metrics: '[{"type":"Object","object":{"target":{"kind":"Pod","name":"prometheus-node-exporter-b25zl","apiVersion":"v1"},"metricName":"node_cpu","currentValue":"3822m"}}]' autoscaling.alpha.kubernetes.io/metrics: '[{"type":"Object","object":{"target":{"kind":"Pod","name":"prometheus-node-exporter-b25zl","apiVersion":"v1"},"metricName":"node_cpu","targetValue":"9805m"}}]' kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"autoscaling/v2beta2","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{},"name":"hpa-asdfvs","namespace":"my-nginx"},"spec":{"maxReplicas":10,"metrics":[{"object":{"describedObject":{"apiVersion":"v1","kind":"Pod","name":"prometheus-node-exporter-b25zl"},"metric":{"name":"node_cpu"},"target":{"type":"Value","value":"9805m"}},"type":"Object"}],"minReplicas":1,"scaleTargetRef":{"apiVersion":"apps/v1beta1","kind":"Deployment","name":"my-nginx"}}} creationTimestamp: "2019-10-29T02:54:45Z" name: hpa-asdfvs namespace: my-nginx resourceVersion: "164701" selfLink: "/apis/autoscaling/v1/namespaces/my-nginx/horizontalpodautoscalers/hpa-asdfvs" uid: 76fa6a19-f9f7-11e9-8930-0242c5ccd054 spec: maxReplicas: 10 minReplicas: 1 scaleTargetRef: apiVersion: apps/v1beta1 kind: Deployment name: my-nginx status: currentReplicas: 1 desiredReplicas: 1 lastScaleTime: "2019-10-29T03:06:10Z"
能夠看到hpa以annotation的方式在v1版本工做,並將matrics字段寫入了annotation中。在annotation的condition字段中,咱們清楚地看到HPA已經獲取到了這個指標。以後咱們能夠嘗試下降target值來讓pod擴展,或者增長target值來使pod縮減。到這裏一個監控自定義指標的HPA就部署成功了。