探索 Kubernetes HPA


作者:魔方雲,原文鏈接

HPA簡介

HPA(Horizontal Pod Autoscaler)是kubernetes(以下簡稱k8s)的一種資源對象,能夠根據某些指標對在statefulSet、replicaController、replicaSet等集合中的pod數量進行動態伸縮,使運行在上面的服務對指標的變化有一定的自適應能力。

HPA目前支持四種類型的指標,分別是Resource、Object、External、Pods。其中在穩定版本autoscaling/v1中只支持對CPU指標的動態伸縮,在測試版本autoscaling/v2beta2中支持memory和自定義指標的動態伸縮,並以annotation的方式工作在autoscaling/v1版本中。

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表示平均值。
  • metrics中的type字段有四種類型的值:Object、Pods、Resource、External。
  • Resource指的是當前伸縮對象下的pod的cpu和memory指標,只支持Utilization和AverageValue類型的目標值。
  • Object指的是指定k8s內部對象的指標,數據需要第三方adapter提供,只支持Value和AverageValue類型的目標值。
  • Pods指的是伸縮對象(statefulSet、replicaController、replicaSet)底下的Pods的指標,數據需要第三方的adapter提供,並且只允許AverageValue類型的目標值。
  • 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

部署prometheus應用,使其正常工作。可以使用官方的helm包快捷部署,之后的應用也基本以helm包的方式部署,不再贅述。

部署需要伸縮的應用。這里我選擇了一個簡單的nginx服務,部署為deployment作為伸縮對象。

在應用的命名空間下,部署提供自定義指標的應用。這里我選擇了官方的prometheus-node-exporter,並以nodeport的方式暴露數據端口,作為自定義指標數據的來源。這個應用會以daemonSet的方式運行在k8s集群的每個node上,並對外開放在自己node上獲取到的指標。

在prometheus的界面上已經可以看到node-exporter暴露出來的指標了。

部署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!=""}'

上述配置文件將secondstotal和_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和監控對象,伸縮對象一定要部署在同一個命名空間下,不然會獲取不到相應的指標。部署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 -o yaml
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就部署成功了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM