--- # Both exporters listen on all addresses (the node exporter on host_exporters_listen_address if set); host_firewall # limits who reaches them. # Its recommends (smartmontools, nvme-cli, ipmitool) start units that fail on hosts without such hardware. - name: Install the node exporter ansible.builtin.apt: name: prometheus-node-exporter install_recommends: false state: present - name: Bind the node exporter to one address ansible.builtin.lineinfile: path: /etc/default/prometheus-node-exporter regexp: '^ARGS=' line: 'ARGS="--web.listen-address={{ host_exporters_listen_address }}:9100"' when: host_exporters_listen_address | length > 0 register: host_exporters_listen # Check mode does not install the package. ignore_errors: "{{ ansible_check_mode }}" - name: Create the node exporter unit drop-in directory ansible.builtin.file: path: /etc/systemd/system/prometheus-node-exporter.service.d state: directory mode: "0755" when: host_exporters_listen_address | length > 0 # The NetBird address exists only after the daemon is up, and a failed bind must not exhaust the start limit. - name: Start the bound node exporter after NetBird and retry ansible.builtin.copy: dest: /etc/systemd/system/prometheus-node-exporter.service.d/listen-address.conf mode: "0644" content: | [Unit] After=netbird.service StartLimitIntervalSec=0 [Service] RestartSec=5s when: host_exporters_listen_address | length > 0 register: host_exporters_listen_unit - name: Enable the node exporter ansible.builtin.systemd: name: prometheus-node-exporter enabled: true state: "{{ 'restarted' if (host_exporters_listen.changed or host_exporters_listen_unit.changed) else 'started' }}" daemon_reload: "{{ host_exporters_listen_unit.changed }}" # Check mode does not install the package. ignore_errors: "{{ ansible_check_mode }}" - name: Install the smartctl exporter when: host_exporters_smart block: - name: Look for an existing backports source ansible.builtin.shell: >- grep -rEqs '^[^#]*{{ ansible_distribution_release }}-backports' --include='*.list' --include='*.sources' --exclude=host-exporters-backports.sources /etc/apt register: host_exporters_backports changed_when: false failed_when: host_exporters_backports.rc > 1 check_mode: false # Backports stay at priority 100, so only packages named with default_release come from there. - name: Add the backports source ansible.builtin.copy: dest: /etc/apt/sources.list.d/host-exporters-backports.sources mode: "0644" content: | Types: deb URIs: http://deb.debian.org/debian Suites: {{ ansible_distribution_release }}-backports Components: main Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg register: host_exporters_source when: host_exporters_backports.rc == 1 # The module rejects default_release while the release is missing from its package cache. - name: Look for the backports release in the package lists ansible.builtin.shell: apt-cache policy | grep -q 'n={{ ansible_distribution_release }}-backports' register: host_exporters_lists changed_when: false failed_when: false check_mode: false - name: Refresh the package lists for the backports source ansible.builtin.apt: update_cache: true when: host_exporters_lists.rc != 0 - name: Install the smartctl exporter from backports ansible.builtin.apt: name: prometheus-smartctl-exporter default_release: "{{ ansible_distribution_release }}-backports" state: present # Check mode does not write the backports source. ignore_errors: "{{ ansible_check_mode }}" # The packaged unit runs as root, which smartctl needs for the disks. - name: Enable the smartctl exporter ansible.builtin.systemd: name: smartctl_exporter enabled: true state: started ignore_errors: "{{ ansible_check_mode }}" # Reads the BMC through /dev/ipmi0, read-only: no raw commands, no configuration. # FreeIPMI refuses in-band access for any uid but 0, so the exporter runs as the packaged # unprivileged user prometheus and starts the FreeIPMI tools it needs through sudo. - name: Install the IPMI exporter when: host_exporters_ipmi block: - name: Install ipmitool and the IPMI exporter ansible.builtin.apt: name: - ipmitool - sudo - prometheus-ipmi-exporter install_recommends: false state: present - name: Allow the exporter user to run the FreeIPMI tools ansible.builtin.copy: dest: /etc/sudoers.d/prometheus-ipmi-exporter mode: "0440" validate: visudo -cf %s content: | Defaults:prometheus !pam_session {% for collector in host_exporters_ipmi_collectors %} prometheus ALL = (root) NOPASSWD: /usr/sbin/{{ host_exporters_ipmi_tools[collector].tool }} {{ host_exporters_ipmi_tools[collector].args }} --config-file {{ host_exporters_ipmi_freeipmi_conf }} {% endfor %} register: host_exporters_ipmi_sudo - name: Create the FreeIPMI configuration directory ansible.builtin.file: path: "{{ host_exporters_ipmi_freeipmi_conf | dirname }}" state: directory owner: root group: root mode: "0755" # See the defaults for why the file is static. - name: Write the FreeIPMI configuration ansible.builtin.copy: dest: "{{ host_exporters_ipmi_freeipmi_conf }}" owner: root group: root mode: "0644" content: | # In-band access needs no settings. Not even sdr-cache-directory: the tools without an SDR cache # reject it, and the cache of the ipmi tool stays in the home of root. # collector_cmd replaces the tool itself, so each collector gets a wrapper that adds sudo. It swaps # the exporter's --config-file for the static one. The exporter fills a named pipe at the path it # passes and waits for a reader; the wrapper drains it so that writer does not hang. - name: Create the sudo wrappers directory ansible.builtin.file: path: /usr/local/libexec/ipmi-exporter state: directory mode: "0755" - name: Write the sudo wrappers ansible.builtin.copy: dest: /usr/local/libexec/ipmi-exporter/{{ item.key }} mode: "0755" content: | #!/bin/sh n=$# while [ "$n" -gt 0 ]; do a=$1; shift; n=$((n - 1)) if [ "$a" = --config-file ] && [ "$n" -gt 0 ]; then timeout 5 cat "$1" > /dev/null 2>&1 shift; n=$((n - 1)) else set -- "$@" "$a" fi done exec sudo -n /usr/sbin/{{ item.value.tool }} "$@" --config-file {{ host_exporters_ipmi_freeipmi_conf }} loop: "{{ host_exporters_ipmi_tools | dict2items | selectattr('key', 'in', host_exporters_ipmi_collectors) | list }}" loop_control: label: "{{ item.key }}" register: host_exporters_ipmi_wrappers - name: Write the IPMI exporter configuration ansible.builtin.copy: dest: /etc/prometheus/ipmi-local.yml mode: "0644" content: | modules: default: collectors: {% for collector in host_exporters_ipmi_collectors %} - {{ collector }} {% endfor %} collector_cmd: {% for collector in host_exporters_ipmi_collectors %} {{ collector }}: /usr/local/libexec/ipmi-exporter/{{ collector }} {% endfor %} register: host_exporters_ipmi_config - name: Create the IPMI exporter unit drop-in directory ansible.builtin.file: path: /etc/systemd/system/prometheus-ipmi-exporter.service.d state: directory mode: "0755" # sudo needs setuid, so NoNewPrivileges stays off. - name: Run the IPMI exporter with that configuration ansible.builtin.copy: dest: /etc/systemd/system/prometheus-ipmi-exporter.service.d/local.conf mode: "0644" content: | [Service] ExecStart= ExecStart=/usr/bin/prometheus-ipmi-exporter --config.file=/etc/prometheus/ipmi-local.yml register: host_exporters_ipmi_unit - name: Enable the IPMI exporter ansible.builtin.systemd: name: prometheus-ipmi-exporter enabled: true state: "{{ 'restarted' if (host_exporters_ipmi_config.changed or host_exporters_ipmi_unit.changed or host_exporters_ipmi_wrappers.changed) else 'started' }}" daemon_reload: true ignore_errors: "{{ ansible_check_mode }}"