Kubernetes n'est pas seulement une solution pour mettre en cluster des machines exécutant des conteneurs ; il renferme tout un tas de composants notamment son API qui peut être étendu. Ces extensions appelées opérateur permettent de rajouter une couche d'abstraction et de pouvoir déployer facilement des applications complexes. Aujourd'hui, nous allons nous intéresser aux opérateurs et pourquoi vous devez absolument les utiliser à travers un exemple de déploiement de Prometheus.
Toute est une question de ressource
Quand on pense à Kubernetes, les termes de "Pod", "Ingress" ou "PersistentVolumeClaim" reviennent très souvent. Ce sont tous des ressources et leur définitions (c'est-à-dire leur schéma) sont renfermées dans le code source de Kubernetes. Ces ressources sont toutes centraliser en groupe d'API qui peuvent être divisés selon leur version.
kind: Job
apiVersion: batch/v1
metadata:
name: example-job
spec:
...
Dans l’exemple ci-dessus, on peut voir que le type de resource appelé “Job” appartient au groupe d’API “batch/v1”.
Les opérateurs permettent de créer de nouveaux groupes et de nouvelles définition de ressources appelées CustomRessourceDefinition (CRD)
Déploiement de Prometheus sans Opérateur
Prometheus est un logiciel libre de surveillance informatique et générateur d'alertes. Il enregistre des métriques en temps réel dans une base de données de séries temporelles (avec une capacité d'acquisition élevée) en se basant sur le contenu de point d'entrée exposé à l'aide du protocole HTTP
Pour déployer, une instance simple de Prometheus, il nous faut au minimum définir plusieurs ressources :
- Un ClusterRole et un ClusterRoleBinding : donnant des autorisations supplémentaires à Prometheus pour qu'il récupère des métriques de Kubernetes
- Une ConfigMap : définissant les points d'entrées où venir chercher les données
- Un Deployment: déployant le serveur Prometheus
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: default
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/proxy
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups:
- extensions
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-server-conf
labels:
name: prometheus-server-conf
namespace: monitoring
data:
prometheus.yml: |-
global:
scrape_interval: 5s
evaluation_interval: 5s
rule_files:
- /etc/prometheus/prometheus.rules
scrape_configs:
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
- job_name: 'kubernetes-nodes'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
- job_name: 'kubernetes-cadvisor'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-deployment
namespace: monitoring
labels:
app: prometheus-server
spec:
replicas: 1
selector:
matchLabels:
app: prometheus-server
template:
metadata:
labels:
app: prometheus-server
spec:
containers:
- name: prometheus
image: prom/prometheus
args:
- "--storage.tsdb.retention.time=12h"
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus/"
ports:
- containerPort: 9090
resources:
requests:
cpu: 500m
memory: 500M
limits:
cpu: 1
memory: 1Gi
volumeMounts:
- name: prometheus-config-volume
mountPath: /etc/prometheus/
- name: prometheus-storage-volume
mountPath: /prometheus/
volumes:
- name: prometheus-config-volume
configMap:
defaultMode: 420
name: prometheus-server-conf
- name: prometheus-storage-volume
emptyDir: {}
Une fois déployée, je vais utiliser l’API de Prometheus pour checker si la configuration s’est bien déployée et si tous les point d’entrées sont actifs :
$ curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | .labels.job + ": " + .health'
"kubernetes-apiservers: up"
"kubernetes-cadvisor: up"
"kubernetes-nodes: up"
"kubernetes-service-endpoints: up"
La maintenance de ce déploiement
Vous êtes surement en train d'écrire un commentaire en me demandant où est le soucis avec ce déploiement. Je vous répondrais que, pour l'instant, il y n'en a pas. Pour l'instant .... Car imaginons que votre chef vous demande de rajouter une règle d'alerte ou un nouveau point d'entrée.
PANIQUE A BORD ! Pas de suite, mais vous allez comprendre votre douleur plus tard. Vous allez donc modifier votre ConfigMap pour ajouter ce qu'il faut. Cependant, pour que Prometheus soit au courant de cette modification, vous devez supprimez manuellement les pods ; en espérant que vous avez configurés un stockage persistent (ce qui n'est pas le cas dans mon exemple) sinon adieu les données !
Bon c'était galère mais pas insurmontable. Maintenant, imaginons que vous devez rajouter les points d'entrée de toutes les applications de vos développeurs. Et oui, vous êtes le seul à pouvoir le faire car la ConfigMap est dans un namespace où seul vous et votre responsable avez les droits pour la modifier. Vous commencez à comprendre la lourdeur de la tâche ? Toute la configuration va être centralisée en un seul point, vous devez la modifier manuellement à chaque changement, etc... C'est là où les opérateurs vont vous être utiles.
Un opérateur, qu'est-ce que c'est ?
Un opérateur est composé de deux éléments :
- Des définitions de ressources personnalisées (CRDs) qui vont vous permettent de déclarer de nouvelles ressources
- Un contrôleur qui va observer les évènements liés à ces nouvelles ressources (création, modification, suppression) et va réagir en conséquence.
L'opérateur kube-prometheus pour vous sauver
Revenons à notre exemple, et voyons comment un opérateur appelé 'kube-prometheus' va sauver votre vie ! Pour simplifier encore plus le déploiement, je vais utiliser la helm chart fournie par Bitnami mais j'aurais pu le faire manuellement.
$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm install prometheus bitnami/kube-prometheus -n monitoring
Et là pouf, en quelques secondes vous avez déployé le contrôleur de l'opérateur, un Prometheus et même un Alertmanager ! Vous avez même en prime un déploiement de Node Exporter et de Kube-stats-metrics mais rien à voir avec l'opérateur en lui même !
Je peux refaire la même commande que dans la partie précédente pour voir si Prometheus est bien déployé et commence à récupérer les données :
$ curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | .labels.job + ": " + .health'
"prometheus-kube-prometheus-alertmanager: up"
"apiserver: up"
"prometheus-kube-prometheus-coredns: up"
"prometheus-kube-prometheus-kube-controller-manager: up"
"prometheus-kube-prometheus-kube-proxy: up"
"prometheus-kube-prometheus-kube-scheduler: up"
"kubelet: up"
"kubelet: up"
"prometheus-kube-prometheus-operator: up"
"prometheus-kube-prometheus-prometheus: up"
"prometheus-kube-state-metrics: up"
"node-exporter: up"
Cet opérateur ajoute de nombreuses nouvelles ressources comme 'Prometheus'. Vous pouvez la voir en tapant cette commande kubectl :
$ kubectl get prometheus -n monitoring
NAME VERSION REPLICAS AGE
prometheus-kube-prometheus-prometheus 1 2m
$ kubectl get statefulsets -n monitoring
NAME READY AGE
alertmanager-prometheus-kube-prometheus-alertmanager 1/1 25m
prometheus-prometheus-kube-prometheus-prometheus 1/1 25m
La dernière commande vous permettent de comprendre comment l'opérateur (= le contrôleur) fonctionne : quand une ressource de type 'Prometheus' va apparaitre, il va créer un StatefulSet et pleins de ressources (ConfigMap, Secret, Service) associées. Et dans la ressource 'Prometheus', il y a des champs qui permettent de le configurer comme la période de retention, etc.. Et à chaque changement, l'opérateur va mettre à jour les ressources associées, sans redémarrage nécessaire. Egalement, si je supprime le StatefulSet, l'opérateur va immédiatement en récréer un.
Une autre nouvelle ressource intéressante est le ServiceMonitor. Il va permettre de définir les point d'entrées dynamiquement sans avoir à gérer une configuration centrale. Pour vous montrer un exemple, je vais déployer une application de test dans le namespace 'web'. Cette application expose des métriques au format Prometheus sur le port 8081 et sous l'URL /metrics. Je souhaite que ces métriques soient accessibles dans Prometheus.
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-metrics
namespace: web
labels:
app: golang
spec:
replicas: 1
selector:
matchLabels:
app: golang
template:
metadata:
labels:
app: golang
spec:
containers:
- name: go-metrics
image: paulbrissaud/go-metrics
ports:
- containerPort: 8081
---
apiVersion: v1
kind: Service
metadata:
name: go-metrics-service
namespace: web
labels:
app: golang
spec:
selector:
app: golang
ports:
- name: http
protocol: TCP
port: 8081
targetPort: 8081
Pour ce faire, je vais donc déployer un serviceMonitor dans le même namespace. J'indique qu'un service avec le label 'app=golang' expose des métriques Prometheus sur le port nommé 'http' et sous le chemin '/metrics'
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: go-metrics
namespace: web
spec:
selector:
matchLabels:
app: golang
endpoints:
- port: http
path: /metrics
En effectuant la même requête à l’API de Prometheus, je vois un nouveau point d'entrée : celui de l'application ! Pour la petite explication du fonctionnement, chaque serviceMonitor écrit dans la configuration "centrale" de Prometheus et un autre composant permet au Pod de recharger la configuration sans avoir à le supprimer. Grâce au ServiceMonitor, je peux laisser les développeurs s'occuper du monitoring de leur propre application sans aucune intervention de ma part.
$ curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | .labels.job + ": " + .health'
"prometheus-kube-prometheus-alertmanager: up"
"apiserver: up"
"prometheus-kube-prometheus-coredns: up"
"prometheus-kube-prometheus-kube-controller-manager: up"
"prometheus-kube-prometheus-kube-proxy: up"
"prometheus-kube-prometheus-kube-scheduler: up"
"kubelet: up"
"kubelet: up"
"prometheus-kube-prometheus-operator: up"
"prometheus-kube-prometheus-prometheus: up"
"prometheus-kube-state-metrics: up"
"node-exporter: up"
"go-metrics-service: up" #L'application
Il existe des dizaines d'autres opérateurs permettant de faciliter le déploiement et le maintien de bases de données, de composants Kubernetes ou d'outils de monitoring. J'espère vous avoir donner l'envie de franchir le pas, vous n'allez pas le regretter ! Et puis, s'il n'existe pas encore d'opérateur pour vos besoins, leur création est relativement simple !