天天看點

騰訊開源雲原生成本優化神器 - FinOps Crane

作者:K8s技術圈

Crane 是一個基于 FinOps 的雲資源分析與成本優化平台,它的願景是在保證客戶應用運作品質的前提下實作極緻的降本。Crane 已經在騰訊内部自研業務實作了大規模落地,部署數百個 K8s 叢集、管控 CPU 核數達百萬,在降本增效方面取得了階段性成果。以騰訊某部門叢集優化為例,通過使用 FinOps Crane,該部門在保障業務穩定的情況下,資源使用率提升了 3 倍;騰訊另一自研業務落地 Crane 後,在一個月内實作了總 CPU 規模 40 萬核的節省量,相當于成本節約超過 1000 萬元/月。

騰訊開源雲原生成本優化神器 - FinOps Crane

Crane 會通過下面 3 個方面來開啟成本優化之旅:

  • 成本展示: Kubernetes 資源( Deployments, StatefulSets )的多元度聚合與展示。
  • 成本分析: 周期性的分析叢集資源的狀态并提供優化建議。
  • 成本優化: 通過豐富的優化工具更新配置達成降本的目标。

核心功能包括:成本可視化和優化評估;内置了多種推薦器 - 資源推薦、副本推薦、閑置資源推薦;基于預測的水準彈性器;負載感覺的排程器;基于 QOS 的混部。下面我們來詳細了解下 Crane 的各項功能。

安裝

我們這裡使用 Helm 的方式來進行安裝,首先需要安裝 Prometheus 和 Grafana(如果您已經在環境中部署了 Prometheus 和 Grafana,可以跳過該步驟)。

Crane 使用 Prometheus 擷取叢集工作負載對資源的使用情況,可以使用如下所示指令安裝 Prometheus:

$ helm repo add prometheus-community https://finops-helm.pkg.coding.net/gocrane/prometheus-community
$ helm upgrade --install prometheus -n crane-system \
    --set pushgateway.enabled=false \
    --set alertmanager.enabled=false \
    --set server.persistentVolume.enabled=false \
    -f https://gitee.com/finops/helm-charts/raw/main/integration/prometheus/override_values.yaml \
    --create-namespace  prometheus-community/prometheus
           

由于 Crane 的 Fadvisor 會使用 Grafana 來展示成本預估,是以我們也需要安裝 Grafana:

$ helm repo add grafana https://finops-helm.pkg.coding.net/gocrane/grafana
$ helm upgrade --install grafana \
    -f https://gitee.com/finops/helm-charts/raw/main/integration/grafana/override_values.yaml \
    -n crane-system \
    --create-namespace grafana/grafana
           

上面我們指定的 values 檔案中配置了 Prometheus 資料源以及一些相關的 Dashboard,直接安裝後即可使用。

然後接下來安裝 crane 與 fadvisor,同樣直接使用 Helm Chart 安裝即可,如下指令所示:

$ helm repo add crane https://finops-helm.pkg.coding.net/gocrane/gocrane
$ helm upgrade --install crane -n crane-system --create-namespace crane/crane
$ helm upgrade --install fadvisor -n crane-system --create-namespace crane/fadvisor
           

安裝後可以檢視 Pod 清單了解應用狀态:

$ kubectl get pods -n crane-system
NAME                                             READY   STATUS             RESTARTS         AGE
crane-agent-8jrs5                                0/1     CrashLoopBackOff   71 (2m26s ago)   3h23m
crane-agent-t2rpz                                0/1     CrashLoopBackOff   71 (65s ago)     3h23m
craned-776c7b6c75-gx8cp                          2/2     Running            0                3h28m
fadvisor-56fcc547b6-zvf6r                        1/1     Running            0                158m
grafana-5cd57f9f6b-d7nk5                         1/1     Running            0                3h32m
metric-adapter-887f6548d-qcbb8                   1/1     Running            0                3h28m
prometheus-kube-state-metrics-5f6f856ffb-4lrrr   1/1     Running            0                3h34m
prometheus-node-exporter-97vmz                   1/1     Running            0                3h27m
prometheus-node-exporter-m2gr9                   1/1     Running            0                3h27m
prometheus-server-7744f66fb4-lw2sz               2/2     Running            0                3h34m
           

需要注意我們這裡 crane-agent 啟動失敗了,這是因為我的 K8s 叢集使用的是 containerd 這種容器運作時,需要明确聲明指定使用的運作時 endpoint:

$ kubectl edit ds crane-agent -n crane-system
# ......
    spec:
      containers:
      - args:
        - --v=2
        - --runtime-endpoint=/run/containerd/containerd.sock  # 指定有containerd的sock檔案
        command:
        - /crane-agent
# ......
           

此外還需要更新 crane-agent 的 rbac 權限:

$ kubectl edit clusterrole crane-agent
# ......
- apiGroups:
  - ensurance.crane.io
  resources:
  - podqosensurancepolicies
  - nodeqoss  # 增加 nodeqoss 和 podqoss 資源的權限
  - podqoss
# ......
           

然後我們可以再建立一個 Ingress 對象來暴露 crane 的 dashboard 服務:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-crane-dashboard
  namespace: crane-system
spec:
  ingressClassName: nginx
  rules:
    - host: crane.k8s.local # change to your domain
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: craned
                port:
                  number: 9090
           

直接應用該 ingress 資源對象即可,當然前提是你已經安裝了 ingress-nginx:

$ kubectl get pods -n ingress-nginx
NAME                                            READY   STATUS    RESTARTS      AGE
ingress-nginx-controller-7647c44fb9-6gcsf       1/1     Running   8 (44m ago)   21d
ingress-nginx-defaultbackend-7fc5bfd66c-gqmmj   1/1     Running   8 (44m ago)   21d
$ kubectl get ingress -n crane-system
NAME                      CLASS   HOSTS             ADDRESS        PORTS   AGE
ingress-crane-dashboard   nginx   crane.k8s.local   192.168.0.52   80      11s
           

将 crane.k8s.local 映射到 192.168.0.52 後就可以通路 crane 的 dashboard 了:

騰訊開源雲原生成本優化神器 - FinOps Crane

第一次通路 dashboard 的時候需要添加一個 K8s 叢集,添加添加叢集按鈕開始添加,填入正确的 CRNE Endpoint 位址即可。

然後切換到叢集總覽可以檢視到目前叢集的一些成本相關資料,由于目前資料還不足,是以會有一些空的圖表。

在成本分布頁面可以按照次元成本、叢集成本和使用率名額以及命名空間成本來展示成本的分布情況。

智能推薦

在 dasbhoard 中開箱後就可以看到相關的成本資料,是因為在添加叢集的時候我們安裝了推薦的規則。

推薦架構會自動分析叢集的各種資源的運作情況并給出優化建議。Crane 的推薦子產品會定期檢測發現叢集資源配置的問題,并給出優化建議。智能推薦提供了多種 Recommender 來實作面向不同資源的優化推薦。

在成本分析>推薦規則頁面可以看到我們安裝的兩個推薦規則。

這些推薦規則實際上是安裝在 K8s 叢集上的 RecommendationRule CRD 對象:

$ kubectl get RecommendationRule
NAME             RUNINTERVAL   AGE
idlenodes-rule   24h           16m
workloads-rule   24h           16m
           

workloads-rule 這個推薦規則的資源對象如下所示:

apiVersion: analysis.crane.io/v1alpha1
kind: RecommendationRule
metadata:
  name: workloads-rule
  labels:
    analysis.crane.io/recommendation-rule-preinstall: "true"
spec:
  resourceSelectors:
    - kind: Deployment
      apiVersion: apps/v1
    - kind: StatefulSet
      apiVersion: apps/v1
  namespaceSelector:
    any: true
  runInterval: 24h
  recommenders:
    - name: Replicas
    - name: Resource
           

RecommendationRule 是一個全部範圍内的對象,該推薦規則會對所有命名空間中的 Deployments 和 StatefulSets 做資源推薦和副本數推薦。相關規範屬性如下所示:

  • 每隔 24 小時運作一次分析推薦,runInterval 格式為時間間隔,比如: 1h,1m,設定為空表示隻運作一次。
  • 待分析的資源通過配置 resourceSelectors 數組設定,每個 resourceSelector 通過 kind、apiVersion、name 選擇 K8s 中的資源,當不指定 name 時表示在 namespaceSelector 基礎上的所有資源。
  • namespaceSelector 定義了待分析資源的命名空間,any: true 表示選擇所有命名空間。
  • recommenders 定義了待分析的資源需要通過哪些 Recommender 進行分析。目前支援兩種 Recommender:
    • 資源推薦(Resource): 通過 VPA 算法分析應用的真實用量推薦更合适的資源配置
    • 副本數推薦(Replicas): 通過 HPA 算法分析應用的真實用量推薦更合适的副本數量

資源推薦

Kubernetes 使用者在建立應用資源時常常是基于經驗值來設定 request 和 limit,通過資源推薦的算法分析應用的真實用量推薦更合适的資源配置,你可以參考并采納它提升叢集的資源使用率。該推薦算法模型采用了 VPA 的滑動視窗(Moving Window)算法進行推薦:

  • 通過監控資料,擷取 Workload 過去一周(可配置)的 CPU 和記憶體的曆史用量。
  • 算法考慮資料的時效性,較新的資料采樣點會擁有更高的權重。
  • CPU 推薦值基于使用者設定的目标百分位值計算,記憶體推薦值基于曆史資料的最大值。

副本數推薦

Kubernetes 使用者在建立應用資源時常常是基于經驗值來設定副本數。通過副本數推薦的算法分析應用的真實用量推薦更合适的副本配置,同樣可以參考并采納它提升叢集的資源使用率。其實作的基本算法是基于工作負載曆史 CPU 負載,找到過去七天内每小時負載最低的 CPU 用量,計算按 50%(可配置)使用率和工作負載 CPU Request 應配置的副本數。

當我們部署 crane 的時候會在同一個命名空間中建立一個名為 recommendation-configuration 的 ConfigMap 對象,包含一個 yaml 格式的 RecommendationConfiguration,該配置訂閱了 recommender 的配置,如下所示:

$ kubectl get cm recommendation-configuration -n crane-system -oyaml
apiVersion: v1
data:
  config.yaml: |-
    apiVersion: analysis.crane.io/v1alpha1
    kind: RecommendationConfiguration
    recommenders:
      - name: Replicas  # 副本數推薦
        acceptedResources:
          - kind: Deployment
            apiVersion: apps/v1
          - kind: StatefulSet
            apiVersion: apps/v1
      - name: Resource  # 資源推薦
        acceptedResources:
          - kind: Deployment
            apiVersion: apps/v1
          - kind: StatefulSet
            apiVersion: apps/v1
kind: ConfigMap
metadata:
  name: recommendation-configuration
  namespace: crane-system
           

需要注意的是資源類型和 recommenders 需要可以比對,比如 Resource 推薦預設隻支援 Deployments 和 StatefulSets。

同樣的也可以再檢視一次閑置節點推薦規則的資源對象,如下所示:

$ kubectl get recommendationrule idlenodes-rule -oyaml
apiVersion: analysis.crane.io/v1alpha1
kind: RecommendationRule
metadata:
  labels:
    analysis.crane.io/recommendation-rule-preinstall: "true"
  name: idlenodes-rule
spec:
  namespaceSelector:
    any: true
  recommenders:
  - name: IdleNode
  resourceSelectors:
  - apiVersion: v1
    kind: Node
  runInterval: 24h
           

建立 RecommendationRule 配置後,RecommendationRule 控制器會根據配置定期運作推薦任務,給出優化建議生成 Recommendation 對象,然後我們可以根據優化建議 Recommendation 調整資源配置。

比如我們這裡叢集中已經生成了多個優化建議 Recommendation 對象。

$ kubectl get recommendations
NAME                            TYPE       TARGETKIND    TARGETNAMESPACE   TARGETNAME       STRATEGY   PERIODSECONDS   ADOPTIONTYPE          AGE
workloads-rule-resource-8whzs   Resource   StatefulSet   default           nacos            Once                       StatusAndAnnotation   34m
workloads-rule-resource-hx4cp   Resource   StatefulSet   default           redis-replicas   Once                       StatusAndAnnotation   34m
# ......
           

可以随便檢視任意一個優化建議對象。

$ kubectl get recommend workloads-rule-resource-g7nwp -n crane-system -oyaml
apiVersion: analysis.crane.io/v1alpha1
kind: Recommendation
metadata:
  name: workloads-rule-resource-g7nwp
  namespace: crane-system
spec:
  adoptionType: StatusAndAnnotation
  completionStrategy:
    completionStrategyType: Once
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: fadvisor
    namespace: crane-system
  type: Resource
status:
  action: Patch
  conditions:
  - lastTransitionTime: "2022-10-20T07:43:49Z"
    message: Recommendation is ready
    reason: RecommendationReady
    status: "True"
    type: Ready
  currentInfo: '{"spec":{"template":{"spec":{"containers":[{"name":"fadvisor","resources":{"requests":{"cpu":"0","memory":"0"}}}]}}}}'
  lastUpdateTime: "2022-10-20T07:43:49Z"
  recommendedInfo: '{"spec":{"template":{"spec":{"containers":[{"name":"fadvisor","resources":{"requests":{"cpu":"114m","memory":"120586239"}}}]}}}}'
  recommendedValue: |
    resourceRequest:
      containers:
      - containerName: fadvisor
        target:
          cpu: 114m
          memory: "120586239"
  targetRef: {}
           

在 dashboard 的資源推薦頁面也能檢視到優化建議清單。

在頁面中可以看到目前資源(容器/CPU/Memory)與推薦的資源資料,點選采納建議即可擷取優化的執行指令。

執行指令即可完成優化,其實就是修改資源對象的 resources 資源資料。

patchData=`kubectl get recommend workloads-rule-resource-g7nwp -n crane-system -o jsonpath='{.status.recommendedInfo}'`;kubectl patch Deployment fadvisor -n crane-system --patch "${patchData}"
           

對于閑置節點推薦,由于節點的下線在不同平台上的步驟不同,使用者可以根據自身需求進行節點的下線或者縮容。

應用在監控系統(比如 Prometheus)中的曆史資料越久,推薦結果就越準确,建議生産上超過兩周時間。對建立應用的預測往往不準。

自定義推薦

Recommendation Framework 提供了一套可擴充的 Recommender 架構并支援了内置的 Recommender,使用者可以實作一個自定義的 Recommender,或者修改一個已有的 Recommender。

和 K8s 排程架構類似,Recommender 接口定義了一次推薦需要實作的四個階段和八個擴充點,這些擴充點會在推薦過程中按順序被調用。這些擴充點中的一些可以改變推薦決策,而另一些僅用來提供資訊。

騰訊開源雲原生成本優化神器 - FinOps Crane

推薦架構架構

Recommender 接口定義如下所示:

type Recommender interface {
 Name() string
 framework.Filter
 framework.PrePrepare
 framework.Prepare
 framework.PostPrepare
 framework.PreRecommend
 framework.Recommend
 framework.PostRecommend
 framework.Observe
}

// Phase: Filter
type Filter interface {
    // Filter 将過濾無法通過目标推薦器推薦的資源
    Filter(ctx *RecommendationContext) error
}

// Phase: Prepare
type PrePrepare interface {
    CheckDataProviders(ctx *RecommendationContext) error
}

type Prepare interface {
    CollectData(ctx *RecommendationContext) error
}

type PostPrepare interface {
    PostProcessing(ctx *RecommendationContext) error
}

type PreRecommend interface {
    PreRecommend(ctx *RecommendationContext) error
}

// Phase: Recommend
type Recommend interface {
    Recommend(ctx *RecommendationContext) error
}

type PostRecommend interface {
    Policy(ctx *RecommendationContext) error
}

// Phase: Observe
type Observe interface {
    Observe(ctx *RecommendationContext) error
}
           

整個推薦過程分成了四個階段:Filter、Prepare、Recommend、Observe,階段的輸入是需要分析的 Kubernetes 資源,輸出是推薦的優化建議。接口中的 RecommendationContext 儲存了一次推薦過程中的上下文,包括推薦目标、RecommendationConfiguration 等資訊,我們可以根據自身需求增加更多的内容。

比如資源推薦就實作了 Recommender 接口,主要做了下面 3 個階段的處理:

  • Filter 階段:過濾沒有 Pod 的工作負載
  • Recommend 推薦:采用 VPA 的滑動視窗算法分别計算每個容器的 CPU 和記憶體并給出對應的推薦值
  • Observe 推薦:将推薦資源配置記錄到 crane_analytics_replicas_recommendation 名額

除了核心的智能推薦功能之外,Crane 還有很多進階特性,比如可以根據實際的節點使用率的動态排程器、基于流量預測的彈性 HPA 等等。

智能排程器

Crane 除了提供了智能推薦功能之外,還提供了一個排程器插件 Crane-scheduler 可以實作智能排程和完成拓撲感覺排程與資源配置設定的工作。

動态排程器

K8s 的原生排程器隻能通過資源的 requests 值來排程 pod,這很容易造成一系列負載不均的問題:

  • 對于某些節點,實際負載與資源請求相差不大,這會導緻很大機率出現穩定性問題。
  • 對于其他節點來說,實際負載遠小于資源請求,這将導緻資源的巨大浪費。

為了解決這些問題,動态排程器根據實際的節點使用率建構了一個簡單但高效的模型,并過濾掉那些負載高的節點來平衡叢集。

動态排程器依賴于 prometheus 和 node-exporter 收集彙總名額資料,它由兩個元件組成:

  • Node-annotator 定期從 Prometheus 拉取資料,并以 annotations 的形式在節點上用時間戳标記它們。
  • Dynamic plugin 直接從節點的 annotations 中讀取負載資料,過濾并基于簡單的算法對候選節點進行評分。

動态排程器提供了一個預設值排程政策,配置檔案如下所示:

# policy.yaml
apiVersion: scheduler.policy.crane.io/v1alpha1
 kind: DynamicSchedulerPolicy
 spec:
   syncPolicy:
     ##cpu usage
     - name: cpu_usage_avg_5m
       period: 3m
     - name: cpu_usage_max_avg_1h
       period: 15m
     - name: cpu_usage_max_avg_1d
       period: 3h
     ##memory usage
     - name: mem_usage_avg_5m
       period: 3m
     - name: mem_usage_max_avg_1h
       period: 15m
     - name: mem_usage_max_avg_1d
       period: 3h

   predicate:
     ##cpu usage
     - name: cpu_usage_avg_5m
       maxLimitPecent: 0.65
     - name: cpu_usage_max_avg_1h
       maxLimitPecent: 0.75
     ##memory usage
     - name: mem_usage_avg_5m
       maxLimitPecent: 0.65
     - name: mem_usage_max_avg_1h
       maxLimitPecent: 0.75

   priority:
     ##cpu usage
     - name: cpu_usage_avg_5m
       weight: 0.2
     - name: cpu_usage_max_avg_1h
       weight: 0.3
     - name: cpu_usage_max_avg_1d
       weight: 0.5
     ##memory usage
     - name: mem_usage_avg_5m
       weight: 0.2
     - name: mem_usage_max_avg_1h
       weight: 0.3
     - name: mem_usage_max_avg_1d
       weight: 0.5

   hotValue:
     - timeRange: 5m
       count: 5
     - timeRange: 1m
       count: 2
           

我們可以根據實際需求自定義該政策配置,預設政策依賴于以下名額:

  • cpu_usage_avg_5m
  • cpu_usage_max_avg_1h
  • cpu_usage_max_avg_1d
  • mem_usage_avg_5m
  • mem_usage_max_avg_1h
  • mem_usage_max_avg_1d

這幾個名額我們這裡是通過記錄規則建立的,可以檢視 Prometheus 的配置檔案來了解詳細資訊:

$ kubectl get cm -n crane-system prometheus-server -oyaml
apiVersion: v1
data:
  alerting_rules.yml: |
    {}
  alerts: |
    {}
  allow-snippet-annotations: "false"
  prometheus.yml: |
    global:
      evaluation_interval: 1m
      scrape_interval: 1m
      scrape_timeout: 10s
    rule_files:
    - /etc/config/recording_rules.yml
    - /etc/config/alerting_rules.yml
    - /etc/config/rules
    - /etc/config/alerts
    scrape_configs:
    - job_name: prometheus
      static_configs:
      - targets:
        - localhost:9090
    # ......
  recording_rules.yml: |
    groups:
    - interval: 3600s
      name: costs.rules
      rules:
    #   ......
    - interval: 30s
      name: scheduler.rules.30s
      rules:
      - expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[90s]))
          * 100)
        record: cpu_usage_active
      - expr: 100*(1-node_memory_MemAvailable_bytes/node_memory_MemTotal_bytes)
        record: mem_usage_active
    - interval: 1m
      name: scheduler.rules.1m
      rules:
      - expr: avg_over_time(cpu_usage_active[5m])
        record: cpu_usage_avg_5m
      - expr: avg_over_time(mem_usage_active[5m])
        record: mem_usage_avg_5m
    - interval: 5m
      name: scheduler.rules.5m
      rules:
      - expr: max_over_time(cpu_usage_avg_5m[1h])
        record: cpu_usage_max_avg_1h
      - expr: max_over_time(cpu_usage_avg_5m[1d])
        record: cpu_usage_max_avg_1d
      - expr: max_over_time(mem_usage_avg_5m[1h])
        record: mem_usage_max_avg_1h
      - expr: max_over_time(mem_usage_avg_5m[1d])
        record: mem_usage_max_avg_1d
  rules: |
    {}
kind: ConfigMap
metadata:
  name: prometheus-server
  namespace: crane-system
           

在排程的 Filter 階段,如果該節點的實際使用率大于上述任一名額的門檻值,則該節點将被過濾。而在 Score 階段,最終得分是這些名額值的權重和。

在生産叢集中,可能會頻繁出現排程熱點,因為建立 Pod 後節點的負載不能立即增加。是以,我們定義了一個額外的名額,名為 hotValue,表示節點最近幾次的排程頻率,并且節點的最終優先級是最終得分減去 hotValue。

我們可以在 K8s 叢集中安裝 Crane-scheduler 作為第二個排程器來進行驗證:

$ helm repo add crane https://finops-helm.pkg.coding.net/gocrane/gocrane
$ helm upgrade --install scheduler -n crane-system --create-namespace --set global.prometheusAddr="http://prometheus-server.crane-system.svc.cluster.local:8080" crane/scheduler
           

安裝後會建立一個名為 scheduler-config 的 ConfigMap 對象,裡面包含的就是排程器的配置檔案,我們會在配置中啟用 Dynamic 動态排程插件:

$ kubectl get cm -n crane-system scheduler-config -oyaml
apiVersion: v1
data:
  scheduler-config.yaml: |
    apiVersion: kubescheduler.config.k8s.io/v1beta2
    kind: KubeSchedulerConfiguration
    leaderElection:
      leaderElect: false
    profiles:
    - schedulerName: crane-scheduler
      plugins:
        filter:
          enabled:
          - name: Dynamic
        score:
          enabled:
          - name: Dynamic
            weight: 3
      pluginConfig:
      - name: Dynamic
        args:
          policyConfigPath: /etc/kubernetes/policy.yaml
kind: ConfigMap
metadata:
  name: scheduler-config
  namespace: crane-system
           

安裝完成後我們可以任意建立一個 Pod,并通過設定 schedulerName: crane-scheduler 屬性明确指定使用該排程器進行排程,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cpu-stress
spec:
  selector:
    matchLabels:
      app: cpu-stress
  replicas: 1
  template:
    metadata:
      labels:
        app: cpu-stress
    spec:
      schedulerName: crane-scheduler
      hostNetwork: true
      tolerations:
        - key: node.kubernetes.io/network-unavailable
          operator: Exists
          effect: NoSchedule
      containers:
        - name: stress
          image: docker.io/gocrane/stress:latest
          command: ["stress", "-c", "1"]
          resources:
            requests:
              memory: "1Gi"
              cpu: "1"
            limits:
              memory: "1Gi"
              cpu: "1"
           

直接建立上面的資源對象,正常建立的 Pod 就會通過 Crane Scheduler 排程器進行排程了:

Events:
  Type    Reason     Age   From             Message
  ----    ------     ----  ----             -------
  Normal  Scheduled  22s   crane-scheduler  Successfully assigned default/cpu-stress-cc8656b6c-hsqdg to node2
  Normal  Pulling    22s   kubelet          Pulling image "docker.io/gocrane/stress:latest"
           

如果想預設使用該動态排程器,則可以使用該排程器去替換掉預設的排程器即可。

拓撲感覺排程

Crane-Scheduler 和 Crane-Agent 配合工作可以完成拓撲感覺排程與資源配置設定的工作。Crane-Agent 從節點采集資源拓撲,包括 NUMA、Socket、裝置等資訊,彙總到 NodeResourceTopology 這個自定義資源對象中。

騰訊開源雲原生成本優化神器 - FinOps Crane

CPU 拓撲感覺

Crane-Scheduler 在排程時會參考節點的 NodeResourceTopology 對象擷取到節點詳細的資源拓撲結構,在排程到節點的同時還會為 Pod 配置設定拓撲資源,并将結果寫到 Pod 的 annotations 中。Crane-Agent 在節點上 Watch 到 Pod 被排程後,從 Pod 的 annotations 中擷取到拓撲配置設定結果,并按照使用者給定的 CPU 綁定政策進行 CPUSet 的細粒度配置設定。

Crane 中提供了四種 CPU 配置設定政策,分别如下:

  • none:該政策不進行特别的 CPUSet 配置設定,Pod 會使用節點 CPU 共享池。exclusive:該政策對應 kubelet 的 static 政策,Pod 會獨占 CPU 核心,其他任何 Pod 都無法使用。numa:該政策會指定 NUMA Node,Pod 會使用該 NUMA Node 上的 CPU 共享池。immovable:該政策會将 Pod 固定在某些 CPU 核心上,但這些核心屬于共享池,其他 Pod 仍可使用。

首先需要在 Crane-Agent 啟動參數中添加 --feature-gates=NodeResourceTopology=true,CraneCPUManager=true 開啟拓撲感覺排程特性。

然後修改 kube-scheduler 的配置檔案(scheduler-config.yaml ) 啟用動态排程插件并配置插件參數:

apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: true
clientConnection:
  kubeconfig: "REPLACE_ME_WITH_KUBE_CONFIG_PATH"
profiles:
  - schedulerName: default-scheduler # 可以改成自己的排程器名稱
    plugins:
      preFilter:
        enabled:
          - name: NodeResourceTopologyMatch
      filter:
        enabled:
          - name: NodeResourceTopologyMatch
      score:
        enabled:
          - name: NodeResourceTopologyMatch
            weight: 2
      reserve:
        enabled:
          - name: NodeResourceTopologyMatch
      preBind:
        enabled:
          - name: NodeResourceTopologyMatch
           

正确安裝元件後,每個節點均會生成 NodeResourceTopology 對象。

$ kubectl get nrt
NAME    CRANE CPU MANAGER POLICY   CRANE TOPOLOGY MANAGER POLICY   AGE
node1   Static                     SingleNUMANodePodLevel          35d
           

可以看出叢集中節點 node1 已生成對應的 NRT 對象,此時 Crane 的 CPU Manager Policy 為 Static,節點預設的 Topology Manager Policy 為 SingleNUMANodePodLevel,代表節點不允許跨 NUMA 配置設定資源。

使用以下執行個體進行排程測試:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      annotations:
        topology.crane.io/topology-awareness: "true" # 添加注解,表示Pod需要感覺CPU拓撲,資源配置設定不允許跨NUMA。若不指定,則拓撲政策預設繼承節點上的topology.crane.io/topology-awareness标簽
        topology.crane.io/cpu-policy: "exclusive" # 添加注解,表示Pod的CPU配置設定政策為exclusive政策。
      labels:
        app: nginx
    spec:
      containers:
        - image: nginx
          name: nginx
          resources:
            limits:
              cpu: "2" # 需要limits.cpu值,如果要開啟綁核,則該值必須等于requests.cpu。
              memory: 2Gi
           

應用後可以從 annotations 中檢視 Pod 的拓撲配置設定結果,發現 Pod 在 NUMA Node0 上被配置設定了 2 個 CPU 核心。

$ kubectl get pod -o custom-columns=name:metadata.name,topology-result:metadata.annotations."topology\.crane\.io/topology-result"
name                                topology-result
nginx-deployment-754d99dcdf-mtcdp   [{"name":"node0","type":"Node","resources":{"capacity":{"cpu":"2"}}}]
           

實作基于流量預測的彈性

Kubernetes HPA 支援了豐富的彈性擴充能力,Kubernetes 平台開發者部署服務實作自定義 Metric 的服務,Kubernetes 使用者配置多項内置的資源名額或者自定義 Metric 名額實作自定義水準彈性。

EffectiveHorizontalPodAutoscaler(簡稱 EHPA)是 Crane 提供的彈性伸縮産品,它基于社群 HPA 做底層的彈性控制,支援更豐富的彈性觸發政策(預測,觀測,周期),讓彈性更加高效,并保障了服務的品質。

  • 提前擴容,保證服務品質:通過算法預測未來的流量洪峰提前擴容,避免擴容不及時導緻的雪崩和服務穩定性故障。
  • 減少無效縮容:通過預測未來可減少不必要的縮容,穩定工作負載的資源使用率,消除突刺誤判。
  • 支援 Cron 配置:支援 Cron-based 彈性配置,應對大促等異常流量洪峰。
  • 相容社群:使用社群 HPA 作為彈性控制的執行層,能力完全相容社群。

Effective HPA 相容社群的 Kubernetes HPA 的能力,提供了更智能的彈性政策,比如基于預測的彈性和基于 Cron 周期的彈性等。在了解如何使用 EHPA 之前,我們有必要來詳細了解下 K8s 中的 HPA 對象。通過此伸縮元件,Kubernetes 叢集可以利用監控名額(CPU 使用率等)自動擴容或者縮容服務中的 Pod 數量,當業務需求增加時,HPA 将自動增加服務的 Pod 數量,提高系統穩定性,而當業務需求下降時,HPA 将自動減少服務的 Pod 數量,減少對叢集資源的請求量,甚至還可以配合 Cluster Autoscaler 實作叢集規模的自動伸縮,節省 IT 成本。

不過目前預設的 HPA 對象隻能支援根據 CPU 和記憶體的門檻值檢測擴縮容,但也可以通過 custom metric api 來調用 Prometheus 實作自定義 metric,這樣就可以實作更加靈活的監控名額實作彈性伸縮了。

預設情況下,HPA 會通過 metrics.k8s.io 這個接口服務來擷取 Pod 的 CPU、記憶體名額,CPU 和記憶體這兩者屬于核心名額,metrics.k8s.io 服務對應的後端服務一般是 metrics-server,是以在使用 HPA 的時候需要安裝該應用。

如果 HPA 要通過非 CPU、記憶體的其他名額來伸縮容器,我們則需要部署一套監控系統如 Prometheus,讓 Prometheus 采集各種名額,但是 Prometheus 采集到的 metrics 名額并不能直接給 K8s 使用,因為兩者資料格式是不相容的,是以需要使用到另外一個元件 prometheus-adapter,該元件可以将 Prometheus 的 metrics 名額資料格式轉換成 K8s API 接口能識别的格式,另外我們還需要在 K8s 注冊一個服務(即 custom.metrics.k8s.io),以便 HPA 能通過 /apis/ 進行通路。

需要注意的是 Crane 提供了一個 metric-adapter 元件,該元件和 prometheus-adapter 都基于 custom-metric-apiserver 實作了 Custom Metric 和 External Metric 的 ApiService,在安裝 Crane 時會将對應的 ApiService 安裝為 Crane 的 metric-adapter,是以它會和 prometheus-adapter 沖突,因為 Prometheus 是當下最流行的開源監控系統,是以我們更願意使用它來擷取使用者的自定義名額,那麼我們就需要去安裝 prometheus-adapter,但是在安裝之前需要删除 Crane 提供的 ApiService。

# 檢視目前叢集 ApiService
$ kubectl get apiservice |grep crane-system
v1beta1.custom.metrics.k8s.io          crane-system/metric-adapter                    True                      3h51m
v1beta1.external.metrics.k8s.io        crane-system/metric-adapter                    True                      3h51m

# 删除 crane 安裝的 ApiService
$ kubectl delete apiservice v1beta1.custom.metrics.k8s.io
$ kubectl delete apiservice v1beta1.external.metrics.k8s.io
           

然後通過 Helm Chart 來安裝 Prometheus Adapter:

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
# 指定有 prometheus 位址
$ helm upgrade --install prometheus-adapter -n crane-system prometheus-community/prometheus-adapter --set image.repository=cnych/prometheus-adapter,prometheus.url=http://prometheus-server.crane-system.svc,prometheus.port=8080
           

當 prometheus-adapter 安裝成功後我們再将 ApiService 改回 Crane 的 metric-adapter,應用下面的資源清單即可:

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.custom.metrics.k8s.io
spec:
  service:
    name: metric-adapter
    namespace: crane-system
  group: custom.metrics.k8s.io
  version: v1beta1
  insecureSkipTLSVerify: true
  groupPriorityMinimum: 100
  versionPriority: 100
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.external.metrics.k8s.io
spec:
  service:
    name: metric-adapter
    namespace: crane-system
  group: external.metrics.k8s.io
  version: v1beta1
  insecureSkipTLSVerify: true
  groupPriorityMinimum: 100
  versionPriority: 100
           

應用了上面的對象後,ApiService 改回了 Crane 的 metric-adapter,那麼就不能使用 prometheus-adapter 的自定義 Metrics 功能,我們可以通過 Crane 的 metric-adapter 提供的 RemoteAdapter 功能将請求轉發給 prometheus-adapter。

修改 metric-adapter 的配置,将 prometheus-adapter 的 Service 配置成 Crane Metric Adapter 的 RemoteAdapter。

$ kubectl edit deploy metric-adapter -n crane-system
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metric-adapter
  namespace: crane-system
spec:
  template:
    spec:
      containers:
      - args:
        # 添加外部 Adapter 配置
        - --remote-adapter=true
        - --remote-adapter-service-namespace=crane-system
        - --remote-adapter-service-name=prometheus-adapter
        - --remote-adapter-service-port=443
# ......
           

這是因為 Kubernetes 限制一個 ApiService 隻能配置一個後端服務,為了在一個叢集内使用 Crane 提供的 Metric 和 prometheus-adapter 提供的 Metric,Crane 支援了 RemoteAdapter 來解決該問題:

  • Crane Metric-Adapter 支援配置一個 Kubernetes Service 作為一個遠端 Adapter
  • Crane Metric-Adapter 處理請求時會先檢查是否是 Crane 提供的 Local Metric,如果不是,則轉發給遠端 Adapter

下面我們來部署一個示例應用,用來測試自定義名額的容器彈性伸縮。如下所示的應用暴露了 Metric 展示每秒收到的 http 請求數量。

# sample-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - image: luxas/autoscale-demo:v0.1.2
          name: metrics-provider
          resources:
            limits:
              cpu: 500m
            requests:
              cpu: 200m
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
spec:
  ports:
    - name: http
      port: 80
      targetPort: 8080
  selector:
    app: sample-app
  type: NodePort
           

當應用部署完成後,我們可以通過指令檢查 http_requests_total 名額資料:

$ curl http://$(kubectl get service sample-app -o jsonpath='{ .spec.clusterIP }')/metrics
# HELP http_requests_total The amount of requests served by the server in total
# TYPE http_requests_total counter
http_requests_total 1
           

然後我們需要在 Prometheus 中配置抓取 sample-app 的名額,我們這裡使用如下所示指令添加抓取配置:

$ kubectl edit cm -n crane-system prometheus-server
# 添加抓取 sample-app 配置
- job_name: sample-app
  kubernetes_sd_configs:
  - role: pod
  relabel_configs:
  - action: keep
    regex: default;sample-app-(.+)
    source_labels:
    - __meta_kubernetes_namespace
    - __meta_kubernetes_pod_name
  - action: labelmap
    regex: __meta_kubernetes_pod_label_(.+)
  - action: replace
    source_labels:
    - __meta_kubernetes_namespace
    target_label: namespace
  - source_labels: [__meta_kubernetes_pod_name]
    action: replace
    target_label: pod
           

配置生效後我們可以在 Prometheus Dashboard 中查詢對應的名額:

為了讓 HPA 能夠用到 Prometheus 采集到的名額,prometheus-adapter 通過使用 promql 語句來擷取名額,然後修改資料格式,并把重新組裝的名額和值通過自己的接口暴露。而 HPA 會通過 /apis/custom.metrics.k8s.io/ 代理到 prometheus-adapter 的 service 上來擷取這些名額。

如果把 Prometheus 的所有名額到擷取一遍并重新組裝,那 adapter 的效率必然十分低下,是以 adapter 将需要讀取的名額設計成可配置,讓使用者通過 ConfigMap 來決定讀取 Prometheus 的哪些監控名額。

我們這裡使用 Helm Chart 方式安裝的 prometheus-adapter,其預設的 Rule 配置如下所示:

$ kubectl get cm -n crane-system prometheus-adapter -oyaml
apiVersion: v1
data:
  config.yaml: |
    rules:
    - seriesQuery: '{__name__=~"^container_.*",container!="POD",namespace!="",pod!=""}'
      seriesFilters: []
      resources:
        overrides:
          namespace:
            resource: namespace
          pod:
            resource: pod
      name:
        matches: ^container_(.*)_seconds_total$
        as: ""
      metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[5m]))
        by (<<.GroupBy>>)
    # ...... 其他規則省略
kind: ConfigMap
metadata:
  name: prometheus-adapter
  namespace: crane-system
           

Prometheus adapter 的配置檔案格式如上所示,它分為兩個部分,第一個是 rules,用于 custom metrics,另一個是 resourceRules,用于 metrics,如果你隻用 Prometheus adapter 做 HPA,那麼 resourceRules 就可以省略。

我們可以看到 rules 規則下面有很多的查詢語句,這些查詢語句的作用就是盡可能多的擷取名額,進而讓這些名額都可以用于 HPA。也就是說通過 prometheus-adapter 可以将 Prometheus 中的任何一個名額都用于 HPA,但是前提是你得通過查詢語句将它拿到(包括名額名稱和其對應的值)。也就是說,如果你隻需要使用一個名額做 HPA,那麼你完全就可以隻寫一條查詢,而不像上面使用了好多個查詢。整體上每個規則大緻可以分為 4 個部分:

  • Discovery:它指定 Adapter 應該如何找到該規則的所有 Prometheus 名額
  • Association:指定 Adapter 應該如何确定和特定的名額關聯的 Kubernetes 資源
  • Naming:指定 Adapter 應該如何在自定義名額 API 中暴露名額
  • Querying:指定如何将對一個獲多個 Kubernetes 對象上的特定名額的請求轉換為對 Prometheus 的查詢

我們這裡使用的 sample-app 應用的名額名叫 http_requests_total,通過上面的規則後會将 http_requests_total 轉換成 Pods 類型的 Custom Metric,可以獲得類似于 pods/http_requests 這樣的資料。

執行以下指令,通過 Custom Metrics 名額查詢方式,檢視 HPA 可用名額詳情。

$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests" | jq .
{
  "kind": "MetricValueList",
  "apiVersion": "custom.metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/http_requests"
  },
  "items": [
    {
      "describedObject": {
        "kind": "Pod",
        "namespace": "default",
        "name": "sample-app-6876d5585b-wv8fl",
        "apiVersion": "/v1"
      },
      "metricName": "http_requests",
      "timestamp": "2022-10-27T11:19:05Z",
      "value": "18m",
      "selector": null
    }
  ]
}
           

接下來我們就可以來測試下基于流量預測的容器彈性伸縮,這就需要用到 Crane 的 EHPA 對象了,我們可以使用上面的 pods/http_requests 自定義名額來實作彈性功能。

許多業務在時間序列上天然存在周期性的,尤其是對于那些直接或間接為“人”服務的業務。這種周期性是由人們日常活動的規律性決定的。例如,人們習慣于中午和晚上點外賣;早晚總有交通高峰;即使是搜尋等模式不那麼明顯的服務,夜間的請求量也遠低于白天時間。對于這類業務相關的應用來說,從過去幾天的曆史資料中推斷出次日的名額,或者從上周一的資料中推斷出下周一的通路量是很自然的想法。通過預測未來 24 小時内的名額或流量模式,我們可以更好地管理我們的應用程式執行個體,穩定我們的系統,同時降低成本。EHPA 對象可以使用 DSP 算法來預測應用未來的時間序列資料,DSP 是一種預測時間序列的算法,它基于 FFT(快速傅裡葉變換),擅長預測一些具有季節性和周期的時間序列。

建立一個如下所示的 EHPA 資源對象,并開啟預測功能:

# sample-app-ehpa.yaml
apiVersion: autoscaling.crane.io/v1alpha1
kind: EffectiveHorizontalPodAutoscaler
metadata:
  name: sample-app-ehpa
  annotations:
    # metric-query.autoscaling.crane.io 是固定的字首,後面是 字首.Metric名字,需跟 spec.metrics 中的 Metric.name 相同,字首支援 pods、resource、external
    metric-query.autoscaling.crane.io/pods.http_requests: "sum(rate(http_requests_total[5m])) by (pod)"
spec:
  # ScaleTargetRef 是對需要縮放的工作負載的引用
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sample-app
  # minReplicas 是可以縮小到的縮放目标的最小副本數
  minReplicas: 1
  # maxReplicas 是可以擴大到的縮放目标的最大副本數
  maxReplicas: 10
  # scaleStrategy 表示縮放目标的政策,值可以是 Auto 或 Manual
  scaleStrategy: Auto
  # metrics 包含用于計算所需副本數的規範。
  metrics:
    # 在使用預測算法預測時,你可能會擔心預測資料不準帶來一定的風險,EHPA 在計算副本數時,不僅會按預測資料計算,同時也會考慮實際監控資料來兜底,提升彈性的安全性,是以可以定義下面的 Resource 監控資料來兜底
    # - type: Resource
    - type: Pods
      pods:
        metric:
          name: http_requests
        target:
          type: AverageValue
          averageValue: 500m # 當出現了小數點,K8s 又需要高精度時,會使用機關 m 或k。例如1001m=1.001,1k=1000。
  # prediction 定義了預測資源的配置,如果未指定,則預設不啟用預測功能
  prediction:
    predictionWindowSeconds: 3600 # PredictionWindowSeconds 是預測未來名額的時間視窗
    predictionAlgorithm:
      algorithmType: dsp # 指定dsp為預測算法
      dsp:
        sampleInterval: "60s" # 監控資料的采樣間隔為1分鐘
        historyLength: "7d" # 拉取過去7天的監控名額作為預測的依據
           

在上面的資源對象中添加了一個 metric-query.autoscaling.crane.io/pods.http_requests: "sum(rate(http_requests_total[5m])) by (pod)" 的 注解,這樣就可以開啟自定義名額的預測功能了。

相應的在規範中定義了 spec.prediction 屬性,用來指定預測資源的配置,其中的 predictionWindowSeconds 屬性用來指定預測未來名額的時間視窗,predictionAlgorithm 屬性用來指定預測的算法,比如我們這裡配置的 algorithmType: dsp 表示使用 DSP(Digital Signal Processing)算法進行預測,該算法使用在數字信号處理領域中常用的的離散傅裡葉變換、自相關函數等手段來識别、預測周期性的時間序列,關于該算法的實作原理可以檢視官方文檔 https://gocrane.io/zh-cn/docs/tutorials/timeseriees-forecasting-by-dsp/ 的相關介紹,或者檢視源碼以了解背後原理,相關代碼位于 pkg/prediction/dsp 目錄下。此外在 prediction.predictionAlgorithm.dsp 下面還可以配置 dsp 算法的相關參數,比如我們這裡配置的 sampleInterval: "60s" 表示監控資料的采樣間隔為 1 分鐘,historyLength: "7d" 表示拉取過去 7 天的監控名額作為預測的依據,此外還可以配置預測方式等。

然後核心的配置就是 spec.metrics 了,用來指定計算所需副本數的規範,我們這裡指定了基于 Pods 名額的計算方式。

- type: Pods
  pods:
    metric:
      name: http_requests
    target:
      type: AverageValue
      averageValue: 500m
           

上面的配置表示當 pods/http_requests 的自定義名額平均值達到 500m 後就可以觸發 HPA 縮放,這裡有一個點需要注意自定義名額的 pods.metric.name 的值必須和 annotations 注解 metric-query.autoscaling.crane.io/pods.<metric name> 名額名保持一緻。

EHPA 對象水準彈性的執行流程如下所示:

  • EffectiveHPAController 建立 HorizontalPodAutoscaler 和 TimeSeriesPrediction 對象
  • PredictionCore 從 Prometheus 擷取曆史 metric 通過預測算法計算,将結果記錄到 TimeSeriesPrediction
  • HPAController 通過 metric client 從 KubeApiServer 讀取 metric 資料
  • KubeApiServer 将請求路由到 Crane 的 Metric-Adapter。
  • HPAController 計算所有的 Metric 傳回的結果得到最終的彈性副本推薦。
  • HPAController 調用 scale API 對目标應用擴/縮容。

整體流程如下所示:

直接應用上面的 EPHA 對象即可:

$ kubectl apply -f sample-app-ehpa.yaml
effectivehorizontalpodautoscaler.autoscaling.crane.io/sample-app-ehpa created
$ kubectl get ehpa
NAME              STRATEGY   MINPODS   MAXPODS   SPECIFICPODS   REPLICAS   AGE
sample-app-ehpa   Auto       1         10                       1          17s
           

由于我們開啟了自動預測功能,是以 EPHA 對象建立後會建立一個對應的 TimeSeriesPrediction 對象:

$ kubectl get tsp
NAME                   TARGETREFNAME   TARGETREFKIND   PREDICTIONWINDOWSECONDS   AGE
ehpa-sample-app-ehpa   sample-app      Deployment      3600                      3m50s
$ kubectl get tsp ehpa-sample-app-ehpa -oyaml
apiVersion: prediction.crane.io/v1alpha1
kind: TimeSeriesPrediction
metadata:
  name: ehpa-sample-app-ehpa
  namespace: default
spec:
  predictionMetrics:
  - algorithm:
      algorithmType: dsp
      dsp:
        estimators: {}
        historyLength: 7d
        sampleInterval: 60s
    expressionQuery:
      expression: sum(http_requests{})
    resourceIdentifier: pods.http_requests
    type: ExpressionQuery
  predictionWindowSeconds: 3600
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sample-app
    namespace: default
status:
  conditions:
  - lastTransitionTime: "2022-10-27T13:01:14Z"
    message: not all metric predicted
    reason: PredictPartial
    status: "False"
    type: Ready
  predictionMetrics:
  - ready: false
    resourceIdentifier: pods.http_requests
           

在 status 中可以看到包含 not all metric predicted 這樣的資訊,這是因為應用運作時間較短,可能會出現無法預測的情況。同樣也會自動建立一個對應的 HPA 對象:

$ kubectl get hpa
NAME                   REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
ehpa-sample-app-ehpa   Deployment/sample-app   16m/500m   1         10        1          69m
           

然後我們可以使用 ab 指令對 sample-app 做一次壓力測試,正常也可以觸發該應用的彈性擴容。

$ kubectl get svc sample-app
NAME         TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
sample-app   NodePort   10.104.163.144   <none>        80:31941/TCP   3h59m
# 對 nodeport 服務做壓力測試
$ ab -c 50 -n 2000 http://192.168.0.106:31941/
$ kubectl get hpa
NAME                   REFERENCE               TARGETS      MINPODS   MAXPODS   REPLICAS   AGE
ehpa-sample-app-ehpa   Deployment/sample-app   7291m/500m   1         10        10         71m
$ kubectl describe hpa ehpa-sample-app-ehpa
Name:                       ehpa-sample-app-ehpa
# ......
Metrics:                    ( current / target )
  "http_requests" on pods:  8350m / 500m
Min replicas:               1
Max replicas:               10
Deployment pods:            10 current / 10 desired
Conditions:
  Type            Status  Reason            Message
  ----            ------  ------            -------
  AbleToScale     True    ReadyForNewScale  recommended size matches current size
  ScalingActive   True    ValidMetricFound  the HPA was able to successfully calculate a replica count from pods metric http_requests
  ScalingLimited  True    TooManyReplicas   the desired replica count is more than the maximum replica count
Events:
  Type    Reason             Age   From                       Message
  ----    ------             ----  ----                       -------
  Normal  SuccessfulRescale  57s   horizontal-pod-autoscaler  New size: 4; reason: pods metric http_requests above target
  Normal  SuccessfulRescale  42s   horizontal-pod-autoscaler  New size: 8; reason: pods metric http_requests above target
  Normal  SuccessfulRescale  27s   horizontal-pod-autoscaler  New size: 10; reason: pods metric http_requests above target
           

我們可以使用如下所示指令來檢視 EHPA 自動生成的 HPA 對象的資源清單:

$ kubectl get hpa.v2beta2.autoscaling ehpa-sample-app-ehpa -oyaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: ehpa-sample-app-ehpa
  namespace: default
spec:
  maxReplicas: 10
  metrics:
  - pods:
      metric:
        name: http_requests
      target:
        averageValue: 500m
        type: AverageValue
    type: Pods
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: sample-app
# ...... 省略其他部分
           

可以觀測到已經建立出基于自定義名額預測的 Metric: http_requests,由于生産環境的複雜性,基于多名額的彈性(CPU/Memory/自定義名額)往往是生産應用的常見選擇,是以 Effective HPA 通過預測算法覆寫了多名額的彈性,達到了幫助更多業務在生産環境落地水準彈性的成效。

除此之外 EHPA 對象還支援基于 cron 的自動縮放,除了基于監控名額,有時節假日和工作日的工作負載流量存在差異,簡單的預測算法可能效果不佳。然後可以通過設定周末 cron 來支援更大數量的副本來彌補預測的不足。對于一些非 web 流量的應用,比如一些應用不需要在周末使用,可以把工作負載的副本數減少到 1,也可以配置 cron 來降低你的服務成本。

QOS 增強與混部

除了上面介紹的主要功能之外,crane 還具有很多 QoS 增強功能,QoS 相關能力保證了運作在 Kubernetes 上的 Pod 的穩定性。crane 具有幹擾檢測和主動回避能力,當較高優先級的 Pod 受到資源競争的影響時,Disable Schedule、Throttle 以及 Evict 将應用于低優先級的 Pod,以保證節點整體的穩定,目前已經支援節點的 cpu/記憶體 負載絕對值/百分比作為水位線,在發生幹擾進行驅逐或壓制時,會進行精确計算,将負載降低到略低于水位線即停止操作,防止誤傷和過渡操作。

同時,crane 還支援自定義名額适配整個幹擾檢測架構,隻需要完成排序定義等一些操作,即可複用包含精确操作在内的幹擾檢測和回避流程。

此外 crane 還具有預測算法增強的彈性資源超賣能力,将叢集内的空閑資源複用起來,同時結合 crane 的預測能力,更好地複用閑置資源,目前已經支援 cpu 和記憶體的空閑資源回收。同時具有彈性資源限制功能,限制使用彈性資源的 workload 最大和最小資源使用量,避免對高優業務的影響和饑餓問題。

同時具備增強的旁路 cpuset 管理能力,在綁核的同時提升資源利用效率。

總結

2022 年,騰訊雲原生 FinOps Crane 項目組,結合行業及産業的發展趨勢,關聯中國産業網際網路發展聯盟、中國信通院、中國電子節能技術協會、FinOps 基金會及中國内外衆多生态合作夥伴,開展及推動技術标準、國内聯盟、國際開源、雙碳更新等多元度的成果落地,輸出了系列白皮書和标準指南,旨在助力企業和生态更良性發展和應用先進技術,達成降本增效,節能減排目标方向。

騰訊開源雲原生成本優化神器 - FinOps Crane

Crane 能力全景圖

我們可以自己在 K8s 叢集中安裝 crane 來擷取這些相關功能,此外這些能力也都會在騰訊雲 TKE 的原生節點産品 Housekeeper 中提供,新推出的 TKE Housekeeper 是騰訊雲推出的全新 K8s 運維範式,可以幫助企業像管理 Workload 一樣聲明式管理 Node 節點,高效解決節點維護、資源規劃等各種各樣的運維問題。

毫無疑問,Crane 已經是 K8s 叢集中用于雲資源分析和經濟的最佳 FinOps 平台了。目前,騰訊雲 Crane 已進入 CNCF LandScape,這意味着 Crane 已成為雲原生領域的重要項目。面向未來,騰訊雲還将持續回報開源社群、共建開源生态,幫助更多企業通過雲原生全面釋放生産力,加速實作數字化和綠色化雙轉型。

最後,上周末舉行的騰訊雲 Techo Day 技術開放日活動也對 Crane 進行了深入解析,相關資料及課件被收錄進了《騰訊雲雲原生工具指南》裡,除此以外,裡面還涵蓋了遨馳分布式雲作業系統、微服務等多款熱門産品的技術原了解讀,幫助開發者用專業方法解決業務痛點,還有多個雲原生實踐标杆案例分享,講述雲原生如何實作價值,非常推薦給感興趣的朋友下載下傳看看。

騰訊開源雲原生成本優化神器 - FinOps Crane

繼續閱讀