Comment installer la stack de monitoring prometheus/grafana avec ansible ?

Print Friendly, PDF & Email

Vous le savez surement mais j’aime beaucoup ansible par sa simplicité. Mais pas seulement, il y a aussi sa communauté qui lui permet de partager un grande nombre de rôles notamment via galaxy. On ne va pas en profiter aujourd’hui mais souvent c’est un bon moyen de voir comment on installe et configure une technologie. Voir de se rendre compte de la complexité ou non de la chose et même si à la fin on ne veut pas forcément le faire avec ansible cela reste une sorte de doc à suivre.

Le monitoring est un point indispensable de notre métier. Il a nettement évolué au cours de la dernière décennie, notamment avec la montée en force de la conteneurisation et du cloud. Ainsi le monitoring a dû se mettre à la page pour adapter une configuration dynamique. On ne peut plus se permettre de rentrer les machines à monitorer une à une.

Je vous propose de joindre l’utile à l’agréable et vous allez prendre, j’espère, un peu de plaisir à faire du ansible tout en découvrant la stack prometheus, grafana et les exporter. Bonne formation ansible à toutes et à tous !!

Vous pouvez retrouver la formation prometheus/grafana and Co ici :

La structure de nos rôles

Alors avec ansible, c’est un exercice assez intéressant pour débuter. Nous allons principalement avoir 3 rôles : node exporter, prometheus et grafana. Ce découpage est intéressant car node exporter doit être installé sur toutes les machines pour remonter les métriques et les deux autres sur une seule machine, le noyau central.

Pourquoi découper en deux prometheus et grafana dans 2 rôles différents ? Généralement plus les briques sont fines et plus on peut faire de choses avec ses legos et bien là c’est le même principe. On se donne le choix d’un jour pouvoir installer juste un prometheus ou juste un grafana ou encore mieux de partager dans la communauté vos rôles.

Pour l’inventaire on va partir sur 3 machines pour avoir quelque chose de simple.

all:
  vars:
    ansible_python_interpreter: /usr/bin/python3
  hosts:
    172.17.0.3:
    172.17.0.4:
  children:
    monito:
      hosts:
        172.17.0.2:

On y retrouve un groupe ALL composé de 3 serveurs dont un fait partie d’un sous groupe MONITO. Eventuellement on précise que l’on va utiliser python 3 sur toutes les machines mais cela n’est plus vraiment nécessaire actuellement.

Et notre playbook ?

La encore on va faire simple, une partie dédiée à node-exporter et l’autre à la stack de monitoring. Le premier va installer node-exporter sur le groupe ALL et le second prometheus/grafana sur la machine du groupe monito.

- name: install node exporter
  hosts: all
  become: yes
  roles:
  - node_exporter

- name: install prometheus/grafana
  hosts: monito
  become: yes
  roles:
  - prometheus
  - grafana

Au passage on passe un become à YES pour jouer l’installation complète avec une élévation de privilège via sudo.

Création du rôle node exporter

Pour la création de nos rôles on commence systématiquement par utiliser la commande galaxy qui permet de créer la structure de base. Cela évite d’avoir des trucs sui ne respecteraient pas les standards de ce côté.

ansible-galaxy init roles/node-exporter

ET donc comme tous les rôles le point d’entrée se fait par le répertoire tasks et son main.yml. Il va comprendre les tasks suivantes (et des variables que l’on va définir après) :

1. Vérification de l’existence ou non du binaire node exporter

- name: check if node exporter exist
  stat:
    path: "{{ node_exporter_bin }}"
  register: __check_node_exporter_present

2. Si il existe on va récupérer sa version en la reprenant via le contenu du fichier systemd de node-exporter (ce n’est pas forcément optimum mais ça marche bien… on aurait pu passer par le –version en redirigeant le stderr vers stdout etc).

- name: if node exporter exist get version
  shell: "cat /etc/systemd/system/node_exporter.service | grep Version | sed s/'.*Version '//g"
  register: __get_node_exporter_version
  when: __check_node_exporter_present.stat.exists == true
  changed_when: false

La variable __get_node_exporter_version permet de stocker la version. Et pour éviter une tâche qui sera toujours avec un changed on passe un changed_when à false. C’est l’inconvénient du module shell.

3. Création d’un utilisateur pour lancer node-exporter.

- name: create node exporter user
  user:
    name: "{{ node_exporter_user }}"
    append: true
    shell: /usr/sbin/nologin
    system: true
    create_home: false
    home: /

Pour la sécurité, cet utilisateur est nologin, sans home et de type system pour son uid.

4. Création d’un répertoire de configuration dans /etc/.

J’ai tendance à le faire pour tous les binaires que l’on l’utilise ou non. Au moins on en a un à notre disposition au cas où.

- name: create node exporter config dir
  file:
    path: "{{ node_exporter_dir_conf }}"
    state: directory
    owner: "{{ node_exporter_user }}"
    group: "{{ node_exporter_group }}"

5. Téléchargement du binaire pour node-exporter

Découvrez  [Ansible] : le samedi c'est debug de mon erreur avec include_tasks

Deux cas de figures se présentent à nous. Soit node exporter n’existe pas, soit il existe mais n’est pas dans la bonne version, donc on s’attache à traiter ces deux cas. Le module unarchive permet à la fois le téléchargement et également le dezip de l’archive.

- name: download and unzip node exporter if not exist
  unarchive:
    src: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
    dest: /tmp/
    remote_src: yes
    validate_certs: false
  when: __check_node_exporter_present.stat.exists == false or not __get_node_exporter_version.stdout == node_exporter_version

6. Déplacement du binaire dans le PATH

C’est une habitude et c’est pratique on ajoute le binaire dans un répertoire du PATH commun.

- name: move the binary to the final destination
  copy:
    src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
    dest: "{{ node_exporter_bin }}"
    owner: "{{ node_exporter_user }}"
    group: "{{ node_exporter_group }}"
    mode: 0755
    remote_src: yes
  when: __check_node_exporter_present.stat.exists == false or not __get_node_exporter_version.stdout == node_exporter_version

Là encore on couvre les deux cas de figure pour être en accord avec la tâche précédente (soit changement de version, soit première installation de node-exporter).

7. Suppression des traces lol

Pour éviter de laisser un /tmp de cochon on clean ce que l’on vient de faire car on a plus besoin de garder l’archive.

- name: clean
  file:
    path: /tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/
    state: absent

8. Installation du service systemd

Avec le module template on va utiliser un template (que l’on va voir juste après) pour créer le fichier du service systemd de node-exporter.

- name: install service
  template:
    src: node_exporter.service.j2
    dest: /etc/systemd/system/node_exporter.service
    owner: root
    group: root
    mode: 0755
  notify: reload_daemon_and_restart_node_exporter

On n’oublie pas de placer un petit notify qui va permetre de pratiquer un systemctl daemon-reload et un restart pour prendre en compte les modifications en cas de changements.

9. Flush du handler

J’aime bien forcer ansible à jouer les handlers au moment où je le souhaite ou définir le moment le plus tardif où je le fais. Donc on flush le handler.

- meta: flush_handlers

10. Démarrage de node exporter dans tous les cas

Et à la fin on veut une chose quoiqu’il arrive : un service node-exporter qui tourne et qui est enable pour prendre en compte un reboot de notre serveur.

- name: service always started
  systemd:
    name: node_exporter
    state: started
    enabled: yes

11. Les différentes variables par défaut

J’adore utiliser le répertoire des variables par défaut de notre rôle. Alors voici les variables que je vais défnir (à actualiser si besoin).

node_exporter_version: "1.0.1"
node_exporter_bin: /usr/local/bin/node_exporter
node_exporter_user: node-exporter
node_exporter_group: "{{ node_exporter_user }}"
node_exporter_dir_conf: /etc/node_exporter

12. Le contenu du main de notre répertoire handlers

- name: reload_daemon_and_restart_node_exporter
  systemd:
    name: node_exporter
    state: restarted
    daemon_reload: yes
    enabled: yes

13. Le template du service systemd

Rien de fou et le stricte minimum on peut y ajouter éventuellement un restart.

[Unit]
Description=Node Exporter Version {{ node_exporter_version }}
After=network-online.target

[Service]
User={{ node_exporter_user }}
Group={{ node_exporter_group }}
Type=simple
ExecStart={{ node_exporter_bin }}
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Voilà pour node exporter. On a un truc à peu près propre et on peut éventuellement le faire évoluer (multi-system…).

Installation et configuration de prometheus

Je vous passe la création du rôle dans le répertoire de rôles. On le fait avec la même méthode que pour node-exporter.

Et on déroule de nouveau les tâches pour installer notre prometheus dans les tasks main.yml

1. Installation de prometheus

Alors on peut améliorer cette tâche en définissant une version fixe c’est toujours un peu mieux et avec sa variable.

- name: update and install prometheus
  apt:
    name: prometheus
    state: latest
    update_cache: yes
    cache_valid_time: 3600

2. Ajout des arguments à la CLI de prometheus

Pour définir les arguments à passer à notre CLI de prometheus on va utiliser /etc/default/prometheus. Pour créer ce fichier, nous allons le gérer à partir d’un template pour y ajouter des variables. Et on oublie pas le notify pour disposer d’un handler qui permettra le restart de prometheus en cas de changement.

- name: prometheus args
  template:
    src: prometheus.j2
    dest: /etc/default/prometheus
    mode: 0644
    owner: root
    group: root
  notify: restart_prometheus

3. Edition de la configuration de prometheus

Nouveau template pour la création de la configuration de prometheus que nous allons voir juste après.

- name: prometheus configuration file
  template:
    src: prometheus.yml.j2
    dest: "{{ prometheus_dir_configuration }}/prometheus.yml"           
    mode: 0755
    owner: prometheus
    group: prometheus
  notify: reload_prometheus

En cas de changement on joue un reload de prometheus (plutôt qu’un restart).

4. Flush du handler

Comme prometheus est normalement toujours démarré à ce stade on peu flusher les handlers (restart ou reload).

- meta: flush_handlers

5. Prometheus doit toujours être up

Comme pour node-exporter on veut à la fin un prometheus up et enable pour prévoir le reboot de la machine.

- name: start prometheus
  systemd:
    name: prometheus
    state: started
    enabled: yes

6. Les variables par défaut

Découvrez  Ansible, son inventaire... et son organisation

Définissons les variables par défaut. On va également définir dedans l’entête de notre fichier de configuration dans la variables prometheus_var_config, c’est assez pratique pour faire des modifications.

prometheus_dir_configuration: "/etc/prometheus"
prometheus_retention_time: "365d"
prometheus_scrape_interval: "30s"
prometheus_node_exporter_group: "all"
prometheus_env: "production"
prometheus_var_config:
  global:
    scrape_interval: "{{ prometheus_scrape_interval }}"
    evaluation_interval: 5s
    external_labels:
      env: '{{ prometheus_env }}'
  scrape_configs:
    - job_name: prometheus
      scrape_interval: 15s
      static_configs:
        - targets: ['{{ inventory_hostname }}:9090']

7. Le template du fichier de configuration prometheus.yml

Voici notre fichier du template de la configuration. Pour traiter strictement notre yaml on y ajoute une entête lstrip_blocks.

On utilise un filtre to_nice_yaml pour aajouter notre variable contenant l’entête du point précédent.

Et on conditionne un bloc dédié à node-exporter sur activation de la variable prometheus_node_exporter_group.

Grâce aux groupes de ansible ou va lister et boucler sur toutes les machines du groupe défini dans la variable prometheus_node_exporter_group (cela permet de définir un autre nom de groupe que dans notre cas ALL).

#jinja2: lstrip_blocks: "True"
{{ prometheus_var_config | to_nice_yaml(indent=2) }}
{% if prometheus_node_exporter_group %}
- job_name: node_exporter
  scrape_interval: 15s
  static_configs:
  - targets:
{% for server in groups[prometheus_node_exporter_group] %}
    - {{ server }}:9100
{% endfor %}
{% endif %}

Et enfin le dernier template que l’on oublie pas, celui des arguments de prometheus (notamment la durée de rétention des métriques dans notre TSDB).

# Set the command-line arguments to pass to the server.
ARGS="--web.enable-lifecycle --storage.tsdb.retention.time={{ prometheus_retention_time }} --web.console.templates=/etc/prometheus/consoles --web.console.libraries=/etc/prometheus/console_libraries

Et enfin le rôle Grafana !!!

On en arrive à la visualisation de nos données, principalement les métriques collectées dans notre prometheus. Alors go on y va. Après avoir crée notre structure de rôles avec la CLI de ansible-galaxy on édite nos tasks.

1. Installation du repository et installation de grafana

On va regrouper les tâches d’installation.

- name: install gpg
  apt:
    name: gnupg,software-properties-common
    state: present
    update_cache: yes
    cache_valid_time: 3600

- name: add gpg hey
  apt_key:
    url: "https://packages.grafana.com/gpg.key"
    validate_certs: no

- name: add repository
  apt_repository:
    repo: "deb https://packages.grafana.com/oss/deb stable main"
    state: present
    validate_certs: no

- name: install grafana
  apt:
    name: grafana
    state: latest
    update_cache: yes
    cache_valid_time: 3600

Rien de compliqué donc on passe rapidement.

2. Changement du user d’admin

Grafana utilise le user admin et le password par défaut admin, c’est plutôt bien de le changer le plus rapidement possible. On fait cela en passant par son fichier de configuration même si on pouvait le faire par la CLI grafana-cli (mais c’est l’occasion de faire un peu de lineinfile pour le découvrir).

- name: change admin user
  lineinfile:
    path: /etc/grafana/grafana.ini
    regexp: "{{ item.before }}"
    line: "{{ item.after }}"
  with_items:
  - { before: "^;admin_user = admin", after: "admin_user = {{ grafana_admin_user }}"}
  - { before: "^;admin_password = admin", after: "admin_password = {{ grafana_admin_password }}"}

3. Stat et enable de grafana

Sans commentaire

- name: start service grafana-server
  systemd:
    name: grafana-server
    state: started
    enabled: yes

Et on peut attendre que Grafana soit up

4. Attente d’une réponse positive de l’interface Grafana

Un petit truc sympa mais pas toujours nécessaire, on va attendre une réponse 200 de la page d’accueil de Grafana. On utilisera un délai d’attente assez large de 2min.

- name: wait for service up
  uri:
    url: "http://127.0.0.1:3000"
    status_code: 200
  register: __result
  until: __result.status == 200
  retries: 120
  delay: 1

5. Ajout de notre prometheus comme première datasource

On sait que l’on a un prometheus juste à côté qui demande à être raccordé à notre grafana, c’est le moment de le faire.

- name: add prometheus datasource
  grafana_datasource:
    name: "prometheus-local"
    grafana_url: "http://127.0.0.1:3000"
    grafana_user: "{{ grafana_admin_user }}"
    grafana_password: "{{ grafana_admin_password }}"
    org_id: "1"
    ds_type: "prometheus"
    ds_url: "http://127.0.0.1:9090"
  changed_when: false

Rien de très particulier si ce n’est que nous utilisons le module grafana qui est bien fonctionnel sur ansible.

6. Téléchargement du dashboard node-exporter

Ensuite on va procéder en deux temps. On télécharge le dashboard dédié à node-exporter.

- name: install node exporter dashboard
  get_url:
    url: https://raw.githubusercontent.com/rfrail3/grafana-dashboards/master/prometheus/node-exporter-full.json
    dest: /var/lib/grafana/node-exporter.json
    mode: '0755'

7. Activation du dashboard

Ensuite pour activer ce dashboard, nous avons besoin de créer un fichier dans le répertoire de configuration de grafana dans /etc/.

- name: activate dashboard for node exporter
  template:
    src: dashboard-node-exporter.yml.j2
    dest: /etc/grafana/provisioning/dashboards/dashboard-node-exporter.yml
    mode: 0755
  notify: restart_grafana

Et pour l’ajouter nous avons besoin de restart en passant par un handler c’est plutôt mieux.

8. Le handler

- name: restart_grafana
  systemd:
    name: grafana-server
    state: restarted
    enabled: yes
    daemon_reload: yes

9. Les variables pour notre rôle grafana

Bien sûr à personnaliser.

grafana_admin_user: "xavki"
grafana_admin_password: "password"

Conclusion

Voilà on a fini nos petits rôles qui ne demande qu’à être personnalisés et adaptés à vos besoins. Tout cela est perfectible car simplifié pour faciliter la pédagogie et l’apprentissage. Néanmoins je pense que c’est une bonne base pour commencer avec cette stack ou encore avac ansible.

Faites vous plaisir, partagez et rejoignez la chaine Xavki !!