
Спасибо 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