From b686a8fb447df673720497de63ac7c568c6d9e95 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 9 Jul 2026 21:13:57 +0200 Subject: [PATCH] alloy(role): pull in alloy role that works from fux --- roles/alloy/defaults/main.yaml | 65 ++++- roles/alloy/handlers/main.yml | 31 +++ roles/alloy/tasks/blackbox-exporter.yaml | 11 + roles/alloy/tasks/install_debian.yml | 20 ++ roles/alloy/tasks/main.yaml | 117 +++++---- roles/alloy/tasks/unpoller.yaml | 76 ++++++ roles/alloy/templates/config.alloy.j2 | 3 + .../templates/config.blackbox-exporter.j2 | 3 + roles/alloy/templates/podman.container.jinja | 29 ++ roles/alloy/templates/unpoller.conf.j2 | 248 ++++++++++++++++++ roles/alloy/vars/main.yaml | 2 + 11 files changed, 555 insertions(+), 50 deletions(-) create mode 100644 roles/alloy/handlers/main.yml create mode 100644 roles/alloy/tasks/blackbox-exporter.yaml create mode 100644 roles/alloy/tasks/install_debian.yml create mode 100644 roles/alloy/tasks/unpoller.yaml create mode 100644 roles/alloy/templates/config.alloy.j2 create mode 100644 roles/alloy/templates/config.blackbox-exporter.j2 create mode 100644 roles/alloy/templates/podman.container.jinja create mode 100644 roles/alloy/templates/unpoller.conf.j2 create mode 100644 roles/alloy/vars/main.yaml diff --git a/roles/alloy/defaults/main.yaml b/roles/alloy/defaults/main.yaml index 09c99ee4..c2b326a2 100644 --- a/roles/alloy/defaults/main.yaml +++ b/roles/alloy/defaults/main.yaml @@ -1,29 +1,33 @@ +--- +alloy_cap_net_bind_service: false +alloy_do_we_have_v6: "{{ (ansible_facts['all_ipv6_addresses'] | select('match', '^[23]') | list | length > 0) | ternary('true', 'false') }}" + alloy_config_default: | prometheus.remote_write "default" { endpoint { url = "https://metrics.hamburg.ccc.de/api/v1/write" basic_auth { username = "chaos" - password = "chaos_password" + password = "{{ metrics__chaos_password }}" } } } - prometheus.relabel "common" { + prometheus.relabel "chaosknoten_common" { forward_to = [prometheus.remote_write.default.receiver] rule { target_label = "org" - replacement = "noorg" + replacement = "ccchh" } rule { target_label = "site" - replacement = "nosite" + replacement = "wieske" } rule { source_labels = ["instance"] target_label = "instance" regex = "([^:]+)" - replacement = "${1}.hosts.test" + replacement = "${1}.hosts.hamburg.ccc.de" action = "replace" } } @@ -38,7 +42,56 @@ alloy_config_default: | prometheus.scrape "scrape_metrics" { targets = prometheus.exporter.unix.local_system.targets - forward_to = [prometheus.relabel.common.receiver] + forward_to = [prometheus.relabel.chaosknoten_common.receiver] } alloy_config_additional: "" + +alloy_blackbox_exporter_config_default: | + modules: + https4_2xx: + prober: http + http: + valid_status_codes: [] # Defaults to 2xx + method: GET + follow_redirects: true + fail_if_ssl: false + fail_if_not_ssl: true + preferred_ip_protocol: "ip4" + ip_protocol_fallback: false + + https6_2xx: + prober: http + http: + valid_status_codes: [] # Defaults to 2xx + method: GET + follow_redirects: true + fail_if_ssl: false + fail_if_not_ssl: true + preferred_ip_protocol: "ip6" + ip_protocol_fallback: false + + tcp4_connect: + prober: tcp + tcp: + preferred_ip_protocol: "ip4" + ip_protocol_fallback: false + + icmp4: + prober: icmp + icmp: + preferred_ip_protocol: "ip4" + ip_protocol_fallback: false + + icmp6: + prober: icmp + icmp: + preferred_ip_protocol: "ip6" + ip_protocol_fallback: false + +alloy_blackbox_exporter_config_additional: "" + +alloy_unpoller_enabled: false +alloy_unpoller_unifi_user: unpoller +alloy_unpoller_unifi_password: +alloy_unpoller_unifi_url: diff --git a/roles/alloy/handlers/main.yml b/roles/alloy/handlers/main.yml new file mode 100644 index 00000000..e24efcb9 --- /dev/null +++ b/roles/alloy/handlers/main.yml @@ -0,0 +1,31 @@ +--- +- name: Alloy.enabled + become: true + ansible.builtin.systemd: + name: alloy + enabled: true + +- name: Alloy.restarted + become: true + ansible.builtin.systemd: + name: alloy + state: restarted + enabled: true + +- name: Alloy.reloaded + become: true + ansible.builtin.systemd: + name: alloy + state: reloaded + enabled: true + +- name: Systemd.daemon_reload + become: true + ansible.builtin.systemd_service: + daemon_reload: true + +- name: Unpoller.restarted + become: true + ansible.builtin.systemd_service: + name: unpoller.service + state: restarted diff --git a/roles/alloy/tasks/blackbox-exporter.yaml b/roles/alloy/tasks/blackbox-exporter.yaml new file mode 100644 index 00000000..b4e784d8 --- /dev/null +++ b/roles/alloy/tasks/blackbox-exporter.yaml @@ -0,0 +1,11 @@ +--- +- name: Render blackbox exporter config + tags: [ blackbox-exporter, alloy ] + become: true + ansible.builtin.template: + src: config.blackbox-exporter.j2 + dest: /etc/alloy/blackbox-exporter.yaml + mode: "0644" + owner: root + notify: + - Alloy.reloaded diff --git a/roles/alloy/tasks/install_debian.yml b/roles/alloy/tasks/install_debian.yml new file mode 100644 index 00000000..438e5d5a --- /dev/null +++ b/roles/alloy/tasks/install_debian.yml @@ -0,0 +1,20 @@ +--- +- name: Add grafana apt repo with signing key + become: true + ansible.builtin.deb822_repository: + name: grafana + types: deb + uris: https://apt.grafana.com + suites: stable + components: + - main + signed_by: https://apt.grafana.com/gpg.key + +- name: Run cache update + ansible.builtin.apt: + update_cache: true + +- name: Install alloy + become: true + ansible.builtin.package: + name: alloy diff --git a/roles/alloy/tasks/main.yaml b/roles/alloy/tasks/main.yaml index 5e3cd64c..69ceb631 100644 --- a/roles/alloy/tasks/main.yaml +++ b/roles/alloy/tasks/main.yaml @@ -1,50 +1,79 @@ -# https://github.com/grafana/grafana-ansible-collection/blob/main/roles/alloy/tasks/deploy.yml#L124 -- name: ensure alloy user exists - ansible.builtin.user: - name: alloy - system: true - append: true - create_home: false - state: present - -- name: ensure the `/etc/alloy/` config directory exists - ansible.builtin.file: - path: /etc/alloy - state: directory - mode: "0770" - owner: root - group: alloy - become: true - -- name: synchronize the additional configuration files directory, if present - when: alloy__additional_configs_dir is defined and alloy__additional_configs_dir != "" +--- +- name: Deploy Grafana Alloy with config + tags: [ alloy ] block: - - name: ensure rsync is installed - ansible.builtin.apt: - name: rsync + - name: Install Alloy on Debian + when: ansible_facts['distribution'] == "Debian" + ansible.builtin.include_tasks: install_debian.yml + + - name: Create systemd override for alloy become: true + when: alloy_cap_net_bind_service + notify: Systemd.daemon_reload + block: + - name: Create systemd override directory + ansible.builtin.file: + path: /etc/systemd/system/alloy.service.d + state: directory + mode: "0755" + owner: root + group: root + - name: Add CAP_NET_BIND_SERVICE capability + ansible.builtin.copy: + dest: /etc/systemd/system/alloy.service.d/override.conf + mode: "0755" + owner: root + group: root + content: | + [Service] + AmbientCapabilities=CAP_NET_BIND_SERVICE - - name: synchronize the additional configuration files directory, if present - ansible.posix.synchronize: - src: "{{ alloy__additional_configs_dir }}" - dest: /etc/alloy/additional - delete: true - recursive: true - use_ssh_args: true - rsync_opts: - - "--chown=root:alloy" + - name: Deploy the Alloy config file become: true + ansible.builtin.template: + src: "config.alloy.j2" + dest: "/etc/alloy/config.alloy" + owner: "alloy" + group: "alloy" + mode: "0640" + notify: + - Alloy.restarted + - Alloy.enabled -- name: delete the additional configuration files directory, if not present - when: alloy__additional_configs_dir is not defined or alloy__additional_configs_dir == "" - ansible.builtin.file: - path: /etc/alloy/additional - state: absent - become: true + - name: Validate Alloy configuration and print error if failed + block: + - name: Validate Alloy configuration + become: true + register: alloy_validation + changed_when: false + ansible.builtin.command: alloy validate /etc/alloy/config.alloy + rescue: + - name: Show error if validation failed + when: alloy_validation.rc != 0 + ansible.builtin.fail: + msg: | + Alloy configuration validation failed! + Return code: {{ alloy_validation.rc }} + Stdout: + {{ alloy_validation.stdout }} + Stderr: + {{ alloy_validation.stderr }} -- name: Setup Alloy - ansible.builtin.import_role: - name: grafana.grafana.alloy - vars: - alloy_config: "{{ alloy_config_default ~ alloy_config_additional }}" - become: true + - name: Deploy blackbox-exporter config + when: alloy_enable_blackbox_exporter + ansible.builtin.include_tasks: blackbox-exporter.yaml + - name: Deploy unpoller exporter + when: alloy_unpoller_enabled + ansible.builtin.include_tasks: unpoller.yaml + + - name: Flush handlers + ansible.builtin.meta: flush_handlers + + - name: Ensure that Alloy is working + ansible.builtin.uri: + url: "http://localhost:12345/-/ready" + method: GET + register: alloy_status_code + retries: 8 + delay: 6 + until: not alloy_status_code.failed diff --git a/roles/alloy/tasks/unpoller.yaml b/roles/alloy/tasks/unpoller.yaml new file mode 100644 index 00000000..ceefbbba --- /dev/null +++ b/roles/alloy/tasks/unpoller.yaml @@ -0,0 +1,76 @@ +--- +- name: Install and Setup unpoller + tags: [ unpoller, alloy, podman, container ] + block: + - name: Install Podman + become: true + ansible.builtin.package: + name: + - podman + - fuse-overlayfs + + - name: Create unpoller group + become: true + register: alloy_unpoller_group + ansible.builtin.group: + name: unpoller + + - name: Create unpoller user + become: true + register: alloy_unpoller_user + ansible.builtin.user: + name: unpoller + group: unpoller + system: true + + - name: Create unpoller configuration directory + become: true + ansible.builtin.file: + path: /etc/unpoller + state: directory + owner: "{{ alloy_unpoller_user.name }}" + mode: "0755" + + - name: Render unpoller configuration + become: true + notify: Unpoller.restarted + ansible.builtin.template: + src: unpoller.conf.j2 + dest: /etc/unpoller/up.conf + owner: "{{ alloy_unpoller_user.name }}" + mode: "0644" + + - name: Create /etc/containers/systemd directory + become: true + ansible.builtin.file: + path: /etc/containers/systemd + state: directory + owner: root + mode: "0755" + + - name: Configure unpoller quadlet + become: true + notify: Unpoller.restarted + register: alloy_unpoller_quadlet + vars: + container_image_with_tag: ghcr.io/unpoller/unpoller:v2 + container_unit_args: + container_exec: + container_envs: [ ] + container_volumes: + - { host: "/etc/unpoller", inside: "/etc/unpoller:ro" } + user_id: "{{ alloy_unpoller_user.uid }}" + group_id: "{{ alloy_unpoller_group.gid }}" + ansible.builtin.template: + src: podman.container.jinja + dest: /etc/containers/systemd/unpoller.container + owner: root + mode: "0644" + + - name: Ensure unpoller is running and enabled + become: true + ansible.builtin.systemd: + name: unpoller.service + state: started + enabled: true + daemon_reload: "{{ alloy_unpoller_quadlet.changed }}" diff --git a/roles/alloy/templates/config.alloy.j2 b/roles/alloy/templates/config.alloy.j2 new file mode 100644 index 00000000..cb94f04b --- /dev/null +++ b/roles/alloy/templates/config.alloy.j2 @@ -0,0 +1,3 @@ +// Ansible Managed +{{ alloy_config_default }} +{{ alloy_config_additional }} \ No newline at end of file diff --git a/roles/alloy/templates/config.blackbox-exporter.j2 b/roles/alloy/templates/config.blackbox-exporter.j2 new file mode 100644 index 00000000..88b9bbe4 --- /dev/null +++ b/roles/alloy/templates/config.blackbox-exporter.j2 @@ -0,0 +1,3 @@ +# {{ ansible_managed }} +{{ alloy_blackbox_exporter_config_default }} +{{ alloy_blackbox_exporter_config_additional }} diff --git a/roles/alloy/templates/podman.container.jinja b/roles/alloy/templates/podman.container.jinja new file mode 100644 index 00000000..4dab765e --- /dev/null +++ b/roles/alloy/templates/podman.container.jinja @@ -0,0 +1,29 @@ +# {{ ansible_managed }} +# man podman-systemd.unit + +{% if container_unit_args is not none %} +[Unit] +{% for container_unit_arg in container_unit_args %} +{{ container_unit_arg }} +{% endfor %} +{% endif %} + +[Container] +Image={{ container_image_with_tag }} +{% if container_exec is not none %} +Exec={{ container_exec }} +{% endif %} +{% for container_env in container_envs %} +Environment={{ container_env.key }}={{ container_env.value }} +{% endfor %} +Network=host +UserNS=host +User={{ user_id }} +Group={{ group_id }} +{% for container_vol in container_volumes %} +Volume={{ container_vol.host }}:{{ container_vol.inside }} +{% endfor %} +LogDriver=journald + +[Install] +WantedBy=default.target diff --git a/roles/alloy/templates/unpoller.conf.j2 b/roles/alloy/templates/unpoller.conf.j2 new file mode 100644 index 00000000..0d47955f --- /dev/null +++ b/roles/alloy/templates/unpoller.conf.j2 @@ -0,0 +1,248 @@ +# {{ ansible_managed }} +# Unpoller v2 primary configuration file. TOML FORMAT # +########################################################### + +[poller] + # Turns on line numbers, microsecond logging, and a per-device log. + # The default is false, but I personally leave this on at home (four devices). + # This may be noisy if you have a lot of devices. It adds one line per device. + debug = false + + # Turns off per-interval logs. Only startup and error logs will be emitted. + # Recommend enabling debug with this setting for better error logging. + quiet = false + + # Load dynamic plugins. Advanced use; only sample mysql plugin provided by default. + plugins = [] + +#### OUTPUTS + + # If you don't use an output, you can disable it. + +[prometheus] + disable = false + # This controls on which ip and port /metrics is exported when mode is "prometheus". + # This has no effect in other modes. Must contain a colon and port. + http_listen = "0.0.0.0:9130" + # Adding an SSL Cert and Cert Key will make Poller listen with SSL/https. + ssl_cert_path = "" + ssl_key_path = "" + # Errors are rare. Setting this to true will report them to Prometheus. + report_errors = false + ## Record data for disabled or down (unlinked) switch ports. + dead_ports = false + +[influxdb] + disable = true + # InfluxDB does not require auth by default, so the user/password are probably unimportant. + url = "http://127.0.0.1:8086" + user = "unifipoller" + # Password for InfluxDB user (above). + # If the password provided here begins with file:// then the password is read in from + # the file path that follows the file:// prefix. ex: file:///etc/influxdb/passwd.file + pass = "unifipoller" + # Be sure to create this database. See the InfluxDB Wiki page for more info. + db = "unifi" + # If your InfluxDB uses a valid SSL cert, set this to true. + verify_ssl = false + # The UniFi Controller only updates traffic stats about every 30 seconds. + # Setting this to something lower may lead to "zeros" in your data. + # If you're getting zeros now, set this to "1m" + interval = "30s" + ## Record data for disabled or down (unlinked) switch ports. + dead_ports = false + +# To enable output of UniFi Events to Loki, add a URL; it's disabled otherwise. +# User, pass and tenant_id are optional and most folks wont set them. +# Pick which logs you want per-controller in the [unifi.controller] section. +# This is a new feature. Feedback welcome! +[loki] + disable = true + url = "" + # The rest of this is advanced & optional. See wiki. + user = "" + pass = "" + verify_ssl = false + tenant_id = "" + interval = "2m" + timeout = "10s" + +[datadog] + # How often to poll UniFi and report to Datadog. + interval = "2m" + + # To enable this output plugin + enable = false + + # Datadog Custom Options + + # address to talk to the datadog agent, by default this uses the local statsd UDP interface + # address = "localhost:8125" + + # namespace to prepend to all data, default is no additional prefix. + # namespace = "" + + # tags to append to all data + # tags = [ "customer:abc_corp" ] + + # For more advanced options for very large amount of data collected see the upstream + # github.com/unpoller/unpoller/pkg/datadogunifi repository README. + + +# Unpoller has an optional web server. To turn it on, set enable to true. If you +# wish to use SSL, provide SSL cert and key paths. This interface is currently +# read-only; it just displays information, like logs, devices and clients. +# Notice: Enabling the web server with many sites will increase memory usage. +# This is a new feature and lacks a UI, enabling only recommended for testing. +[webserver] + enable = false + port = 37288 + # The HTML path is different on Windows and BSD/macOS. + html_path = "/usr/local/lib/unpoller/web" + ssl_cert_path = "" + ssl_key_path = "" + # How many events per event group to hold. 200-2000. Use fewer with many sites. + # With 1 site, you'll have a max total of 9 event groups; 1 per plugin, 4 per site. + # Each site adds 1 event group for each of these inputs that is enabled: + # save_ids, save_events, save_anomalies, save_alarms. + max_events = 200 + +# By default the web interface does not require authentication. You can change +# that by adding a username and password hash (or multiple) below. +# To create a hash, run unifi-poller with the -e CLI argument. See Wiki for more! +[webserver.accounts] +# username = "password-hash" +# captain = "$2a$04$mxw6i0LKH6u46oaLK2cq5eCTAAFkfNiRpzNbz.EyvJZZWNa2FzIlS" + +#### INPUTS + +[unifi] + # Setting this to true and providing default credentials allows you to skip + # configuring controllers in this config file. Instead you configure them in + # your prometheus.yml config. Prometheus then sends the controller URL to + # Unpoller when it performs the scrape. This is useful if you have many, + # or changing controllers. See wiki for more. + dynamic = false + +# The following section contains the default credentials/configuration for any +# dynamic controller (see above section), or the primary controller if you do not +# provide one and dynamic is disabled. In other words, you can just add your +# controller here and delete the following section. The internal defaults are +# shown below. Any missing values will assume these displayed defaults. + +[unifi.defaults] + # URL for the UniFi Controller. Do not add any paths after the host:port. + # Do not use port 8443 if you have a UDM; just use "https://ip". + url = "https://invalid.invalid:8443" + + # Make a read-only user in the UniFi Admin Settings, allow it access to all sites. + user = "{{ alloy_unpoller_unifi_user }}" + + # Password for UniFi controller user (above). + # If the password provided here begins with file:// then the password is read in from + # the file path that follows the file:// prefix. ex: file:///etc/unifi/password.file + # ex: file:///etc/unifi/passwd.file, windows: file://C:\\UserData\\Unifi\\Passwd.txt + pass = "" + + # API Key + # Unifi now supports API Key authentication. This is exclusive of user/pass auth. + # api_key = "unifiapikey" + + # If the controller has more than one site, specify which sites to poll here. + # Set this to ["default"] to poll only the first site on the controller. + # A setting of ["all"] will poll all sites; this works if you only have 1 site too. + sites = ["all"] + + # Added an example for overriding the default site name. + # default_site_name_override = "My Custom Default Site" + + # Specify a timeout, leave missing to declare infinite wait. This determines the maximum + # time to wait for a response from the unifi controller on any API request. + # timeout = 60s + + # Enable collection of site data. This data powers the Network Sites dashboard. + # It's not valuable to everyone and setting this to false will save resources. + save_sites = false + + # Hash, with md5, client names and MAC addresses. This attempts to protect + # personally identifiable information. Most users won't want to enable this. + hash_pii = false + + # Enable collection of Intrusion Detection System Data (InfluxDB/Loki only). + # Only useful if IDS or IPS are enabled on one of the sites. This may store + # a lot of information. Only recommended for testing and debugging. There + # may not be any dashboards to display this data. It can be used for annotations. + # Enable this only if using InfluxDB or Loki. This will leak PII data! + save_ids = false + + # Enable collection of UniFi Events (InfluxDB/Loki only). + # This may store a lot of information. Only recommended for testing and debugging. + # There are no dashboards to display this data. It can be used for annotations. + # This is a new (June, 2020) feature. Please provide feedback if you try it out! + # Enable this only if using InfluxDB or Loki. This will leak PII data! + save_events = false + + # Enable collection of UniFi Alarms (InfluxDB/Loki only). + # There are no dashboards to display this data. It can be used for annotations. + # This is a new (June, 2020) feature. Please provide feedback if you try it out! + # Enable this only if using InfluxDB or Loki. This will leak PII data! + save_alarms = false + + # Enable collection of UniFi Anomalies (InfluxDB/Loki only). + # There are no dashboards to display this data. It can be used for annotations. + # This is a new (June, 2020) feature. Please provide feedback if you try it out! + # Enable this only if using InfluxDB or Loki. + save_anomalies = false + + # Enable collection of Deep Packet Inspection data. This data breaks down traffic + # types for each client and site, it powers a dedicated DPI dashboard. + # Enabling this adds roughly 150 data points per client. That's 6000 metrics for + # 40 clients. This adds a little bit of poller run time per interval and causes + # more API requests to your controller(s). Don't let these "cons" sway you: + # it's cool data. Please provide feedback on your experience with this feature. + save_dpi = false + + ## Enabling save_rogue stores even more data in your time series databases. + ## This saves neighboring access point metrics in a dedicated table or namespace. + save_rogue = false + + # If your UniFi controller has a valid SSL certificate (like lets encrypt), + # you can enable this option to validate it. Otherwise, any SSL certificate is + # valid. If you don't know if you have a valid SSL cert, then you don't have one. + verify_ssl = false + + ## You may provide a list of SSL cert files (PEM format) that you expect your + ## controller to use. As long as one of the certs you provide here shows up in + ## the cert trust chain the controller presents it will be accepted and allowed. + ## These files may be re-read while poller is running. + ## Example: ssl_cert_paths = ["/path/to/cert.pem", "/another/cert.pem"] + ssl_cert_paths = [] + +# The following is optional and used for configurations with multiple UniFi controllers. + +# You may repeat the following [[unifi.controller]] section as many times as needed to +# poll multiple controllers. Uncomment the entire section including [[unifi.controller]]. +# Omitted variables will have their values taken from the defaults, above. +# +#[[unifi.controller]] +# url = "https://127.0.0.1:8443" +# user = "unifipoller" +# pass = "unifipoller" +# sites = ["all"] +# save_sites = true +# hash_pii = false +# save_ids = false +# save_events = false +# save_alarms = false +# save_anomalies = false +# save_dpi = false +# save_rogue = false +# verify_ssl = false +# ssl_cert_paths = [] + +[[unifi.controller]] +url = "{{ alloy_unpoller_unifi_url | mandatory }}" +user = "{{ alloy_unpoller_unifi_user }}" +pass = "{{ alloy_unpoller_unifi_password | mandatory }}" +hash_pii = true +save_anomalies = false diff --git a/roles/alloy/vars/main.yaml b/roles/alloy/vars/main.yaml new file mode 100644 index 00000000..d9fbe4bb --- /dev/null +++ b/roles/alloy/vars/main.yaml @@ -0,0 +1,2 @@ +--- +alloy_enable_blackbox_exporter: "{{ 'prometheus.exporter.blackbox' in (alloy_config_default + alloy_config_additional) }}"