From e5b941520978ea32e5d380f4a9438b3730e46cef Mon Sep 17 00:00:00 2001 From: Elveg Date: Mon, 6 Jul 2026 13:37:47 +0300 Subject: [PATCH] Add grafana_alloy role: scrape log files to Loki --- roles/grafana_alloy/README.md | 30 ++++++++ roles/grafana_alloy/defaults/main.yml | 7 ++ roles/grafana_alloy/handlers/main.yml | 7 ++ .../molecule/default/converge.yml | 6 ++ .../molecule/default/molecule.yml | 27 +++++++ .../grafana_alloy/molecule/default/verify.yml | 43 +++++++++++ roles/grafana_alloy/tasks/configure.yml | 10 +++ roles/grafana_alloy/tasks/install.yml | 71 +++++++++++++++++++ roles/grafana_alloy/tasks/main.yml | 9 +++ .../grafana_alloy/templates/alloy.service.j2 | 30 ++++++++ roles/grafana_alloy/templates/config.alloy.j2 | 24 +++++++ 11 files changed, 264 insertions(+) create mode 100644 roles/grafana_alloy/README.md create mode 100644 roles/grafana_alloy/defaults/main.yml create mode 100644 roles/grafana_alloy/handlers/main.yml create mode 100644 roles/grafana_alloy/molecule/default/converge.yml create mode 100644 roles/grafana_alloy/molecule/default/molecule.yml create mode 100644 roles/grafana_alloy/molecule/default/verify.yml create mode 100644 roles/grafana_alloy/tasks/configure.yml create mode 100644 roles/grafana_alloy/tasks/install.yml create mode 100644 roles/grafana_alloy/tasks/main.yml create mode 100644 roles/grafana_alloy/templates/alloy.service.j2 create mode 100644 roles/grafana_alloy/templates/config.alloy.j2 diff --git a/roles/grafana_alloy/README.md b/roles/grafana_alloy/README.md new file mode 100644 index 0000000..316050b --- /dev/null +++ b/roles/grafana_alloy/README.md @@ -0,0 +1,30 @@ +# grafana_alloy + +Install Grafana Alloy binary from GitHub. + +Install Grafana Alloy from the release binary and configures it to scrape log files and push them to Loki. RHEL / CentOS / Rocky. + +## Variables + +```yaml +alloy_version: "1.17.1" +alloy_config_dir: "/etc/alloy" +alloy_data_dir: "/var/lib/alloy" +alloy_loki_url: "http://localhost:3100/loki/api/v1/push" +alloy_log_paths: + - "/var/log/*.log" +``` + +## Usage + +```yaml +- hosts: log_shippers + become: true + roles: + - role: grafana_alloy + vars: + alloy_loki_url: "http://loki.example.com:3100/loki/api/v1/push" + alloy_log_paths: + - "/var/log/*.log" + - "/var/log/myapp/*.log" +``` diff --git a/roles/grafana_alloy/defaults/main.yml b/roles/grafana_alloy/defaults/main.yml new file mode 100644 index 0000000..173c0b2 --- /dev/null +++ b/roles/grafana_alloy/defaults/main.yml @@ -0,0 +1,7 @@ +--- +grafana_alloy_version: "1.17.1" +grafana_alloy_config_dir: "/etc/alloy" +grafana_alloy_data_dir: "/var/lib/alloy" +grafana_alloy_loki_url: "http://localhost:3100/loki/api/v1/push" +grafana_alloy_log_paths: + - "/var/log/*.log" diff --git a/roles/grafana_alloy/handlers/main.yml b/roles/grafana_alloy/handlers/main.yml new file mode 100644 index 0000000..cc10465 --- /dev/null +++ b/roles/grafana_alloy/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: "(Re)start Alloy service" + ansible.builtin.systemd: + name: alloy.service + state: restarted + enabled: true + daemon_reload: true diff --git a/roles/grafana_alloy/molecule/default/converge.yml b/roles/grafana_alloy/molecule/default/converge.yml new file mode 100644 index 0000000..26f739e --- /dev/null +++ b/roles/grafana_alloy/molecule/default/converge.yml @@ -0,0 +1,6 @@ +--- +- name: Converge + hosts: all + + roles: + - role: genlab.common.grafana_alloy diff --git a/roles/grafana_alloy/molecule/default/molecule.yml b/roles/grafana_alloy/molecule/default/molecule.yml new file mode 100644 index 0000000..8fce403 --- /dev/null +++ b/roles/grafana_alloy/molecule/default/molecule.yml @@ -0,0 +1,27 @@ +--- +dependency: + name: galaxy + +driver: + name: docker + +platforms: + - name: rocky + image: geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux9}-ansible:latest + pre_build_image: true + command: ${MOLECULE_DOCKER_COMMAND:-""} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host + privileged: true + +provisioner: + name: ansible + +verifier: + name: ansible + +lint: | + set -e + yamllint . + ansible-lint . diff --git a/roles/grafana_alloy/molecule/default/verify.yml b/roles/grafana_alloy/molecule/default/verify.yml new file mode 100644 index 0000000..20a3de0 --- /dev/null +++ b/roles/grafana_alloy/molecule/default/verify.yml @@ -0,0 +1,43 @@ +--- +- name: Verify + hosts: all + gather_facts: false + any_errors_fatal: true + + tasks: + - name: "Include default vars" + ansible.builtin.include_vars: + dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/" + extensions: ['yml'] + + - name: "Check if Alloy is installed" + changed_when: false + ansible.builtin.command: "alloy --version" + register: grafana_alloy_installed_version + + - name: "Check Alloy version" + ansible.builtin.assert: + that: "grafana_alloy_version in grafana_alloy_installed_version.stdout" + success_msg: "Alloy version {{ grafana_alloy_version }} is available and executable" + fail_msg: "Alloy version {{ grafana_alloy_version }} not found or not executable" + + - name: "Check if Alloy service is active" + ansible.builtin.systemd: + name: alloy + register: grafana_alloy_service + failed_when: "grafana_alloy_service.status.ActiveState != 'active'" + + # kics-scan ignore-block + - name: "Check if Alloy is ready" + ansible.builtin.uri: + url: "http://localhost:12345/-/ready" + return_content: true + status_code: 200 + method: GET + register: grafana_alloy_ready + + - name: "Assert Alloy readiness" + ansible.builtin.assert: + that: "grafana_alloy_ready.content | trim == 'Alloy is ready.'" + success_msg: "Alloy is ready" + fail_msg: "Alloy is not ready" diff --git a/roles/grafana_alloy/tasks/configure.yml b/roles/grafana_alloy/tasks/configure.yml new file mode 100644 index 0000000..e0cd392 --- /dev/null +++ b/roles/grafana_alloy/tasks/configure.yml @@ -0,0 +1,10 @@ +--- +- name: "Configure | Deploy Alloy configuration" + notify: "(Re)start Alloy service" + ansible.builtin.template: + src: config.alloy.j2 + dest: "{{ grafana_alloy_config_dir }}/config.alloy" + owner: "alloy" + group: "alloy" + mode: "0660" + validate: "alloy fmt %s" diff --git a/roles/grafana_alloy/tasks/install.yml b/roles/grafana_alloy/tasks/install.yml new file mode 100644 index 0000000..6caa6f3 --- /dev/null +++ b/roles/grafana_alloy/tasks/install.yml @@ -0,0 +1,71 @@ +--- +- name: "Install | Create Alloy system user" + ansible.builtin.user: + name: "alloy" + system: true + shell: "/sbin/nologin" + groups: "adm,systemd-journal" + append: true + create_home: false + state: present + +- name: "Install | Create configuration and data directories" + loop: + - "{{ grafana_alloy_config_dir }}" + - "{{ grafana_alloy_data_dir }}" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "alloy" + group: "alloy" + mode: "0755" + +- name: "Install | Install the binary" + block: + - name: "Install | Check Alloy version" + changed_when: false + ansible.builtin.command: + cmd: "alloy --version" + register: grafana_alloy_ver + + - name: "Install | Assert version correctness" + ansible.builtin.assert: + that: "grafana_alloy_version in grafana_alloy_ver.stdout" + success_msg: "alloy version {{ grafana_alloy_version }} is installed and working" + fail_msg: "alloy version {{ grafana_alloy_version }} is not installed or not working correctly" + + rescue: + - name: "Install | Ensure unzip is present" + ansible.builtin.package: + name: "unzip" + state: present + + - name: "Install | Fetch and unpack the distribution" + ansible.builtin.unarchive: + src: "https://github.com/grafana/alloy/releases/download/v{{ grafana_alloy_version }}/alloy-linux-amd64.zip" + dest: "/tmp" + remote_src: true + + - name: "Install | Put the binary under the PATH" + notify: "(Re)start Alloy service" + ansible.builtin.copy: + remote_src: true + src: "/tmp/alloy-linux-amd64" + dest: "/usr/bin/alloy" + owner: "alloy" + group: "alloy" + mode: "0755" + + - name: "Install | Clean up the downloads" + ansible.builtin.file: + path: "/tmp/alloy-linux-amd64" + state: absent + +- name: "Install | Create a systemd service unit" + notify: "(Re)start Alloy service" + ansible.builtin.template: + src: alloy.service.j2 + dest: /etc/systemd/system/alloy.service + owner: "alloy" + group: "alloy" + mode: "0660" diff --git a/roles/grafana_alloy/tasks/main.yml b/roles/grafana_alloy/tasks/main.yml new file mode 100644 index 0000000..83eb4ce --- /dev/null +++ b/roles/grafana_alloy/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- name: "Install Grafana Alloy from binary" + ansible.builtin.include_tasks: install.yml + +- name: "Configure Alloy" + ansible.builtin.include_tasks: configure.yml + +- name: "Flush handlers" + ansible.builtin.meta: "flush_handlers" diff --git a/roles/grafana_alloy/templates/alloy.service.j2 b/roles/grafana_alloy/templates/alloy.service.j2 new file mode 100644 index 0000000..9a109eb --- /dev/null +++ b/roles/grafana_alloy/templates/alloy.service.j2 @@ -0,0 +1,30 @@ +[Unit] +Description=Grafana Alloy +Wants=network-online.target +After=network-online.target +Documentation="https://grafana.com/docs/alloy/" + +[Service] +User=alloy +Group=alloy +Type=simple +Environment=HOSTNAME=%H +ExecStart=/usr/bin/alloy run {{ grafana_alloy_config_dir }}/config.alloy \ + --storage.path={{ grafana_alloy_data_dir }} \ + --server.http.listen-addr=127.0.0.1:12345 \ + --disable-reporting +ExecReload=/bin/kill -HUP $MAINPID + +# Security hardening +ReadWritePaths={{ grafana_alloy_data_dir }} +ProtectSystem=strict +NoNewPrivileges=true +PrivateTmp=true +ProtectKernelModules=true +ProtectControlGroups=true +ProtectKernelTunables=true +ProtectClock=yes +RestrictSUIDSGID=true + +[Install] +WantedBy=multi-user.target diff --git a/roles/grafana_alloy/templates/config.alloy.j2 b/roles/grafana_alloy/templates/config.alloy.j2 new file mode 100644 index 0000000..b8081a9 --- /dev/null +++ b/roles/grafana_alloy/templates/config.alloy.j2 @@ -0,0 +1,24 @@ +// {{ ansible_managed }} + +local.file_match "logs" { + path_targets = [ +{% for path in grafana_alloy_log_paths %} + {__path__ = "{{ path }}", job = "varlogs", host = constants.hostname}, +{% endfor %} + ] +} + +loki.source.file "logs" { + targets = local.file_match.logs.targets + forward_to = [loki.process.default.receiver] +} + +loki.process "default" { + forward_to = [loki.write.default.receiver] +} + +loki.write "default" { + endpoint { + url = "{{ grafana_alloy_loki_url }}" + } +}