DEV Community

BeardDemon
BeardDemon

Posted on

Comment j'ai mis en place une stack de monitoring complète sur Kubernetes

Le problème

Avant le monitoring, débugger c'était:

  1. SSH sur le master
  2. kubectl get pods pour trouver le pod qui plante
  3. kubectl logs <pod> pour voir les logs
  4. Répéter pour chaque pod
  5. Perdre 30 minutes pour comprendre ce qui se passe

Résultat: c'était insupportable avec 10+ pods qui tournent.

La solution: Loki + Fluent Bit + Grafana

L'idée c'est simple:

  • Fluent Bit: collecte les logs de TOUS les pods
  • Loki: stocke les logs (comme ElasticSearch mais en plus simple)
  • Grafana: interface web pour chercher et visualiser

Tout centralisé, recherche en temps réel, c'est le rêve.

Architecture

┌─────────────────────────────────────────┐
│         Cluster Kubernetes              │
│                                         │
│  ┌────────┐  ┌────────┐  ┌────────┐   │
│  │  Pod 1 │  │  Pod 2 │  │  Pod N │   │
│  └───┬────┘  └───┬────┘  └───┬────┘   │
│      │           │            │        │
│      └───────────┼────────────┘        │
│                  ▼                      │
│          ┌──────────────┐               │
│          │ Fluent Bit   │               │
│          │ (DaemonSet)  │               │
│          └──────┬───────┘               │
│                 │                       │
│                 ▼                       │
│          ┌──────────────┐               │
│          │     Loki     │               │
│          └──────┬───────┘               │
│                 │                       │
│                 ▼                       │
│          ┌──────────────┐               │
│          │   Grafana    │               │
│          └──────────────┘               │
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Installation de Loki

Voici mon manifest Kubernetes pour Loki:

apiVersion: v1
kind: ConfigMap
metadata:
  name: loki-config
  namespace: logging
data:
  loki.yaml: |
    auth_enabled: false

    server:
      http_listen_port: 3100
      log_level: info

    common:
      path_prefix: /loki
      storage:
        filesystem:
          chunks_directory: /loki/chunks
          rules_directory: /loki/rules
      replication_factor: 1

    schema_config:
      configs:
        - from: 2020-10-24
          store: tsdb
          object_store: filesystem
          schema: v13
          index:
            prefix: index_
            period: 24h
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: loki
  namespace: logging
spec:
  serviceName: loki
  replicas: 1
  template:
    spec:
      containers:
        - name: loki
          image: grafana/loki:3.0.0
          args:
            - -config.file=/etc/loki/loki.yaml
          ports:
            - containerPort: 3100
          volumeMounts:
            - name: config
              mountPath: /etc/loki
            - name: storage
              mountPath: /loki
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
      volumes:
        - name: config
          configMap:
            name: loki-config
  volumeClaimTemplates:
    - metadata:
        name: storage
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 5Gi
Enter fullscreen mode Exit fullscreen mode

Le StatefulSet c'est important pour garder les données même si le pod redémarre.

Configuration de Fluent Bit

Fluent Bit tourne en DaemonSet (= 1 pod par node) pour collecter les logs:

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush        5
        Log_Level    info

    [INPUT]
        Name              tail
        Path              /var/log/containers/*.log
        Parser            docker
        Tag               kube.*
        Refresh_Interval  5
        Mem_Buf_Limit     5MB

    [FILTER]
        Name                kubernetes
        Match               kube.*
        Kube_URL            https://kubernetes.default.svc:443
        Merge_Log           On
        Keep_Log            Off

    [OUTPUT]
        Name              loki
        Match             *
        Host              loki
        Port              3100
        Labels            job=fluentbit
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  template:
    spec:
      serviceAccountName: fluent-bit
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:3.1.9
          volumeMounts:
            - name: varlog
              mountPath: /var/log
              readOnly: true
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
            - name: config
              mountPath: /fluent-bit/etc/
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
        - name: config
          configMap:
            name: fluent-bit-config
Enter fullscreen mode Exit fullscreen mode

Explication:

  • [INPUT]: Lit les logs dans /var/log/containers/*.log
  • [FILTER]: Ajoute les métadonnées Kubernetes (namespace, pod name, etc.)
  • [OUTPUT]: Envoie tout à Loki

Le hostPath monte les dossiers de logs de l'hôte dans le container.

Permissions RBAC pour Fluent Bit

Fluent Bit a besoin de permissions pour lire les infos Kubernetes:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fluent-bit
  namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fluent-bit
rules:
  - apiGroups: [""]
    resources: ["pods", "namespaces"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: fluent-bit
subjects:
  - kind: ServiceAccount
    name: fluent-bit
    namespace: logging
roleRef:
  kind: ClusterRole
  name: fluent-bit
  apiGroup: rbac.authorization.k8s.io
Enter fullscreen mode Exit fullscreen mode

Sans ça, Fluent Bit peut pas enrichir les logs avec les infos des pods.

Installation de Grafana

apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources
  namespace: logging
data:
  datasources.yaml: |
    apiVersion: 1
    datasources:
      - name: Loki
        type: loki
        access: proxy
        url: http://loki:3100
        isDefault: true
        jsonData:
          maxLines: 1000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: logging
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: grafana
          image: grafana/grafana:10.0.0
          ports:
            - containerPort: 3000
          env:
            - name: GF_SECURITY_ADMIN_USER
              value: "admin"
            - name: GF_SECURITY_ADMIN_PASSWORD
              value: "monmotdepasse"
            - name: GF_PATHS_PROVISIONING
              value: /etc/grafana/provisioning
          volumeMounts:
            - name: grafana-datasources
              mountPath: /etc/grafana/provisioning/datasources
      volumes:
        - name: grafana-datasources
          configMap:
            name: grafana-datasources
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: logging
spec:
  type: NodePort
  ports:
    - port: 3000
      targetPort: 3000
      nodePort: 30300
  selector:
    app: grafana
Enter fullscreen mode Exit fullscreen mode

Le ConfigMap configure automatiquement Loki comme datasource.

Déploiement avec Ansible

Mon rôle Ansible qui déploie tout:

- name: Créer namespace logging
  command: kubectl create namespace logging
  environment:
    KUBECONFIG: /etc/kubernetes/admin.conf
  ignore_errors: yes

- name: Déployer Loki
  command: kubectl apply -f /vagrant/manifests/logging/loki-values.yml
  environment:
    KUBECONFIG: /etc/kubernetes/admin.conf

- name: Attendre que Loki démarre
  command: kubectl wait --for=condition=ready pod/loki-0 -n logging --timeout=120s
  environment:
    KUBECONFIG: /etc/kubernetes/admin.conf

- name: Déployer Fluent Bit + Grafana
  command: kubectl apply -f /vagrant/manifests/logging/fluentbit-grafana.yaml
  environment:
    KUBECONFIG: /etc/kubernetes/admin.conf

- name: Afficher les infos de connexion
  debug:
    msg:
      - "Stack de Logging déployée!"
      - "Grafana: http://192.168.56.10:30300"
      - "Username: admin"
      - "Password: monmotdepasse"
Enter fullscreen mode Exit fullscreen mode

Utilisation dans Grafana

Une fois connecté à Grafana (http://192.168.56.10:30300):

Explore → Loki

Requête de base:

{job="fluentbit"}
Enter fullscreen mode Exit fullscreen mode

Ça affiche tous les logs.

Filtrer par namespace:

{job="fluentbit", namespace="apps"}
Enter fullscreen mode Exit fullscreen mode

Filtrer par pod:

{job="fluentbit", pod="apicatalogue-xyz"}
Enter fullscreen mode Exit fullscreen mode

Chercher des erreurs:

{job="fluentbit"} |= "error"
Enter fullscreen mode Exit fullscreen mode

Combiner plusieurs filtres:

{job="fluentbit", namespace="apps"} |= "error" != "404"
Enter fullscreen mode Exit fullscreen mode

Créer un dashboard

  1. Dans Grafana: DashboardsNew Dashboard
  2. Add visualization
  3. Query:
   sum by (pod) (count_over_time({job="fluentbit"}[5m]))
Enter fullscreen mode Exit fullscreen mode
  1. Ça affiche le nombre de logs par pod sur 5 minutes

Pratique pour voir quel pod spam les logs.

Les erreurs que j'ai faites

Erreur #1: Pas assez de ressources pour Loki

Au début j'avais mis:

resources:
  limits:
    memory: 128Mi
Enter fullscreen mode Exit fullscreen mode

Loki plantait dès qu'il y avait trop de logs.

Solution: Au moins 512Mi de RAM.

Erreur #2: Fluent Bit sans permissions

J'avais oublié le ServiceAccount et les ClusterRole.

Résultat: Fluent Bit tournait mais envoyait rien à Loki.

Solution: Toujours créer les permissions RBAC.

Erreur #3: Parser les logs JSON manuellement

Au début je parsais pas les logs JSON de Gunicorn.

Résultat: tout en texte brut, impossible de filtrer par niveau de log.

Solution: Ajouter un parser dans la config Fluent Bit.

Vérifier que ça marche

# Logs de Loki
kubectl logs -n logging loki-0

# Logs de Fluent Bit (sur tous les nodes)
kubectl logs -n logging daemonset/fluent-bit

# Tester l'API Loki directement
curl http://192.168.56.10:30300/loki/api/v1/query_range?query={job="fluentbit"}
Enter fullscreen mode Exit fullscreen mode

Requêtes LogQL avancées

Top 10 des pods qui loguent le plus:

topk(10, sum by (pod) (count_over_time({job="fluentbit"}[1h])))
Enter fullscreen mode Exit fullscreen mode

Logs d'erreurs des 10 dernières minutes:

{job="fluentbit"} |= "ERROR" [10m]
Enter fullscreen mode Exit fullscreen mode

Rate de logs par seconde:

rate({job="fluentbit"}[1m])
Enter fullscreen mode Exit fullscreen mode

Conclusion

Cette stack m'a vraiment changé la vie pour le debug:

  • Plus besoin de SSH
  • Recherche instantanée dans tous les logs
  • Dashboards custom
  • Historique complet (tant que le PVC tient)

Le seul défaut: ça consomme pas mal de ressources (Loki + Grafana = ~700MB de RAM).

Mais ça vaut le coup, vraiment.

Code complet: [GitHub]

Top comments (0)