Kubernetes: мониторинг кластера с Prometheus Operator

Автор: | 18/06/2020

В продолжение поста Kubernetes: мониторинг с Prometheus, в котором мы настроили мониторинг вручную, и более-менее разобрались с тем, как оно всё внутри работает – теперь попробуем прикрутить Prometheus Operator из Helm-репозитория.

Напомню, задача – поднять Prometheus и все необходимые експортёры в AWS Elastic Kubernetes Cluster, и с него через /federation передавать метрики на наш “центровой” Prometheus, где уже есть Alertmanager и Grafana.

Смутил целый набор чартов – есть просто Prometheus, есть kube-prometheus, есть prometheus-operator, причём от разных разработчиков:

Хотя в репозитории находится только один prometheus-operator:

[simterm]

$ helm search repo stable/prometheus-operator -o yaml
- app_version: 0.38.1
  description: Provides easy monitoring definitions for Kubernetes services, and deployment
    and management of Prometheus instances.
  name: stable/prometheus-operator
  version: 8.14.0

[/simterm]

Разница между stable/prometheus и stable/prometheus-operator в том, что в Operator включена Grafana с набором готовых дашборд и набор ServiceMonitors для сбора метрик с сервисов кластера, таких как CoreDNS, API Server, Scheduler, etc.

Собственно – используем stable/prometheus-operator.

Prometheus Operator deployment

Деплоим:

[simterm]

$ helm install --namespace monitoring --create-namespace prometheus stable/prometheus-operator
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
manifest_sorter.go:192: info: skipping unknown hook: "crd-install"
NAME: prometheus
LAST DEPLOYED: Mon Jun 15 17:54:27 2020
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
The Prometheus Operator has been installed. Check its status by running:
  kubectl --namespace monitoring get pods -l "release=prometheus"

Visit https://github.com/coreos/prometheus-operator for instructions on how
to create & configure Alertmanager and Prometheus instances using the Operator.

[/simterm]

Проверяем поды:

[simterm]

$ kk -n monitoring get pod
NAME                                                     READY   STATUS    RESTARTS   AGE
alertmanager-prometheus-prometheus-oper-alertmanager-0   2/2     Running   0          41s
prometheus-grafana-85c9fbc85c-ll58c                      2/2     Running   0          46s
prometheus-kube-state-metrics-66d969ff69-6b7t8           1/1     Running   0          46s
prometheus-prometheus-node-exporter-89mf4                1/1     Running   0          46s
prometheus-prometheus-node-exporter-bpn67                1/1     Running   0          46s
prometheus-prometheus-node-exporter-l9wjm                1/1     Running   0          46s
prometheus-prometheus-node-exporter-zk4cm                1/1     Running   0          46s
prometheus-prometheus-oper-operator-7d5f8ff449-fl6x4     2/2     Running   0          46s
prometheus-prometheus-prometheus-oper-prometheus-0       3/3     Running   1          31

[/simterm]

Note: alias kk="kubectl" >> ~/.bashrc

Итак, Prometheus Operator деплоит нам целый набор сервисов – и сам Prometheus, и Alertmanager, и Grafana, плюс набор ServiceMonitors:

[simterm]

$ kk -n monitoring get servicemonitor
NAME                                                 AGE
prometheus-prometheus-oper-alertmanager              3m53s
prometheus-prometheus-oper-apiserver                 3m53s
prometheus-prometheus-oper-coredns                   3m53s
prometheus-prometheus-oper-grafana                   3m53s
prometheus-prometheus-oper-kube-controller-manager   3m53s
prometheus-prometheus-oper-kube-etcd                 3m53s
prometheus-prometheus-oper-kube-proxy                3m53s
prometheus-prometheus-oper-kube-scheduler            3m53s
prometheus-prometheus-oper-kube-state-metrics        3m53s
prometheus-prometheus-oper-kubelet                   3m53s
prometheus-prometheus-oper-node-exporter             3m53s
prometheus-prometheus-oper-operator                  3m53s
prometheus-prometheus-oper-prometheus                3m53s

[/simterm]

Роль ServiceMonitors рассмотрим ниже, когда будем добавлять свой ServiceMonitor в Добавление сервиса в мониторинг.

Grafana access

Пока всё это тестируем – пробросим порт на Grafana, что бы посмотреть какие дашборды там есть.

Находим под с Grafana:

[simterm]

$ kk -n monitoring get pod
NAME                                                     READY   STATUS    RESTARTS   AGE
alertmanager-prometheus-prometheus-oper-alertmanager-0   2/2     Running   0          103s
prometheus-grafana-85c9fbc85c-wl856                      2/2     Running   0          107s
...

[/simterm]

И вызываем port-forward:

[simterm]

$ kk -n monitoring port-forward prometheus-grafana-85c9fbc85c-wl856 3000:3000
Forwarding from 127.0.0.1:3000 -> 3000
Forwarding from [::1]:3000 -> 3000

[/simterm]

Открываем в браузере localhost:3000, логинимся с admin и паролем prom-operator, и получаем целый набор готовых графиков, например:

На момент написания – Prometheus Operator запускал Grafana 7.0.3 (на нашем “центровом” сервере мониторинга всё ещё 6.5).

Собственно, из всего этого в будущем просто можно будет надёргать примеров всяких запросов.

На сейчас нам тут не нужны ни сама Grafana, ни Alertmanager, так что попозже мы их выпилим.

Prometheus Operator configuration

Prometheus Operator использует Custom Resource Definitions, которые описывают ресурсы:

[simterm]

$ kk -n monitoring get crd
NAME                                    CREATED AT
alertmanagers.monitoring.coreos.com     2020-06-15T14:47:44Z
eniconfigs.crd.k8s.amazonaws.com        2020-04-10T07:21:20Z
podmonitors.monitoring.coreos.com       2020-06-15T14:47:45Z
prometheuses.monitoring.coreos.com      2020-06-15T14:47:46Z
prometheusrules.monitoring.coreos.com   2020-06-15T14:47:47Z
servicemonitors.monitoring.coreos.com   2020-06-15T14:47:47Z
thanosrulers.monitoring.coreos.com      2020-06-15T14:47:48Z

[/simterm]

Например, в prometheuses.monitoring.coreos.com описывается Custom Resource с именем Prometheus:

[simterm]

$ kk -n monitoring get crd prometheuses.monitoring.coreos.com -o yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  names:
    kind: Prometheus
    listKind: PrometheusList
    plural: prometheuses
    singular: prometheus
   ...

[/simterm]

И далее к нему можно обращаться, как к обычному ресурсу Kubernetes, используя имя из names:

[simterm]

$ kk -n monitoring get prometheus -o yaml
apiVersion: v1
items:
- apiVersion: monitoring.coreos.com/v1
  kind: Prometheus
  metadata:
    annotations:
      meta.helm.sh/release-name: prometheus
      meta.helm.sh/release-namespace: monitoring
      ...
  spec:
    alerting:
      alertmanagers:
      - apiVersion: v2
        name: prometheus-prometheus-oper-alertmanager
        namespace: monitoring
        pathPrefix: /
        port: web
    baseImage: quay.io/prometheus/prometheus
    enableAdminAPI: false
    externalUrl: http://prometheus-prometheus-oper-prometheus.monitoring:9090
    listenLocal: false
    logFormat: logfmt
    logLevel: info
    paused: false
    podMonitorNamespaceSelector: {}
    podMonitorSelector:
      matchLabels:
        release: prometheus
    portName: web
    replicas: 1
    retention: 10d
    routePrefix: /
    ruleNamespaceSelector: {}
    ruleSelector:
      matchLabels:
        app: prometheus-operator
        release: prometheus
    securityContext:
      fsGroup: 2000
      runAsGroup: 2000
      runAsNonRoot: true
      runAsUser: 1000
    serviceAccountName: prometheus-prometheus-oper-prometheus
    serviceMonitorNamespaceSelector: {}
    serviceMonitorSelector:
      matchLabels:
        release: prometheus
    version: v2.18.1
...

[/simterm]

Нам тут интересно значение serviceMonitorSelector:

...
serviceMonitorSelector: 
  matchLabels:
    release: prometheus

Которое определяет какие ServiceMonitors будут включены под “наблюдение” Prometheus.

Добавление сервиса в мониторинг

Теперь попробуем добавить новый сервис в наш мониторинг:

  1. запустим Redis server
  2. запустип redis_exporter
  3. добавим ServiceMonitor
  4. настроим Prometheus Operator использование этого ServiceMonitor для сбора метрик с експортёра

Запуск Redis server

Создаём неймспейс, что бы максимально приблизить условия к “боевым”, когда мониторинг и приложения работают не в одном default namespace, а в разных:

[simterm]

$ kk create ns redis-test
namespace/redis-test created

[/simterm]

Запускаем Redis:

[simterm]

$ kk -n redis-test run redis --image=redis
deployment.apps/redis created

[/simterm]

Создаём для него сервис:

[simterm]

$ kk -n redis-test expose deploy redis --type=ClusterIP --name redis-svc --port 6379
service/redis-svc exposed

[/simterm]

Проверяем:

[simterm]

$ kk -n redis-test get svc redis-svc
NAME        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
redis-svc   ClusterIP   172.20.237.116   <none>        6379/TCP   25s

[/simterm]

Окей – сам Redis работает, добавляем его експортер.

Запуск redis_exporter

Запускаем из Helm:

[simterm]

$ helm install -n monitoring redis-exporter --set "redisAddress=redis://redis-svc.redis-test.svc.cluster.local:6379" stable/prometheus-redis-exporter

[/simterm]

Проверяем его сервис:

[simterm]

$ kk -n monitoring get svc
NAME                                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
alertmanager-operated                      ClusterIP   None             <none>        9093/TCP,9094/TCP,9094/UDP   89m
...
redis-exporter-prometheus-redis-exporter   ClusterIP   172.20.239.67    <none>        9121/TCP                     84s

[/simterm]

Его ендпоинт:

[simterm]

$ kk -n monitoring get endpoints redis-exporter-prometheus-redis-exporter -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  ...
  labels:
    app: prometheus-redis-exporter
    app.kubernetes.io/managed-by: Helm
    chart: prometheus-redis-exporter-3.4.1
    heritage: Helm
    release: redis-exporter
  name: redis-exporter-prometheus-redis-exporter
  namespace: monitoring
  ...
  ports:
  - name: redis-exporter
    port: 9121
    protocol: TCP

[/simterm]

Поверим доступ к метрикам – запустим дебаг-под с Debian, устанавливаем в нём curl:

[simterm]

$ kk -n monitoring run --rm -ti debug --image=debian --restart=Never bash
If you don't see a command prompt, try pressing enter.
root@debug:/# apt update && apt -y install curl

[/simterm]

И обращаемся к ендпоинту сервиса redis_exporter:

[simterm]

root@debug:/# curl redis-exporter-prometheus-redis-exporter:9121/metrics
...
# HELP redis_up Information about the Redis instance
# TYPE redis_up gauge
redis_up 1
# HELP redis_uptime_in_seconds uptime_in_seconds metric
# TYPE redis_uptime_in_seconds gauge
redis_uptime_in_seconds 2793

[/simterm]

Либо без дополнительного пода – делаем port-forward на redis-svc:

[simterm]

$ kk -n monitoring port-forward svc/redis-exporter-prometheus-redis-exporter 9121:9121
Forwarding from 127.0.0.1:9121 -> 9121
Forwarding from [::1]:9121 -> 9121

[/simterm]

И проверяем с локальной машины:

[simterm]

$ curl localhost:9121/metrics
...
redis_up 1
# HELP redis_uptime_in_seconds uptime_in_seconds metric
# TYPE redis_uptime_in_seconds gauge
redis_uptime_in_seconds 8818

[/simterm]

Хорошо – у нас есть приложение – Редис, есть его експортёр, который отдаёт нам метрики на порту 9121 по URI /metrics – теперь надо настроить Prometheus Operator на сбор метрик с него.

Создание Kubernetes ServiceMonitor

Проверим лейблы нашего redis_exporter:

[simterm]

$ kk -n monitoring get deploy redis-exporter-prometheus-redis-exporter -o yaml                                                                                                                  
apiVersion: extensions/v1beta1                                                                                                                                                                                                                
kind: Deployment                                                                                                                                                                                                                              
metadata:                                                                                                                                                                                                                                     
  ...                                                                                                                                                                                                  
  generation: 1                                                                                                                                                                                                                               
  labels:                                                                                                                                                                                                                                     
    app: prometheus-redis-exporter                                                                                                                                                                                                            
    app.kubernetes.io/managed-by: Helm                                                                                                                                                                                                        
    chart: prometheus-redis-exporter-3.4.1                                                                                                                                                                                                    
    heritage: Helm                                                                                                                                                                                                                            
    release: redis-exporter
...

[/simterm]

И ещё раз глянем селектор serviceMonitorSelector ресурса prometheus:

[simterm]

$ kk -n monitoring get prometheus -o yaml

[/simterm]

В конце манифеста находим:

...
serviceMonitorSelector: 
  matchLabels:
    release: prometheus

Т.е. Prometheus ищет ServiceMonitor-ы с тегом release, у которых значение prometheus.

Далее – создаём ServiceMonitor:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    serviceapp: redis-servicemonitor
    release: prometheus
  name: redis-servicemonitor
  namespace: monitoring
spec:
  endpoints:
  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
    interval: 15s
    port: redis-exporter
  namespaceSelector:
    matchNames:
    - monitoring
  selector:
    matchLabels:
      release: redis-exporter

В его labels задаём release: prometheus, что бы Prometheus его обнаружил, а в selector.matchLabels – указываем поиск сервисов с тегом release: redis-exporter.

Применяем:

[simterm]

$ kk -n monitoring apply -f redis-service-monitor.yaml 
servicemonitor.monitoring.coreos.com/redis-servicemonitor created

[/simterm]

И проверяем таргеты Prometheus:

Метрики Редиса:

Что надо дальше?

А дальше надо выпилить из Operator запуск Alertmanager и Grafana.

Prometheus Operator Helm deployment и конфигурация сервисов

Хотя – а зачем выпиливать Графану? Там уже есть пачка готовых дашборд – пусть остаётся.

Давайте сделаем иначе:

  • на каждый EKS кластер выкатываем Operator с Grafana, но без Alertmanager
  • на локальных кластеру Prometheus retention period для хранения метрик используем дефолтный – 2 недели, и локальные Grafana будут выводить графики за две недели
  • Alertmanager выпиливаем – будем использовать его с центрального сервера мониторинга – там уже настроены роуты, каналы, и прочее – надо будет только добавить алерты
  • центральный сервер мониторинга хранит метрики год, и там нарисуем свою дашборду(ы) Grafana

Значит надо будет создать два LoadBalancer – один с типом internet-facing для Grafana, и один для Prometheus – internal, т.к. к Prometheus в кластере будет ходить “центральный” Prometheus через AWS VPC Peering, и с /federation забирать у него метрики.

И всё это надо заинтегрировать с нашей автоматизацией – Ansible, см. AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl.

Но сначала сделаем вручную, конечно.

Итак, что нам надо изменить в дефолтном деплое Prometheus Operator?

  • убрать деплой Alertmanager
  • добавить настройки для:
    • Prometheus и Grafana – должны деплоится за LoadBalancer
    • логин-пароль для Grafana

Дальше смотрим доступные параметры в документации – https://github.com/helm/charts/tree/master/stable/prometheus-operator.

Пока начнём с того, что передеплоим стек без Алертменеджера, для этого надо передать alertmanager.enabled.

Проверяем под сейчас:

[simterm]

$ kk -n monitoring get pod
NAME                                                        READY   STATUS             RESTARTS   AGE
alertmanager-prometheus-prometheus-oper-alertmanager-0      2/2     Running            0          24h
...

[/simterm]

Редеплоим, через --set задаём alertmanager.enabled=false:

[simterm]

$ helm upgrade --install --namespace monitoring --create-namespace prometheus stable/prometheus-operator --set "alertmanager.enabled=false"

[/simterm]

Проверяем поды ещё раз – Алертменеджера нет, отлично.

Настройка LoadBalancer

Итак, мы хотим открыть доступ из мира к Grafana – значит нужен будет ресурс Ingress для неё, и отдельный Ingress для Prometheus.

Что есть в доке:

grafana.ingress.enabled Enables Ingress for Grafana false
grafana.ingress.hosts Ingress accepted hostnames for Grafana []

Добавляем Ingress для Графаны:

[simterm]

$ helm upgrade --install --namespace monitoring --create-namespace prometheus stable/prometheus-operator --set "alertmanager.enabled=false" --set grafana.ingress.enabled=true 
...
Error: UPGRADE FAILED: failed to create resource: Ingress.extensions "prometheus-grafana" is invalid: spec: Invalid value: []networking.IngressRule(nil): either `backend` or `rules` must be specified

[/simterm]

Эм…

Думаете – в документации указано как его настроить? Счас…

Пригорает от такой “документации” иногда.

Какую-то подсказку удалось нагуглить тут: https://zero-to-jupyterhub.readthedocs.io/en/latest/administrator/advanced.html#ingress

Пробуем – теперь уже через values.yaml, что бы не городить кучу --set, добавляем hosts:

grafana:
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "alb"
      alb.ingress.kubernetes.io/scheme: "internet-facing"
    hosts:
      - "dev-0.eks.monitor.example.com"

Деплоим:

[simterm]

$ helm upgrade --install --namespace monitoring --create-namespace prometheus stable/prometheus-operator -f oper.yaml

[/simterm]

И смотрим логи ALB Controller:

I0617 11:37:48.272749       1 tags.go:43] monitoring/prometheus-grafana: modifying tags {  ingress.k8s.aws/cluster: “bttrm-eks-dev-0”,  ingress.k8s.aws/stack: “monitoring/prometheus-grafana”,  kubernetes.io/service-name: “prometheus-grafa
na”,  kubernetes.io/service-port: “80”,  ingress.k8s.aws/resource: “monitoring/prometheus-grafana-prometheus-grafana:80”,  kubernetes.io/cluster/bttrm-eks-dev-0: “owned”,  kubernetes.io/namespace: “monitoring”,  kubernetes.io/ingress-name
: “prometheus-grafana”} on arn:aws:elasticloadbalancing:us-east-2:534***385:targetgroup/96759da8-e0b8253ac04c7ceacd7/35de144cca011059
E0617 11:37:48.310083       1 controller.go:217] kubebuilder/controller “msg”=”Reconciler error” “error”=”failed to reconcile targetGroups due to failed to reconcile targetGroup targets due to prometheus-grafana service is not of type NodePort or LoadBalancer and target-type is instance”  “controller”=”alb-ingress-controller” “request”={“Namespace”:”monitoring”,”Name”:”prometheus-grafana”}

Хорошо – давайте добавим /target-type: "ip", что бы AWS ALB слал трафик прямо на под с Grafana, а не на WorkerNodes, заодно добавим валидные коды ответов:

grafana:
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "alb"  
      alb.ingress.kubernetes.io/scheme: "internet-facing"
      alb.ingress.kubernetes.io/target-type: "ip"
      alb.ingress.kubernetes.io/success-codes: 200,302
    hosts:
      - "dev-0.eks.monitor.example.com"

Либо, что бы использовать Instance type – можно переопределить тип Service для Grafana, и задать его в NodePort:

grafana:
  service:
    type: NodePort
    port: 80
    annotations: {}
    labels: {}  
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "alb"
      alb.ingress.kubernetes.io/scheme: "internet-facing"
      alb.ingress.kubernetes.io/success-codes: 200,302
    hosts:
      - "dev-0.eks.monitor.example.com"

Передеплоиваем:

[simterm]

$ helm upgrade --install --namespace monitoring --create-namespace prometheus stable/prometheus-operator -f oper.yaml

[/simterm]

Через минуту поднялся ALB:

[simterm]

$ kk -n monitoring get ingress
NAME                 HOSTS                              ADDRESS                                                                 PORTS   AGE
prometheus-grafana   dev-0.eks.monitor.example.com   96759da8-monitoring-promet-***.us-east-2.elb.amazonaws.com   80      30m

[/simterm]

Но теперь при открытии страницы dev-0.eks.monitor.example.com – ALB отдаёт 404:

[simterm]

$ curl -vL dev-0.eks.monitor.example.com
*   Trying 3.***.***.247:80...
* Connected to dev-0.eks.monitor.example.com (3.***.***.247) port 80 (#0)
> GET / HTTP/1.1
> Host: dev-0.eks.monitor.example.com
> User-Agent: curl/7.70.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
...
< Location: /login
...
* Connection #0 to host dev-0.eks.monitor.example.com left intact
* Issue another request to this URL: 'http://dev-0.eks.monitor.example.com/login'
...
> GET /login HTTP/1.1
...
< HTTP/1.1 404 Not Found
< Server: awselb/2.0

[/simterm]

Что тут происходит?

  1. ALB принимает запрос к dev-0.eks.monitor.example.com, отправляет его на TargetGroup с Grafana
  2. Grafana возвращает 302 /login
  3. мы возвращаемся к ALB, но теперь в URI передаём /login

Проверяем правила Listener:

Ну, да – а в правилах балансера на все запросы кроме / мы возвращаем 404. Соответсвенно, и на /login тоже возвращается 404.

Очень хотелось бы увидеть комментарии разработчика, который дефолтным рулом path задал именно такое.

Возвращаемся к нашему values.yaml, добавляем path равным /*.

А hosts, что бы избежать ошибки “Invalid value: []networking.IngressRule(nil): either `backend` or `rules` must be specified” и не привязываться к конкретному домену можно задать просто в виде "":

grafana:
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "alb"
      alb.ingress.kubernetes.io/target-type: "ip"
      alb.ingress.kubernetes.io/scheme: "internet-facing"
      alb.ingress.kubernetes.io/success-codes: 200,302
    hosts:
      - ""
    path: /*

Редеплоим, проверяем:

[simterm]

$ kk -n monitoring get ingress
NAME                 HOSTS   ADDRESS                                                                  PORTS   AGE
prometheus-grafana   *       96759da8-monitoring-promet-bb6c-2076436438.us-east-2.elb.amazonaws.com   80      15m

[/simterm]

И открываем в браузере:

Prometheus и Lens

И даже в Lens появились все графики вместо ошибок “Metrics are not available due to missing or invalid Prometheus configuration” и “Metrics not available at the moment“:

В целом – на этом, думаю, всё основное рассмотрели.

Можно прикручивать автоматизацию, и выкатывать в тестирование.

Ссылки по теме