From 4c2902f20971ea4d5513151f3b18a4ffdef409bb Mon Sep 17 00:00:00 2001 From: gardar Date: Fri, 23 Feb 2024 00:39:05 +0000 Subject: [PATCH] feat: add process_exporter role Signed-off-by: gardar --- roles/process_exporter/README.md | 51 +++++++++++++ roles/process_exporter/defaults/main.yml | 24 ++++++ roles/process_exporter/handlers/main.yml | 8 ++ roles/process_exporter/meta/main.yml | 30 ++++++++ .../molecule/default/molecule.yml | 1 + .../molecule/default/tests/test_default.py | 48 ++++++++++++ roles/process_exporter/tasks/configure.yml | 40 ++++++++++ roles/process_exporter/tasks/install.yml | 69 ++++++++++++++++++ roles/process_exporter/tasks/main.yml | 56 ++++++++++++++ roles/process_exporter/tasks/preflight.yml | 73 +++++++++++++++++++ roles/process_exporter/tasks/selinux.yml | 23 ++++++ .../templates/process_exporter.service.j2 | 23 ++++++ roles/process_exporter/vars/main.yml | 11 +++ .../runme.sh | 4 + 14 files changed, 461 insertions(+) create mode 100644 roles/process_exporter/README.md create mode 100644 roles/process_exporter/defaults/main.yml create mode 100644 roles/process_exporter/handlers/main.yml create mode 100644 roles/process_exporter/meta/main.yml create mode 100644 roles/process_exporter/molecule/default/molecule.yml create mode 100644 roles/process_exporter/molecule/default/tests/test_default.py create mode 100644 roles/process_exporter/tasks/configure.yml create mode 100644 roles/process_exporter/tasks/install.yml create mode 100644 roles/process_exporter/tasks/main.yml create mode 100644 roles/process_exporter/tasks/preflight.yml create mode 100644 roles/process_exporter/tasks/selinux.yml create mode 100644 roles/process_exporter/templates/process_exporter.service.j2 create mode 100644 roles/process_exporter/vars/main.yml create mode 100755 tests/integration/targets/molecule-process_exporter-default/runme.sh diff --git a/roles/process_exporter/README.md b/roles/process_exporter/README.md new file mode 100644 index 0000000..4831082 --- /dev/null +++ b/roles/process_exporter/README.md @@ -0,0 +1,51 @@ +# Ansible Role: process_exporter + +[![Build Status](https://travis-ci.com/cloudalchemy/ansible-process_exporter.svg?branch=master)](https://travis-ci.com/cloudalchemy/ansible-process_exporter) +[![License](https://img.shields.io/badge/license-MIT%20License-brightgreen.svg)](https://opensource.org/licenses/MIT) +[![Ansible Role](https://img.shields.io/badge/ansible%20role-cloudalchemy.process_exporter-blue.svg)](https://galaxy.ansible.com/cloudalchemy/process_exporter/) +[![GitHub tag](https://img.shields.io/github/tag/cloudalchemy/ansible-process_exporter.svg)](https://github.com/cloudalchemy/ansible-process_exporter/tags) + +## Description + +Deploy [process-exporter](https://github.com/ncabatoff/process-exporter) using ansible. + +Note. This repository and role uses the name process_exporter to conform with ansible galaxy constraints. + +## Requirements + +- Ansible >= 2.9 (It might work on previous versions, but we cannot guarantee it) + +## 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 | +| -------------- | ------------- | -----------------------------------| +| `process_exporter_version` | 0.7.5 | Process exporter package version. Also accepts latest as parameter | +| `process_exporter_web_listen_address` | "0.0.0.0:9256" | Address on which process_exporter will listen | +| `process_exporter_config_dir` | "/etc/process_exporter" | Path to directory with process_exporter configuration | +`process_exporter_names` handling has been set up in an unusual way to handle recommended process-exporter 'Template variables' (such as {{.Comm}}). Follow the example in [defaults/main.yml](defaults/main.yml) if you want to define custom filtering/grouping of processes that use Template variables and make sure to keep the {% raw %} block delimiters. + +## Example + +### Playbook + +Use it in a playbook as follows: +```yaml +- hosts: all + roles: + - prometheus.prometheus.process_exporter +``` + + +## 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. diff --git a/roles/process_exporter/defaults/main.yml b/roles/process_exporter/defaults/main.yml new file mode 100644 index 0000000..47c22fc --- /dev/null +++ b/roles/process_exporter/defaults/main.yml @@ -0,0 +1,24 @@ +--- +process_exporter_version: 0.7.5 +process_exporter_binary_local_dir: "" +process_exporter_binary_url: "https://github.com/{{ _process_exporter_repo }}/releases/download/v{{ process_exporter_version }}/\ + process_exporter-{{ process_exporter_version }}.linux-{{ go_arch }}.tar.gz" +process_exporter_checksums_url: "https://github.com/{{ _process_exporter_repo }}/releases/download/v{{ process_exporter_version }}/sha256sums.txt" +process_exporter_skip_install: false + + +process_exporter_web_listen_address: "0.0.0.0:9256" + +# Process names +# "raw" section is needed to avoid attempted interpretation +# of process-exporter Template varables (like {{.Comm}}) +process_exporter_names: | + {% raw %} + - name: "{{.Comm}}" + cmdline: + - '.+' + {% endraw %} + +process_exporter_binary_install_dir: "/usr/local/bin" +process_exporter_system_group: "process-exp" +process_exporter_system_user: "{{ process_exporter_system_group }}" diff --git a/roles/process_exporter/handlers/main.yml b/roles/process_exporter/handlers/main.yml new file mode 100644 index 0000000..7b17604 --- /dev/null +++ b/roles/process_exporter/handlers/main.yml @@ -0,0 +1,8 @@ +--- +- name: Restart process_exporter + listen: "restart process_exporter" + become: true + ansible.builtin.systemd: + daemon_reload: true + name: process_exporter + state: restarted diff --git a/roles/process_exporter/meta/main.yml b/roles/process_exporter/meta/main.yml new file mode 100644 index 0000000..5c89712 --- /dev/null +++ b/roles/process_exporter/meta/main.yml @@ -0,0 +1,30 @@ +--- +galaxy_info: + author: "Prometheus Community" + description: "Prometheus Process Exporter" + license: "Apache" + min_ansible_version: "2.9" + platforms: + - name: "Ubuntu" + versions: + - "focal" + - "jammy" + - name: "Debian" + versions: + - "bullseye" + - "buster" + - name: "EL" + versions: + - "7" + - "8" + - "9" + - name: "Fedora" + versions: + - "37" + - '38' + galaxy_tags: + - "monitoring" + - "prometheus" + - "exporter" + - "metrics" + - "system" diff --git a/roles/process_exporter/molecule/default/molecule.yml b/roles/process_exporter/molecule/default/molecule.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/process_exporter/molecule/default/molecule.yml @@ -0,0 +1 @@ +--- diff --git a/roles/process_exporter/molecule/default/tests/test_default.py b/roles/process_exporter/molecule/default/tests/test_default.py new file mode 100644 index 0000000..6d21309 --- /dev/null +++ b/roles/process_exporter/molecule/default/tests/test_default.py @@ -0,0 +1,48 @@ +import os +import testinfra.utils.ansible_runner + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') + + +def test_directories(host): + dirs = [ + "/etc/process_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_files(host): + files = [ + "/etc/systemd/system/process_exporter.service", + "/usr/local/bin/process_exporter", + ] + for file in files: + f = host.file(file) + assert f.exists + assert f.is_file + + +def test_user(host): + assert host.group("process-exp").exists + assert "process-exp" in host.user("process-exp").groups + assert host.user("process-exp").shell == "/usr/sbin/nologin" + assert host.user("process-exp").home == "/" + + +def test_service(host): + s = host.service("process_exporter") +# assert s.is_enabled + assert s.is_running + + +def test_socket(host): + sockets = [ + "tcp://0.0.0.0:9256" + ] + for socket in sockets: + s = host.socket(socket) + assert s.is_listening diff --git a/roles/process_exporter/tasks/configure.yml b/roles/process_exporter/tasks/configure.yml new file mode 100644 index 0000000..e3acf1e --- /dev/null +++ b/roles/process_exporter/tasks/configure.yml @@ -0,0 +1,40 @@ +--- +- name: Copy the process_exporter systemd service file + ansible.builtin.template: + src: process_exporter.service.j2 + dest: /etc/systemd/system/process_exporter.service + owner: root + group: root + mode: 0644 + notify: restart process_exporter + +- name: Create process_exporter config directory + ansible.builtin.file: + path: "/etc/process_exporter" + state: directory + owner: root + group: root + mode: u+rwX,g+rwX,o=rX + +- name: Create/Update configuration file + ansible.builtin.copy: + dest: "/etc/process_exporter/config.yml" + content: | + process_names: + {{ process_exporter_names }} + owner: root + group: root + mode: 0644 + when: + - process_exporter_names != [] + notify: restart process_exporter + +- name: Allow process_exporter port in SELinux on RedHat OS family + community.general.seport: + ports: "{{ process_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" diff --git a/roles/process_exporter/tasks/install.yml b/roles/process_exporter/tasks/install.yml new file mode 100644 index 0000000..7af9682 --- /dev/null +++ b/roles/process_exporter/tasks/install.yml @@ -0,0 +1,69 @@ +--- +- name: Create the process_exporter group + ansible.builtin.group: + name: "{{ process_exporter_system_group }}" + state: present + system: true + when: process_exporter_system_group != "root" + +- name: Create the process_exporter user + ansible.builtin.user: + name: "{{ process_exporter_system_user }}" + groups: "{{ process_exporter_system_group }}" + append: true + shell: /usr/sbin/nologin + system: true + create_home: false + home: / + when: process_exporter_system_user != "root" + +- name: Get binary + when: + - process_exporter_binary_local_dir | length == 0 + - not process_exporter_skip_install + block: + + - name: Download process_exporter binary to local folder + become: false + ansible.builtin.get_url: + url: "{{ process_exporter_binary_url }}" + dest: "/tmp/process_exporter-{{ process_exporter_version }}.linux-{{ go_arch }}.tar.gz" + checksum: "sha256:{{ __process_exporter_checksum }}" + mode: '0644' + register: _download_binary + until: _download_binary is succeeded + retries: 5 + delay: 2 + delegate_to: localhost + check_mode: false + + - name: Unpack process_exporter binary + become: false + ansible.builtin.unarchive: + src: "/tmp/process_exporter-{{ process_exporter_version }}.linux-{{ go_arch }}.tar.gz" + dest: "/tmp" + creates: "/tmp/process_exporter-{{ process_exporter_version }}.linux-{{ go_arch }}/process_exporter" + delegate_to: localhost + check_mode: false + + - name: Propagate process_exporter binaries + ansible.builtin.copy: + src: "/tmp/process_exporter-{{ process_exporter_version }}.linux-{{ go_arch }}/process_exporter" + dest: "{{ process_exporter_binary_install_dir }}/process_exporter" + mode: 0755 + owner: root + group: root + notify: restart process_exporter + when: not ansible_check_mode + +- name: Propagate locally distributed process_exporter binary + ansible.builtin.copy: + src: "{{ process_exporter_binary_local_dir }}/process_exporter" + dest: "{{ process_exporter_binary_install_dir }}/process_exporter" + mode: 0755 + owner: root + group: root + when: + - process_exporter_binary_local_dir | length > 0 + - not process_exporter_skip_install + notify: restart process_exporter diff --git a/roles/process_exporter/tasks/main.yml b/roles/process_exporter/tasks/main.yml new file mode 100644 index 0000000..44ee66d --- /dev/null +++ b/roles/process_exporter/tasks/main.yml @@ -0,0 +1,56 @@ +--- +- name: Preflight + ansible.builtin.include_tasks: + file: preflight.yml + apply: + tags: + - process_exporter_install + - process_exporter_configure + - process_exporter_run + tags: + - process_exporter_install + - process_exporter_configure + - process_exporter_run + +- name: Install + ansible.builtin.include_tasks: + file: install.yml + apply: + become: true + tags: + - process_exporter_install + tags: + - process_exporter_install + +- name: SELinux + ansible.builtin.include_tasks: + file: selinux.yml + apply: + become: true + tags: + - process_exporter_configure + when: ansible_selinux.status == "enabled" + tags: + - process_exporter_configure + +- name: Configure + ansible.builtin.include_tasks: + file: configure.yml + apply: + become: true + tags: + - process_exporter_configure + tags: + - process_exporter_configure + +- name: Ensure process_exporter is enabled on boot + become: true + ansible.builtin.systemd: + daemon_reload: true + name: process_exporter + enabled: true + state: started + when: + - not ansible_check_mode + tags: + - process_exporter_run diff --git a/roles/process_exporter/tasks/preflight.yml b/roles/process_exporter/tasks/preflight.yml new file mode 100644 index 0000000..77bb347 --- /dev/null +++ b/roles/process_exporter/tasks/preflight.yml @@ -0,0 +1,73 @@ +--- +- 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 + become: true + 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 process_exporter_web_listen_address" + +- name: Check if process_exporter is installed + ansible.builtin.stat: + path: "{{ process_exporter_binary_install_dir }}/process_exporter" + register: __process_exporter_is_installed + check_mode: false + tags: + - process_exporter_install + +- name: Gather currently installed process_exporter version (if any) + ansible.builtin.command: "{{ process_exporter_binary_install_dir }}/process_exporter --version" + changed_when: false + register: __process_exporter_current_version_output + check_mode: false + when: __process_exporter_is_installed.stat.exists + tags: + - process_exporter_install + +- name: Discover latest version + ansible.builtin.set_fact: + process_exporter_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ _process_exporter_repo }}/releases/latest', headers=_github_api_headers, + split_lines=False) | from_json).get('tag_name') | replace('v', '') }}" + run_once: true + until: process_exporter_version is version('0.0.0', '>=') + retries: 10 + when: + - process_exporter_version == "latest" + - process_exporter_binary_local_dir | length == 0 + - not process_exporter_skip_install + +- name: Get process_exporter binary checksum + when: + - process_exporter_binary_local_dir | length == 0 + - not process_exporter_skip_install + block: + - name: Get checksum list from github + ansible.builtin.set_fact: + __process_exporter_checksums: "{{ lookup('url', process_exporter_checksums_url, headers=_github_api_headers, wantlist=True) | list }}" + run_once: true + until: __process_exporter_checksums is search('linux-' + go_arch + '.tar.gz') + retries: 10 + + - name: "Get checksum for {{ go_arch }}" + ansible.builtin.set_fact: + __process_exporter_checksum: "{{ item.split(' ')[0] }}" + with_items: "{{ __process_exporter_checksums }}" + when: "('linux-' + go_arch + '.tar.gz') in item" diff --git a/roles/process_exporter/tasks/selinux.yml b/roles/process_exporter/tasks/selinux.yml new file mode 100644 index 0000000..754cbd0 --- /dev/null +++ b/roles/process_exporter/tasks/selinux.yml @@ -0,0 +1,23 @@ +--- +- name: Install selinux python packages [RedHat] + ansible.builtin.package: + name: "{{ ['libselinux-python', 'policycoreutils-python'] + 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" diff --git a/roles/process_exporter/templates/process_exporter.service.j2 b/roles/process_exporter/templates/process_exporter.service.j2 new file mode 100644 index 0000000..977a760 --- /dev/null +++ b/roles/process_exporter/templates/process_exporter.service.j2 @@ -0,0 +1,23 @@ +{{ ansible_managed | comment }} + +[Unit] +Description=Prometheus Process Exporter +After=network-online.target + +[Service] +Type=simple +User={{ process_exporter_system_user }} +Group={{ process_exporter_system_group }} +ExecStart={{ process_exporter_binary_install_dir/process_exporter \ +{% if process_exporter_names != [] -%} + '--config.path=/etc/process_exporter/config.yml' \ +{% endif -%} + '--web.listen-address={{ process_exporter_web_listen_address }}' + +SyslogIdentifier=process_exporter +Restart=always +RestartSec=1 +StartLimitInterval=0 + +[Install] +WantedBy=multi-user.target diff --git a/roles/process_exporter/vars/main.yml b/roles/process_exporter/vars/main.yml new file mode 100644 index 0000000..0a6b669 --- /dev/null +++ b/roles/process_exporter/vars/main.yml @@ -0,0 +1,11 @@ +--- +go_arch_map: + i386: '386' + x86_64: 'amd64' + aarch64: 'arm64' + armv7l: 'armv7' + armv6l: 'armv6' + +go_arch: "{{ go_arch_map[ansible_architecture] | default(ansible_architecture) }}" +_process_exporter_repo: "ncabatoff/process_exporter" +_github_api_headers: "{{ {'GITHUB_TOKEN': lookup('ansible.builtin.env', 'GITHUB_TOKEN')} if (lookup('ansible.builtin.env', 'GITHUB_TOKEN')) else {} }}" diff --git a/tests/integration/targets/molecule-process_exporter-default/runme.sh b/tests/integration/targets/molecule-process_exporter-default/runme.sh new file mode 100755 index 0000000..d094c3e --- /dev/null +++ b/tests/integration/targets/molecule-process_exporter-default/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh"