diff --git a/roles/redis_exporter/README.md b/roles/redis_exporter/README.md new file mode 100644 index 00000000..1b003599 --- /dev/null +++ b/roles/redis_exporter/README.md @@ -0,0 +1,43 @@ +

graph logo

+ +# Ansible Role: redis_exporter + +## Description + +Deploy prometheus [redis_exporter](https://github.com/oliver006/redis_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 [meta/argument_specs.yml](meta/argument_specs.yml). +Please refer to the [collection docs](https://prometheus-community.github.io/ansible/branch/main/redis_exporter_role.html) for description and default values of the variables. + +## Example + +### Demo site + +We provide demo site for full monitoring solution based on prometheus and grafana. Repository with code and links to running instances is [available on github](https://github.com/prometheus/demo-site) and site is hosted on [DigitalOcean](https://digitalocean.com). + +## 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. diff --git a/roles/redis_exporter/defaults/main.yml b/roles/redis_exporter/defaults/main.yml new file mode 100644 index 00000000..75ef80c2 --- /dev/null +++ b/roles/redis_exporter/defaults/main.yml @@ -0,0 +1,51 @@ +--- +redis_exporter_version: 1.58.0 +redis_exporter_binary_local_dir: "" +redis_exporter_binary_url: "https://github.com/{{ _redis_exporter_repo }}/releases/download/v{{ redis_exporter_version }}/\ + redis_exporter-v{{ redis_exporter_version }}.linux-{{ go_arch }}.tar.gz" +redis_exporter_checksums_url: "https://github.com/{{ _redis_exporter_repo }}/releases/download/v{{ redis_exporter_version }}/sha256sums.txt" +redis_exporter_skip_install: false + +# https://github.com/oliver006/redis_exporter?tab=readme-ov-file#command-line-flags +redis_exporter_addr: "redis://localhost:6379" +redis_exporter_user: "" +redis_exporter_password: "" +redis_exporter_passwords: {} +redis_exporter_check_keys: [] +redis_exporter_check_single_keys: [] +redis_exporter_check_streams: [] +redis_exporter_check_single_streams: [] +redis_exporter_check_keys_batch_size: 1000 +redis_exporter_count_keys: [] +redis_exporter_script: [] +redis_exporter_debug: false +redis_exporter_log_format: "txt" +redis_exporter_namespace: "redis" +redis_exporter_connection_timeout: "15s" +redis_exporter_web_listen_address: "0.0.0.0:9121" +redis_exporter_web_telemetry_path: "/metrics" +redis_exporter_redis_only_metrics: false +redis_exporter_incl_config_metrics: false +redis_exporter_incl_system_metrics: false +redis_exporter_redact_config_metrics: false +redis_exporter_ping_on_connect: false +redis_exporter_is_tile38: false +redis_exporter_is_cluster: false +redis_exporter_export_client_list: false +redis_exporter_export_client_port: false +redis_exporter_skip_tls_verification: false +redis_exporter_tls_client_key_file: "" +redis_exporter_tls_client_cert_file: "" +redis_exporter_tls_server_key_file: "" +redis_exporter_tls_server_cert_file: "" +redis_exporter_tls_server_ca_cert_file: "" +redis_exporter_tls_server_min_version: "TLS1.2" +redis_exporter_tls_ca_cert_file: "" +redis_exporter_set_client_name: true +redis_exporter_check_key_groups: [] +redis_exporter_max_distinct_key_groups: 100 +redis_exporter_config_command: "CONFIG" + +redis_exporter_binary_install_dir: "/usr/local/bin" +redis_exporter_system_group: "redis-exp" +redis_exporter_system_user: "{{ redis_exporter_system_group }}" diff --git a/roles/redis_exporter/handlers/main.yml b/roles/redis_exporter/handlers/main.yml new file mode 100644 index 00000000..9d07eb33 --- /dev/null +++ b/roles/redis_exporter/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart redis_exporter + listen: "restart redis_exporter" + become: true + ansible.builtin.systemd: + daemon_reload: true + name: redis_exporter + state: restarted + when: + - not ansible_check_mode diff --git a/roles/redis_exporter/meta/argument_specs.yml b/roles/redis_exporter/meta/argument_specs.yml new file mode 100644 index 00000000..f883365d --- /dev/null +++ b/roles/redis_exporter/meta/argument_specs.yml @@ -0,0 +1,181 @@ +--- +# yamllint disable rule:line-length +argument_specs: + main: + short_description: "Prometheus redis_exporter" + description: + - "Deploy prometheus L(redis exporter,https://github.com/oliver006/redis_exporter) using ansible" + author: + - "Prometheus Community" + options: + redis_exporter_version: + description: "redis_exporter package version. Also accepts latest as parameter." + default: "1.58.0" + redis_exporter_skip_install: + description: "redis_exporter installation tasks gets skipped when set to true." + type: bool + default: false + redis_exporter_binary_local_dir: + description: + - "Enables the use of local packages instead of those distributed on github." + - "The parameter may be set to a directory where the C(redis_exporter) binary is stored on the host where ansible is run." + - "This overrides the I(redis_exporter_version) parameter" + redis_exporter_binary_url: + description: "URL of the redis_exporter binaries .tar.gz file" + default: "https://github.com/{{ _redis_exporter_repo }}/releases/download/v{{ redis_exporter_version }}/redis_exporter-v{{ redis_exporter_version }}.linux-{{ go_arch }}.tar.gz" + redis_exporter_checksums_url: + description: "URL of the redis_exporter checksums file" + default: "https://github.com/{{ _redis_exporter_repo }}/releases/download/v{{ redis_exporter_version }}/sha256sums.txt" + redis_exporter_addr: + description: "Address of the Redis instance" + default: "redis://localhost:6379" + redis_exporter_user: + description: "User name to use for authentication (Redis ACL for Redis 6.0 and newer)" + default: "" + redis_exporter_password: + description: "Password of the Redis instance" + default: "" + redis_exporter_passwords: + description: + - "Dictionary with passwords for instances." + - "Read more official L(documentation, https://github.com/oliver006/redis_exporter?tab=readme-ov-file#authenticating-with-redis)" + type: "dict" + default: {} + redis_exporter_check_keys: + description: "List of key patterns to export value and length/size, eg: db3=user_count will export key user_count from db 3. db defaults to 0 if omitted. The key patterns specified with this flag will be found using SCAN. Use this option if you need glob pattern matching; check-single-keys is faster for non-pattern keys. Warning: using --check-keys to match a very large number of keys can slow down the exporter to the point where it doesn't finish scraping the redis instance." + type: "list" + default: [] + redis_exporter_check_single_keys: + description: "List of keys to export value and length/size, eg: db3=user_count will export key user_count from db 3. db defaults to 0 if omitted. The keys specified with this flag will be looked up directly without any glob pattern matching. Use this option if you don't need glob pattern matching; it is faster than check-keys." + type: "list" + default: [] + redis_exporter_check_streams: + description: "List of stream-patterns to export info about streams, groups and consumers. Syntax is the same as check-keys." + type: "list" + default: [] + redis_exporter_check_single_streams: + description: "List of streams to export info about streams, groups and consumers. The streams specified with this flag will be looked up directly without any glob pattern matching. Use this option if you don't need glob pattern matching; it is faster than check-streams." + type: "list" + default: [] + redis_exporter_check_keys_batch_size: + description: "Approximate number of keys to process in each execution. This is basically the COUNT option that will be passed into the SCAN command as part of the execution of the key or key group metrics, see COUNT option. Larger value speeds up scanning. Still Redis is a single-threaded app, huge COUNT can affect production environment." + type: "int" + default: 1000 + redis_exporter_count_keys: + description: "List of patterns to count, eg: db3=sessions:* will count all keys with prefix sessions: from db 3. db defaults to 0 if omitted. Warning: The exporter runs SCAN to count the keys. This might not perform well on large databases." + type: "list" + default: [] + redis_exporter_script: + description: "List of path(s) to Redis Lua script(s) for gathering extra metrics." + type: "list" + default: [] + redis_exporter_debug: + description: "Verbose debug output" + type: bool + default: false + redis_exporter_log_format: + description: "Output format of log messages. One of: [txt, json]" + default: "txt" + redis_exporter_namespace: + description: "Namespace for the metrics" + default: "redis" + redis_exporter_connection_timeout: + description: "Timeout for connection to Redis instance" + default: "15s" + redis_exporter_web_listen_address: + description: "Address to listen on for web interface and telemetry" + default: "0.0.0.0:9121" + redis_exporter_web_telemetry_path: + description: "Path under which to expose metrics" + default: "/metrics" + redis_exporter_redis_only_metrics: + description: "Whether to also export go runtime metrics" + type: bool + default: false + redis_exporter_incl_config_metrics: + description: "Whether to include all config settings as metrics" + type: bool + default: false + redis_exporter_incl_system_metrics: + description: "Whether to include system metrics like total_system_memory_bytes" + type: bool + default: false + redis_exporter_redact_config_metrics: + description: "Whether to redact config settings that include potentially sensitive information like passwords." + type: bool + default: false + redis_exporter_ping_on_connect: + description: "Whether to ping the redis instance after connecting and record the duration as a metric." + type: bool + default: false + redis_exporter_is_tile38: + description: "Whether to scrape Tile38 specific metrics" + type: bool + default: false + redis_exporter_is_cluster: + description: "Whether this is a redis cluster (Enable this if you need to fetch key level data on a Redis Cluster)." + type: bool + default: false + redis_exporter_export_client_list: + description: "Whether to scrape Client List specific metrics" + type: bool + default: false + redis_exporter_export_client_port: + description: "Whether to include the client's port when exporting the client list. Warning: including the port increases the number of metrics generated and will make your Prometheus server take up more memory" + type: bool + default: false + redis_exporter_skip_tls_verification: + description: "Whether to to skip TLS verification when the exporter connects to a Redis instance" + type: bool + default: false + redis_exporter_tls_client_key_file: + description: "Name of the client key file (including full path) if the server requires TLS client authentication" + default: "" + redis_exporter_tls_client_cert_file: + description: "Name the client cert file (including full path) if the server requires TLS client authentication" + default: "" + redis_exporter_tls_server_key_file: + description: "Name of the server key file (including full path) if the web interface and telemetry should use TLS" + default: "" + redis_exporter_tls_server_cert_file: + description: "Name of the server certificate file (including full path) if the web interface and telemetry should use TLS" + default: "" + redis_exporter_tls_server_ca_cert_file: + description: "Name of the CA certificate file (including full path) if the web interface and telemetry should use TLS" + default: "" + redis_exporter_tls_server_min_version: + description: "Minimum TLS version that is acceptable by the web interface and telemetry when using TLS" + default: "TLS1.2" + redis_exporter_tls_ca_cert_file: + description: "Name of the CA certificate file (including full path) if the server requires TLS client authentication" + default: "" + redis_exporter_set_client_name: + description: "Whether to set client name to redis_exporter" + type: bool + default: true + redis_exporter_check_key_groups: + description: "List of LUA regexes for classifying keys into groups. The regexes are applied in specified order to individual keys, and the group name is generated by concatenating all capture groups of the first regex that matches a key. A key will be tracked under the unclassified group if none of the specified regexes matches it." + type: "list" + default: [] + redis_exporter_max_distinct_key_groups: + description: "Maximum number of distinct key groups that can be tracked independently per Redis database. If exceeded, only key groups with the highest memory consumption within the limit will be tracked separately, all remaining key groups will be tracked under a single overflow key group." + type: "int" + default: 100 + redis_exporter_config_command: + description: "What to use for the CONFIG command" + default: "CONFIG" + redis_exporter_binary_install_dir: + description: + - "I(Advanced)" + - "Directory to install redis_exporter binary" + default: "/usr/local/bin" + redis_exporter_system_group: + description: + - "I(Advanced)" + - "System group for redis_exporter" + default: "redis-exp" + redis_exporter_system_user: + description: + - "I(Advanced)" + - "redis_exporter user" + default: "redis-exp" diff --git a/roles/redis_exporter/meta/main.yml b/roles/redis_exporter/meta/main.yml new file mode 100644 index 00000000..39c7309d --- /dev/null +++ b/roles/redis_exporter/meta/main.yml @@ -0,0 +1,31 @@ +--- +galaxy_info: + author: "Prometheus Community" + description: "Prometheus redis_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" + - "redis" diff --git a/roles/redis_exporter/molecule/alternative/molecule.yml b/roles/redis_exporter/molecule/alternative/molecule.yml new file mode 100644 index 00000000..b0120f9f --- /dev/null +++ b/roles/redis_exporter/molecule/alternative/molecule.yml @@ -0,0 +1,13 @@ +--- +provisioner: + inventory: + group_vars: + all: + redis_exporter_binary_local_dir: "/tmp/redis_exporter-linux-amd64" + redis_exporter_web_listen_address: "127.0.0.1:8080" + + redis_exporter_tls_server_ca_cert_file: /etc/redis_exporter/tls.cert + redis_exporter_tls_server_cert_file: /etc/redis_exporter/tls.cert + redis_exporter_tls_server_key_file: /etc/redis_exporter/tls.key + go_arch: amd64 + redis_exporter_version: 1.58.0 diff --git a/roles/redis_exporter/molecule/alternative/prepare.yml b/roles/redis_exporter/molecule/alternative/prepare.yml new file mode 100644 index 00000000..aa25cde7 --- /dev/null +++ b/roles/redis_exporter/molecule/alternative/prepare.yml @@ -0,0 +1,78 @@ +--- +- name: Run local preparation + hosts: localhost + gather_facts: false + tasks: + - name: Download redis_exporter binary to local folder + become: false + ansible.builtin.get_url: + url: "https://github.com/oliver006/redis_exporter/releases/download/v{{\ + \ redis_exporter_version }}/redis_exporter-v{{ redis_exporter_version }}.linux-{{\ + \ go_arch }}.tar.gz" + dest: "/tmp/redis_exporter-v{{ redis_exporter_version }}.linux-{{ go_arch }}.tar.gz" + mode: 0644 + register: _download_binary + until: _download_binary is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: Unpack redis_exporter binary + become: false + ansible.builtin.unarchive: + src: "/tmp/redis_exporter-v{{ redis_exporter_version }}.linux-{{ go_arch }}.tar.gz" + dest: "/tmp" + creates: "/tmp/redis_exporter-v{{ redis_exporter_version }}.linux-{{ go_arch\ + \ }}/redis_exporter" + check_mode: false + + - name: Link to redis_exporter binaries directory + become: false + ansible.builtin.file: + src: "/tmp/redis_exporter-v{{ redis_exporter_version }}.linux-amd64" + dest: "/tmp/redis_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 + +- name: Run target preparation + hosts: all + any_errors_fatal: true + tasks: + - name: Create redis_exporter cert dir + ansible.builtin.file: + path: "{{ redis_exporter_tls_server_cert_file | dirname }}" + state: directory + owner: root + group: root + mode: u+rwX,g+rwX,o=rX + + - name: Copy cert and key + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: "{{ item.mode | default('0644') }}" + loop: + - src: "/tmp/tls.cert" + dest: "{{ redis_exporter_tls_server_cert_file }}" + - src: "/tmp/tls.key" + dest: "{{ redis_exporter_tls_server_key_file }}" diff --git a/roles/redis_exporter/molecule/alternative/tests/test_alternative.py b/roles/redis_exporter/molecule/alternative/tests/test_alternative.py new file mode 100644 index 00000000..4a377bb8 --- /dev/null +++ b/roles/redis_exporter/molecule/alternative/tests/test_alternative.py @@ -0,0 +1,46 @@ +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_directories(host): + dirs = [ + "/etc/redis_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_service(host): + s = host.service("redis_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u redis_exporter --since "1 hour ago"') + print("\n==== journalctl -u redis_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("redis_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +def test_socket(host): + sockets = [ + "tcp://127.0.0.1:8080" + ] + for socket in sockets: + s = host.socket(socket) + assert s.is_listening diff --git a/roles/redis_exporter/molecule/default/molecule.yml b/roles/redis_exporter/molecule/default/molecule.yml new file mode 100644 index 00000000..8b977b2a --- /dev/null +++ b/roles/redis_exporter/molecule/default/molecule.yml @@ -0,0 +1,6 @@ +--- +provisioner: + inventory: + group_vars: + all: + redis_exporter_web_listen_address: "127.0.0.1:9121" diff --git a/roles/redis_exporter/molecule/default/tests/test_default.py b/roles/redis_exporter/molecule/default/tests/test_default.py new file mode 100644 index 00000000..146e77e5 --- /dev/null +++ b/roles/redis_exporter/molecule/default/tests/test_default.py @@ -0,0 +1,64 @@ +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_directories(host): + dirs = [ + "/etc/redis_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_files(host): + files = [ + "/etc/systemd/system/redis_exporter.service", + "/usr/local/bin/redis_exporter" + ] + for file in files: + f = host.file(file) + assert f.exists + assert f.is_file + + +def test_user(host): + assert host.group("redis-exp").exists + assert "redis-exp" in host.user("redis-exp").groups + assert host.user("redis-exp").shell == "/usr/sbin/nologin" + assert host.user("redis-exp").home == "/" + + +def test_service(host): + s = host.service("redis_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u redis_exporter --since "1 hour ago"') + print("\n==== journalctl -u redis_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("redis_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +def test_socket(host): + sockets = [ + "tcp://127.0.0.1:9121" + ] + for socket in sockets: + s = host.socket(socket) + assert s.is_listening diff --git a/roles/redis_exporter/molecule/latest/molecule.yml b/roles/redis_exporter/molecule/latest/molecule.yml new file mode 100644 index 00000000..2acfec8a --- /dev/null +++ b/roles/redis_exporter/molecule/latest/molecule.yml @@ -0,0 +1,6 @@ +--- +provisioner: + inventory: + group_vars: + all: + redis_exporter_version: latest diff --git a/roles/redis_exporter/molecule/latest/tests/test_latest.py b/roles/redis_exporter/molecule/latest/tests/test_latest.py new file mode 100644 index 00000000..360663e8 --- /dev/null +++ b/roles/redis_exporter/molecule/latest/tests/test_latest.py @@ -0,0 +1,43 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import testinfra.utils.ansible_runner +import pytest + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all') + + +@pytest.mark.parametrize("files", [ + "/etc/systemd/system/redis_exporter.service", + "/usr/local/bin/redis_exporter" +]) +def test_files(host, files): + f = host.file(files) + assert f.exists + assert f.is_file + + +def test_service(host): + s = host.service("redis_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u redis_exporter --since "1 hour ago"') + print("\n==== journalctl -u redis_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("redis_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +def test_socket(host): + s = host.socket("tcp://0.0.0.0:9121") + assert s.is_listening diff --git a/roles/redis_exporter/tasks/configure.yml b/roles/redis_exporter/tasks/configure.yml new file mode 100644 index 00000000..88c33050 --- /dev/null +++ b/roles/redis_exporter/tasks/configure.yml @@ -0,0 +1,37 @@ +--- +- name: Copy the redis_exporter systemd service file + ansible.builtin.template: + src: redis_exporter.service.j2 + dest: /etc/systemd/system/redis_exporter.service + owner: root + group: root + mode: 0644 + notify: restart redis_exporter + +- name: Create redis_exporter config directory + ansible.builtin.file: + path: "/etc/redis_exporter" + state: directory + owner: root + group: root + mode: u+rwX,g+rwX,o=rX + +- name: Copy the passwords file + ansible.builtin.copy: + dest: /etc/redis_exporter/passwords.json + content: "{{ redis_exporter_passwords | to_json(indent=2, sort_keys=True) }}" + owner: root + group: root + mode: 0644 + notify: restart redis_exporter + when: redis_exporter_passwords | length > 0 + +- name: Allow redis_exporter port in SELinux on RedHat OS family + community.general.seport: + ports: "{{ redis_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/redis_exporter/tasks/install.yml b/roles/redis_exporter/tasks/install.yml new file mode 100644 index 00000000..b84413ec --- /dev/null +++ b/roles/redis_exporter/tasks/install.yml @@ -0,0 +1,69 @@ +--- +- name: Create the redis_exporter group + ansible.builtin.group: + name: "{{ redis_exporter_system_group }}" + state: present + system: true + when: redis_exporter_system_group != "root" + +- name: Create the redis_exporter user + ansible.builtin.user: + name: "{{ redis_exporter_system_user }}" + groups: "{{ redis_exporter_system_group }}" + append: true + shell: /usr/sbin/nologin + system: true + create_home: false + home: / + when: redis_exporter_system_user != "root" + +- name: Get binary + when: + - redis_exporter_binary_local_dir | length == 0 + - not redis_exporter_skip_install + block: + + - name: Download redis_exporter binary to local folder + become: false + ansible.builtin.get_url: + url: "{{ redis_exporter_binary_url }}" + dest: "/tmp/redis_exporter-{{ redis_exporter_version }}.linux-{{ go_arch }}.tar.gz" + checksum: "sha256:{{ __redis_exporter_checksum }}" + mode: '0644' + register: _download_binary + until: _download_binary is succeeded + retries: 5 + delay: 2 + delegate_to: localhost + check_mode: false + + - name: Unpack redis_exporter binary + become: false + ansible.builtin.unarchive: + src: "/tmp/redis_exporter-{{ redis_exporter_version }}.linux-{{ go_arch }}.tar.gz" + dest: "/tmp" + creates: "/tmp/redis_exporter-{{ redis_exporter_version }}.linux-{{ go_arch }}/redis_exporter" + delegate_to: localhost + check_mode: false + + - name: Propagate redis_exporter binaries + ansible.builtin.copy: + src: "/tmp/redis_exporter-v{{ redis_exporter_version }}.linux-{{ go_arch }}/redis_exporter" + dest: "{{ redis_exporter_binary_install_dir }}/redis_exporter" + mode: 0755 + owner: root + group: root + notify: restart redis_exporter + when: not ansible_check_mode + +- name: Propagate locally distributed redis_exporter binary + ansible.builtin.copy: + src: "{{ redis_exporter_binary_local_dir }}/redis_exporter" + dest: "{{ redis_exporter_binary_install_dir }}/redis_exporter" + mode: 0755 + owner: root + group: root + when: + - redis_exporter_binary_local_dir | length > 0 + - not redis_exporter_skip_install + notify: restart redis_exporter diff --git a/roles/redis_exporter/tasks/main.yml b/roles/redis_exporter/tasks/main.yml new file mode 100644 index 00000000..be1acf12 --- /dev/null +++ b/roles/redis_exporter/tasks/main.yml @@ -0,0 +1,63 @@ +--- +- name: Preflight + ansible.builtin.include_tasks: + file: preflight.yml + apply: + tags: + - redis_exporter_install + - redis_exporter_configure + - redis_exporter_run + tags: + - redis_exporter_install + - redis_exporter_configure + - redis_exporter_run + +- name: Install + ansible.builtin.include_tasks: + file: install.yml + apply: + become: true + tags: + - redis_exporter_install + when: + ( not __redis_exporter_is_installed.stat.exists ) or + ( (__redis_exporter_current_version_output.stderr_lines | length > 0) + and (__redis_exporter_current_version_output.stderr_lines[0].split(" ")[2] != redis_exporter_version) ) or + ( (__redis_exporter_current_version_output.stdout_lines | length > 0) + and (__redis_exporter_current_version_output.stdout_lines[0].split(" ")[2] != redis_exporter_version) ) or + ( redis_exporter_binary_local_dir | length > 0 ) + tags: + - redis_exporter_install + +- name: SELinux + ansible.builtin.include_tasks: + file: selinux.yml + apply: + become: true + tags: + - redis_exporter_configure + when: ansible_selinux.status == "enabled" + tags: + - redis_exporter_configure + +- name: Configure + ansible.builtin.include_tasks: + file: configure.yml + apply: + become: true + tags: + - redis_exporter_configure + tags: + - redis_exporter_configure + +- name: Ensure redis_exporter is enabled on boot + become: true + ansible.builtin.systemd: + daemon_reload: true + name: redis_exporter + enabled: true + state: started + when: + - not ansible_check_mode + tags: + - redis_exporter_run diff --git a/roles/redis_exporter/tasks/preflight.yml b/roles/redis_exporter/tasks/preflight.yml new file mode 100644 index 00000000..63c8267e --- /dev/null +++ b/roles/redis_exporter/tasks/preflight.yml @@ -0,0 +1,112 @@ +--- +- 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: Assert that used version supports listen address type + ansible.builtin.assert: + that: + - >- + redis_exporter_web_listen_address is string + +- name: Naive assertion of proper listen address + ansible.builtin.assert: + that: + - >- + [redis_exporter_web_listen_address] | + flatten | + reject('match', '.+:\\d+$') | + list | + length == 0 + +- name: Assert that TLS config is correct + when: + - redis_exporter_tls_server_key_file | length > 0 + - redis_exporter_tls_server_cert_file | length > 0 + block: + - name: Assert that TLS key and cert path are set + ansible.builtin.assert: + that: + - "redis_exporter_tls_server_cert_file is defined" + - "redis_exporter_tls_server_key_file is defined" + + - name: Check existence of TLS cert file + ansible.builtin.stat: + path: "{{ redis_exporter_tls_server_cert_file }}" + register: __redis_exporter_cert_file + + - name: Check existence of TLS key file + ansible.builtin.stat: + path: "{{ redis_exporter_tls_server_key_file }}" + register: __redis_exporter_key_file + + - name: Assert that TLS key and cert are present + ansible.builtin.assert: + that: + - "__redis_exporter_cert_file.stat.exists" + - "__redis_exporter_key_file.stat.exists" + +- name: Check if redis_exporter is installed + ansible.builtin.stat: + path: "{{ redis_exporter_binary_install_dir }}/redis_exporter" + register: __redis_exporter_is_installed + check_mode: false + tags: + - redis_exporter_install + +- name: Gather currently installed redis_exporter version (if any) + ansible.builtin.command: "{{ redis_exporter_binary_install_dir }}/redis_exporter --version" + changed_when: false + register: __redis_exporter_current_version_output + check_mode: false + when: __redis_exporter_is_installed.stat.exists + tags: + - redis_exporter_install + +- name: Discover latest version + ansible.builtin.set_fact: + redis_exporter_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ _redis_exporter_repo }}/releases/latest', headers=_github_api_headers, + split_lines=False) | from_json).get('tag_name') | replace('v', '') }}" + run_once: true + until: redis_exporter_version is version('0.0.0', '>=') + retries: 10 + when: + - redis_exporter_version == "latest" + - redis_exporter_binary_local_dir | length == 0 + - not redis_exporter_skip_install + +- name: Get redis_exporter binary checksum + when: + - redis_exporter_binary_local_dir | length == 0 + - not redis_exporter_skip_install + block: + - name: Get checksum list from github + ansible.builtin.set_fact: + __redis_exporter_checksums: "{{ lookup('url', redis_exporter_checksums_url, headers=_github_api_headers, wantlist=True) | list }}" + run_once: true + until: __redis_exporter_checksums is search('linux-' + go_arch + '.tar.gz') + retries: 10 + + - name: "Get checksum for {{ go_arch }}" + ansible.builtin.set_fact: + __redis_exporter_checksum: "{{ item.split(' ')[0] }}" + with_items: "{{ __redis_exporter_checksums }}" + when: + - "('linux-' + go_arch + '.tar.gz') in item" diff --git a/roles/redis_exporter/tasks/selinux.yml b/roles/redis_exporter/tasks/selinux.yml new file mode 100644 index 00000000..754cbd0a --- /dev/null +++ b/roles/redis_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/redis_exporter/templates/redis_exporter.service.j2 b/roles/redis_exporter/templates/redis_exporter.service.j2 new file mode 100644 index 00000000..bee70ce4 --- /dev/null +++ b/roles/redis_exporter/templates/redis_exporter.service.j2 @@ -0,0 +1,73 @@ +{{ ansible_managed | comment }} + +[Unit] +Description=Prometheus redis_exporter +After=network-online.target + +[Service] +Type=simple +User={{ redis_exporter_system_user }} +Group={{ redis_exporter_system_group }} + +Environment="REDIS_ADDR={{ redis_exporter_addr }}" +Environment="REDIS_USER={{ redis_exporter_user }}" +Environment="REDIS_PASSWORD={{ redis_exporter_password }}" +{% if redis_exporter_passwords | length > 0 -%} +Environment="REDIS_PASSWORD_FILE=/etc/redis_exporter/passwords.json" +{% endif -%} +Environment="REDIS_EXPORTER_CHECK_KEYS={{ redis_exporter_check_keys | join(',') }}" +Environment="REDIS_EXPORTER_CHECK_SINGLE_KEYS={{ redis_exporter_check_single_keys | join(',') }}" +Environment="REDIS_EXPORTER_CHECK_STREAMS={{ redis_exporter_check_streams | join(',') }}" +Environment="REDIS_EXPORTER_CHECK_SINGLE_STREAMS={{ redis_exporter_check_single_streams | join(',') }}" +Environment="REDIS_EXPORTER_CHECK_KEYS_BATCH_SIZE={{ redis_exporter_check_keys_batch_size }}" +Environment="REDIS_EXPORTER_COUNT_KEYS={{ redis_exporter_count_keys | join(',') }}" +Environment="REDIS_EXPORTER_SCRIPT={{ redis_exporter_script | join(',') }}" +Environment="REDIS_EXPORTER_DEBUG={{ redis_exporter_debug }}" +Environment="REDIS_EXPORTER_LOG_FORMAT={{ redis_exporter_log_format }}" +Environment="REDIS_EXPORTER_NAMESPACE={{ redis_exporter_namespace }}" +Environment="REDIS_EXPORTER_CONNECTION_TIMEOUT={{ redis_exporter_connection_timeout }}" +Environment="REDIS_EXPORTER_WEB_LISTEN_ADDRESS={{ redis_exporter_web_listen_address }}" +Environment="REDIS_EXPORTER_WEB_TELEMETRY_PATH={{ redis_exporter_web_telemetry_path }}" +Environment="REDIS_EXPORTER_REDIS_ONLY_METRICS={{ redis_exporter_redis_only_metrics }}" +Environment="REDIS_EXPORTER_INCL_CONFIG_METRICS={{ redis_exporter_incl_config_metrics }}" +Environment="REDIS_EXPORTER_INCL_SYSTEM_METRICS={{ redis_exporter_incl_system_metrics }}" +Environment="REDIS_EXPORTER_REDACT_CONFIG_METRICS={{ redis_exporter_redact_config_metrics }}" +Environment="REDIS_EXPORTER_PING_ON_CONNECT={{ redis_exporter_ping_on_connect }}" +Environment="REDIS_EXPORTER_IS_TILE38={{ redis_exporter_is_tile38 }}" +Environment="REDIS_EXPORTER_IS_CLUSTER={{ redis_exporter_is_cluster }}" +Environment="REDIS_EXPORTER_EXPORT_CLIENT_LIST={{ redis_exporter_export_client_list }}" +Environment="REDIS_EXPORTER_EXPORT_CLIENT_PORT={{ redis_exporter_export_client_port }}" +Environment="REDIS_EXPORTER_SKIP_TLS_VERIFICATION={{ redis_exporter_skip_tls_verification }}" +Environment="REDIS_EXPORTER_TLS_CLIENT_KEY_FILE={{ redis_exporter_tls_client_key_file }}" +Environment="REDIS_EXPORTER_TLS_CLIENT_CERT_FILE={{ redis_exporter_tls_client_cert_file }}" +Environment="REDIS_EXPORTER_TLS_SERVER_KEY_FILE={{ redis_exporter_tls_server_key_file }}" +Environment="REDIS_EXPORTER_TLS_SERVER_CERT_FILE={{ redis_exporter_tls_server_cert_file }}" +Environment="REDIS_EXPORTER_TLS_SERVER_CA_CERT_FILE={{ redis_exporter_tls_server_ca_cert_file }}" +Environment="REDIS_EXPORTER_TLS_SERVER_MIN_VERSION={{ redis_exporter_tls_server_min_version }}" +Environment="REDIS_EXPORTER_TLS_CA_CERT_FILE={{ redis_exporter_tls_ca_cert_file }}" +Environment="REDIS_EXPORTER_SET_CLIENT_NAME={{ redis_exporter_set_client_name }}" +Environment="REDIS_EXPORTER_CHECK_KEY_GROUPS={{ redis_exporter_check_key_groups | join(',') }}" +Environment="REDIS_EXPORTER_MAX_DISTINCT_KEY_GROUPS={{ redis_exporter_max_distinct_key_groups }}" +Environment="REDIS_EXPORTER_CONFIG_COMMAND={{ redis_exporter_config_command }}" + +ExecStart={{ redis_exporter_binary_install_dir }}/redis_exporter + +SyslogIdentifier=redis_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 diff --git a/roles/redis_exporter/vars/main.yml b/roles/redis_exporter/vars/main.yml new file mode 100644 index 00000000..7233a5c1 --- /dev/null +++ b/roles/redis_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) }}" +_redis_exporter_repo: "oliver006/redis_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-redis_exporter-alternative/runme.sh b/tests/integration/targets/molecule-redis_exporter-alternative/runme.sh new file mode 100755 index 00000000..d094c3e1 --- /dev/null +++ b/tests/integration/targets/molecule-redis_exporter-alternative/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" diff --git a/tests/integration/targets/molecule-redis_exporter-default/runme.sh b/tests/integration/targets/molecule-redis_exporter-default/runme.sh new file mode 100755 index 00000000..d094c3e1 --- /dev/null +++ b/tests/integration/targets/molecule-redis_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" diff --git a/tests/integration/targets/molecule-redis_exporter-latest/runme.sh b/tests/integration/targets/molecule-redis_exporter-latest/runme.sh new file mode 100755 index 00000000..d094c3e1 --- /dev/null +++ b/tests/integration/targets/molecule-redis_exporter-latest/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"