diff --git a/README-ansible-galaxy.md b/README-ansible-galaxy.md index ff7ba4f91..ebe8e1c97 100644 --- a/README-ansible-galaxy.md +++ b/README-ansible-galaxy.md @@ -123,6 +123,7 @@ These roles should be called together, we advise using the `seapath_setup_harden ## Management +- `deploy_prometheus_exporters`: Deploy Prometheus exporters as Podman quadlet services - `snmp`: Configure SNMP - `vmmgrapi`: Configure the vm manager API @@ -190,6 +191,7 @@ The other playbooks can be called alone to re-configure a specific part, when yo ## Management +- `seapath_setup_prometheus_exporters.yaml`: Deploy Prometheus exporters per host group - `seapath_setup_snmp.yaml`: Configure SNMP on all machines - `seapath_setup_vmmgrapi.yaml`: Configure vm-mgr API on all machines diff --git a/playbooks/seapath_setup_main.yaml b/playbooks/seapath_setup_main.yaml index 84343a028..c46b205bc 100644 --- a/playbooks/seapath_setup_main.yaml +++ b/playbooks/seapath_setup_main.yaml @@ -54,6 +54,9 @@ - name: Import seapath_setup_snmp playbook import_playbook: seapath_setup_snmp.yaml +- name: Import seapath_setup_prometheus_exporters playbook + import_playbook: seapath_setup_prometheus_exporters.yaml + - name: Import cluster_setup_cephadm playbook import_playbook: cluster_setup_cephadm.yaml diff --git a/playbooks/seapath_setup_prometheus_exporters.yaml b/playbooks/seapath_setup_prometheus_exporters.yaml new file mode 100644 index 000000000..5ce101369 --- /dev/null +++ b/playbooks/seapath_setup_prometheus_exporters.yaml @@ -0,0 +1,12 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Deploy prometheus exporters + hosts: + - cluster_machines + - standalone_machine + become: true + roles: + - detect_seapath_distro + - deploy_prometheus_exporters diff --git a/roles/cephadm/README.md b/roles/cephadm/README.md index 5ae865711..4f279cf30 100644 --- a/roles/cephadm/README.md +++ b/roles/cephadm/README.md @@ -8,11 +8,14 @@ The `cephadm_install` role must have been applied to `cluster_machines` before t ## Role Variables -| Variable | Required | Type | Default | Comments | -|------------------------|----------|--------|--------------|---------------------------------------------------------------------------------------| -| cephadm_release | No | String | "20.2.0" | Version of the ceph container image | -| cephadm_spec_path | No | String | spec.yaml.j2 | Path to the spec file of cephadm. Use it to override the default config | -| cephadm_network | Yes | String | | Ceph network (e.g. "192.168.55.0/24") | +| Variable | Required | Type | Default | Comments | +|-----------------------------------|----------|---------|--------------|---------------------------------------------------------------------------------------| +| seapath_distro | No | String | Not set | SEAPATH distribution | +| cephadm_release | No | String | "20.2.0" | Version of the ceph container image | +| cephadm_spec_path | No | String | spec.yaml.j2 | Path to the spec file of cephadm. Use it to override the default config | +| cephadm_network | Yes | String | | Ceph network (e.g. "192.168.55.0/24") | +| cephadm_prometheus_exporter_enabled | No | Boolean | `true` | Enable the built-in Ceph mgr prometheus module after the cluster is healthy | +| cephadm_prometheus_listen_address | No | String | see defaults | Administration IP the mgr prometheus exporter binds to on each host. Defaults to `ip_addr`, same as `deploy_prometheus_exporters_listen_address`. Override per host in inventory if needed. | Note that for each node you want in the cluster, those host vars need to be defined: @@ -68,6 +71,30 @@ the whole-disk layout, remove its OSD with `ceph orch osd rm`, remove the LVM layout of the disk, and re-run the playbook with the disk in `ceph_osd_disks`. +## Ceph prometheus exporter + +When `cephadm_prometheus_exporter_enabled` is `true` (default), the role enables the +built-in Ceph mgr prometheus module once the cluster reaches `HEALTH_OK`: + +- `ceph mgr module enable prometheus` +- `ceph config set mgr mgr/prometheus/exclude_perf_counters false` +- `ceph config set mgr.. mgr/prometheus/server_addr ` on each mgr + daemon, using `cephadm_prometheus_listen_address` (defaults to `ip_addr`, same as + `deploy_prometheus_exporters_listen_address`) + +The role discovers mgr daemon names with `ceph orch ps --daemon-type=mgr`, matches each +daemon to an inventory host via the `hostname` host variable, and sets the listen address +to that host's administration IP. + +This exporter is part of the Ceph cluster (mgr daemon, port 9283). It is separate +from the host-level exporters deployed by the `deploy_prometheus_exporters` role. + +The cluster bootstrap uses `--skip-monitoring-stack`, so Ceph does not deploy its own +Prometheus server. Configure an external Prometheus scrape job for the mgr endpoints +after this role runs. + +Set `cephadm_prometheus_exporter_enabled: false` to skip this step. + ## Example Playbook ```yaml diff --git a/roles/cephadm/defaults/main.yml b/roles/cephadm/defaults/main.yml index ceab2cbe2..fa8bea31b 100644 --- a/roles/cephadm/defaults/main.yml +++ b/roles/cephadm/defaults/main.yml @@ -3,3 +3,5 @@ --- cephadm_release: "20.2.0" +cephadm_prometheus_exporter_enabled: true +cephadm_prometheus_listen_address: "{{ ip_addr | default(ansible_host) }}" diff --git a/roles/cephadm/tasks/main.yml b/roles/cephadm/tasks/main.yml index 2fe299585..014e77b6d 100644 --- a/roles/cephadm/tasks/main.yml +++ b/roles/cephadm/tasks/main.yml @@ -306,6 +306,75 @@ delegate_to: "{{ cephadm_first_node }}" until: cephadm_cephs.stdout | from_json | community.general.json_query('health.status') == "HEALTH_OK" +- name: Enable Ceph prometheus exporter + when: cephadm_prometheus_exporter_enabled | bool + block: + - name: List enabled Ceph mgr modules + ansible.builtin.command: ceph mgr module ls --format json + register: cephadm_mgr_modules + changed_when: false + run_once: true + delegate_to: "{{ cephadm_first_node }}" + + - name: Enable prometheus mgr module + ansible.builtin.command: ceph mgr module enable prometheus + when: >- + 'prometheus' not in ( + cephadm_mgr_modules.stdout | from_json + ).enabled_modules | default([]) + changed_when: true + run_once: true + delegate_to: "{{ cephadm_first_node }}" + + - name: Get prometheus perf counter exclusion setting + ansible.builtin.command: ceph config get mgr mgr/prometheus/exclude_perf_counters + register: cephadm_prometheus_exclude_perf_counters + changed_when: false + failed_when: false + run_once: true + delegate_to: "{{ cephadm_first_node }}" + + - name: Include all perf counters in prometheus exporter metrics + ansible.builtin.command: ceph config set mgr mgr/prometheus/exclude_perf_counters false + when: >- + cephadm_prometheus_exclude_perf_counters.rc != 0 + or cephadm_prometheus_exclude_perf_counters.stdout | trim != 'false' + changed_when: true + run_once: true + delegate_to: "{{ cephadm_first_node }}" + + - name: Get Ceph mgr daemons + ansible.builtin.command: ceph orch ps --daemon-type=mgr --format json + register: cephadm_mgr_ps + changed_when: false + run_once: true + delegate_to: "{{ cephadm_first_node }}" + + - name: Bind mgr prometheus exporter to administration IP on each host + ansible.builtin.include_tasks: prometheus_mgr_bind.yml + vars: + host_hostname: "{{ hostvars[cephadm_target_host].hostname | default(cephadm_target_host) }}" + mgr_daemon: >- + {{ + cephadm_mgr_ps.stdout | from_json + | selectattr('hostname', 'equalto', host_hostname) + | list | first | default(none) + }} + listen_address: >- + {{ + hostvars[cephadm_target_host]['cephadm_prometheus_listen_address'] + | default( + hostvars[cephadm_target_host]['ip_addr'] + | default(hostvars[cephadm_target_host]['ansible_host']) + ) + }} + when: mgr_daemon is not none + loop: "{{ groups['cluster_machines'] }}" + loop_control: + loop_var: cephadm_target_host + label: "{{ mgr_daemon.daemon_name | default(cephadm_target_host) }}" + run_once: true + - name: Check if RBD pool exists ansible.builtin.shell: cmd: set -o pipefail && ceph osd lspools | grep -w rbd diff --git a/roles/cephadm/tasks/prometheus_mgr_bind.yml b/roles/cephadm/tasks/prometheus_mgr_bind.yml new file mode 100644 index 000000000..0b9948f51 --- /dev/null +++ b/roles/cephadm/tasks/prometheus_mgr_bind.yml @@ -0,0 +1,21 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Get mgr prometheus server_addr + ansible.builtin.command: >- + ceph config get {{ mgr_daemon.daemon_name }} mgr/prometheus/server_addr + register: cephadm_mgr_prometheus_server_addr + failed_when: false + changed_when: false + delegate_to: "{{ cephadm_first_node }}" + +- name: Set mgr prometheus server_addr to administration IP + ansible.builtin.command: >- + ceph config set {{ mgr_daemon.daemon_name }} + mgr/prometheus/server_addr {{ listen_address }} + when: >- + cephadm_mgr_prometheus_server_addr.rc != 0 + or cephadm_mgr_prometheus_server_addr.stdout | trim != listen_address + changed_when: true + delegate_to: "{{ cephadm_first_node }}" diff --git a/roles/deploy_cukinia_tests/README.md b/roles/deploy_cukinia_tests/README.md index ca20c5220..aadf665ab 100644 --- a/roles/deploy_cukinia_tests/README.md +++ b/roles/deploy_cukinia_tests/README.md @@ -11,6 +11,11 @@ No requirement. | `deploy_cukinia_tests_extra_essential_services` | list | `[]` | Additional systemd services to whitelist in the unrecognized services check (SEAPATH-00200). | | `deploy_cukinia_tests_extra_essential_packages` | list | `[]` | Additional apt packages to whitelist in the unrecognized packages check (SEAPATH-00198). | +Roles may also publish extra essential services as drop-in files under +`/etc/cukinia/extra-essential-services.d/`. These files are read at +cukinia test runtime, even when tests are deployed or executed later in a +separate playbook run. + ## Example Playbook ```yaml - name: deploy cukinia tests diff --git a/roles/deploy_cukinia_tests/cukinia-tests/cukinia/common_security_tests.d/systemd.conf.j2 b/roles/deploy_cukinia_tests/cukinia-tests/cukinia/common_security_tests.d/systemd.conf.j2 index b5beaaf6d..aa858cc72 100644 --- a/roles/deploy_cukinia_tests/cukinia-tests/cukinia/common_security_tests.d/systemd.conf.j2 +++ b/roles/deploy_cukinia_tests/cukinia-tests/cukinia/common_security_tests.d/systemd.conf.j2 @@ -6,11 +6,23 @@ cukinia_log "$(_colorize yellow "--- check systemd settings ---")" test_id "SEAPATH-00199" as "No systemd service failed" cukinia_systemd_failed +ESSENTIAL_SERVICES_EXTRA="" +_cukinia_extra_services_dir="/etc/cukinia/extra-essential-services.d" +if [ -d "$_cukinia_extra_services_dir" ]; then + for _cukinia_extra_services_file in "$_cukinia_extra_services_dir"/*; do + [ -f "$_cukinia_extra_services_file" ] || continue + while IFS= read -r _cukinia_extra_service || [ -n "$_cukinia_extra_service" ]; do + case "$_cukinia_extra_service" in + ''|\#*) continue ;; + esac + ESSENTIAL_SERVICES_EXTRA="${ESSENTIAL_SERVICES_EXTRA}|${_cukinia_extra_service}" + done < "$_cukinia_extra_services_file" + done +fi + {% if deploy_cukinia_tests_extra_essential_services | default([]) %} -ESSENTIAL_SERVICES_EXTRA="|\ +ESSENTIAL_SERVICES_EXTRA="${ESSENTIAL_SERVICES_EXTRA}|\ {{ deploy_cukinia_tests_extra_essential_services | join('|\\\n') }}" -{% else %} -ESSENTIAL_SERVICES_EXTRA="" {% endif %} ESSENTIAL_SERVICES_DEBIAN="\ diff --git a/roles/deploy_cukinia_tests/tasks/main.yml b/roles/deploy_cukinia_tests/tasks/main.yml index d97ddd279..f440e1f0a 100644 --- a/roles/deploy_cukinia_tests/tasks/main.yml +++ b/roles/deploy_cukinia_tests/tasks/main.yml @@ -12,6 +12,7 @@ group: false rsync_opts: - "--exclude=*.j2" + - "--exclude=extra-essential-services.d/" - "--chmod=D0755,F0644" - name: Copy Cukinia's tests templates diff --git a/roles/deploy_cukinia_tests/vars/main.yml b/roles/deploy_cukinia_tests/vars/main.yml new file mode 100644 index 000000000..669a1fc78 --- /dev/null +++ b/roles/deploy_cukinia_tests/vars/main.yml @@ -0,0 +1,5 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +# Drop-in directory read at cukinia test runtime by systemd.conf (SEAPATH-00200). +deploy_cukinia_tests_extra_essential_services_dir: /etc/cukinia/extra-essential-services.d diff --git a/roles/deploy_prometheus_exporters/README.md b/roles/deploy_prometheus_exporters/README.md new file mode 100644 index 000000000..85287e61d --- /dev/null +++ b/roles/deploy_prometheus_exporters/README.md @@ -0,0 +1,141 @@ +# Deploy Prometheus exporters Role + +This role deploys Prometheus exporters as Podman quadlet container units under +`/etc/containers/systemd/`, then starts the corresponding systemd services. +Activation at boot comes from the `[Install]` section of the quadlet units. + +The exporters are bound to the administration IP address, which defaults to the +standard SEAPATH `ip_addr` inventory variable. + +## Requirements + +- Podman with quadlet/systemd container support +- Yocto and SLES are not supported yet; the role is skipped when + `seapath_distro` is `Yocto` or `SLES` until compatibility is added +- `podman.socket` for the podman exporter (API socket at `/run/podman/podman.sock`) +- Libvirt socket access for libvirt and insatomcat exporters (`libvirtd.service` on + Debian, `virtqemud.service` on Oracle Linux) +- Pacemaker/Corosync tools on the host for the HA cluster exporter +- `seapath_distro` should be set, typically by calling `detect_seapath_distro` + before this role + +## Role Variables + +| Variable | Required | Type | Default | Comments | +|---|---|---|---|---| +| `deploy_prometheus_exporters_enabled` | No | Boolean | `true` | Set to `false` on Yocto and SLES until support is added | +| `deploy_prometheus_exporters_exporters` | No | List | see below | Exporters to deploy on the host | +| `deploy_prometheus_exporters_listen_address` | No | String | `"{{ ip_addr \| default(ansible_host) }}"` | IP address exporters listen on | +| `deploy_prometheus_exporters_images` | No | Dict | `{}` | Container images keyed by exporter name | +| `deploy_prometheus_exporters_node_exporter_image` | No | String | see defaults | Node exporter container image | +| `deploy_prometheus_exporters_podman_exporter_image` | No | String | see defaults | Podman exporter container image | +| `deploy_prometheus_exporters_libvirt_exporter_image` | No | String | see defaults | Libvirt exporter container image | +| `deploy_prometheus_exporters_insatomcat_exporter_image` | No | String | see defaults | Insatomcat exporter container image | +| `deploy_prometheus_exporters_ha_cluster_exporter_image` | No | String | see defaults | HA cluster exporter container image | +| `deploy_prometheus_exporters_manage_services` | No | Boolean | `true` | Start systemd units and restart them on unit file changes | +| `deploy_prometheus_exporters_register_essential_services` | No | Boolean | `true` | Publish deployed services for cukinia tests | +| `deploy_prometheus_exporters_libvirt_exporter_socket` | No | String | see vars | Host libvirt socket mounted in libvirt exporter | +| `deploy_prometheus_exporters_insatomcat_libvirt_socket` | No | String | see vars | Host libvirt socket mounted in insatomcat exporter | +| `deploy_prometheus_exporters_insatomcat_qemu_dir` | No | String | `/var/run/libvirt/qemu` | Host QEMU runtime dir for insatomcat exporter | +| `deploy_prometheus_exporters_libvirt_systemd_unit` | No | String | see vars | Libvirt systemd unit to order exporter startup after | + +Default upstream images are defined in `vars/main.yml` under +`deploy_prometheus_exporters_default_images`. Libvirt socket paths and the +related systemd unit are defined in `vars/.yml`. Override them +per exporter with +either `deploy_prometheus_exporters_images` or the dedicated `*_image` +variables. + +### Supported exporters + +| Name | Port | Hosts | +|---|---|---| +| `node-exporter` | 9100 | all | +| `podman-exporter` | 9882 | hypervisors | +| `libvirt-exporter` | 9177 | hypervisors | +| `insatomcat-exporter` | 9184 | hypervisors | +| `ha_cluster_exporter` | 9664 | cluster machines | + +### Default exporter selection + +When `deploy_prometheus_exporters_exporters` is not overridden, exporters are +added from the inventory groups the host belongs to: + +| Exporter | Added when the host is in | +|---|---| +| `node-exporter` | always | +| `ha_cluster_exporter` | `cluster_machines` | +| `podman-exporter`, `libvirt-exporter`, `insatomcat-exporter` | `hypervisors` | + +Examples: + +| Host | Groups | Result | +|---|---|---| +| Cluster hypervisor | `cluster_machines`, `hypervisors` | all exporters | +| Observer | `cluster_machines` only | node + ha cluster | +| Standalone hypervisor | `hypervisors` | node + podman + libvirt + insatomcat | +| VM | `VMs` | node only | + +The mapping is defined in `vars/main.yml` as +`deploy_prometheus_exporters_group_exporters`. + +### Libvirt socket paths per flavor + +| Flavor | Libvirt exporter socket | insatomcat libvirt socket | systemd unit | +|---|---|---|---| +| Debian, CentOS, SLES | `/var/run/libvirt/libvirt-sock-ro` | `/var/run/libvirt/libvirt-sock` | `libvirtd.service` | +| Oracle Linux | `/run/libvirt/virtqemud-sock` | `/run/libvirt/virtqemud-sock` | `virtqemud.service` | + +## Example Playbook + +```yaml +- name: Deploy prometheus exporters + hosts: + - cluster_machines + - standalone_machine + - VMs + become: true + roles: + - detect_seapath_distro + - deploy_prometheus_exporters +``` + +The role derives the exporter list from the inventory groups. Override it +explicitly when needed: + +```yaml +deploy_prometheus_exporters_exporters: + - node-exporter +``` + +## Offline or internal registry + +When nodes cannot reach public registries, mirror the exporter images on a +local registry and override the image references from the inventory. + +Override all images at once: + +```yaml +# group_vars/all.yml +deploy_prometheus_exporters_images: + node-exporter: registry.local/seapath/node-exporter:1.8.2 + podman-exporter: registry.local/seapath/prometheus-podman-exporter:1.14.0 + libvirt-exporter: registry.local/seapath/prometheus-libvirt-exporter:2.3.1 + insatomcat-exporter: registry.local/seapath/insatomcat-exporter:1.0.0 + ha_cluster_exporter: registry.local/seapath/ha-cluster-exporter:0.0.1 +``` + +Override a single exporter: + +```yaml +deploy_prometheus_exporters_node_exporter_image: registry.local/seapath/node-exporter:1.8.2 +``` + +Podman pulls the image when systemd starts each quadlet service. In offline +environments, point the image URLs to a reachable internal registry where the +images have been mirrored. + +When cukinia tests are used, deployed exporter services are written to +`/etc/cukinia/extra-essential-services.d/deploy_prometheus_exporters`. +This drop-in is read at test runtime, so it still applies when cukinia tests +are deployed or executed in a separate playbook run. diff --git a/roles/deploy_prometheus_exporters/defaults/main.yml b/roles/deploy_prometheus_exporters/defaults/main.yml new file mode 100644 index 000000000..418bf5dcc --- /dev/null +++ b/roles/deploy_prometheus_exporters/defaults/main.yml @@ -0,0 +1,49 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +deploy_prometheus_exporters_listen_address: "{{ ip_addr | default(ansible_host) }}" + +deploy_prometheus_exporters_images: {} + +deploy_prometheus_exporters_node_exporter_image: >- + {{ deploy_prometheus_exporters_images.get( + 'node-exporter', + deploy_prometheus_exporters_default_images['node-exporter']) }} +deploy_prometheus_exporters_podman_exporter_image: >- + {{ deploy_prometheus_exporters_images.get( + 'podman-exporter', + deploy_prometheus_exporters_default_images['podman-exporter']) }} +deploy_prometheus_exporters_libvirt_exporter_image: >- + {{ deploy_prometheus_exporters_images.get( + 'libvirt-exporter', + deploy_prometheus_exporters_default_images['libvirt-exporter']) }} +deploy_prometheus_exporters_insatomcat_exporter_image: >- + {{ deploy_prometheus_exporters_images.get( + 'insatomcat-exporter', + deploy_prometheus_exporters_default_images['insatomcat-exporter']) }} +deploy_prometheus_exporters_ha_cluster_exporter_image: >- + {{ deploy_prometheus_exporters_images.get( + 'ha_cluster_exporter', + deploy_prometheus_exporters_default_images['ha_cluster_exporter']) }} + +deploy_prometheus_exporters_exporters: >- + {{ + ['node-exporter'] + + ( + deploy_prometheus_exporters_group_exporters.cluster_machines + if inventory_hostname in groups.get('cluster_machines', []) + else [] + ) + + ( + deploy_prometheus_exporters_group_exporters.hypervisors + if inventory_hostname in groups.get('hypervisors', []) + else [] + ) + | unique + }} + +deploy_prometheus_exporters_enabled: true +deploy_prometheus_exporters_manage_services: true +deploy_prometheus_exporters_register_essential_services: true +deploy_prometheus_exporters_cukinia_extra_essential_services_dir: /etc/cukinia/extra-essential-services.d +deploy_prometheus_exporters_cukinia_extra_essential_services_file: "{{ deploy_prometheus_exporters_cukinia_extra_essential_services_dir }}/deploy_prometheus_exporters" diff --git a/roles/deploy_prometheus_exporters/handlers/main.yml b/roles/deploy_prometheus_exporters/handlers/main.yml new file mode 100644 index 000000000..b4ecf9716 --- /dev/null +++ b/roles/deploy_prometheus_exporters/handlers/main.yml @@ -0,0 +1,21 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +# Handlers run in declaration order, not in notification order: the daemon +# reload must stay first so that the restarts act on the regenerated units. +- name: Reload systemd after exporter container unit changes + ansible.builtin.systemd: + daemon_reload: true + +- name: Restart changed exporter services + ansible.builtin.systemd: + name: "{{ item }}" + state: restarted + loop: >- + {{ + deploy_prometheus_exporters_unit_files.results + | selectattr('changed') + | map(attribute='item') + | list + }} + when: deploy_prometheus_exporters_manage_services | bool diff --git a/roles/deploy_prometheus_exporters/meta/main.yml b/roles/deploy_prometheus_exporters/meta/main.yml new file mode 100644 index 000000000..3dc48dbc0 --- /dev/null +++ b/roles/deploy_prometheus_exporters/meta/main.yml @@ -0,0 +1,10 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +galaxy_info: + author: "Seapath" + namespace: "seapath" + description: Deploy Prometheus exporters as Podman quadlet container units. + min_ansible_version: 2.9.10 + license: Apache-2.0 +dependencies: [] diff --git a/roles/deploy_prometheus_exporters/molecule/default/converge.yml b/roles/deploy_prometheus_exporters/molecule/default/converge.yml new file mode 100644 index 000000000..0d14d46c0 --- /dev/null +++ b/roles/deploy_prometheus_exporters/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + gather_facts: true + tasks: + - name: Apply role under test + ansible.builtin.include_role: + name: deploy_prometheus_exporters diff --git a/roles/deploy_prometheus_exporters/molecule/default/molecule.yml b/roles/deploy_prometheus_exporters/molecule/default/molecule.yml new file mode 100644 index 000000000..f5ff70bc3 --- /dev/null +++ b/roles/deploy_prometheus_exporters/molecule/default/molecule.yml @@ -0,0 +1,36 @@ +--- +dependency: + name: galaxy + +driver: + name: podman + +platforms: + - name: observer + image: docker.io/geerlingguy/docker-debian13-ansible:latest + privileged: true + command: /sbin/init + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + +provisioner: + name: ansible + config_options: + defaults: + roles_path: "${MOLECULE_PROJECT_DIRECTORY}/.." + playbooks: + prepare: prepare.yml + converge: converge.yml + verify: verify.yml + inventory: + host_vars: + observer: + seapath_distro: Debian + ip_addr: 10.10.10.10 + deploy_prometheus_exporters_exporters: + - node-exporter + deploy_prometheus_exporters_node_exporter_image: quay.io/prometheus/node-exporter:latest + deploy_prometheus_exporters_manage_services: false + +verifier: + name: ansible diff --git a/roles/deploy_prometheus_exporters/molecule/default/prepare.yml b/roles/deploy_prometheus_exporters/molecule/default/prepare.yml new file mode 100644 index 000000000..99e178997 --- /dev/null +++ b/roles/deploy_prometheus_exporters/molecule/default/prepare.yml @@ -0,0 +1,19 @@ +--- +- name: Prepare test environment (Debian 13) + hosts: all + become: true + gather_facts: false + + tasks: + - name: Install podman + ansible.builtin.apt: + name: + - podman + state: present + update_cache: true + + - name: Ensure quadlet directory exists + ansible.builtin.file: + path: /etc/containers/systemd + state: directory + mode: "0755" diff --git a/roles/deploy_prometheus_exporters/molecule/default/verify.yml b/roles/deploy_prometheus_exporters/molecule/default/verify.yml new file mode 100644 index 000000000..038d3f8d8 --- /dev/null +++ b/roles/deploy_prometheus_exporters/molecule/default/verify.yml @@ -0,0 +1,74 @@ +--- +- name: Verify observer prometheus exporters deployment + hosts: all + become: true + gather_facts: false + + vars: + deploy_prometheus_exporters_other_exporters: + - podman-exporter + - libvirt-exporter + - insatomcat-exporter + - ha_cluster_exporter + + tasks: + - name: Check node-exporter quadlet unit exists + ansible.builtin.stat: + path: /etc/containers/systemd/node-exporter.container + register: deploy_prometheus_exporters_node_exporter_unit + + - name: Assert node-exporter quadlet unit exists with expected permissions + ansible.builtin.assert: + that: + - deploy_prometheus_exporters_node_exporter_unit.stat.exists + - deploy_prometheus_exporters_node_exporter_unit.stat.mode == "0644" + - deploy_prometheus_exporters_node_exporter_unit.stat.pw_name == "root" + - deploy_prometheus_exporters_node_exporter_unit.stat.gr_name == "root" + + - name: Read node-exporter quadlet unit content + ansible.builtin.slurp: + path: /etc/containers/systemd/node-exporter.container + register: deploy_prometheus_exporters_node_exporter_content + + - name: Assert node-exporter quadlet unit content + ansible.builtin.assert: + that: + - >- + ('--web.listen-address=' ~ ip_addr ~ ':9100') + in (deploy_prometheus_exporters_node_exporter_content.content | b64decode) + - >- + deploy_prometheus_exporters_node_exporter_image + in (deploy_prometheus_exporters_node_exporter_content.content | b64decode) + - >- + 'Restart=always' + in (deploy_prometheus_exporters_node_exporter_content.content | b64decode) + fail_msg: >- + node-exporter.container content is missing expected listen address, + image or systemd restart policy + + - name: Check other exporter quadlet units are absent + ansible.builtin.stat: + path: "/etc/containers/systemd/{{ item }}.container" + loop: "{{ deploy_prometheus_exporters_other_exporters }}" + register: deploy_prometheus_exporters_other_units + + - name: Assert other exporter quadlet units were not deployed + ansible.builtin.assert: + that: + - not item.stat.exists + fail_msg: "{{ item.item }}.container should not be deployed in observer scenario" + loop: "{{ deploy_prometheus_exporters_other_units.results }}" + loop_control: + label: "{{ item.item }}" + + - name: Check node exporter textfile collector directory exists + ansible.builtin.stat: + path: /var/lib/prometheus/node_exporter + register: deploy_prometheus_exporters_textfile_dir + + - name: Assert node exporter textfile collector directory exists + ansible.builtin.assert: + that: + - deploy_prometheus_exporters_textfile_dir.stat.exists + - deploy_prometheus_exporters_textfile_dir.stat.isdir + - deploy_prometheus_exporters_textfile_dir.stat.mode == "0755" diff --git a/roles/deploy_prometheus_exporters/tasks/main.yml b/roles/deploy_prometheus_exporters/tasks/main.yml new file mode 100644 index 000000000..8e93019f1 --- /dev/null +++ b/roles/deploy_prometheus_exporters/tasks/main.yml @@ -0,0 +1,98 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 + +--- +- name: Load distribution-specific variables + ansible.builtin.include_vars: "{{ seapath_distro }}.yml" + +- name: Deploy prometheus exporters + when: deploy_prometheus_exporters_enabled | bool + block: + - name: Validate prometheus exporters list + ansible.builtin.assert: + that: + - deploy_prometheus_exporters_exporters | length > 0 + - deploy_prometheus_exporters_exporters | difference(deploy_prometheus_exporters_supported_exporters) | length == 0 + fail_msg: >- + deploy_prometheus_exporters_exporters must be a non-empty subset of + {{ deploy_prometheus_exporters_supported_exporters | join(', ') }}. + Got: {{ deploy_prometheus_exporters_exporters | join(', ') }} + + - name: Ensure node exporter textfile collector directory exists + ansible.builtin.file: + path: /var/lib/prometheus/node_exporter + state: directory + owner: root + group: root + mode: "0755" + when: "'node-exporter' in deploy_prometheus_exporters_exporters" + + - name: Ensure podman quadlet directory exists + ansible.builtin.file: + path: /etc/containers/systemd + state: directory + owner: root + group: root + mode: "0755" + + - name: Deploy prometheus exporter container units + ansible.builtin.template: + src: "{{ item }}.container.j2" + dest: "/etc/containers/systemd/{{ item }}.container" + owner: root + group: root + mode: "0644" + loop: "{{ deploy_prometheus_exporters_exporters }}" + register: deploy_prometheus_exporters_unit_files + notify: + - Reload systemd after exporter container unit changes + - Restart changed exporter services + + - name: Enable and start podman API socket + ansible.builtin.systemd: + name: podman.socket + enabled: true + state: started + when: + - "'podman-exporter' in deploy_prometheus_exporters_exporters" + - deploy_prometheus_exporters_manage_services | bool + + - name: Flush handlers before starting exporter services + ansible.builtin.meta: flush_handlers + + # Boot activation comes from the [Install] section of the quadlet units: + # systemd refuses to enable a generated unit, so there is nothing to enable + # here. + - name: Start prometheus exporter services + ansible.builtin.systemd: + name: "{{ item }}" + state: started + loop: "{{ deploy_prometheus_exporters_exporters }}" + when: deploy_prometheus_exporters_manage_services | bool + + - name: Publish prometheus exporter services for cukinia tests + when: deploy_prometheus_exporters_register_essential_services | bool + block: + - name: Ensure cukinia extra essential services drop-in directory exists + ansible.builtin.file: + path: "{{ deploy_prometheus_exporters_cukinia_extra_essential_services_dir }}" + state: directory + owner: root + group: root + mode: "0755" + + - name: Publish prometheus exporter services for cukinia tests + ansible.builtin.template: + src: cukinia-extra-essential-services.j2 + dest: "{{ deploy_prometheus_exporters_cukinia_extra_essential_services_file }}" + owner: root + group: root + mode: "0644" + +- name: Remove prometheus exporters cukinia extra services drop-in + ansible.builtin.file: + path: "{{ deploy_prometheus_exporters_cukinia_extra_essential_services_file }}" + state: absent + when: >- + not deploy_prometheus_exporters_enabled | bool + or not deploy_prometheus_exporters_register_essential_services | bool diff --git a/roles/deploy_prometheus_exporters/templates/cukinia-extra-essential-services.j2 b/roles/deploy_prometheus_exporters/templates/cukinia-extra-essential-services.j2 new file mode 100644 index 000000000..cc55898fd --- /dev/null +++ b/roles/deploy_prometheus_exporters/templates/cukinia-extra-essential-services.j2 @@ -0,0 +1,6 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +# {{ ansible_managed }} +{% for exporter in deploy_prometheus_exporters_exporters %} +{{ exporter }} +{% endfor %} diff --git a/roles/deploy_prometheus_exporters/templates/ha_cluster_exporter.container.j2 b/roles/deploy_prometheus_exporters/templates/ha_cluster_exporter.container.j2 new file mode 100644 index 000000000..9324a6089 --- /dev/null +++ b/roles/deploy_prometheus_exporters/templates/ha_cluster_exporter.container.j2 @@ -0,0 +1,21 @@ +[Unit] +Description=HA Cluster Exporter +After=network.target + +[Container] +Image={{ deploy_prometheus_exporters_ha_cluster_exporter_image }} +PublishPort={{ deploy_prometheus_exporters_listen_address }}:9664:9664 +PodmanArgs=--pid=host --ipc=host --privileged +Volume=/var/run:/var/run +Exec=\ + --crm-mon-path=/usr/local/bin/crm_mon \ + --cibadmin-path=/usr/local/bin/cibadmin \ + --corosync-cfgtoolpath-path=/usr/local/bin/corosync-cfgtool \ + --corosync-quorumtool-path=/usr/local/bin/corosync-quorumtool + +[Service] +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/roles/deploy_prometheus_exporters/templates/insatomcat-exporter.container.j2 b/roles/deploy_prometheus_exporters/templates/insatomcat-exporter.container.j2 new file mode 100644 index 000000000..ed71d53d1 --- /dev/null +++ b/roles/deploy_prometheus_exporters/templates/insatomcat-exporter.container.j2 @@ -0,0 +1,19 @@ +[Unit] +Description=Prometheus insatomcat Exporter (vhost CPU monitoring) +After=network-online.target {{ deploy_prometheus_exporters_libvirt_systemd_unit }} +Wants=network-online.target + +[Container] +Image={{ deploy_prometheus_exporters_insatomcat_exporter_image }} +PublishPort={{ deploy_prometheus_exporters_listen_address }}:9184:9184 +Volume={{ deploy_prometheus_exporters_insatomcat_libvirt_socket }}:{{ deploy_prometheus_exporters_insatomcat_libvirt_socket }}:ro +Volume={{ deploy_prometheus_exporters_insatomcat_qemu_dir }}:{{ deploy_prometheus_exporters_insatomcat_qemu_dir }}:ro +PodmanArgs=--pid=host +SecurityLabelDisable=true + +[Service] +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/roles/deploy_prometheus_exporters/templates/libvirt-exporter.container.j2 b/roles/deploy_prometheus_exporters/templates/libvirt-exporter.container.j2 new file mode 100644 index 000000000..4322b1fee --- /dev/null +++ b/roles/deploy_prometheus_exporters/templates/libvirt-exporter.container.j2 @@ -0,0 +1,17 @@ +[Unit] +Description=Prometheus Libvirt Exporter +After=network-online.target {{ deploy_prometheus_exporters_libvirt_systemd_unit }} +Wants=network-online.target + +[Container] +Image={{ deploy_prometheus_exporters_libvirt_exporter_image }} +PublishPort={{ deploy_prometheus_exporters_listen_address }}:9177:9177 +Volume={{ deploy_prometheus_exporters_libvirt_exporter_socket }}:{{ deploy_prometheus_exporters_libvirt_exporter_socket }}:ro +SecurityLabelDisable=true + +[Service] +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/roles/deploy_prometheus_exporters/templates/node-exporter.container.j2 b/roles/deploy_prometheus_exporters/templates/node-exporter.container.j2 new file mode 100644 index 000000000..2e6277e25 --- /dev/null +++ b/roles/deploy_prometheus_exporters/templates/node-exporter.container.j2 @@ -0,0 +1,23 @@ +[Unit] +Description=Prometheus Node Exporter +After=network-online.target +Wants=network-online.target + +[Container] +Image={{ deploy_prometheus_exporters_node_exporter_image }} +PodmanArgs=--pid=host +PodmanArgs=--network=host +PodmanArgs=--hostname=%H +Volume=/:/host:ro,rslave +Volume=/var/run/dbus:/var/run/dbus:ro +# The host rootfs cannot be relabelled, so confinement has to be disabled. +SecurityLabelDisable=true +Exec=--path.rootfs=/host --collector.textfile.directory=/host/var/lib/prometheus/node_exporter --web.listen-address={{ deploy_prometheus_exporters_listen_address }}:9100 --collector.systemd + +[Service] +Restart=always +RestartSec=5 +TimeoutStartSec=900 + +[Install] +WantedBy=multi-user.target default.target diff --git a/roles/deploy_prometheus_exporters/templates/podman-exporter.container.j2 b/roles/deploy_prometheus_exporters/templates/podman-exporter.container.j2 new file mode 100644 index 000000000..bd8e9e145 --- /dev/null +++ b/roles/deploy_prometheus_exporters/templates/podman-exporter.container.j2 @@ -0,0 +1,19 @@ +[Unit] +Description=Prometheus Podman Exporter +After=network.target podman.socket +Requires=podman.socket + +[Container] +Image={{ deploy_prometheus_exporters_podman_exporter_image }} +Environment=CONTAINER_HOST=unix:///run/podman/podman.sock +Volume=/run/podman/podman.sock:/run/podman/podman.sock +User=root +PublishPort={{ deploy_prometheus_exporters_listen_address }}:9882:9882 +SecurityLabelDisable=true + +[Service] +Restart=always +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/roles/deploy_prometheus_exporters/vars/CentOS.yml b/roles/deploy_prometheus_exporters/vars/CentOS.yml new file mode 100644 index 000000000..da3286052 --- /dev/null +++ b/roles/deploy_prometheus_exporters/vars/CentOS.yml @@ -0,0 +1,7 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +deploy_prometheus_exporters_libvirt_exporter_socket: /var/run/libvirt/libvirt-sock-ro +deploy_prometheus_exporters_insatomcat_libvirt_socket: /var/run/libvirt/libvirt-sock +deploy_prometheus_exporters_insatomcat_qemu_dir: /var/run/libvirt/qemu +deploy_prometheus_exporters_libvirt_systemd_unit: libvirtd.service diff --git a/roles/deploy_prometheus_exporters/vars/Debian.yml b/roles/deploy_prometheus_exporters/vars/Debian.yml new file mode 100644 index 000000000..da3286052 --- /dev/null +++ b/roles/deploy_prometheus_exporters/vars/Debian.yml @@ -0,0 +1,7 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +deploy_prometheus_exporters_libvirt_exporter_socket: /var/run/libvirt/libvirt-sock-ro +deploy_prometheus_exporters_insatomcat_libvirt_socket: /var/run/libvirt/libvirt-sock +deploy_prometheus_exporters_insatomcat_qemu_dir: /var/run/libvirt/qemu +deploy_prometheus_exporters_libvirt_systemd_unit: libvirtd.service diff --git a/roles/deploy_prometheus_exporters/vars/OracleLinux.yml b/roles/deploy_prometheus_exporters/vars/OracleLinux.yml new file mode 100644 index 000000000..e39971c4a --- /dev/null +++ b/roles/deploy_prometheus_exporters/vars/OracleLinux.yml @@ -0,0 +1,7 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +deploy_prometheus_exporters_libvirt_exporter_socket: /run/libvirt/virtqemud-sock +deploy_prometheus_exporters_insatomcat_libvirt_socket: /run/libvirt/virtqemud-sock +deploy_prometheus_exporters_insatomcat_qemu_dir: /var/run/libvirt/qemu +deploy_prometheus_exporters_libvirt_systemd_unit: virtqemud.service diff --git a/roles/deploy_prometheus_exporters/vars/SLES.yml b/roles/deploy_prometheus_exporters/vars/SLES.yml new file mode 100644 index 000000000..24b35604b --- /dev/null +++ b/roles/deploy_prometheus_exporters/vars/SLES.yml @@ -0,0 +1,11 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +# SLES support is not implemented yet; skip deployment until it is added. +deploy_prometheus_exporters_enabled: false +deploy_prometheus_exporters_register_essential_services: false + +deploy_prometheus_exporters_libvirt_exporter_socket: /var/run/libvirt/libvirt-sock-ro +deploy_prometheus_exporters_insatomcat_libvirt_socket: /var/run/libvirt/libvirt-sock +deploy_prometheus_exporters_insatomcat_qemu_dir: /var/run/libvirt/qemu +deploy_prometheus_exporters_libvirt_systemd_unit: libvirtd.service diff --git a/roles/deploy_prometheus_exporters/vars/Yocto.yml b/roles/deploy_prometheus_exporters/vars/Yocto.yml new file mode 100644 index 000000000..e9fb7b78e --- /dev/null +++ b/roles/deploy_prometheus_exporters/vars/Yocto.yml @@ -0,0 +1,11 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +# Yocto support is not implemented yet; skip deployment until it is added. +deploy_prometheus_exporters_enabled: false +deploy_prometheus_exporters_register_essential_services: false + +deploy_prometheus_exporters_libvirt_exporter_socket: /var/run/libvirt/libvirt-sock-ro +deploy_prometheus_exporters_insatomcat_libvirt_socket: /var/run/libvirt/libvirt-sock +deploy_prometheus_exporters_insatomcat_qemu_dir: /var/run/libvirt/qemu +deploy_prometheus_exporters_libvirt_systemd_unit: libvirtd.service diff --git a/roles/deploy_prometheus_exporters/vars/main.yml b/roles/deploy_prometheus_exporters/vars/main.yml new file mode 100644 index 000000000..f6d906efa --- /dev/null +++ b/roles/deploy_prometheus_exporters/vars/main.yml @@ -0,0 +1,24 @@ +# Copyright (C) 2026 RTE +# SPDX-License-Identifier: Apache-2.0 +--- +deploy_prometheus_exporters_supported_exporters: + - node-exporter + - podman-exporter + - libvirt-exporter + - insatomcat-exporter + - ha_cluster_exporter + +deploy_prometheus_exporters_default_images: + node-exporter: quay.io/prometheus/node-exporter:latest + podman-exporter: quay.io/navidys/prometheus-podman-exporter + libvirt-exporter: ghcr.io/inovex/prometheus-libvirt-exporter:2.3.1 + insatomcat-exporter: docker.io/insatomcat/insatomcat-exporter:latest + ha_cluster_exporter: docker.io/insatomcat/ha-cluster-exporter:0.0.1 + +deploy_prometheus_exporters_group_exporters: + cluster_machines: + - ha_cluster_exporter + hypervisors: + - podman-exporter + - libvirt-exporter + - insatomcat-exporter