Introduction au Service Discovery Kubernetes dans Prometheus

BrunoBruno
6 min read

Présentation

Les clusters Kubernetes possèdent des labels et annotations pour ses différents composants (pods, services, etc...) Pour découvrir les cibles, Prometheus a besoin d'utiliser l'API Kubernetes.

Prometheus est livré avec un plugin d'auto-découverte Kubernetes du nom de <kubernetes_sd_config>. Les configurations Service Discovery de Kubernetes permettent la récupération automatique des cibles à scraper de façon à toujours être synchronisés avec l'état du cluster.

Plusieurs types de rôles peuvent être configurés pour la découverte des targets. Selon le type, Prometheus met à disposition différents meta_labels. Voir la documentation officielle

Pour que le service discovery soit opérationnel, les Targets Prometheus sont scrapées avec des labels particuliers attendus : __address__, __scheme__, __metrics_path__.

Les URL cibles qui seront utilisés seront formés ainsi : __scheme__://__address____metrics_path__

Une directive <relabel_config> existe, qui va nous permettre de transformer et créer un ensemble de labels pour correspondre à ce qu'attend Prometheus pour permettre le scraping.

Pour ce faire, nous allons transformer les metalabels vers les labels __ vus plus haut.

L'idée est de construire les labels Prometheus scheme, address et metrics_path avec l'aide des informations transmises dans les annotations des services que l'on souhaite scraper.

Architecture

Pour fonctionner, Prometheus va avoir besoin d'un ServiceAccount, un ClusterRole et un ClusterRoleBinding, le tout pour permettre à ce dernier de pouvoir lire les objets Kubernetes sur l’intégralité du cluster et récupérer les différentes annotations.

Créons le fichier rbac.yaml pour définir le compte de service que nous utiliserons pour Prometheus et lui affecter quelques autorisations sur les objets Kubernetes.

# rbac.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: monitoring
---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: monitoring
  name: prometheus
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: discoverer
rules:
- apiGroups: [""]
  resources:
  - nodes
  - services
  - endpoints
  - pods
  verbs: ["get", "list", "watch"]
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: prometheus-discoverer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: discoverer
subjects:
- kind: ServiceAccount
  name: prometheus
  namespace: monitoring

Appliquons les modifications.

$ kubectl apply -f rbac.yaml

kubernetes_sd_config

kubernetes_sd_config permet le scraping de targets depuis l'API REST Kubernetes.

Plusieurs types de roles peuvent être configurés pour la découverte de cibles:

  • node

  • service

  • pod

  • endpoint

  • endpointslice

  • ingress

Chaque rôle met à disposition différents metalabels. La liste de ces metalabels se trouvent sur ce lien.

Configuration

La syntaxe des annotations K8S se présente sous forme de paire clé/valeur. Les clés d'annotation présentent 2 segments. Un préfixe optionnel et un nom, séparé par un /.

Partons du principe que nous créons une application sous forme de déploiement ainsi que son service de type ClusterIP associé. L’application utilisée ici pour la démo est RabbitMQ, qui expose un path pour la collecte de métriques au format Prometheus sur le port 15692.

Prometheus

Procédons à la création des manifestes pour l’installation de Prometheus

# prometheus.yaml
apiVersion: v1
kind: Service
metadata:
    name: prometheus-service
    namespace: monitoring
    annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port:   '9090'

spec:
    selector:
        app: prometheus
    type: NodePort
    ports:
    - port: 8080
      targetPort: 9090
      nodePort: 30909
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
  labels:
    app: prometheus
spec:
  replicas: 1
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
    spec:
      serviceAccountName: prometheus
      containers:
      - name: prometheus
        image: prom/prometheus
        args:
          - '--storage.tsdb.retention=6h'
          - '--storage.tsdb.path=/prometheus'
          - '--config.file=/etc/prometheus/prometheus.yml'
        ports:
        - name: web
          containerPort: 9090
        volumeMounts:
        - name: prometheus-config-volume
          mountPath: /etc/prometheus
      volumes:
      - name: prometheus-config-volume
        configMap:
            defaultMode: 420
            name: prometheus-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
  labels:
    name: prometheus-config
data:
  prometheus.yml: |-
    scrape_configs:
      - job_name: 'kubernetes-service-endpoints'
        scrape_interval: 10s
        scrape_timeout: 5s
        kubernetes_sd_configs:
        - role: endpoints
        relabel_configs:
        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_scrape]
          action: keep
          regex: true
        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_scheme]
          action: replace
          target_label: __scheme__
          regex: (https?)
        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_path]
          action: replace
          target_label: __metrics_path__
          regex: (.+)
        - source_labels: [__address__, __meta_kubernetes_service_annotation_io_prometheus_port]
          action: replace
          target_label: __address__
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:$2
        - source_labels: [__meta_kubernetes_namespace]
          action: replace
          target_label: kubernetes_namespace
        - source_labels: [__meta_kubernetes_service_name]
          action: replace
          target_label: kubernetes_service
        - source_labels: [__meta_kubernetes_pod_name]
          action: replace
          target_label: kubernetes_pod
        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_app]
          action: replace
          target_label: app

En consultant la documentation, nous pouvons voir que, pour le role: service il existe un metalabel appelé __meta_kubernetes_service_annotation_<annotationname> qui va faire le mapping avec l'annotation correspondante (slug) dans l'objet service.

Un slug est une représentation textuelle simplifiée d'une ressource (ou de son titre) et dont le format lui permet d'être passé en paramètre d'une url tout comme un identifiant. Le format n'est pas normalisé, mais il respecte au minimum ces quelques règles :

  • Uniquement des caractères minuscules

  • Pas de caractères accentués

  • Aucun espace ni aucun caractère spécial à part "-" ou "_"

Arrêtons nous sur l’analyse du ConfigMap:

        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_scrape]
          action: keep
          regex: true

⬆️ Cette première instruction permet de définir si nous voulons scraper ou pas les métriques. Seule la valeur à true permettra à Prometheus de venir scraper de la data.

        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_scheme]
          action: replace
          target_label: __scheme__
          regex: (https?)
        - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_path]
          action: replace
          target_label: __metrics_path__
          regex: (.+)
        - source_labels: [__address__, __meta_kubernetes_service_annotation_io_prometheus_port]
          action: replace
          target_label: __address__
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:$2

⬆️ Ces instructions vont définir ce que nous évoquions plus haut, à savoir la construction de l’URL que va scraper Prometheus.

Souvenez vous, Prometheus attend l’URL de scraping avec les labels suivants __scheme__://__address____metrics_path__

source_labels peut être une liste de labels concaténés en utilisant un séparateur qui peut être personnalisé et qui est ; par défaut.

        - source_labels: [__meta_kubernetes_namespace]
          action: replace
          target_label: kubernetes_namespace
        - source_labels: [__meta_kubernetes_service_name]
          action: replace
          target_label: kubernetes_service
        - source_labels: [__meta_kubernetes_pod_name]
          action: replace
          target_label: kubernetes_pod

⬆️ Ici, nous récupérons et transformons certains metalabels mis à disposition nativement par des labels qui pourront nous être utiles lors de requêtes PromQL.

  - source_labels: [__meta_kubernetes_service_annotation_io_prometheus_app]
    action: replace
    target_label: app

⬆️ Il est possible, comme le montre le code au-dessus de créer des labels personnalisés depuis les metalabels __meta_kubernetes_service_annotation_<annotationname> que nous transformons par un label particulier, ici app.

Après les différents relabels évalués, la cible des métriques de l'application sera scapée sous cette forme : __scheme__://__address____metrics_path__

Installons maintenant Prometheus

$ kubectl apply -f prometheus.yaml

Application cible (RabbitMQ)

Procédons maintenant à la création des manifestes pour notre application RabbitMQ

# rabbitmq.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rabbitmq
  namespace: default
  labels:
    app: rabbitmq
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rabbitmq
  template:
    metadata:
      labels:
        app: rabbitmq
    spec:
      containers:
      - name: rabbitmq
        image: rabbitmq:3-management
        imagePullPolicy: Always
        ports:
        - containerPort: 15692
---
apiVersion: v1
kind: Service
metadata:
  namespace: default
  name: rabbitmq
  annotations:
    io.prometheus/app: "rabbitmq"
    io.prometheus/scrape: "true"
    io.prometheus/scheme: "http"
    io.prometheus/path: "/metrics"
    io.prometheus/port: "15692"
spec:
  selector:
    app: rabbitmq
  ports:
  - port: 15692

Procédons à l’installation

$ kubectl apply -f rabbitmq.yaml

Il ne nous reste plus qu’à vérifier que Prometheus a bien pris en compte l’arrivée de notre application RabbitMQ et que le scraping est maintenant actif.

Great !

Conclusion

Nous sommes maintenant parfaitement capables de pouvoir scraper n’importe quelle nouvelle solution applicative installée sur notre cluster Kubernetes sans même devoir toucher à la configuration Prometheus mais simplement en y ajoutant certaines annnotations sur l’objet Kubernetes service de notre application.


Références

0
Subscribe to my newsletter

Read articles from Bruno directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bruno
Bruno

Depuis août 2024, j'accompagne divers projets sur l'optimisation des processus DevOps. Ces compétences, acquises par plusieurs années d'expérience dans le domaine de l'IT, me permettent de contribuer de manière significative à la réussite et l'évolution des infrastructures de mes clients. Mon but est d'apporter une expertise technique pour soutenir la mission et les valeurs de mes clients, en garantissant la scalabilité et l'efficacité de leurs services IT.