From f6ab0795375389d40219f28f69b8997515034775 Mon Sep 17 00:00:00 2001 From: Anton Gura Date: Fri, 14 Feb 2020 19:05:13 +0300 Subject: [PATCH] Initial commit --- .gitignore | 5 ++ LICENSE | 21 ++++++++ README.md | 68 ++++++++++++++++++++++++ defaults/main.yml | 17 ++++++ handlers/main.yml | 7 +++ meta/main.yml | 33 ++++++++++++ tasks/configure.yml | 19 +++++++ tasks/install.yml | 67 +++++++++++++++++++++++ tasks/main.yml | 33 ++++++++++++ tasks/preflight.yml | 88 +++++++++++++++++++++++++++++++ tasks/selinux.yml | 38 +++++++++++++ templates/frr_exporter.service.j2 | 51 ++++++++++++++++++ vars/main.yml | 10 ++++ 13 files changed, 457 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 tasks/configure.yml create mode 100644 tasks/install.yml create mode 100644 tasks/main.yml create mode 100644 tasks/preflight.yml create mode 100644 tasks/selinux.yml create mode 100644 templates/frr_exporter.service.j2 create mode 100644 vars/main.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf420b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.retry +*.log +.cache +__pycache__/ +.pytest_cache diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c8b9a05 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Anton Gura + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..32ec6ae --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Ansible Role: frr exporter + +> This role is fork from [ansible node exporter v0.19.0](https://github.com/cloudalchemy/ansible-node-exporter) role and works with similar principles. + +## Description + +Deploy prometheus [frr exporter](https://github.com/tynany/frr_exporter) using ansible. + +## Requirements + +- Ansible >= 2.7 (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 | +| ---------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `frr_exporter_version` | 0.2.3 | Frr exporter package version. Also accepts latest as parameter. | +| `frr_exporter_binary_local_dir` | "" | Allows to use local packages instead of ones distributed on github. As parameter it takes a directory where `frr_exporter` binary is stored on host on which ansible is ran. This overrides `frr_exporter_version` parameter | +| `frr_exporter_web_listen_address` | "0.0.0.0:9100" | Address on which frr exporter will listen | +| `frr_exporter_enabled_collectors` | [ bgp6, bgpl2vpn ] | List of additionally enabled collectors. It adds collectors to [those enabled by default](https://github.com/tynany/frr_exporter#enabled-by-default) | +| `frr_exporter_disabled_collectors` | [] | List of disabled collectors. By default frr_exporter disables collectors listed [here](https://github.com/tynany/frr_exporter#disabled-by-default). | + +## Example + +### Playbook for Debian + +Use it in a playbook as follows: +```yaml +- hosts: all + roles: + - satandyh.frr-exporter +``` +### Playbook for VyOS + +You should reuse frr user/group. For do that use in your group_vars `frr_exporter_create_usergroup: false` +```yaml +# Prometheus exporters: ... frr (BGP), ... +- name: Configure exporters + hosts: all + become: true + gather_facts: false + roles: + # Frr exporter + # extra vars need only for VyOS - because we not gather any facts + # be careful with selinux installation - it will try to install selinux by default + - role: frr-exporter + vars: + ansible_service_mgr: "systemd" + ansible_architecture: "x86_64" + ansible_distribution: "Debian" + ansible_selinux: + status: false + ansible_mounts: + - { mount: "/" } + tags: + - frr-exporter +``` + +## Limitations + +Pay Your attention despite that I add a lot of OS Platforms in reality I do tests only at Debian and VyOS system only. But I'm sure that this role will also work for other too. + +## License + +This project is licensed under MIT License. See [LICENSE](/LICENSE) for more details. diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..0711c69 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,17 @@ +--- +frr_exporter_version: 0.2.3 +frr_exporter_binary_local_dir: "" +frr_exporter_web_listen_address: "0.0.0.0:9342" + +frr_exporter_enabled_collectors: + - bgp + - bgp.peer-types + - bgp.peer-descriptions + - ospf + +frr_exporter_disabled_collectors: [] + +_frr_exporter_binary_install_dir: "/usr/local/bin" +_frr_exporter_system_group: "frr-exp" +_frr_exporter_system_user: "{{ _frr_exporter_system_group }}" +frr_exporter_create_usergroup: true diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..bf581c7 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: restart frr_exporter + become: true + systemd: + daemon_reload: true + name: frr_exporter + state: restarted diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..905c4e9 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,33 @@ +--- +galaxy_info: + author: Anton Gura + description: Prometheus Frr Exporter + license: MIT + company: none + min_ansible_version: 2.7 + platforms: + - name: Ubuntu + versions: + - bionic + - xenial + - name: Debian + versions: + - stretch + - buster + - name: EL + versions: + - 7 + - 8 + - name: Fedora + versions: + - 30 + - 31 + galaxy_tags: + - frr + - monitoring + - prometheus + - exporter + - metrics + - system + +dependencies: [] diff --git a/tasks/configure.yml b/tasks/configure.yml new file mode 100644 index 0000000..f809f45 --- /dev/null +++ b/tasks/configure.yml @@ -0,0 +1,19 @@ +--- +- name: Copy the Frr Exporter systemd service file + template: + src: frr_exporter.service.j2 + dest: /etc/systemd/system/frr_exporter.service + owner: root + group: root + mode: 0644 + notify: restart frr_exporter + +- name: Allow Frr Exporter port in SELinux on RedHat OS family + seport: + ports: "{{ frr_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/tasks/install.yml b/tasks/install.yml new file mode 100644 index 0000000..314c7c9 --- /dev/null +++ b/tasks/install.yml @@ -0,0 +1,67 @@ +--- +- name: Create the frr_exporter group + group: + name: "{{ _frr_exporter_system_group }}" + state: present + system: true + when: + - _frr_exporter_system_group != "root" + - frr_exporter_create_usergroup + +- name: Create the frr_exporter user + user: + name: "{{ _frr_exporter_system_user }}" + groups: "{{ _frr_exporter_system_group }}" + append: true + shell: /usr/sbin/nologin + system: true + create_home: false + home: / + when: + - _frr_exporter_system_user != "root" + - frr_exporter_create_usergroup + +- name: Download frr_exporter from localhost + block: + - name: Download frr_exporter binary to local folder + become: false + get_url: + url: "https://github.com/tynany/frr_exporter/releases/download/v{{ frr_exporter_version }}/frr_exporter_{{ frr_exporter_version }}_linux_{{ go_arch }}.tar.gz" + dest: "/tmp/frr_exporter_{{ frr_exporter_version }}_linux_{{ go_arch }}.tar.gz" + #checksum: "sha256:{{ frr_exporter_checksum }}" + register: _download_binary + until: _download_binary is succeeded + retries: 5 + delay: 2 + delegate_to: localhost + check_mode: false + + - name: Unpack frr_exporter binary + become: false + unarchive: + src: "/tmp/frr_exporter_{{ frr_exporter_version }}_linux_{{ go_arch }}.tar.gz" + dest: "/tmp" + creates: "/tmp/frr_exporter_{{ frr_exporter_version }}_linux_{{ go_arch }}/frr_exporter" + delegate_to: localhost + check_mode: false + + - name: Propagate frr_exporter binaries + copy: + src: "/tmp/frr_exporter_{{ frr_exporter_version }}_linux_{{ go_arch }}/frr_exporter" + dest: "{{ _frr_exporter_binary_install_dir }}/frr_exporter" + mode: 0755 + owner: root + group: root + notify: restart frr_exporter + when: not ansible_check_mode + when: frr_exporter_binary_local_dir | length == 0 + +- name: propagate locally distributed frr_exporter binary + copy: + src: "{{ frr_exporter_binary_local_dir }}/frr_exporter" + dest: "{{ _frr_exporter_binary_install_dir }}/frr_exporter" + mode: 0755 + owner: root + group: root + when: frr_exporter_binary_local_dir | length > 0 + notify: restart frr_exporter diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..20a5475 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,33 @@ +--- +- import_tasks: preflight.yml + tags: + - frr_exporter_install + - frr_exporter_configure + - frr_exporter_run + +- import_tasks: install.yml + become: true + when: (not __frr_exporter_is_installed.stat.exists) or (__frr_exporter_current_version_output.stderr_lines[0].split(" ")[2] != frr_exporter_version) + tags: + - frr_exporter_install + +- import_tasks: selinux.yml + become: true + when: ansible_selinux.status == "enabled" + tags: + - frr_exporter_configure + +- import_tasks: configure.yml + become: true + tags: + - frr_exporter_configure + +- name: Ensure Frr Exporter is enabled on boot + become: true + systemd: + daemon_reload: true + name: frr_exporter + enabled: true + state: started + tags: + - frr_exporter_run diff --git a/tasks/preflight.yml b/tasks/preflight.yml new file mode 100644 index 0000000..6f9acab --- /dev/null +++ b/tasks/preflight.yml @@ -0,0 +1,88 @@ +--- +- name: Assert usage of systemd as an init system + assert: + that: ansible_service_mgr == 'systemd' + msg: "This role only works with systemd" + +- name: Get systemd version + command: systemctl --version + changed_when: false + check_mode: false + register: __systemd_version + tags: + - skip_ansible_lint + +- name: Set systemd version fact + set_fact: + frr_exporter_systemd_version: "{{ __systemd_version.stdout_lines[0] | regex_replace('^systemd\\s(\\d+).*$', '\\1') }}" + +- name: Naive assertion of proper listen address + assert: + that: + - "':' in frr_exporter_web_listen_address" + +- name: Assert collectors are not both disabled and enabled at the same time + assert: + that: + - "item not in frr_exporter_enabled_collectors" + with_items: "{{ frr_exporter_disabled_collectors }}" + +- name: Check if frr_exporter is installed + stat: + path: "{{ _frr_exporter_binary_install_dir }}/frr_exporter" + register: __frr_exporter_is_installed + check_mode: false + tags: + - frr_exporter_install + +- name: Gather currently installed frr_exporter version (if any) + command: "{{ _frr_exporter_binary_install_dir }}/frr_exporter --version" + args: + warn: false + changed_when: false + register: __frr_exporter_current_version_output + check_mode: false + when: __frr_exporter_is_installed.stat.exists + tags: + - frr_exporter_install + - skip_ansible_lint + +- name: Get latest release version + block: + - name: Get latest release + uri: + url: "https://api.github.com/repos/tynany/frr_exporter/releases/latest" + method: GET + return_content: true + status_code: 200 + body_format: json + validate_certs: false + user: "{{ lookup('env', 'GH_USER') | default(omit) }}" + password: "{{ lookup('env', 'GH_TOKEN') | default(omit) }}" + no_log: "{{ not lookup('env', 'MOLECULE_DEBUG') | bool }}" + register: _latest_release + until: _latest_release.status == 200 + retries: 5 + + - name: "Set frr_exporter version to {{ _latest_release.json.tag_name[1:] }}" + set_fact: + frr_exporter_version: "{{ _latest_release.json.tag_name[1:] }}" + when: + - frr_exporter_version == "latest" + - frr_exporter_binary_local_dir | length == 0 + delegate_to: localhost + run_once: true +#- name: Get checksum list from github according to version +# block: +# - name: Get checksum list from github +# set_fact: +# _checksums: "{{ lookup('url', 'https://github.com/tynany/frr_exporter/releases/download/v' + frr_exporter_version + '/sha256sums.txt', wantlist=True) | list }}" +# run_once: true +# +# - name: "Get checksum for {{ go_arch }} architecture" +# set_fact: +# frr_exporter_checksum: "{{ item.split(' ')[0] }}" +# with_items: "{{ _checksums }}" +# when: +# - "('linux-' + go_arch + '.tar.gz') in item" +# when: frr_exporter_binary_local_dir | length == 0 diff --git a/tasks/selinux.yml b/tasks/selinux.yml new file mode 100644 index 0000000..4c8199b --- /dev/null +++ b/tasks/selinux.yml @@ -0,0 +1,38 @@ +--- +- name: Install selinux python packages [RHEL] + package: + name: + - "{{ ( (ansible_facts.distribution_major_version | int) < 8) | ternary('libselinux-python','python3-libselinux') }}" + - "{{ ( (ansible_facts.distribution_major_version | int) < 8) | ternary('libselinux-python','python3-policycoreutils') }}" + state: present + register: _install_selinux_packages + until: _install_selinux_packages is success + retries: 5 + delay: 2 + when: + - (ansible_distribution | lower == "redhat") or + (ansible_distribution | lower == "centos") + +- name: Install selinux python packages [Fedora] + package: + name: + - "{{ ( (ansible_facts.distribution_major_version | int) < 29) | ternary('libselinux-python','python3-libselinux') }}" + - "{{ ( (ansible_facts.distribution_major_version | int) < 29) | ternary('libselinux-python','python3-policycoreutils') }}" + state: present + register: _install_selinux_packages + until: _install_selinux_packages is success + retries: 5 + delay: 2 + when: + - ansible_distribution | lower == "fedora" + +- name: Install selinux python packages [clearlinux] + 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/templates/frr_exporter.service.j2 b/templates/frr_exporter.service.j2 new file mode 100644 index 0000000..1cc3098 --- /dev/null +++ b/templates/frr_exporter.service.j2 @@ -0,0 +1,51 @@ +{{ ansible_managed | comment }} + +[Unit] +Description=Prometheus Frr Exporter +After=network-online.target +StartLimitInterval=0 + +[Service] +Type=simple +User={{ _frr_exporter_system_user }} +Group={{ _frr_exporter_system_group }} +ExecStart={{ _frr_exporter_binary_install_dir }}/frr_exporter \ +{% for collector in frr_exporter_enabled_collectors -%} +{% if not collector is mapping %} + --collector.{{ collector }} \ +{% else -%} +{% set name, options = (collector.items()|list)[0] -%} + --collector.{{ name }} \ +{% for k,v in options|dictsort %} + --collector.{{ name }}.{{ k }}={{ v }} \ +{% endfor -%} +{% endif -%} +{% endfor -%} +{% for collector in frr_exporter_disabled_collectors %} + --no-collector.{{ collector }} \ +{% endfor %} + --web.listen-address={{ frr_exporter_web_listen_address }} + +SyslogIdentifier=frr_exporter +Restart=always +RestartSec=1 + +PrivateTmp=yes +{% for m in ansible_mounts if m.mount == '/home' %} +ProtectHome=read-only +{% else %} +ProtectHome=yes +{% endfor %} +NoNewPrivileges=yes + +{% if frr_exporter_systemd_version | int >= 232 %} +ProtectSystem=strict +ProtectControlGroups=true +ProtectKernelModules=true +ProtectKernelTunables=yes +{% else %} +ProtectSystem=full +{% endif %} + +[Install] +WantedBy=multi-user.target diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..452c159 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,10 @@ +--- +go_arch_map: + i386: "i386" + x86_64: "x86_64" + aarch64: "arm64" + armv7l: "armv7" + armv6l: "armv6" + armv5l: "armv5" + +go_arch: "{{ go_arch_map[ansible_architecture] | default(ansible_architecture) }}"