Спасибо Daniel Vaughan за данную статью.
Для горизонтального автоскалирования подов (HPA) на основе кастомных метрик, нам понадобятся:
Все это, мы будем устанавливать используя Helm. Установку Helm'а можно посмотреть тут.
Создадим директорию для хранения Helm values:
mkdir helm-values
Создадим values файл для Prometheus Operator'а:
cat <<EOF >helm-values/prometheus-operator-values.yaml # This configuration means all ServiceMonitors in the namespace will be picked up # Use with caution! prometheus: prometheusSpec: serviceMonitorSelectorNilUsesHelmValues: false serviceMonitorSelector: {} EOF
Устанавливаем Prometheus Operator:
helm install stable/prometheus-operator -n prom \ -f helm-values/prometheus-operator-values.yaml
Создадим values файл для Prometheus Adapter'а:
cat <<EOF >helm-values/prometheus-adapter-values.yaml rules: default: false custom: - seriesQuery: 'demo_app_button_clicks_total' resources: { template: "<<.Resource>>" } name: matches: "^(.*)_total" as: "${1}_per_second" metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)" prometheus: url: http://prom-prometheus-operator-prometheus.default EOF
Тут мы указываем, что нам не нужны предустановленные кастомные метрики, и добавляем свою. И важно, чтобы URL сервиса "Prometheus Operator" был указан правильным
Устанавливаем Prometheus Adapter:
helm install stable/prometheus-adapter -n prom-adapter \ -f helm-values/prometheus-adapter-values.yaml
Через минуту-две можно проверить, что API кастомных метрик работает:
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/" | jq . { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "custom.metrics.k8s.io/v1beta1", "resources": [] }
На данном этапе мы тут не видим нашу метрику "demo_app_button_clicks_total", так и должно быть, ведь мы только Prometheus Adapter'у указали как обрабатывать ее.
Создадим deployment и service для нашего тестового приложения:
cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: demo-app labels: app: demo-app spec: selector: matchLabels: app: demo-app template: metadata: labels: app: demo-app spec: containers: - name: demo-app image: flipstone42/k8s-prometheus-custom-scaling:latest resources: limits: memory: "128Mi" cpu: "100m" ports: - containerPort: 8000 name: http --- apiVersion: v1 kind: Service metadata: name: demo-app labels: app: demo-app spec: type: ClusterIP selector: app: demo-app ports: - port: 8080 targetPort: 8000 name: web EOF
Теперь создадим HPA для нашего deployment'а:
cat <<EOF | kubectl apply -f - kind: HorizontalPodAutoscaler apiVersion: autoscaling/v2beta1 metadata: name: demo-app spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: demo-app # autoscale between 1 and 10 replicas minReplicas: 1 maxReplicas: 10 metrics: # use a "Pods" metric, which takes the average of the # given metric across all pods controlled by the autoscaling target - type: Pods pods: metricName: demo_app_button_clicks_per_second targetAverageValue: 10m EOF
Тут главное указать правильно имя Deployment'а и имя метрики, которое отдает наше приложение.
Создадим ServiceMonitor для отслеживания нашей метрики:
cat <<EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: demo-app spec: endpoints: - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token interval: 15s port: web selector: matchLabels: app: demo-app EOF
Проверяем HPA. Если в столбце "TARGETS" вы видите "0/10m", а не "<unknown>/10m", значит метрика считывается.
kubectl get hpa demo-app NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE demo-app Deployment/demo-app 0/10m 1 10 1 1m
Выполним describe HPA чтобы окончательно убедится, что метрика считывается.
kubectl describe hpa demo-app Name: demo-app Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"autoscaling/v2beta1","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{},"name":"demo-app","namespace":"default"}... CreationTimestamp: Tue, 26 Nov 2019 20:51:36 +0000 Reference: Deployment/demo-app Metrics: ( current / target ) "demo_app_button_clicks_per_second" on pods: 0 / 10m Min replicas: 1 Max replicas: 10 Deployment pods: 1 current / 1 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 demo_app_button_clicks_per_second ScalingLimited True TooFewReplicas the desired replica count is less than the minimum replica count Events: <none>
Теперь можем видеть нашу метрику в Custom Metrics API:
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq . { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "custom.metrics.k8s.io/v1beta1", "resources": [ { "name": "pods/demo_app_button_clicks_per_second", "singularName": "", "namespaced": true, "kind": "MetricValueList", "verbs": [ "get" ] }, { "name": "services/demo_app_button_clicks_per_second", "singularName": "", "namespaced": true, "kind": "MetricValueList", "verbs": [ "get" ] }, { "name": "jobs.batch/demo_app_button_clicks_per_second", "singularName": "", "namespaced": true, "kind": "MetricValueList", "verbs": [ "get" ] }, { "name": "namespaces/demo_app_button_clicks_per_second", "singularName": "", "namespaced": false, "kind": "MetricValueList", "verbs": [ "get" ] } ] }
Создадим для нашего приложения Ingress:
cat <<EOF | kubectl apply -f - apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: demo namespace: default spec: rules: - host: demo.artem.services http: paths: - backend: serviceName: demo-app servicePort: 8080 path: / EOF
Перейдем по URL нашего приложения:
Кликнем по кнопке "Hit me!" пять раз (чтобы сразу не скалировать поды в максимум)
Проверяем HPA:
kubectl get hpa demo-app NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE demo-app Deployment/demo-app 38m/10m 1 10 1 10m
И смотрим количество Pod'ов:
kubectl get po -l app=demo-app NAME READY STATUS RESTARTS AGE demo-app-78c7869955-9vzsr 1/1 Running 0 2m50s demo-app-78c7869955-fgnsx 1/1 Running 0 2m50s demo-app-78c7869955-sl9f8 1/1 Running 0 2m50s demo-app-78c7869955-zxrtt 1/1 Running 0 13m
Через 10 минут, после того как значение метрики опустится, Pod'ы начнут скалироваться в обратную сторону.
Trableshooting
Если вы все сделали верно, но у вас не работают кастомные метрики, убедитесь, что у вас включен Aggregation API:
kubectl get pod kube-apiserver-master -n kube-system -o yaml spec: containers: - command: ... - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt - --requestheader-allowed-names=front-proxy-client - --requestheader-extra-headers-prefix=X-Remote-Extra- - --requestheader-group-headers=X-Remote-Group - --requestheader-username-headers=X-Remote-User - --proxy-client-key-file=/etc/kubernetes/pki/sa.key - --proxy-client-cert-file=/etc/kubernetes/pki/ca.crt ...
Так же могут понадобится следующие флаги для Kubelet'а:
sudo cat /var/lib/kubelet/kubeadm-flags.env ... --anonymous-auth=false --authorization-mode=Webhook --authentication-token-webhook=true
После добавления флагов, не забудьте перезапустить Kubelet:
sudo systemctl restart kubelet