feat: add systemd exporter role

Signed-off-by: gardar <gardar@users.noreply.github.com>
This commit is contained in:
gardar 2023-03-09 01:20:32 +00:00
parent d389ad29e2
commit bbb131056d
No known key found for this signature in database
GPG key ID: 00872BAF59D98753
25 changed files with 676 additions and 0 deletions

View file

@ -0,0 +1,60 @@
<p><img src="https://brand.systemd.io/assets/png/systemd-logomark.png" alt="systemd logo" title="systemd" align="right" height="60" /></p>
# Ansible Role: systemd exporter
## Description
Deploy prometheus [systemd exporter](https://github.com/povilasv/systemd_exporter) using ansible.
## Requirements
- Ansible >= 2.9 (It might work on previous versions, but we cannot guarantee it)
- gnu-tar on Mac deployer host (`brew install gnu-tar`)
## Role Variables
All variables which can be overridden are stored in [defaults/main.yml](defaults/main.yml) file as well as in table below.
| Name | Default Value | Description |
| -------------- | ------------- | -----------------------------------|
| `systemd_exporter_version` | 0.4.0 | SystemD exporter package version. Also accepts latest as parameter. |
| `systemd_exporter_binary_local_dir` | "" | Allows to use local packages instead of ones distributed on github. As parameter it takes a directory where `systemd_exporter` binary is stored on host on which ansible is ran. This overrides `systemd_exporter_version` parameter |
| `systemd_exporter_binary_url` | `https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz` | URL of the systemd exporter binaries .tar.gz file |
| `systemd_exporter_checksums_url` | `https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/sha256sums.txt` | URL of the systemd exporter checksums file |
| `systemd_exporter_web_listen_address` | "0.0.0.0:9558" | Address on which systemd exporter will listen |
| `systemd_exporter_enable_restart_count` | false | Enables service restart count metrics. This feature only works with systemd 235 and above |
| `systemd_exporter_enable_ip_accounting` | false | Enables service ip accounting metrics. This feature only works with systemd 235 and above |
| `systemd_exporter_enable_file_descriptor_size` | false | Enables file descriptor size metrics. This feature will cause exporter to run as root as it needs access to /proc/X/fd |
| `systemd_exporter_unit_allowlist` | "" | Include some systemd units. Expects a regex. More in https://github.com/povilasv/systemd_exporter#configuration |
| `systemd_exporter_unit_denylist` | "" | Exclude some systemd units. Expects a regex. More in https://github.com/povilasv/systemd_exporter#configuration |
## Example
### Playbook
Use it in a playbook as follows:
```yaml
- hosts: all
roles:
- prometheus.prometheus.systemd_exporter
```
## Local Testing
The preferred way of locally testing the role is to use Docker and [molecule](https://github.com/ansible-community/molecule) (v3.x). You will have to install Docker on your system. See "Get started" for a Docker package suitable to for your system. Running your tests is as simple as executing `molecule test`.
## Continuous Intergation
Combining molecule and circle CI allows us to test how new PRs will behave when used with multiple ansible versions and multiple operating systems. This also allows use to create test scenarios for different role configurations. As a result we have a quite large test matrix which can take more time than local testing, so please be patient.
## Contributing
See [contributor guideline](CONTRIBUTING.md).
## Troubleshooting
See [troubleshooting](TROUBLESHOOTING.md).
## License
This project is licensed under MIT License. See [LICENSE](/LICENSE) for more details.

View file

@ -0,0 +1,18 @@
---
systemd_exporter_version: 0.4.0
systemd_exporter_binary_local_dir: ""
systemd_exporter_binary_url: "https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/\
systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
systemd_exporter_checksums_url: "https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/sha256sums.txt"
systemd_exporter_web_listen_address: "0.0.0.0:9558"
systemd_exporter_enable_restart_count: false
systemd_exporter_enable_ip_accounting: false
systemd_exporter_enable_file_descriptor_size: false
systemd_exporter_unit_whitelist: ""
systemd_exporter_unit_blacklist: ""
systemd_exporter_binary_install_dir: "/usr/local/bin"
systemd_exporter_system_group: "systemd-exporter"
systemd_exporter_system_user: "{{ systemd_exporter_system_group }}"

View file

@ -0,0 +1,8 @@
---
- name: Restart systemd_exporter
ansible.builtin.systemd:
daemon_reload: true
name: systemd_exporter
state: restarted
become: true
listen: restart systemd_exporter

View file

@ -0,0 +1,58 @@
---
# yamllint disable rule:line-length
argument_specs:
main:
short_description: "Prometheus Systemd Exporter"
description:
- "Deploy prometheus L(systemd exporter,,https://github.com/povilasv/systemd_exporter) using ansible."
author:
- "Prometheus Community"
options:
systemd_exporter_version:
description: "SystemD exporter package version. Also accepts latest as parameter."
default: "0.4.0"
systemd_exporter_binary_local_dir:
description:
- "Allows to use local packages instead of ones distributed on github."
- "As parameter it takes a directory where C(systemd_exporter) binary is stored on host on which ansible is run."
- "This overrides I(systemd_exporter_version) parameter"
systemd_exporter_binary_url:
description: URL of the systemd exporter binaries .tar.gz file"
default: "https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
systemd_exporter_checksums_url:
description: "URL of the systemd exporter checksums file"
default: "https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/sha256sums.txt"
systemd_exporter_web_listen_address:
description: Address on which systemd exporter will listen"
default: "0.0.0.0:9558"
systemd_exporter_enable_restart_count:
description: Enables service restart count metrics. This feature only works with systemd 235 and above"
type: "bool"
default: false
systemd_exporter_enable_ip_accounting:
description: Enables service ip accounting metrics. This feature only works with systemd 235 and above"
type: "bool"
default: false
systemd_exporter_enable_file_descriptor_size:
description: Enables file descriptor size metrics. This feature will cause exporter to run as root as it needs access to /proc/X/fd"
type: "bool"
default: false
systemd_exporter_unit_allowlist:
description: "Include some systemd units. Expects a regex. More in L(https://github.com/povilasv/systemd_exporter#configuration)"
systemd_exporter_unit_denylist:
description: "Exclude some systemd units. Expects a regex. More in L(https://github.com/povilasv/systemd_exporter#configuration)"
systemd_exporter_binary_install_dir:
description:
- "I(Advanced)"
- "Directory to install systemd_exporter binary"
default: "/usr/local/bin"
systemd_exporter_system_group:
description:
- "I(Advanced)"
- "System group for systemd exporter"
default: "systemd-exporter"
systemd_exporter_system_user:
description:
- "I(Advanced)"
- "Systemd exporter user"
default: "systemd-exporter"

View file

@ -0,0 +1,32 @@
---
galaxy_info:
author: Prometheus Community
description: Prometheus Systemd Exporter
license: Apache
company: none
min_ansible_version: "2.9"
platforms:
- name: Ubuntu
versions:
- bionic
- xenial
- name: Debian
versions:
- stretch
- buster
- name: EL
versions:
- '7'
- '8'
- name: Fedora
versions:
- '30'
- '31'
galaxy_tags:
- monitoring
- prometheus
- exporter
- metrics
- systemd
dependencies: []

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1,9 @@
---
- name: Run role
hosts: all
any_errors_fatal: true
roles:
- prometheus.prometheus.systemd_exporter
vars:
systemd_exporter_binary_local_dir: "/tmp/systemd_exporter-linux-amd64"
systemd_exporter_web_listen_address: "127.0.0.1:9000"

View file

@ -0,0 +1,55 @@
---
- name: Prepare
hosts: localhost
gather_facts: false
vars:
go_arch: amd64
systemd_exporter_version: 0.4.0
tasks:
- name: Download systemd_exporter binary to local folder
become: false
ansible.builtin.get_url:
url: "https://github.com/povilasv/systemd_exporter/releases/download/v{{ systemd_exporter_version }}/\
systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
dest: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
register: _download_binary
until: _download_binary is succeeded
retries: 5
delay: 2
check_mode: false
- name: Unpack systemd_exporter binary
become: false
ansible.builtin.unarchive:
src: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
dest: "/tmp"
creates: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}/systemd_exporter"
check_mode: false
- name: Link to systemd_exporter binaries directory
become: false
ansible.builtin.file:
src: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-amd64"
dest: "/tmp/systemd_exporter-linux-amd64"
state: link
check_mode: false
- name: Install pyOpenSSL for certificate generation
ansible.builtin.pip:
name: "pyOpenSSL"
- name: Create private key
community.crypto.openssl_privatekey:
path: "/tmp/tls.key"
- name: Create CSR
community.crypto.openssl_csr:
path: "/tmp/tls.csr"
privatekey_path: "/tmp/tls.key"
- name: Create certificate
community.crypto.x509_certificate:
path: "/tmp/tls.cert"
csr_path: "/tmp/tls.csr"
privatekey_path: "/tmp/tls.key"
provider: selfsigned

View file

@ -0,0 +1,23 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
def test_service(host):
s = host.service("systemd_exporter")
# assert s.is_enabled
assert s.is_running
def test_socket(host):
sockets = [
"tcp://127.0.0.1:9000"
]
for socket in sockets:
s = host.socket(socket)
assert s.is_listening

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1,8 @@
---
- name: Run role
hosts: all
any_errors_fatal: true
roles:
- prometheus.prometheus.systemd_exporter
vars:
systemd_exporter_web_listen_address: "127.0.0.1:9558"

View file

@ -0,0 +1,56 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
def test_files(host):
files = [
"/etc/systemd/system/systemd_exporter.service",
"/usr/local/bin/systemd_exporter"
]
for file in files:
f = host.file(file)
assert f.exists
assert f.is_file
def test_permissions_didnt_change(host):
dirs = [
"/etc",
"/root",
"/usr",
"/var"
]
for file in dirs:
f = host.file(file)
assert f.exists
assert f.is_directory
assert f.user == "root"
assert f.group == "root"
def test_user(host):
assert host.group("systemd-exporter").exists
assert "systemd-exporter" in host.user("systemd-exporter").groups
assert host.user("systemd-exporter").shell == "/usr/sbin/nologin"
assert host.user("systemd-exporter").home == "/"
# def test_service(host):
# s = host.service("systemd_exporter")
# # assert s.is_enabled
# assert s.is_running
def test_socket(host):
sockets = [
"tcp://127.0.0.1:9558"
]
for socket in sockets:
s = host.socket(socket)
assert s.is_listening

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1,8 @@
---
- name: Run role
hosts: all
any_errors_fatal: true
roles:
- prometheus.prometheus.systemd_exporter
vars:
systemd_exporter_version: latest

View file

@ -0,0 +1,30 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
@pytest.mark.parametrize("files", [
"/etc/systemd/system/systemd_exporter.service",
"/usr/local/bin/systemd_exporter"
])
def test_files(host, files):
f = host.file(files)
assert f.exists
assert f.is_file
def test_service(host):
s = host.service("systemd_exporter")
# assert s.is_enabled
assert s.is_running
def test_socket(host):
s = host.socket("tcp://0.0.0.0:9558")
assert s.is_listening

View file

@ -0,0 +1,19 @@
---
- name: Copy the systemd_exporter systemd service file
ansible.builtin.template:
src: systemd_exporter.service.j2
dest: /etc/systemd/system/systemd_exporter.service
owner: root
group: root
mode: 0644
notify: restart systemd_exporter
- name: Allow systemd_exporter port in SELinux on RedHat OS family
community.general.seport:
ports: "{{ systemd_exporter_web_listen_address.split(':')[-1] }}"
proto: tcp
setype: http_port_t
state: present
when:
- ansible_version.full is version_compare('2.4', '>=')
- ansible_selinux.status == "enabled"

View file

@ -0,0 +1,64 @@
---
- name: Create the systemd_exporter group
ansible.builtin.group:
name: "{{ systemd_exporter_system_group }}"
state: present
system: true
when: systemd_exporter_system_group != "root"
- name: Create the systemd_exporter user
ansible.builtin.user:
name: "{{ systemd_exporter_system_user }}"
groups: "{{ systemd_exporter_system_group }}"
append: true
shell: /usr/sbin/nologin
system: true
create_home: false
home: /
when: systemd_exporter_system_user != "root"
- name: Get systemd exporter binary
when: systemd_exporter_binary_local_dir | length == 0
block:
- name: Download systemd_exporter binary to local folder
become: false
ansible.builtin.get_url:
url: "{{ systemd_exporter_binary_url }}"
dest: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
checksum: "sha256:{{ _systemd_exporter_checksum }}"
mode: '0644'
register: _download_binary
until: _download_binary is succeeded
retries: 5
delay: 2
delegate_to: localhost
check_mode: false
- name: Unpack systemd_exporter binary
become: false
ansible.builtin.unarchive:
src: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}.tar.gz"
dest: "/tmp"
creates: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}/systemd_exporter"
delegate_to: localhost
check_mode: false
- name: Propagate systemd_exporter binaries
ansible.builtin.copy:
src: "/tmp/systemd_exporter-{{ systemd_exporter_version }}.linux-{{ go_arch }}/systemd_exporter"
dest: "{{ systemd_exporter_binary_install_dir }}/systemd_exporter"
mode: 0755
owner: root
group: root
notify: restart systemd_exporter
when: not ansible_check_mode
- name: Propagate locally distributed systemd_exporter binary
ansible.builtin.copy:
src: "{{ systemd_exporter_binary_local_dir }}/systemd_exporter"
dest: "{{ systemd_exporter_binary_install_dir }}/systemd_exporter"
mode: 0755
owner: root
group: root
when: systemd_exporter_binary_local_dir | length > 0
notify: restart systemd_exporter

View file

@ -0,0 +1,49 @@
---
- name: Preflight
ansible.builtin.include_tasks: preflight.yml
tags:
- systemd_exporter_install
- systemd_exporter_configure
- systemd_exporter_run
- name: Install
ansible.builtin.include_tasks:
file: install.yml
apply:
become: true
when:
( not __systemd_exporter_is_installed.stat.exists ) or
( __systemd_exporter_current_version_output.stderr_lines[0].split(" ")[2] != systemd_exporter_version ) or
( systemd_exporter_binary_local_dir | length > 0 )
tags:
- systemd_exporter_install
- name: SELinux
ansible.builtin.include_tasks:
file: selinux.yml
apply:
become: true
become: true
when: ansible_selinux.status == "enabled"
tags:
- systemd_exporter_configure
- name: Configure
ansible.builtin.include_tasks:
file: configure.yml
apply:
become: true
tags:
- systemd_exporter_configure
- name: Ensure systemd exporter is enabled on boot
become: true
ansible.builtin.systemd:
daemon_reload: true
name: systemd_exporter
enabled: true
state: started
when:
- not ansible_check_mode
tags:
- systemd_exporter_run

View file

@ -0,0 +1,85 @@
---
- name: Assert usage of systemd as an init system
ansible.builtin.assert:
that: ansible_service_mgr == 'systemd'
msg: "This role only works with systemd"
- name: Install package fact dependencies
ansible.builtin.package:
name: "{{ _pkg_fact_req }}"
state: present
when: (_pkg_fact_req)
vars:
_pkg_fact_req: "{% if (ansible_pkg_mgr == 'apt') %}\
{{ ('python-apt' if ansible_python_version is version('3', '<') else 'python3-apt') }}
{% else %}\
{% endif %}"
- name: Gather package facts
ansible.builtin.package_facts:
when: "not 'packages' in ansible_facts"
- name: Naive assertion of proper listen address
ansible.builtin.assert:
that:
- "':' in systemd_exporter_web_listen_address"
- name: Assert that systemd version is >= 235 when enabling ip accounting or measuring restart count
ansible.builtin.assert:
that:
- (ansible_facts.packages.systemd | first).version is version('235', '>=')
when: systemd_exporter_enable_ip_accounting or systemd_exporter_enable_restart_count
- name: Set user and group to root to allow access to /proc/X/fd
ansible.builtin.set_fact:
systemd_exporter_system_group: "root"
systemd_exporter_system_user: "root"
when: systemd_exporter_enable_file_descriptor_size
- name: Check if systemd_exporter is installed
ansible.builtin.stat:
path: "{{ systemd_exporter_binary_install_dir }}/systemd_exporter"
register: __systemd_exporter_is_installed
check_mode: false
tags:
- systemd_exporter_install
- name: Gather currently installed systemd_exporter version (if any)
command: "{{ systemd_exporter_binary_install_dir }}/systemd_exporter --version"
args:
warn: false
changed_when: false
register: __systemd_exporter_current_version_output
check_mode: false
when: __systemd_exporter_is_installed.stat.exists
tags:
- systemd_exporter_install
- skip_ansible_lint
- name: Discover latest version
ansible.builtin.set_fact:
systemd_exporter_version: "{{ (lookup('url', 'https://api.github.com/repos/povilasv/systemd_exporter/releases/latest', split_lines=False) |
from_json).get('tag_name') | replace('v', '') }}"
run_once: true
until: systemd_exporter_version is version('0.0.0', '>=')
retries: 10
when:
- systemd_exporter_version == "latest"
- systemd_exporter_binary_local_dir | length == 0
- name: Get systemd exporter binary checksum
when: systemd_exporter_binary_local_dir | length == 0
block:
- name: Get checksum list from github
ansible.builtin.set_fact:
_systemd_exporter_checksums: "{{ lookup('url', systemd_exporter_checksums_url, wantlist=True) | list }}"
run_once: true
until: _systemd_exporter_checksums is search('linux-' + go_arch + '.tar.gz')
retries: 10
- name: "Get checksum for {{ go_arch }}"
ansible.builtin.set_fact:
_systemd_exporter_checksum: "{{ item.split(' ')[0] }}"
with_items: "{{ _systemd_exporter_checksums }}"
when:
- "('linux-' + go_arch + '.tar.gz') in item"

View file

@ -0,0 +1,23 @@
---
- name: Install selinux python packages [RedHat]
ansible.builtin.package:
name: "{{ ['libselinux-python', 'python-policycoreutils']
if ansible_python_version is version('3', '<') else
['python3-libselinux', 'python3-policycoreutils'] }}"
state: present
register: _install_selinux_packages
until: _install_selinux_packages is success
retries: 5
delay: 2
when: ansible_os_family | lower == "redhat"
- name: Install selinux python packages [clearlinux]
ansible.builtin.package:
name: sysadmin-basic
state: present
register: _install_selinux_packages
until: _install_selinux_packages is success
retries: 5
delay: 2
when:
- ansible_distribution | lower == "clearlinux"

View file

@ -0,0 +1,47 @@
{{ ansible_managed | comment }}
[Unit]
Description=Prometheus SystemD Exporter
After=network-online.target
[Service]
Type=simple
User={{ systemd_exporter_system_user }}
Group={{ systemd_exporter_system_group }}
ExecStart={{ systemd_exporter_binary_install_dir }}/systemd_exporter \
{% if systemd_exporter_enable_restart_count %}
--collector.enable-restart-count \
{% endif %}
{% if systemd_exporter_enable_file_descriptor_size %}
--collector.enable-file-descriptor-size \
{% endif %}
{% if systemd_exporter_enable_ip_accounting %}
--collector.enable-ip-accounting \
{% endif %}
{% if systemd_exporter_unit_whitelist != ""%}
--collector.unit-whitelist={{ systemd_exporter_unit_whitelist }} \
{% endif %}
{% if systemd_exporter_unit_blacklist != "" %}
--collector.unit-blacklist={{ systemd_exporter_unit_blacklist }} \
{% endif %}
--web.listen-address={{ systemd_exporter_web_listen_address }}
SyslogIdentifier=systemd_exporter
Restart=always
RestartSec=1
StartLimitInterval=0
ProtectHome=yes
NoNewPrivileges=yes
{% if (ansible_facts.packages.systemd | first).version is version('232', '>=') %}
ProtectSystem=strict
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=yes
{% else %}
ProtectSystem=full
{% endif %}
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,9 @@
---
go_arch_map:
i386: '386'
x86_64: 'amd64'
aarch64: 'arm64'
armv7l: 'armv7'
armv6l: 'armv6'
go_arch: "{{ go_arch_map[ansible_architecture] | default(ansible_architecture) }}"

View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+")
source "$collection_root/tests/integration/molecule.sh"

View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+")
source "$collection_root/tests/integration/molecule.sh"

View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+")
source "$collection_root/tests/integration/molecule.sh"