FederatedHPA scales with custom metrics

In Karmada, a FederatedHPA scales up/down the workload’s replicas across multiple clusters, with the aim of automatically scaling the workload to match the demand.

FederatedHPA not only supports resource metrics such as CPU and memory, but also supports custom metrics which may expand the use cases of FederatedHPA.

This document walks you through an example of enabling FederatedHPA to automatically manage scale for a cross-cluster app with custom metrics.

The walkthrough example will do as follows:

federatedhpa-custom-metrics-demo

  • One sample-deployment’s pod exists in member1 cluster.
  • The service is deployed in member1 and member2 cluster.
  • Request the multi-cluster service and trigger an increase in the pod’s custom metrics(http_requests_total).
  • The replicas will be scaled up in member1 and member2 cluster.

Prerequisites

Karmada has been installed

You can install Karmada by referring to Quick Start, or directly run hack/local-up-karmada.sh script which is also used to run our E2E cases.

Member Cluster Network

Ensure that at least two clusters have been added to Karmada, and the container networks between member clusters are connected.

  • If you use the hack/local-up-karmada.sh script to deploy Karmada, Karmada will have three member clusters, and the container networks of the member1 and member2 will be connected.
  • You can use Submariner or other related open source projects to connect networks between member clusters.

Note: In order to prevent routing conflicts, Pod and Service CIDRs of clusters need non-overlapping.

The ServiceExport and ServiceImport CRDs have been installed

You need to install ServiceExport and ServiceImport in the member clusters to enable multi-cluster service.

After ServiceExport and ServiceImport have been installed on the Karmada Control Plane, you can create ClusterPropagationPolicy to propagate those two CRDs to the member clusters.

  1. # propagate ServiceExport CRD
  2. apiVersion: policy.karmada.io/v1alpha1
  3. kind: ClusterPropagationPolicy
  4. metadata:
  5. name: serviceexport-policy
  6. spec:
  7. resourceSelectors:
  8. - apiVersion: apiextensions.k8s.io/v1
  9. kind: CustomResourceDefinition
  10. name: serviceexports.multicluster.x-k8s.io
  11. placement:
  12. clusterAffinity:
  13. clusterNames:
  14. - member1
  15. - member2
  16. ---
  17. # propagate ServiceImport CRD
  18. apiVersion: policy.karmada.io/v1alpha1
  19. kind: ClusterPropagationPolicy
  20. metadata:
  21. name: serviceimport-policy
  22. spec:
  23. resourceSelectors:
  24. - apiVersion: apiextensions.k8s.io/v1
  25. kind: CustomResourceDefinition
  26. name: serviceimports.multicluster.x-k8s.io
  27. placement:
  28. clusterAffinity:
  29. clusterNames:
  30. - member1
  31. - member2

prometheus and prometheus-adapter have been installed in member clusters

You need to install prometheus and prometheus-adapter for member clusters to provide the custom metrics. You can install it by running the following in member clusters:

  1. git clone https://github.com/prometheus-operator/kube-prometheus.git
  2. cd kube-prometheus
  3. kubectl apply --server-side -f manifests/setup
  4. kubectl wait \
  5. --for condition=Established \
  6. --all CustomResourceDefinition \
  7. --namespace=monitoring
  8. kubectl apply -f manifests/

You can verify the installation by the following command:

  1. $ kubectl --kubeconfig=/root/.kube/members.config --context=member1 get po -nmonitoring
  2. NAME READY STATUS RESTARTS AGE
  3. alertmanager-main-0 2/2 Running 0 30h
  4. alertmanager-main-1 2/2 Running 0 30h
  5. alertmanager-main-2 2/2 Running 0 30h
  6. blackbox-exporter-6bc47b9578-zcbb7 3/3 Running 0 30h
  7. grafana-6b68cd6b-vmw74 1/1 Running 0 30h
  8. kube-state-metrics-597db7f85d-2hpfs 3/3 Running 0 30h
  9. node-exporter-q8hdx 2/2 Running 0 30h
  10. prometheus-adapter-57d9587488-86ckj 1/1 Running 0 29h
  11. prometheus-adapter-57d9587488-zrt29 1/1 Running 0 29h
  12. prometheus-k8s-0 2/2 Running 0 30h
  13. prometheus-k8s-1 2/2 Running 0 30h
  14. prometheus-operator-7d4b94944f-kkwkk 2/2 Running 0 30h

karmada-metrics-adapter has been installed in Karmada control plane

You need to install karmada-metrics-adapter in Karmada control plane to provide the metrics API, install it by running:

  1. hack/deploy-metrics-adapter.sh ${host_cluster_kubeconfig} ${host_cluster_context} ${karmada_apiserver_kubeconfig} ${karmada_apiserver_context_name}

If you use the hack/local-up-karmada.sh script to deploy Karmada, karmada-metrics-adapter will be installed by default.

Deploy workload in member1 and member2 cluster

You need to deploy a sample deployment(1 replica) and service in member1 and member2.

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: sample-app
  5. labels:
  6. app: sample-app
  7. spec:
  8. replicas: 1
  9. selector:
  10. matchLabels:
  11. app: sample-app
  12. template:
  13. metadata:
  14. labels:
  15. app: sample-app
  16. spec:
  17. containers:
  18. - image: luxas/autoscale-demo:v0.1.2
  19. name: metrics-provider
  20. ports:
  21. - name: http
  22. containerPort: 8080
  23. ---
  24. apiVersion: v1
  25. kind: Service
  26. metadata:
  27. labels:
  28. app: sample-app
  29. name: sample-app
  30. spec:
  31. ports:
  32. - name: http
  33. port: 80
  34. protocol: TCP
  35. targetPort: 8080
  36. selector:
  37. app: sample-app
  38. type: ClusterIP
  39. ---
  40. apiVersion: policy.karmada.io/v1alpha1
  41. kind: PropagationPolicy
  42. metadata:
  43. name: app-propagation
  44. spec:
  45. resourceSelectors:
  46. - apiVersion: apps/v1
  47. kind: Deployment
  48. name: sample-app
  49. - apiVersion: v1
  50. kind: Service
  51. name: sample-app
  52. placement:
  53. clusterAffinity:
  54. clusterNames:
  55. - member1
  56. - member2
  57. replicaScheduling:
  58. replicaDivisionPreference: Weighted
  59. replicaSchedulingType: Divided
  60. weightPreference:
  61. staticWeightList:
  62. - targetCluster:
  63. clusterNames:
  64. - member1
  65. weight: 1
  66. - targetCluster:
  67. clusterNames:
  68. - member2
  69. weight: 1

After deploying, you can check the distribution of the pods and service:

  1. $ karmadactl get pods --operation-scope members
  2. NAME CLUSTER READY STATUS RESTARTS AGE
  3. sample-app-9b7d8c9f5-xrnfx member1 1/1 Running 0 111s
  4. $ karmadactl get svc --operation-scope members
  5. NAME CLUSTER TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ADOPTION
  6. sample-app member1 ClusterIP 10.11.29.250 <none> 80/TCP 3m53s Y

Monitor your application in member1 and member2 cluster

In order to monitor your application, you’ll need to set up a ServiceMonitor pointing at the application. Assuming you’ve set up your Prometheus instance to use ServiceMonitors with the app: sample-app label, create a ServiceMonitor to monitor the app’s metrics via the service:

  1. apiVersion: monitoring.coreos.com/v1
  2. kind: ServiceMonitor
  3. metadata:
  4. name: sample-app
  5. labels:
  6. app: sample-app
  7. spec:
  8. selector:
  9. matchLabels:
  10. app: sample-app
  11. endpoints:
  12. - port: http
  1. kubectl create -f sample-app.monitor.yaml

Now, you should see your metrics (http_requests_total) appear in your Prometheus instance. Look them up via the dashboard, and make sure they have the namespace and pod labels. If not, check the labels on the service monitor match the ones on the Prometheus CRD.

Launch you adapter in member1 and member2 cluster

After you deploy prometheus-adapter, you need to update to the adapter config which is necessary in order to expose custom metrics.

  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4. name: adapter-config
  5. namespace: monitoring
  6. data:
  7. config.yaml: |-
  8. "rules":
  9. - "seriesQuery": |
  10. {namespace!="",__name__!~"^container_.*"}
  11. "resources":
  12. "template": "<<.Resource>>"
  13. "name":
  14. "matches": "^(.*)_total"
  15. "as": ""
  16. "metricsQuery": |
  17. sum by (<<.GroupBy>>) (
  18. irate (
  19. <<.Series>>{<<.LabelMatchers>>}[1m]
  20. )
  21. )
  1. kubectl apply -f prom-adapter.config.yaml
  2. # Restart prom-adapter pods
  3. kubectl rollout restart deployment prometheus-adapter -n monitoring

Register metrics API in member1 and member2 cluster

You also need to register the custom metrics API with the API aggregator (part of the main Kubernetes API server). For that you need to create an APIService resource.

  1. apiVersion: apiregistration.k8s.io/v1
  2. kind: APIService
  3. metadata:
  4. name: v1beta2.custom.metrics.k8s.io
  5. spec:
  6. group: custom.metrics.k8s.io
  7. groupPriorityMinimum: 100
  8. insecureSkipTLSVerify: true
  9. service:
  10. name: prometheus-adapter
  11. namespace: monitoring
  12. version: v1beta2
  13. versionPriority: 100
  1. kubectl create -f api-service.yaml

The API is registered as custom.metrics.k8s.io/v1beta2, and you can use the following command to verify:

  1. kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta2/namespaces/default/pods/*/http_requests?selector=app%3Dsample-app"

The output is similar to:

  1. {
  2. "kind": "MetricValueList",
  3. "apiVersion": "custom.metrics.k8s.io/v1beta2",
  4. "metadata": {},
  5. "items": [
  6. {
  7. "describedObject": {
  8. "kind": "Pod",
  9. "namespace": "default",
  10. "name": "sample-app-9b7d8c9f5-9lw6b",
  11. "apiVersion": "/v1"
  12. },
  13. "metric": {
  14. "name": "http_requests",
  15. "selector": null
  16. },
  17. "timestamp": "2023-06-14T09:09:54Z",
  18. "value": "66m"
  19. }
  20. ]
  21. }

If karmada-metrics-adapter is installed successfully, you can also verify it with the above command in Karmada control plane.

Deploy FederatedHPA in Karmada control plane

Then let’s deploy FederatedHPA in Karmada control plane.

  1. apiVersion: autoscaling.karmada.io/v1alpha1
  2. kind: FederatedHPA
  3. metadata:
  4. name: sample-app
  5. spec:
  6. scaleTargetRef:
  7. apiVersion: apps/v1
  8. kind: Deployment
  9. name: sample-app
  10. minReplicas: 1
  11. maxReplicas: 10
  12. behavior:
  13. scaleDown:
  14. stabilizationWindowSeconds: 10
  15. scaleUp:
  16. stabilizationWindowSeconds: 10
  17. metrics:
  18. - type: Pods
  19. pods:
  20. metric:
  21. name: http_requests
  22. target:
  23. averageValue: 700m
  24. type: Value

After deploying, you can check the FederatedHPA:

  1. NAME REFERENCE-KIND REFERENCE-NAME MINPODS MAXPODS REPLICAS AGE
  2. sample-app Deployment sample-app 1 10 1 15d

Export service to member1 cluster

As mentioned before, you need a multi-cluster service to route the requests to the pods in member1 and member2 cluster, so let create this mult-cluster service.

  • Create a ServiceExport object on Karmada Control Plane, and then create a PropagationPolicy to propagate the ServiceExport object to member1 and member2 cluster.

    1. apiVersion: multicluster.x-k8s.io/v1alpha1
    2. kind: ServiceExport
    3. metadata:
    4. name: sample-app
    5. ---
    6. apiVersion: policy.karmada.io/v1alpha1
    7. kind: PropagationPolicy
    8. metadata:
    9. name: serve-export-policy
    10. spec:
    11. resourceSelectors:
    12. - apiVersion: multicluster.x-k8s.io/v1alpha1
    13. kind: ServiceExport
    14. name: sample-app
    15. placement:
    16. clusterAffinity:
    17. clusterNames:
    18. - member1
    19. - member2
  • Create a ServiceImport object on Karmada Control Plane, and then create a PropagationPolicy to propagate the ServiceImport object to member1 cluster.

    1. apiVersion: multicluster.x-k8s.io/v1alpha1
    2. kind: ServiceImport
    3. metadata:
    4. name: sample-app
    5. spec:
    6. type: ClusterSetIP
    7. ports:
    8. - port: 80
    9. protocol: TCP
    10. ---
    11. apiVersion: policy.karmada.io/v1alpha1
    12. kind: PropagationPolicy
    13. metadata:
    14. name: serve-import-policy
    15. spec:
    16. resourceSelectors:
    17. - apiVersion: multicluster.x-k8s.io/v1alpha1
    18. kind: ServiceImport
    19. name: sample-app
    20. placement:
    21. clusterAffinity:
    22. clusterNames:
    23. - member1

After deploying, you can check the multi-cluster service:

  1. $ karmadactl get svc --operation-scope members
  2. NAME CLUSTER TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ADOPTION
  3. derived-sample-app member1 ClusterIP 10.11.59.213 <none> 80/TCP 9h Y

Install hey http load testing tool in member1 cluster

In order to do http requests, here you can use hey.

  • Download hey and copy it to kind cluster container.
  1. wget https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64
  2. chmod +x hey_linux_amd64
  3. docker cp hey_linux_amd64 member1-control-plane:/usr/local/bin/hey

Test scaling up

  • Check the pod distribution firstly.

    1. $ karmadactl get pods --operation-scope members
    2. NAME CLUSTER READY STATUS RESTARTS AGE
    3. sample-app-9b7d8c9f5-xrnfx member1 1/1 Running 0 111s
  • Check multi-cluster service ip.

    1. $ karmadactl get svc --operation-scope members
    2. NAME CLUSTER TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ADOPTION
    3. derived-sample-app member1 ClusterIP 10.11.59.213 <none> 80/TCP 20m Y
  • Request multi-cluster service with hey to increase the nginx pods’ custom metrics(http_requests_total).

    1. docker exec member1-control-plane hey -c 1000 -z 1m http://10.11.59.213/metrics
  • Wait 15s, the replicas will be scaled up, then you can check the pod distribution again.

    1. $ karmadactl get po --operation-scope members -l app=sample-app
    2. NAME CLUSTER READY STATUS RESTARTS AGE
    3. sample-app-9b7d8c9f5-454vz member2 1/1 Running 0 84s
    4. sample-app-9b7d8c9f5-7fjhn member2 1/1 Running 0 69s
    5. sample-app-9b7d8c9f5-ddf4s member2 1/1 Running 0 69s
    6. sample-app-9b7d8c9f5-mxqmh member2 1/1 Running 0 84s
    7. sample-app-9b7d8c9f5-qbc2j member2 1/1 Running 0 69s
    8. sample-app-9b7d8c9f5-2tgxt member1 1/1 Running 0 69s
    9. sample-app-9b7d8c9f5-66n9s member1 1/1 Running 0 69s
    10. sample-app-9b7d8c9f5-fbzps member1 1/1 Running 0 84s
    11. sample-app-9b7d8c9f5-ldmhz member1 1/1 Running 0 84s
    12. sample-app-9b7d8c9f5-xrnfx member1 1/1 Running 0 87m

Test scaling down

After 1 minute, the load testing tool will be stopped, then you can see the workload is scaled down across clusters.

  1. $ karmadactl get pods --operation-scope members -l app=sample-app
  2. NAME CLUSTER READY STATUS RESTARTS AGE
  3. sample-app-9b7d8c9f5-xrnfx member1 1/1 Running 0 91m