Vendor Galaxy Roles and Collections
Some checks failed
/ Ansible Lint (push) Failing after 5m45s
/ Ansible Lint (pull_request) Failing after 4m59s

This commit is contained in:
Stefan Bethke 2026-02-06 22:07:16 +01:00
commit 2aed20393f
3553 changed files with 387444 additions and 2 deletions

View file

@ -0,0 +1,71 @@
# Ansible role - Alloy
[![License](https://img.shields.io/github/license/grafana/grafana-ansible-collection)](LICENSE)
This Ansible role to install and configure [Alloy](https://grafana.com/docs/alloy/latest/), which can be used to collect traces, metrics, and logs.
This role is tailored for operating systems such as **RedHat**, **Rocky Linux**, **AlmaLinux**, **Ubuntu**, **Debian**, **macOS**, and **openSUSE**.
## Table of Content
- [Requirements](#requirements)
- [Role Variables](#role-variables)
- [Playbook](#playbook)
## Requirements
- Ansible 2.13+
- `ansible.utils` collection is required. Additionally, you must install the `netaddr` Python library on the host where you are running Ansible (not on the target remote host) if is not present.
- `community.general` collection is required for macOS support
- **macOS**: Homebrew must be installed
## Role Variables
| Variable Name | Description | Default Value |
|-----------------------|----------------------------------------------------------------------|---------------------------------------------------------------------|
| `alloy_version` | The version of Alloy to download and deploy. Supported standard version "1.4.2" format or "latest". | `latest` |
| `alloy_uninstall` | If set to `true` will perfom uninstall instead of deployment. | `false` |
| `alloy_expose_port` | By default, this is set to false. It supports only simple firewalld configurations. If set to true, a firewalld rule is added to expose the TCP alloy port. The Port is automatically extracted from the environment variable `alloy_env_file_vars` in CUSTOM_ARGS when --server.http.listen-addr=0.0.0.0:12345 is defined. If set to false, configuration is skipped. If the firewalld.service is not active, all firewalld tasks are skipped. | `false` |
| `alloy_user_groups` | Appends the alloy user to specific groups. | `[]` |
| `alloy_github_api_url` | The default Github API URL to check for the latest version available. | `"https://api.github.com/repos/grafana/alloy/releases/latest"` |
| `alloy_download_url_rpm` | The default download URL for the Alloy rpm package from GitHub. | `"https://github.com/grafana/alloy/releases/download/v{{ aloy_version }}/alloy-{{ aloy_version }}-1.{{ __alloy_arch }}.rpm"` |
| `alloy_download_url_deb` | The default download URL for the Alloy deb package from GitHub. | `"https://github.com/grafana/alloy/releases/download/v{{ aloy_version }}/alloy-{{ aloy_version }}-1.{{ __alloy_arch }}.deb"` |
| `alloy_readiness_check_use_https` | This boolean variable determines whether the readiness check for the Alloy server should use HTTPS or HTTP when validating the `/-/ready` endpoint. This variable does not enable TLS on the Alloy server itself. | `false` |
| `alloy_readiness_check_use_proxy` | This boolean variable determines whether the readiness check for the Alloy server should use a proxy when validating the `/-/ready` endpoint. If false, it will not use a proxy, even if one is defined in an environment variable on the target hosts. | `true` |
| `alloy_env_file_vars` | You can use environment variables to control the run-time behavior of Grafana Alloy. | `{}` |
| `alloy_systemd_override` | Systemd unit drop-in file used to override or extend the default configuration of a systemd unit. | `{}` |
| `alloy_config` | This is the configuration that sets up Alloy. Refer to the [configuration blocks](https://grafana.com/docs/alloy/latest/reference/config-blocks/) and [components](https://grafana.com/docs/alloy/latest/reference/components/) documentation for more details. Since the purpose of using Alloy varies, no default configuration is provided. ⚠️ **You must provide either `alloy_config` for single config or set `alloy_env_file_vars.CONFIG_FILE` for multi-config setup**. Note that if you use `alloy_env_file_vars.CONFIG_FILE`, the content of `alloy_config` will not be templated. It is expected that you manage the multi-config content using pre_tasks or with your own role. | `{}` |
## Dependencies
No Dependencies
## Playbook
```yaml
- name: Manage alloy service
hosts: all
become: true
vars:
# alloy_config: |
# Your Config Content
roles:
- role: grafana.grafana.alloy
```
- Playbook execution example
```shell
# Deploy Alloy
ansible-playbook function_alloy_play.yml
# Uninstall Alloy
ansible-playbook function_alloy_play.yml -e "alloy_uninstall=true"
```
## License
See [LICENSE](https://github.com/grafana/grafana-ansible-collection/blob/main/LICENSE)
## Author Information
- [Ishan Jain](https://github.com/ishanjainn)
- [VoidQuark](https://github.com/voidquark)

View file

@ -0,0 +1,36 @@
---
# defaults file for alloy
alloy_version: "latest"
alloy_uninstall: false
alloy_expose_port: false
alloy_github_api_url: "https://api.github.com/repos/grafana/alloy/releases/latest"
alloy_download_url_rpm: "https://github.com/grafana/alloy/releases/download/v{{ alloy_version }}/alloy-{{ alloy_version }}-1.{{ __alloy_arch }}.rpm"
alloy_download_url_deb: "https://github.com/grafana/alloy/releases/download/v{{ alloy_version }}/alloy-{{ alloy_version }}-1.{{ __alloy_arch }}.deb"
alloy_readiness_check_use_https: false
alloy_readiness_check_use_proxy: true
alloy_user_groups: []
# alloy_user_groups:
# - "systemd-journal"
alloy_env_file_vars: {}
# alloy_env_file_vars:
# CONFIG_FILE: "/custom/path"
# CUSTOM_ARGS: "--server.http.listen-addr=0.0.0.0:12345 --stability.level=public-preview --feature.community-components.enabled=true"
alloy_systemd_override: {}
# alloy_systemd_override: |
# [Service]
# User=root
alloy_config: {}
# alloy_config: |
# prometheus.scrape "default" {
# targets = [{"__address__" = "127.0.0.1:12345"}]
# forward_to = [prometheus.remote_write.prom.receiver]
# }
# prometheus.remote_write "prom" {
# endpoint {
# url = "http://mimir:9009/api/v1/push"
# }
# }

View file

@ -0,0 +1,17 @@
---
# handlers file for alloy
- name: Restart alloy
listen: "restart alloy"
ansible.builtin.systemd:
daemon_reload: true
name: alloy.service
state: restarted
enabled: true
when: not ansible_check_mode
- name: Restart alloy macos
listen: "restart alloy macos"
ansible.builtin.command: "brew services restart {{ __alloy_brew_package }}"
when:
- not ansible_check_mode
- ansible_facts['os_family'] == 'Darwin'

View file

@ -0,0 +1,34 @@
---
galaxy_info:
role_name: alloy
author: Ishan Jain, voidquark
description: Role to Install and Configure Grafana Alloy
license: "GPL-3.0-or-later"
min_ansible_version: "2.13"
platforms:
- name: EL
versions:
- "8"
- "9"
- name: Fedora
versions:
- all
- name: Debian
versions:
- all
- name: Ubuntu
versions:
- all
- name: macOS
versions:
- all
- name: opensuse
versions:
- all
galaxy_tags:
- alloy
- grafana
- observability
- monitoring
- opentelemetry
- telemetry

View file

@ -0,0 +1,17 @@
---
- name: Converge
hosts: all
vars:
alloy_version: "1.4.2"
alloy_config: |
prometheus.scrape "default" {
targets = [{"__address__" = "127.0.0.1:12345"}]
forward_to = [prometheus.remote_write.prom.receiver]
}
prometheus.remote_write "prom" {
endpoint {
url = "http://mimir:9009/api/v1/push"
}
}
roles:
- role: grafana.grafana.alloy

View file

@ -0,0 +1,20 @@
---
dependency:
name: galaxy
options:
ignore-errors: true
driver:
name: docker
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: converge.yml

View file

@ -0,0 +1,176 @@
---
- name: Obtain the latest version from the GitHub repo
when: alloy_version == "latest"
block:
- name: Scrape Github API endpoint to obtain latest Alloy version
ansible.builtin.uri:
url: "{{ alloy_github_api_url }}"
method: GET
body_format: json
become: false
delegate_to: localhost
run_once: true
check_mode: false
register: __github_latest_version
- name: Latest available Alloy version
ansible.builtin.set_fact:
alloy_version: "{{ __github_latest_version.json.tag_name | regex_replace('^v?(\\d+\\.\\d+\\.\\d+)$', '\\1') }}"
- name: Verify current deployed version
when: ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
block:
- name: Check if Alloy binary is present
ansible.builtin.stat:
path: "/usr/bin/alloy"
register: __already_deployed
- name: Obtain current deployed Alloy version
ansible.builtin.command:
cmd: "/usr/bin/alloy --version"
changed_when: false
register: __current_deployed_version
when: __already_deployed.stat.exists | bool
- name: Verify current deployed version on macOS
when: ansible_facts['os_family'] == 'Darwin'
block:
- name: Check if Alloy is installed via Homebrew
ansible.builtin.command: brew list --versions {{ __alloy_brew_package }}
register: __brew_alloy_version
failed_when: false
changed_when: false
- name: Extract current Alloy version on macOS
ansible.builtin.set_fact:
__current_deployed_version:
stdout: "{{ __brew_alloy_version.stdout }}"
when: __brew_alloy_version.rc == 0
- name: Include RedHat/Rocky setup
ansible.builtin.include_tasks:
file: setup-RedHat.yml
when: ansible_facts['os_family'] in ['RedHat', 'Rocky']
- name: Include Debian/Ubuntu setup
ansible.builtin.include_tasks:
file: setup-Debian.yml
when: ansible_facts['os_family'] == 'Debian'
- name: Include macOS/Darwin setup
ansible.builtin.include_tasks:
file: setup-Darwin.yml
when: ansible_facts['os_family'] == 'Darwin'
- name: Include SUSE setup
ansible.builtin.include_tasks:
file: setup-Suse.yml
when: ansible_facts['os_family'] == 'Suse'
- name: Alloy systemd override
when:
- alloy_systemd_override | length > 0
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
block:
- name: Ensure that Alloy systemd override path exist
ansible.builtin.file:
path: "/etc/systemd/system/alloy.service.d"
state: directory
owner: "root"
group: "root"
mode: "0750"
notify: restart alloy
- name: Template Alloy systemd override.conf - /etc/systemd/system/alloy.service.d/override.conf
ansible.builtin.template:
src: "override.conf.j2"
dest: "/etc/systemd/system/alloy.service.d/override.conf"
owner: "root"
group: "root"
mode: "0644"
notify: restart alloy
- name: Template Alloy env file - {{ __alloy_env_file }}
ansible.builtin.template:
src: "alloy.j2"
dest: "{{ __alloy_env_file }}"
owner: "root"
group: "root"
mode: "0644"
notify: restart alloy
when: ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
- name: Template Alloy config - /etc/alloy/config.alloy
ansible.builtin.template:
src: "config.alloy.j2"
dest: "/etc/alloy/config.alloy"
owner: "root"
group: "root"
mode: "0644"
when:
- alloy_config | length > 0
- alloy_env_file_vars.CONFIG_FILE is not defined
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
notify: restart alloy
- name: Ensure that /etc/alloy/alloy.config is absent when a custom configuration file/dir is specified in alloy_env_file_vars.CONFIG_FILE
ansible.builtin.file:
path: "/etc/alloy/config.alloy"
state: absent
when:
- alloy_config | length < 1 or alloy_env_file_vars.CONFIG_FILE is defined
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
- name: Add the Alloy system user to additional group
ansible.builtin.user:
name: "alloy"
groups: "{{ item }}"
system: true
append: true
create_home: false
state: present
loop: "{{ alloy_user_groups }}"
when:
- alloy_user_groups | length > 0
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
- name: Get firewalld state
ansible.builtin.systemd:
name: "firewalld"
register: __firewalld_service_state
when: ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
- name: Enable firewalld rule to expose Alloy tcp port {{ __alloy_server_http_listen_port }}
ansible.posix.firewalld:
immediate: true
permanent: true
port: "{{ __alloy_server_http_listen_port }}/tcp"
state: enabled
when:
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
- __firewalld_service_state.status.ActiveState == "active"
- alloy_expose_port | bool
- name: Flush handlers after deployment
ansible.builtin.meta: flush_handlers
- name: Ensure that Alloy is started (Linux)
ansible.builtin.systemd:
name: alloy.service
state: started
when:
- not ansible_check_mode
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']
- name: Verify that Alloy URL is responding
ansible.builtin.uri:
url: "{{ alloy_readiness_check_use_https | ansible.builtin.ternary('https', 'http') }}://{{ __alloy_server_http_listen_address }}:{{ __alloy_server_http_listen_port }}/-/ready"
method: GET
use_proxy: "{{ alloy_readiness_check_use_proxy | bool }}"
register: __alloy_verify_url_status_code
retries: 5
delay: 8
until: __alloy_verify_url_status_code.status == 200
when:
- not ansible_check_mode
- ansible_facts['os_family'] in ['RedHat', 'Debian', 'Suse']

View file

@ -0,0 +1,19 @@
---
# tasks file for alloy
- name: Include OS specific variables
ansible.builtin.include_vars:
file: "{{ ansible_facts['os_family'] }}.yml"
- name: Preflight
ansible.builtin.include_tasks:
file: "preflight.yml"
- name: Deploy Alloy service
ansible.builtin.include_tasks:
file: "deploy.yml"
when: not alloy_uninstall
- name: Uninstall Alloy service
ansible.builtin.include_tasks:
file: "uninstall.yml"
when: alloy_uninstall

View file

@ -0,0 +1,26 @@
---
- name: Fail when alloy_config or alloy_env_file_vars.CONFIG_FILE is not defined
ansible.builtin.fail:
msg: Variable alloy_config or alloy_env_file_vars.CONFIG_FILE is required!
when:
- alloy_config | length < 1
- alloy_env_file_vars.CONFIG_FILE is not defined
- not alloy_uninstall
- name: Extract IP address and PORT from alloy_env_file_vars
when: alloy_env_file_vars.CUSTOM_ARGS is defined and alloy_env_file_vars.CUSTOM_ARGS | length > 0
block:
- name: Search for server.http.listen-addr string
ansible.builtin.set_fact:
__alloy_server_http_listen_addr_regex: "{{ alloy_env_file_vars.CUSTOM_ARGS | regex_search('--server.http.listen-addr=([\\d\\.]+):(\\d+)', '\\1', '\\2') or [] }}"
- name: Extract IP address and port
ansible.builtin.set_fact:
__alloy_server_http_listen_address: "{{ __alloy_server_http_listen_addr_regex[0] }}"
__alloy_server_http_listen_port: "{{ __alloy_server_http_listen_addr_regex[1] }}"
when: __alloy_server_http_listen_addr_regex | length > 0
- name: Assert that extracted IP address is valid
ansible.builtin.assert:
that: (__alloy_server_http_listen_address | ansible.utils.ipaddr) != ""
when: __alloy_server_http_listen_addr_regex | length > 0

View file

@ -0,0 +1,96 @@
---
- name: Check if Homebrew is installed
ansible.builtin.command: which brew
register: __brew_check
changed_when: false
failed_when: false
- name: Fail if Homebrew is not installed
ansible.builtin.fail:
msg: "Homebrew is required but not installed"
when: __brew_check.rc != 0
- name: Get Homebrew prefix
ansible.builtin.command: brew --prefix
register: __brew_prefix
changed_when: false
- name: Set Alloy config directory path
ansible.builtin.set_fact:
__alloy_config_path: "{{ __alloy_config_path_default }}"
- name: Add Grafana tap to Homebrew
community.general.homebrew_tap:
name: "{{ __alloy_brew_tap }}"
state: present
- name: Install Alloy via Homebrew
community.general.homebrew:
name: "{{ __alloy_brew_package }}"
state: present
update_homebrew: true
- name: Ensure Alloy config directory exists
ansible.builtin.file:
path: "{{ __alloy_config_path }}"
state: directory
owner: "{{ ansible_user_id }}"
group: "{{ ansible_user_gid }}"
mode: '0755'
- name: Template Alloy config
ansible.builtin.template:
src: "config.alloy.j2"
dest: "{{ __alloy_config_path }}/config.alloy"
owner: "{{ ansible_user_id }}"
group: "{{ ansible_user_gid }}"
mode: '0644'
backup: true
when: alloy_config | length > 0
notify: restart alloy macos
- name: Check if Alloy service is loaded
ansible.builtin.command: brew services list
register: __brew_services
changed_when: false
- name: Stop Alloy service if it exists (to clean up any issues)
ansible.builtin.command: "brew services stop {{ __alloy_brew_package }}"
register: __stop_result
failed_when: false
changed_when: "'Successfully stopped' in __stop_result.stdout"
when: "'alloy' in __brew_services.stdout"
- name: Start Alloy service
ansible.builtin.command: "brew services start {{ __alloy_brew_package }}"
when:
- "'alloy' not in __brew_services.stdout or 'started' not in __brew_services.stdout"
register: __service_start
failed_when: __service_start.rc != 0
- name: Restart Alloy service if already running
ansible.builtin.command: "brew services restart {{ __alloy_brew_package }}"
when:
- "'alloy' in __brew_services.stdout and 'started' in __brew_services.stdout"
register: __service_restart
failed_when: __service_restart.rc != 0
- name: Check final service status
ansible.builtin.command: brew services list
register: __final_brew_services
changed_when: false
- name: Verify Alloy installation
ansible.builtin.command: alloy --version
register: __alloy_version_output
changed_when: false
failed_when: false
- name: Display Alloy version
ansible.builtin.debug:
msg: "Alloy version: {{ __alloy_version_output.stdout }}"
when: __alloy_version_output.rc == 0
- name: Display service status
ansible.builtin.debug:
msg: "Alloy service status: {{ __final_brew_services.stdout_lines | select('match', '.*alloy.*') | list }}"

View file

@ -0,0 +1,7 @@
---
- name: APT - Install Alloy
ansible.builtin.apt:
deb: "{{ alloy_download_url_deb }}"
state: present
notify: restart alloy
when: __current_deployed_version.stdout is not defined or alloy_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,8 @@
---
- name: DNF - Install Alloy from remote URL
ansible.builtin.package:
name: "{{ alloy_download_url_rpm }}"
state: present
disable_gpg_check: true
notify: restart alloy
when: __current_deployed_version.stdout is not defined or alloy_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,8 @@
---
- name: Zypper - Install Alloy from remote URL
community.general.zypper:
name: "{{ alloy_download_url_rpm }}"
state: present
disable_gpg_check: true
notify: restart alloy
when: __current_deployed_version.stdout is not defined or alloy_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,75 @@
---
- name: Stop Alloy service
ansible.builtin.systemd: # noqa ignore-errors
name: alloy
state: stopped
ignore_errors: true
- name: Stop Alloy service on macOS
ansible.builtin.command: "brew services stop {{ __alloy_brew_package }}"
when: ansible_facts['os_family'] == 'Darwin'
failed_when: false
- name: Uninstall Alloy rpm package
ansible.builtin.package:
name: "alloy"
state: absent
autoremove: true
when: ansible_facts['os_family'] in ['RedHat', 'Rocky']
- name: Uninstall Alloy deb package
ansible.builtin.apt:
name: "alloy"
state: absent
purge: true
when: ansible_facts['os_family'] == 'Debian'
- name: Uninstall Alloy via Homebrew
community.general.homebrew:
name: "{{ __alloy_brew_package }}"
state: absent
when: ansible_facts['os_family'] == 'Darwin'
- name: Uninstall Alloy rpm package (SUSE)
community.general.zypper:
name: "alloy"
state: absent
when: ansible_facts['os_family'] == 'Suse'
- name: Ensure that Alloy firewalld rule is not present - tcp port {{ __alloy_server_http_listen_port }}
ansible.posix.firewalld: # noqa ignore-errors
immediate: true
permanent: true
port: "{{ __alloy_server_http_listen_port }}/tcp"
state: disabled
ignore_errors: true
- name: Remove Alloy directories
ansible.builtin.file:
path: "{{ remove_me }}"
state: absent
loop:
- "/etc/alloy"
- "/etc/systemd/system/alloy.service.d"
- "/var/lib/alloy"
- "/etc/sysconfig/alloy"
- "/etc/default/alloy"
loop_control:
loop_var: remove_me
- name: Remove Alloy config directory on macOS
ansible.builtin.file:
path: "{{ __alloy_config_path_default }}"
state: absent
when: ansible_facts['os_family'] == 'Darwin'
- name: Remove the Alloy system user
ansible.builtin.user:
name: "alloy"
force: true
state: absent
- name: Remove Alloy system group
ansible.builtin.group:
name: "alloy"
state: absent

View file

@ -0,0 +1,10 @@
# Ansible Managed
{% if alloy_env_file_vars.CONFIG_FILE is not defined or alloy_env_file_vars.CONFIG_FILE | length < 1 %}
CONFIG_FILE="/etc/alloy/config.alloy"
{% endif %}
RESTART_ON_UPGRADE=true
{% for key, value in alloy_env_file_vars.items() %}
{{ key}}="{{value}}"
{% endfor %}

View file

@ -0,0 +1,3 @@
// Ansible Managed
{{ alloy_config }}

View file

@ -0,0 +1,3 @@
# Ansible Managed
{{ alloy_systemd_override }}

View file

@ -0,0 +1,5 @@
---
# macOS/Darwin specific variables
__alloy_brew_tap: "grafana/grafana"
__alloy_brew_package: "grafana/grafana/alloy"
__alloy_config_path_default: "{{ __brew_prefix.stdout | default('/opt/homebrew') }}"

View file

@ -0,0 +1,2 @@
---
__alloy_env_file: "/etc/default/alloy"

View file

@ -0,0 +1,2 @@
---
__alloy_env_file: "/etc/sysconfig/alloy"

View file

@ -0,0 +1,2 @@
---
__alloy_env_file: "/etc/sysconfig/alloy"

View file

@ -0,0 +1,9 @@
---
__alloy_server_http_listen_address: 127.0.0.1
__alloy_server_http_listen_port: 12345
__alloy_arch_map:
x86_64: 'amd64'
armv6l: 'arm'
armv7l: 'arm'
aarch64: 'arm64'
__alloy_arch: "{{ __alloy_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}"

View file

@ -0,0 +1,132 @@
<p><img src="https://grafana.com/blog/assets/img/blog/timeshift/grafana_release_icon.png" alt="grafana logo" title="grafana" align="right" height="60" /></p>
# Ansible Role: grafana.grafana.grafana
[![License](https://img.shields.io/badge/license-MIT%20License-brightgreen.svg)](https://opensource.org/licenses/MIT)
Provision and manage [Grafana](https://github.com/grafana/grafana) - platform for analytics and monitoring
## Requirements
- Ansible >= 2.9 (It might work on previous versions, but we cannot guarantee it)
- libselinux-python on deployer host (only when deployer machine has SELinux)
- Grafana >= 5.1 (for older Grafana versions use this role in version 0.10.1 or earlier)
- jmespath on deployer machine. If you are using Ansible from a Python virtualenv, install *jmespath* to the same virtualenv via pip.
## 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 |
| -------------- | ------------- | -----------------------------------|
| `grafana_use_provisioning` | true | Use Grafana provisioning capability when possible (**grafana_version=latest will assume >= 5.0**). |
| `grafana_provisioning_synced` | false | Ensure no previously provisioned dashboards are kept if not referenced anymore. |
| `grafana_version` | latest | Grafana package version |
| `grafana_manage_repo` | true | Manage package repository (or don't) |
| `grafana_yum_repo` | https://rpm.grafana.com | Yum repository URL |
| `grafana_yum_key` | https://rpm.grafana.com/gpg.key | Yum repository gpg key |
| `grafana_rhsm_subscription` | | rhsm subscription name (redhat subscription-manager) |
| `grafana_rhsm_repo` | | rhsm repository name (redhat subscription-manager) |
| `grafana_apt_release_channel` | stable | Apt release chanel (stable or beta) |
| `grafana_apt_arch` | {{ 'arm64' if ansible_facts['architecture'] == 'aarch64' else 'amd64' }} | Apt architecture |
| `grafana_apt_repo` | deb [arch={{ grafana_apt_arch }} signed-by=/usr/share/keyrings/grafana.asc] https://apt.grafana.com/ {{ grafana_apt_release_channel }} main | Apt repository string |
| `grafana_apt_key` | https://apt.grafana.com/gpg.key | Apt repository gpg key |
| `grafana_ini.instance_name` | {{ ansible_facts['fqdn'] \| default(ansible_host) \| default(inventory_hostname) }} | Grafana instance name |
| `grafana_ini.paths.logs` | /var/log/grafana | Path to logs directory |
| `grafana_ini.paths.data` | /var/lib/grafana | Path to database directory |
| `grafana_ini.server.http_addr` | 0.0.0.0 | Address on which Grafana listens |
| `grafana_ini.server.http_port` | 3000 | port on which Grafana listens |
| `grafana_cap_net_bind_service` | false | Enables the use of ports below 1024 without root privileges by leveraging the 'capabilities' of the linux kernel. read: http://man7.org/linux/man-pages/man7/capabilities.7.html |
| `grafana_ini.server.root_url` | "http://{{ grafana_ini.server.http_addr }}:{{ grafana_ini.server.http_port }}" | Full URL used to access Grafana from a web browser |
| `grafana_api_url` | "{{ grafana_url }}" | URL used for API calls in provisioning if different from public URL. See [this issue](https://github.com/cloudalchemy/ansible-grafana/issues/70). |
| `grafana_ini.server.domain` | "{{ ansible_facts['fqdn'] \| default(ansible_host) \| default('localhost') }}" | setting is only used in as a part of the `root_url` option. Useful when using GitHub or Google OAuth |
| `grafana_ini.server` | { protocol: http, enforce_domain: false, socket: "", cert_key: "", cert_file: "", enable_gzip: false, static_root_path: public, router_logging: false } | [server](http://docs.grafana.org/installation/configuration/#server) configuration section |
| `grafana_ini.security` | { admin_user: admin, admin_password: "" } | [security](http://docs.grafana.org/installation/configuration/#security) configuration section |
| `grafana_ini.database` | { type: sqlite3 } | [database](http://docs.grafana.org/installation/configuration/#database) configuration section |
| `grafana_ini.users` | { allow_sign_up: false, auto_assign_org_role: Viewer, default_theme: dark } | [users](http://docs.grafana.org/installation/configuration/#users) configuration section |
| `grafana_ini.auth` | {} | [authorization](http://docs.grafana.org/installation/configuration/#auth) configuration section |
| `grafana_ldap` | {} | [ldap](http://docs.grafana.org/installation/ldap/) configuration section. group_mappings are expanded, see defaults for example |
| `grafana_dashboards` | [] | List of dashboards which should be imported |
| `grafana_dashboards_dir` | "dashboards" | Path to a local directory containing dashboards files in `json` format |
| `grafana_datasources` | [] | List of datasources which should be configured |
| `grafana_environment` | {} | Optional Environment param for Grafana installation, useful ie for setting http_proxy |
| `grafana_plugins` | [] | List of Grafana plugins which should be installed |
| `grafana_alert_notifications` | [] | List of alert notification channels to be created, updated, or deleted |
Data source example:
```yaml
grafana_datasources:
- name: prometheus
type: prometheus
access: proxy
url: 'http://{{ prometheus_web_listen_address }}'
basicAuth: false
```
Dashboard example:
```yaml
grafana_dashboards:
- dashboard_id: 111
revision_id: 1
datasource: prometheus
```
Alert notification channel example:
**NOTE**: setting the variable `grafana_alert_notifications` will only come into
effect when `grafana_use_provisioning` is `true`. That means the new
provisioning system using config files, which is available starting from Grafana
v5.0, needs to be in use.
```yaml
grafana_alert_notifications:
notifiers:
- name: Channel 1
type: email
uid: channel1
is_default: false
send_reminder: false
settings:
addresses: "example@example.com"
autoResolve: true
delete_notifiers:
- name: Channel 2
uid: channel2
```
## Supported CPU Architectures
Historically packages were taken from different channels according to CPU architecture. Specifically, armv6/armv7 and aarch64/arm64 packages were via [unofficial packages distributed by fg2it](https://github.com/fg2it/grafana-on-raspberry). Now that Grafana publishes official ARM builds, all packages are taken from the official [Debian/Ubuntu](http://docs.grafana.org/installation/debian/#installing-on-debian-ubuntu) or [RPM](http://docs.grafana.org/installation/rpm/) packages.
## Example
### Playbook
Fill in the admin password field with your choice, the Grafana web page won't ask to change it at the first login.
```yaml
- hosts: all
roles:
- role: grafana.grafana.grafana
vars:
grafana_ini:
security:
admin_user: admin
admin_password: enter_your_secure_password
```
## Local Testing
The preferred way of locally testing the role is to use Docker and [molecule](https://github.com/ansible-community/molecule). You will have to install Docker on your system.
For more information about molecule go to their [docs](http://molecule.readthedocs.io/en/latest/).
## License
This project is licensed under MIT License. See [LICENSE](/LICENSE) for more details.
## Credits
This role was migrated from [cloudalchemy.grafana](https://github.com/cloudalchemy/ansible-grafana).

View file

@ -0,0 +1,211 @@
---
grafana_version: latest
grafana_manage_repo: true
grafana_yum_repo: "https://rpm.grafana.com"
grafana_yum_key: "https://rpm.grafana.com/gpg.key"
grafana_rhsm_subscription: ""
grafana_rhsm_repo: ""
grafana_apt_release_channel: stable
grafana_apt_arch: "{{ 'arm64' if ansible_facts['architecture'] == 'aarch64' else 'amd64' }}"
grafana_apt_repo_uri: "https://apt.grafana.com/"
grafana_apt_repo: "deb [arch={{ grafana_apt_arch }} signed-by=/usr/share/keyrings/grafana.asc] {{ grafana_apt_repo_uri }} {{ grafana_apt_release_channel }} main"
grafana_apt_key: "https://apt.grafana.com/gpg.key"
grafana_apt_name: "grafana"
# Should we use the provisioning capability when possible (provisioning require grafana >= 5.0)
grafana_use_provisioning: true
# Should the provisioning be kept synced. If true, previous provisioned objects will be removed if not referenced anymore.
grafana_provisioning_synced: false
# Should we provision dashboards by following the files structure. This sets the foldersFromFilesStructure option
grafana_provisioning_dashboards_from_file_structure: false
# To enable the use of ports below 1024 for unprivileged processes linux needs to set CAP_NET_BIND_SERVICE.
# This has some security implications, and should be a conscious choice.
# Get informed by reading: http://man7.org/linux/man-pages/man7/capabilities.7.html
grafana_cap_net_bind_service: false
grafana_ini_default:
instance_name: "{{ ansible_facts['fqdn'] | default(ansible_host) | default(inventory_hostname) }}"
paths:
logs: "/var/log/grafana"
data: "/var/lib/grafana"
server:
http_addr: "0.0.0.0"
http_port: 3000
# External Grafana address. Variable maps to "root_url" in grafana server section
#root_url: "http://{{ grafana_ini.server.http_addr }}:{{ grafana_ini.server.http_port }}"
domain: "{{ ansible_facts['fqdn'] | default(ansible_host) | default('localhost') }}"
# Additional options for grafana "server" section
# This section WILL omit options for: http_addr, http_port, domain, and root_url, as those settings are set by variables listed before
protocol: http
enforce_domain: false
socket: ""
cert_key: ""
cert_file: ""
enable_gzip: false
static_root_path: public
router_logging: false
serve_from_sub_path: false
# Variables correspond to ones in grafana.ini configuration file
# Security
security:
admin_user: admin
admin_password: ""
# secret_key: ""
# login_remember_days: 7
# cookie_username: grafana_user
# cookie_remember_name: grafana_remember
# disable_gravatar: true
# data_source_proxy_whitelist:
# Database setup
database:
type: sqlite3
# host: 127.0.0.1:3306
# name: grafana
# user: root
# password: ""
# url: ""
# ssl_mode: disable
# path: grafana.db
# max_idle_conn: 2
# max_open_conn: ""
# log_queries: ""
# User management and registration
users:
allow_sign_up: false
# allow_org_create: true
# auto_assign_org: true
auto_assign_org_role: Viewer
# login_hint: "email or username"
default_theme: dark
# external_manage_link_url: ""
# external_manage_link_name: ""
# external_manage_info: ""
# grafana authentication mechanisms
auth: {}
# disable_login_form: false
# oauth_auto_login: false
# disable_signout_menu: false
# signout_redirect_url: ""
# anonymous:
# org_name: "Main Organization"
# org_role: Viewer
# ldap:
# config_file: "/etc/grafana/ldap.toml"
# allow_sign_up: false
# basic:
# enabled: true
grafana_api_url: "{{ grafana_ini.server.root_url }}"
grafana_ldap: {}
# verbose_logging: false
# servers:
# host: 127.0.0.1
# port: 389 # 636 for SSL
# use_ssl: false
# start_tls: false
# ssl_skip_verify: false
# root_ca_cert: /path/to/certificate.crt
# bind_dn: "cn=admin,dc=grafana,dc=org"
# bind_password: grafana
# search_filter: "(cn=%s)" # "(sAMAccountName=%s)" on AD
# search_base_dns:
# - "dc=grafana,dc=org"
# group_search_filter: "(&(objectClass=posixGroup)(memberUid=%s))"
# group_search_base_dns:
# - "ou=groups,dc=grafana,dc=org"
# attributes:
# name: givenName
# surname: sn
# username: sAMAccountName
# member_of: memberOf
# email: mail
# group_mappings:
# - name: Main Org.
# id: 1
# groups:
# - group_dn: "cn=admins,ou=groups,dc=grafana,dc=org"
# org_role: Admin
# - group_dn: "cn=editors,ou=groups,dc=grafana,dc=org"
# org_role: Editor
# - group_dn: "*"
# org_role: Viewer
# - name: Alternative Org
# id: 2
# groups:
# - group_dn: "cn=alternative_admins,ou=groups,dc=grafana,dc=org"
# org_role: Admin
#######
# Plugins to install from https://grafana.com/plugins
grafana_plugins: []
# - raintank-worldping-app
# Dashboards from https://grafana.com/dashboards
grafana_dashboards: []
# - dashboard_id: '4271'
# revision_id: '3'
# datasource: 'Prometheus'
# - dashboard_id: '1860'
# revision_id: '4'
# datasource: 'Prometheus'
# - dashboard_id: '358'
# revision_id: '1'
# datasource: 'Prometheus'
grafana_dashboards_dir: "dashboards"
# Alert notification channels to configure
grafana_alert_notifications: []
# - name: "Email Alert"
# type: "email"
# uid: channel1
# is_default: true
# settings:
# addresses: "example@example.com"
# Alert resources channels to configure
grafana_alert_resources: {}
# Datasources to configure
grafana_datasources: []
# - name: "Prometheus"
# type: "prometheus"
# access: "proxy"
# url: "http://prometheus.mydomain"
# basicAuth: true
# basicAuthUser: "admin"
# basicAuthPassword: "password"
# isDefault: true
# jsonData:
# tlsAuth: false
# tlsAuthWithCACert: false
# tlsSkipVerify: true
# API keys to configure
grafana_api_keys: []
# - name: "admin"
# role: "Admin"
# - name: "viewer"
# role: "Viewer"
# - name: "editor"
# role: "Editor"
# The location where the keys should be stored.
grafana_api_keys_dir: "{{ lookup('env', 'HOME') }}/grafana/keys"
grafana_environment: {}

View file

@ -0,0 +1,48 @@
---
- name: "Restart grafana"
ansible.builtin.service:
name: grafana-server
state: restarted
become: true
listen: "restart_grafana"
tags:
- grafana_run
- name: "Set privileges on provisioned dashboards"
ansible.builtin.file:
path: "{{ grafana_ini.paths.data }}/dashboards"
recurse: true
owner: "grafana"
group: "grafana"
mode: "u=rwX,g=rX,o=rX"
become: true
listen: "provisioned dashboards changed"
- name: "Set privileges on provisioned dashboards directory"
ansible.builtin.file:
path: "{{ grafana_ini.paths.data }}/dashboards"
state: "directory"
recurse: false
mode: "0755"
become: true
listen: "provisioned dashboards changed"
- name: "Find dashboards subdirectories"
ansible.builtin.find:
paths: "{{ grafana_ini.paths.data }}/dashboards"
recurse: yes
file_type: directory
register: __dashboards_subdirs
become: true
listen: "provisioned dashboards changed"
- name: "Set privileges on provisioned dashboards sub-directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
recurse: false
mode: "0755"
with_items:
- "{{ __dashboards_subdirs.files | map(attribute='path') | list }}"
become: true
listen: "provisioned dashboards changed"

View file

@ -0,0 +1,31 @@
---
galaxy_info:
author: "Grafana"
description: "Grafana - platform for analytics and monitoring"
license: "GPL-3.0-or-later"
min_ansible_version: "2.9"
platforms:
- name: Ubuntu
versions:
- bionic
- xenial
- name: Debian
versions:
- stretch
- buster
- name: EL
versions:
- "7"
- "8"
- name: Fedora
versions:
- "30"
- "31"
galaxy_tags:
- grafana
- dashboard
- alerts
- alerting
- presentation
- monitoring
- metrics

View file

@ -0,0 +1,107 @@
---
- name: "Run role"
hosts: all
any_errors_fatal: true
roles:
- grafana.grafana.grafana
vars:
grafana_version: 6.2.5
grafana_ini:
security:
admin_user: admin
admin_password: "password"
server:
http_addr: "127.0.0.1"
auth:
login_maximum_inactive_lifetime_days: 42
disable_login_form: false
oauth_auto_login: false
disable_signout_menu: false
signout_redirect_url: ""
anonymous:
org_name: "Main Organization"
org_role: Viewer
ldap:
config_file: "/etc/grafana/ldap.toml"
allow_sign_up: false
basic:
enabled: true
log:
mode: syslog
level: warn
grafana_ldap:
verbose_logging: false
servers:
host: 127.0.0.1
port: 389
use_ssl: false
start_tls: false
ssl_skip_verify: false
root_ca_cert: /path/to/certificate.crt
bind_dn: "cn=admin,dc=grafana,dc=org"
bind_password: grafana
search_filter: "(cn=%s)"
search_base_dns:
- "dc=grafana,dc=org"
group_search_filter: "(&(objectClass=posixGroup)(memberUid=%s))"
group_search_base_dns:
- "ou=groups,dc=grafana,dc=org"
attributes:
name: givenName
surname: sn
username: sAMAccountName
member_of: memberOf
email: mail
group_mappings:
- name: "Main Organization"
id: 1
groups:
- group_dn: "cn=admins,ou=groups,dc=grafana,dc=org"
org_role: Admin
- group_dn: "cn=editors,ou=groups,dc=grafana,dc=org"
org_role: Editor
- group_dn: "*"
org_role: Viewer
- name: "Alternative Org"
id: 2
groups:
- group_dn: "cn=alternative_admins,ou=groups,dc=grafana,dc=org"
org_role: Admin
grafana_api_keys:
- name: "admin"
role: "Admin"
- name: "viewer"
role: "Viewer"
- name: "editor"
role: "Editor"
grafana_api_keys_dir: "/tmp/grafana/keys"
grafana_plugins:
- raintank-worldping-app
grafana_alert_notifications:
notifiers:
- name: "Email Alert"
type: "email"
uid: notifier1
is_default: true
settings:
addresses: "example@example.com"
grafana_dashboards:
- dashboard_id: '1860'
revision_id: '4'
datasource: 'Prometheus'
- dashboard_id: '358'
revision_id: '1'
datasource: 'Prometheus'
grafana_datasources:
- name: "Prometheus"
type: "prometheus"
access: "proxy"
url: "http://prometheus.mydomain"
basicAuth: true
basicAuthUser: "admin"
basicAuthPassword: "password"
isDefault: true
jsonData:
tlsAuth: false
tlsAuthWithCACert: false
tlsSkipVerify: true

View file

@ -0,0 +1,52 @@
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/grafana",
"/var/log/grafana",
"/var/lib/grafana",
"/var/lib/grafana/dashboards",
"/var/lib/grafana/plugins",
"/var/lib/grafana/plugins/raintank-worldping-app"
]
files = [
"/etc/grafana/grafana.ini",
"/etc/grafana/ldap.toml"
]
for directory in dirs:
d = host.file(directory)
assert d.is_directory
assert d.exists
for file in files:
f = host.file(file)
assert f.exists
assert f.is_file
def test_service(host):
s = host.service("grafana-server")
# assert s.is_enabled
assert s.is_running
def test_packages(host):
p = host.package("grafana")
assert p.is_installed
assert p.version == "6.2.5"
def test_socket(host):
assert host.socket("tcp://127.0.0.1:3000").is_listening
def test_custom_auth_option(host):
f = host.file("/etc/grafana/grafana.ini")
assert f.contains("login_maximum_inactive_lifetime_days = 42")

View file

@ -0,0 +1,11 @@
---
- name: "Run role"
hosts: all
any_errors_fatal: true
roles:
- grafana.grafana.grafana
vars:
grafana_ini:
security:
admin_user: admin
admin_password: password

View file

@ -0,0 +1,50 @@
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/grafana",
"/var/log/grafana",
"/var/lib/grafana",
"/var/lib/grafana/dashboards",
"/var/lib/grafana/plugins"
]
files = [
"/etc/grafana/grafana.ini"
]
for directory in dirs:
d = host.file(directory)
assert d.is_directory
assert d.exists
for file in files:
f = host.file(file)
assert f.exists
assert f.is_file
def test_service(host):
s = host.service("grafana-server")
# assert s.is_enabled
assert s.is_running
def test_packages(host):
p = host.package("grafana")
assert p.is_installed
def test_socket(host):
assert host.socket("tcp://0.0.0.0:3000").is_listening
def test_yum_repo(host):
if host.system_info.distribution in ['centos', 'redhat', 'fedora']:
f = host.file("/etc/yum.repos.d/grafana.repo")
assert f.exists

View file

@ -0,0 +1,43 @@
---
- name: "Ensure grafana key directory exists"
ansible.builtin.file:
path: "{{ grafana_api_keys_dir }}/{{ inventory_hostname }}"
state: directory
mode: "0755"
become: false
delegate_to: localhost
- name: "Check api key list"
ansible.builtin.uri:
url: "{{ grafana_api_url }}/api/auth/keys"
user: "{{ grafana_ini.security.admin_user }}"
password: "{{ grafana_ini.security.admin_password }}"
force_basic_auth: true
return_content: true
register: __existing_api_keys
no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}"
- name: "Create grafana api keys"
ansible.builtin.uri:
url: "{{ grafana_api_url }}/api/auth/keys"
user: "{{ grafana_ini.security.admin_user }}"
password: "{{ grafana_ini.security.admin_password }}"
force_basic_auth: true
method: POST
body_format: json
body: "{{ item | to_json }}"
loop: "{{ grafana_api_keys }}"
register: __new_api_keys
no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}"
when: "((__existing_api_keys['json'] | selectattr('name', 'equalto', item['name'])) | list) | length == 0"
- name: "Create api keys file to allow the keys to be seen and used by other automation"
ansible.builtin.copy:
dest: "{{ grafana_api_keys_dir }}/{{ inventory_hostname }}/{{ item['item']['name'] }}.key"
content: "{{ item['json']['key'] }}"
backup: false
mode: "0644"
loop: "{{ __new_api_keys['results'] }}"
become: false
delegate_to: localhost
when: "item['json'] is defined"

View file

@ -0,0 +1,107 @@
---
- name: "Ensure grafana directories exist"
ansible.builtin.file:
path: "{{ item.path }}"
state: "directory"
owner: "{{ item.owner | default('root') }}"
group: "{{ item.group | default('grafana') }}"
mode: "{{ item.mode | default('0755') }}"
loop:
- path: "/etc/grafana"
- path: "/etc/grafana/datasources"
- path: "/etc/grafana/provisioning"
- path: "/etc/grafana/provisioning/datasources"
- path: "/etc/grafana/provisioning/dashboards"
- path: "/etc/grafana/provisioning/notifiers"
- path: "/etc/grafana/provisioning/notification"
- path: "/etc/grafana/provisioning/plugins"
- path: "/etc/grafana/provisioning/alerting"
- path: "{{ grafana_ini.paths.logs }}"
owner: grafana
- path: "{{ grafana_ini.paths.data }}"
owner: grafana
- path: "{{ grafana_ini.paths.data }}/dashboards"
owner: grafana
- path: "{{ grafana_ini.paths.data }}/plugins"
owner: grafana
- name: "Create grafana main configuration file"
ansible.builtin.template:
src: "grafana.ini.j2"
dest: "/etc/grafana/grafana.ini"
owner: "root"
group: "grafana"
mode: "0640"
no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}"
notify: restart_grafana
- name: "Create grafana LDAP configuration file"
ansible.builtin.template:
src: "ldap.toml.j2"
dest: "{{ grafana_ini.auth.ldap.config_file | default('/etc/grafana/ldap.toml') }}"
owner: "root"
group: "grafana"
mode: "0640"
no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}"
notify: restart_grafana
when:
- "'ldap' in grafana_ini.auth"
- "'enabled' not in grafana_ini.auth.ldap or grafana_ini.auth.ldap.enabled"
- name: "Enable grafana socket"
when:
- "grafana_ini.server.protocol is defined and grafana_ini.server.protocol == 'socket'"
- "grafana_ini.server.socket | dirname != '/var/run'"
block:
- name: "Create grafana socket directory"
ansible.builtin.file:
path: "{{ grafana_ini.server.socket | dirname }}"
state: "directory"
mode: "0775"
owner: "grafana"
group: "grafana"
- name: "Ensure grafana socket directory created on startup"
ansible.builtin.template:
src: "tmpfiles.j2"
dest: "/etc/tmpfiles.d/grafana.conf"
owner: "root"
group: "root"
mode: "0644"
- name: "Enable grafana to ports lower than port 1024"
community.general.capabilities:
path: /usr/sbin/grafana-server
capability: CAP_NET_BIND_SERVICE+ep
state: present
when:
- "grafana_ini.server.http_port | int <= 1024"
- "grafana_cap_net_bind_service"
- name: Create a directory for overrides.conf unit file if it does not exist
ansible.builtin.file:
path: /etc/systemd/system/grafana-server.service.d
state: directory
mode: '0755'
when:
- "grafana_ini.server.http_port | int <= 1024"
- "grafana_cap_net_bind_service"
- name: "Enable grafana to ports lower than port 1024 in systemd unitfile"
ansible.builtin.blockinfile:
path: /etc/systemd/system/grafana-server.service.d/overrides.conf
create: true
block: |
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
when:
- "grafana_ini.server.http_port | int <= 1024"
- "grafana_cap_net_bind_service"
- name: "Enable and start Grafana systemd unit"
ansible.builtin.systemd:
name: "grafana-server"
enabled: true
state: started
daemon_reload: true

View file

@ -0,0 +1,245 @@
---
- name: "Create local grafana dashboard directory"
become: false
delegate_to: localhost
run_once: true
ansible.builtin.tempfile:
state: directory
register: __tmp_dashboards
changed_when: false
check_mode: false
- name: "Download grafana.net dashboards"
become: false
delegate_to: localhost
when:
- not ansible_check_mode
- "grafana_dashboards | length > 0"
block:
- name: "Get latest revision id"
ansible.builtin.uri:
url: "https://grafana.com/api/dashboards/{{ item.dashboard_id }}"
method: GET
return_content: yes
register: __dashboard_info
loop: "{{ grafana_dashboards }}"
when: item.dashboard_url is not defined
- name: "Extract revision_id if not defined in grafana_dashboards"
ansible.builtin.set_fact:
__dashboards_with_revision: >-
{{
__dashboard_info.results | map(attribute='json.revision')
| map('default', 1) | map('community.general.dict_kv', 'revision_id')
| zip(grafana_dashboards) | map('combine')
}}
- name: "Download grafana dashboard from grafana.net to local directory"
ansible.builtin.get_url:
url: "{{ item.dashboard_url if item.dashboard_url is defined else
('https://grafana.com/api/dashboards/' ~ item.dashboard_id ~
'/revisions/' ~ item.revision_id | default(1) ~ '/download') }}"
dest: "{{ __tmp_dashboards.path }}/{{ item.dashboard_id ~ '.json'
if item.dashboard_id is defined else item.dashboard_url | basename}}"
mode: "0644"
register: __download_dashboards
until: "__download_dashboards is succeeded"
retries: 5
delay: 2
changed_when: false
loop: "{{ __dashboards_with_revision }}"
# As noted in [1] an exported dashboard replaces the exporter's datasource
# name with a representative name, something like 'DS_GRAPHITE'. The name
# is different for each datasource plugin, but always begins with 'DS_'.
# In the rest of the data, the same name is used, but captured in braces,
# for example: '${DS_GRAPHITE}'.
#
# [1] http://docs.grafana.org/reference/export_import/#import-sharing-with-grafana-2-x-or-3-0
#
# The data structure looks (massively abbreviated) something like:
#
# "name": "DS_GRAPHITE",
# "datasource": "${DS_GRAPHITE}",
#
# If we import the downloaded dashboard verbatim, it will not automatically
# be connected to the data source like we want it. The Grafana UI expects
# us to do the final connection by hand, which we do not want to do.
# So, in the below task we ensure that we replace instances of this string
# with the data source name we want.
# To make sure that we're not being too greedy with the regex replacement
# of the data source to use for each dashboard that's uploaded, we make the
# regex match very specific by using the following:
#
# 1. Literal boundaries for " on either side of the match.
# 2. Non-capturing optional group matches for the ${} bits which may, or
# or may not, be there..
# 3. A case-sensitive literal match for DS .
# 4. A one-or-more case-sensitive match for the part that follows the
# underscore, with only A-Z, 0-9 and - or _ allowed.
#
# This regex can be tested and understood better by looking at the
# matches and non-matches in https://regex101.com/r/f4Gkvg/6
- name: "Set the correct data source name in the dashboard"
ansible.builtin.replace:
dest: "{{ item.dest }}"
regexp: '"(?:\${)?DS_[A-Z0-9_-]+(?:})?"'
replace: '"{{ item.item.datasource }}"'
changed_when: false
loop: "{{ __download_dashboards.results }}"
loop_control:
label: "{{ item.item }}"
- name: "Import grafana dashboards via api"
community.grafana.grafana_dashboard:
grafana_url: "{{ grafana_api_url }}"
grafana_user: "{{ grafana_ini.security.admin_user }}"
grafana_password: "{{ grafana_ini.security.admin_password }}"
path: "{{ item }}"
commit_message: "Updated by ansible role {{ ansible_role_name }}"
state: present
overwrite: true
no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}"
with_fileglob:
- "{{ __tmp_dashboards.path }}/*"
- "{{ grafana_dashboards_dir }}/*.json"
when: "not grafana_use_provisioning"
- name: "Import grafana dashboards through provisioning"
when: grafana_use_provisioning
block:
- name: "Create/Update dashboards file (provisioning)"
ansible.builtin.copy:
dest: "/etc/grafana/provisioning/dashboards/ansible.yml"
content: |
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: ''
type: file
options:
path: "{{ grafana_ini.paths.data }}/dashboards"
foldersFromFilesStructure: {{ grafana_provisioning_dashboards_from_file_structure | bool | to_nice_yaml }}
backup: false
owner: root
group: grafana
mode: "0640"
become: true
notify: restart_grafana
- name: "Register previously copied dashboards"
ansible.builtin.find:
paths: "{{ grafana_ini.paths.data }}/dashboards"
hidden: true
recurse: true
patterns:
- "*.json"
register: __dashboards_present
when: grafana_provisioning_synced | bool
- name: "Register previously created folders"
ansible.builtin.find:
paths: "{{ grafana_ini.paths.data }}/dashboards/"
recurse: yes
file_type: directory
register: __dashboards_dir_present
become: true
when: grafana_provisioning_synced
- name: "Import grafana.net dashboards"
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ grafana_ini.paths.data }}/dashboards/{{ item | basename }}"
owner: root
group: grafana
mode: "0644"
with_fileglob:
- "{{ __tmp_dashboards.path }}/*"
become: true
register: __dashboards_copied
notify: "provisioned dashboards changed"
when: not ansible_check_mode
- name: "Verify if custom grafana dashboards dir exist"
ansible.builtin.stat:
path: "{{ grafana_dashboards_dir }}"
delegate_to: localhost
become: false
register: __grafana_custom_dashboards_dir
- name: "Import custom grafana dashboards"
when: __grafana_custom_dashboards_dir.stat.exists
block:
- name: "Find which directories to create"
ansible.builtin.find:
paths: "{{ grafana_dashboards_dir }}/"
recurse: yes
file_type: directory
register: __dashboards_subdirs
delegate_to: localhost
become: false
- name: "Create dashboard folders"
ansible.builtin.file:
path: "{{ grafana_ini.paths.data }}/dashboards/{{ item }}"
state: "directory"
recurse: false
owner: "grafana"
group: "grafana"
mode: "0755"
loop: "{{ __dashboards_subdirs.files | map(attribute='path') | sort | regex_replace(grafana_dashboards_dir + '/*', '') }}"
become: true
register: __dashboards_dir_created
when: not ansible_check_mode
- name: "Copy dashboard files"
ansible.builtin.copy:
src: "{{ item.path }}"
dest: '{{ grafana_ini.paths.data }}/dashboards/{{ item.path | regex_replace(grafana_dashboards_dir + "/*", "") }}'
owner: root
group: grafana
mode: "0644"
loop: "{{ __found_dashboards.files | default([]) }}"
become: true
register: __dashboards_copied_custom
notify: "provisioned dashboards changed"
when: not ansible_check_mode
- name: "Register present and copied folders list"
ansible.builtin.set_fact:
__folders_present_list: "{{ __dashboards_dir_present.files | default([]) | map(attribute='path') | list }}"
__folders_copied_list: "{{ __dashboards_dir_created.results | default([]) | map(attribute='path') | list }}"
when: not ansible_check_mode
- name: "Register present and copied dashboards list"
ansible.builtin.set_fact:
__dashboards_present_list: "{{ __dashboards_present.files | default([]) | map(attribute='path') | list }}"
__dashboards_copied_list: "{{
(
(__dashboards_copied.results | default([]) | map(attribute='dest') | list) +
(__dashboards_copied_custom.results | default([]) | map(attribute='dest') | list)
) | list
}}"
when: not ansible_check_mode
- name: "Remove dashboards not present on deployer machine (synchronize)"
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ __dashboards_present_list | difference(__dashboards_copied_list) }}"
become: true
when:
- __dashboards_present_list is defined and __dashboards_present_list
- __dashboards_copied_list is defined and __dashboards_copied_list
- grafana_provisioning_synced | bool
- not ansible_check_mode
- name: "Remove folders not present on deployer machine (synchronize)"
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ __folders_present_list | difference(__folders_copied_list) }}"
become: true
when: grafana_provisioning_synced and not ansible_check_mode

View file

@ -0,0 +1,40 @@
---
- name: "Ensure datasources exist (via API)"
community.grafana.grafana_datasource:
grafana_url: "{{ grafana_api_url }}"
grafana_user: "{{ grafana_ini.security.admin_user }}"
grafana_password: "{{ grafana_ini.security.admin_password }}"
name: "{{ item.name }}"
ds_url: "{{ item.url }}"
ds_type: "{{ item.type }}"
access: "{{ item.access | default(omit) }}"
is_default: "{{ item.isDefault | default(omit) }}"
basic_auth_user: "{{ item.basicAuthUser | default(omit) }}"
basic_auth_password: "{{ item.basicAuthPassword | default(omit) }}"
database: "{{ item.database | default(omit) }}"
user: "{{ item.user | default(omit) }}"
password: "{{ item.password | default(omit) }}"
aws_auth_type: "{{ item.aws_auth_type | default(omit) }}"
aws_default_region: "{{ item.aws_default_region | default(omit) }}"
aws_access_key: "{{ item.aws_access_key | default(omit) }}"
aws_secret_key: "{{ item.aws_secret_key | default(omit) }}"
aws_credentials_profile: "{{ item.aws_credentials_profile | default(omit) }}"
aws_custom_metrics_namespaces: "{{ item.aws_custom_metrics_namespaces | default(omit) }}"
loop: "{{ grafana_datasources }}"
when: "not grafana_use_provisioning"
- name: "Create/Update datasources file (provisioning)"
ansible.builtin.copy:
dest: "/etc/grafana/provisioning/datasources/ansible.yml"
content: |
apiVersion: 1
deleteDatasources: []
datasources:
{{ grafana_datasources | to_nice_yaml }}
backup: false
owner: root
group: grafana
mode: 0640
notify: restart_grafana
become: true
when: "grafana_use_provisioning"

View file

@ -0,0 +1,133 @@
---
- name: "Remove conflicting grafana packages"
ansible.builtin.package:
name: grafana-data
state: absent
- name: "Install dependencies"
ansible.builtin.package:
name: "{{ _grafana_dependencies }}"
state: present
update_cache: true
when:
- _grafana_dependencies is defined
- _grafana_dependencies | length > 0
- name: "Prepare zypper"
when:
- "ansible_facts['pkg_mgr'] == 'zypper'"
- "(grafana_manage_repo)"
environment: "{{ grafana_environment }}"
block:
- name: import Grafana RPM Key
ansible.builtin.rpm_key:
state: present
key: "{{ grafana_yum_key }}"
- name: "Add Grafana zypper repository"
community.general.zypper_repository:
name: grafana
description: grafana
repo: "{{ grafana_yum_repo }}"
enabled: true
disable_gpg_check : "{{ false if (grafana_yum_key) else omit }}"
runrefresh: true
when: "(not grafana_rhsm_repo)"
- name: "Prepare yum/dnf"
when:
- "ansible_facts['pkg_mgr'] in ['yum', 'dnf']"
- "(grafana_manage_repo)"
environment: "{{ grafana_environment }}"
block:
- name: "Add Grafana yum/dnf repository"
ansible.builtin.yum_repository:
name: grafana
description: grafana
baseurl: "{{ grafana_yum_repo }}"
enabled: true
gpgkey: "{{ grafana_yum_key | default(omit) }}"
repo_gpgcheck: "{{ true if (grafana_yum_key) else omit }}"
gpgcheck: "{{ true if (grafana_yum_key) else omit }}"
when: "(not grafana_rhsm_repo)"
- name: "Attach RHSM subscription"
when: "(grafana_rhsm_subscription)"
block:
- name: "Check if Grafana RHSM subscription is enabled"
ansible.builtin.command:
cmd: "subscription-manager list --consumed --matches={{ grafana_rhsm_subscription | quote }} --pool-only"
register: __subscription_manager_consumed
changed_when: false
when: (grafana_rhsm_subscription)
- name: "Find RHSM repo subscription pool id"
ansible.builtin.command:
cmd: "subscription-manager list --available --matches={{ grafana_rhsm_subscription | quote }} --pool-only"
register: __subscription_manager_available
changed_when: false
when:
- "(grafana_rhsm_subscription)"
- "__subscription_manager_consumed.stdout | length <= 0"
- name: "Attach RHSM subscription"
ansible.builtin.command:
cmd: "subscription-manager attach --pool={{ __subscription_manager_available.stdout }}"
register: __subscription_manager_attach
changed_when: "__subscription_manager_attach.stdout is search('Successfully attached a subscription')"
failed_when: "__subscription_manager_attach.stdout is search('could not be found')"
when:
- "(grafana_rhsm_subscription)"
- "__subscription_manager_consumed.stdout | default() | length <= 0"
- "__subscription_manager_available.stdout | default() | length > 0"
- name: "Enable RHSM repository"
community.general.rhsm_repository:
name: "{{ grafana_rhsm_repo }}"
state: enabled
when: (grafana_rhsm_repo)
- name: "Prepare apt"
when:
- "ansible_facts['pkg_mgr'] == 'apt'"
- "(grafana_manage_repo)"
environment: "{{ grafana_environment }}"
block:
- name: "Use deb822 format?"
ansible.builtin.set_fact:
__use_deb822: "{{ (ansible_facts['distribution'] == 'Debian' and (ansible_facts['distribution_major_version'] | int) >= 12) or
(ansible_facts['distribution'] == 'Ubuntu' and (ansible_facts['distribution_major_version'] | int) >= 24) }}"
- name: "Import Grafana apt gpg key"
ansible.builtin.get_url:
url: "{{ grafana_apt_key }}"
dest: /usr/share/keyrings/grafana.asc
mode: "0644"
when: not __use_deb822
- name: "Add Grafana apt repository"
when: not __use_deb822
ansible.builtin.apt_repository:
repo: "{{ grafana_apt_repo }}"
update_cache: true
- name: "Add Grafana apt repository"
when: __use_deb822
ansible.builtin.deb822_repository:
name: "{{ grafana_apt_name }}"
types: deb
uris: "{{ grafana_apt_repo_uri }}"
suites: "{{ grafana_apt_release_channel }}"
components:
- main
architectures:
- "{{ grafana_apt_arch }}"
signed_by: "{{ grafana_apt_key }}"
register: __update_cache
- name: "Update apt cache"
ansible.builtin.apt:
update_cache: true
when: __update_cache is defined and __update_cache.changed
- name: "Install Grafana"
ansible.builtin.package:
name: "{{ grafana_package }}"
state: "{{ (grafana_version == 'latest') | ternary('latest', 'present') }}"
notify: restart_grafana

View file

@ -0,0 +1,133 @@
---
- name: Inherit default vars
ansible.builtin.set_fact:
grafana_ini: "{{ grafana_ini_default | ansible.builtin.combine(grafana_ini | default({}), recursive=true) }}"
no_log: "{{ 'false' if lookup('env', 'CI') else 'true' }}"
tags:
- always
- name: "Gather variables for each operating system"
ansible.builtin.include_vars: "{{ distrovars }}"
vars:
distrovars: "{{ lookup('first_found', params, errors='ignore') }}"
params:
skip: true
files:
- "{{ ansible_facts['distribution'] | lower }}-{{ ansible_facts['distribution_version'] | lower }}.yml"
- "{{ ansible_facts['distribution'] | lower }}-{{ ansible_facts['distribution_major_version'] | lower }}.yml"
- "{{ ansible_facts['os_family'] | lower }}-{{ ansible_facts['distribution_major_version'] | lower }}.yml"
- "{{ ansible_facts['distribution'] | lower }}.yml"
- "{{ ansible_facts['os_family'] | lower }}.yml"
paths:
- "vars/distro"
tags:
- grafana_install
- grafana_configure
- grafana_datasources
- grafana_notifications
- grafana_dashboards
- name: Preflight
ansible.builtin.include_tasks:
file: preflight.yml
apply:
tags:
- grafana_install
- grafana_configure
- grafana_datasources
- grafana_notifications
- grafana_dashboards
- name: Install
ansible.builtin.include_tasks:
file: install.yml
apply:
become: true
tags:
- grafana_install
- name: Configure
ansible.builtin.include_tasks:
file: configure.yml
apply:
become: true
tags:
- grafana_configure
- name: Plugins
ansible.builtin.include_tasks:
file: plugins.yml
apply:
tags:
- grafana_configure
when: "grafana_plugins != []"
- name: "Restart grafana before configuring datasources and dashboards"
ansible.builtin.meta: flush_handlers
tags:
- grafana_install
- grafana_configure
- grafana_datasources
- grafana_notifications
- grafana_dashboards
- grafana_run
- name: "Wait for grafana to start"
ansible.builtin.wait_for:
host: "{{ grafana_ini.server.http_addr if grafana_ini.server.protocol is undefined or grafana_ini.server.protocol in ['http', 'https'] else omit }}"
port: "{{ grafana_ini.server.http_port if grafana_ini.server.protocol is undefined or grafana_ini.server.protocol in ['http', 'https'] else omit }}"
path: "{{ grafana_ini.server.socket | default() if grafana_ini.server.protocol is defined and grafana_ini.server.protocol == 'socket' else omit }}"
tags:
- grafana_install
- grafana_configure
- grafana_datasources
- grafana_notifications
- grafana_dashboards
- grafana_run
- name: "Api keys"
ansible.builtin.include_tasks:
file: api_keys.yml
apply:
tags:
- grafana_configure
- grafana_run
when: "grafana_api_keys | length > 0"
- name: Datasources
ansible.builtin.include_tasks:
file: datasources.yml
apply:
tags:
- grafana_configure
- grafana_datasources
- grafana_run
when: "grafana_datasources != []"
- name: Notifications
ansible.builtin.include_tasks:
file: notifications.yml
apply:
tags:
- grafana_configure
- grafana_notifications
- grafana_run
when: "grafana_alert_notifications | length > 0 or grafana_alert_resources | length > 0"
- name: Find dashboards to be provisioned
ansible.builtin.find:
paths: "{{ grafana_dashboards_dir }}"
recurse: true
patterns: "*.json"
delegate_to: localhost
become: false
register: __found_dashboards
- name: Dashboards
ansible.builtin.include_tasks:
file: dashboards.yml
apply:
tags:
- grafana_configure
- grafana_dashboards
- grafana_run
when: "grafana_dashboards | length > 0 or __found_dashboards['files'] | length > 0"

View file

@ -0,0 +1,28 @@
---
# legacy config
- name: "Create/Delete/Update alert notifications channels (provisioning)"
ansible.builtin.copy:
content: |
apiVersion: 1
{{ grafana_alert_notifications | to_nice_yaml }}
dest: /etc/grafana/provisioning/notification/ansible.yml
owner: root
group: grafana
mode: "0640"
become: true
notify: restart_grafana
when: grafana_use_provisioning and grafana_alert_notifications | length > 0
# new alert resources
- name: "Create/Delete/Update alert resources (provisioning)"
ansible.builtin.copy:
content: |
apiVersion: 1
{{ grafana_alert_resources | to_nice_yaml }}
dest: /etc/grafana/provisioning/alerting/ansible.yml
owner: root
group: grafana
mode: "0640"
become: true
notify: restart_grafana
when: grafana_use_provisioning and grafana_alert_resources | length > 0

View file

@ -0,0 +1,20 @@
---
- name: "Check which plugins are installed"
ansible.builtin.find:
file_type: directory
recurse: false
paths: "{{ grafana_ini.paths.data }}/plugins"
register: __installed_plugins
- name: "Install plugins"
become: true
ansible.builtin.command:
cmd: "grafana-cli --pluginsDir {{ grafana_ini.paths.data }}/plugins plugins install {{ item }}"
creates: "{{ grafana_ini.paths.data }}/plugins/{{ item }}"
loop: "{{ grafana_plugins | difference(__installed_plugins.files) }}"
register: __plugin_install
until: "__plugin_install is succeeded"
retries: 5
delay: 2
notify:
- restart_grafana

View file

@ -0,0 +1,104 @@
---
- name: "Check variable types"
ansible.builtin.assert:
that:
- grafana_logs_dir is undefined
- grafana_data_dir is undefined
- grafana_server is undefined
- grafana_database is undefined
- grafana_security is undefined
- grafana_remote_cache is undefined
- grafana_welcome_email_on_sign_up is undefined
- grafana_users is undefined
- grafana_auth is undefined
- grafana_auth_generic_oauth is undefined
- grafana_session is undefined
- grafana_analytics is undefined
- grafana_smtp is undefined
- grafana_alerting is undefined
- grafana_unified_alerting is undefined
- grafana_log is undefined
- grafana_metrics is undefined
- grafana_tracing is undefined
- grafana_snapshots is undefined
- grafana_image_storage is undefined
- grafana_date_formats is undefined
- grafana_feature_toggles is undefined
- grafana_plugins_ops is undefined
- grafana_instance is undefined
- grafana_address is undefined
- grafana_port is undefined
- grafana_domain is undefined
- grafana_url is undefined
- grafana_panels is undefined
fail_msg: Check upgrade notes
- name: "Fail when datasources aren't configured when dashboards are set to be installed"
ansible.builtin.fail:
msg: "You need to specify datasources for dashboards!!!"
when: "grafana_dashboards != [] and grafana_datasources == []"
- name: "Fail when grafana admin user isn't set"
ansible.builtin.fail:
msg: "Please specify grafana admin user (grafana_ini.security.admin_user)"
when:
- "grafana_ini.security.admin_user == '' or
grafana_ini.security.admin_user is not defined"
- name: "Fail when grafana admin password isn't set"
ansible.builtin.fail:
msg: "Please specify grafana admin password (grafana_ini.security.admin_password)"
when:
- "grafana_ini.security.admin_password == '' or
grafana_ini.security.admin_password is not defined"
- name: "Fail on incorrect variable types in datasource definitions"
ansible.builtin.fail:
msg: "Boolean variables in grafana_datasources shouldn't be passed as strings. Please remove unneeded apostrophes."
when: "( item.isDefault is defined and item.isDefault is string ) or
( item.basicAuth is defined and item.basicAuth is string )"
loop: "{{ grafana_datasources }}"
- name: "Fail on bad database configuration"
ansible.builtin.fail:
msg: "Invalid database configuration. Please look at http://docs.grafana.org/installation/configuration/#database"
when: "( grafana_ini.database.type == 'sqlite3' and grafana_ini.database.url is defined ) or
( grafana_ini.database.type != 'sqlite3' and grafana_ini.database.path is defined ) or
( grafana_ini.database.type == 'sqlite3' and grafana_ini.database.host is defined ) or
( grafana_ini.database.type == 'sqlite3' and grafana_ini.database.user is defined ) or
( grafana_ini.database.type == 'sqlite3' and grafana_ini.database.password is defined ) or
( grafana_ini.database.type == 'sqlite3' and grafana_ini.database.server_cert_name is defined )"
- name: "Fail when grafana_api_keys uses invalid role names"
ansible.builtin.fail:
msg: "Check grafana_api_keys. The role can only be one of the following values: Viewer, Editor or Admin."
when: "item.role not in ['Viewer', 'Editor', 'Admin']"
loop: "{{ grafana_api_keys }}"
- name: "Fail when grafana_ldap isn't set when grafana_ini.auth.ldap is"
ansible.builtin.fail:
msg: "You need to configure grafana_ldap.servers and grafana_ldap.group_mappings when grafana_ini.auth.ldap is set"
when:
- "'ldap' in grafana_ini.auth"
- "grafana_ldap is not defined or ('servers' not in grafana_ldap or 'group_mappings' not in grafana_ldap)"
- name: "Force grafana_use_provisioning to false if grafana_version is < 5.0 ( grafana_version is set to '{{ grafana_version }}' )"
ansible.builtin.set_fact:
grafana_use_provisioning: false
when:
- "grafana_version != 'latest'"
- "grafana_version is version_compare('5.0', '<')"
- name: "Fail if grafana_ini.server.http_port is lower than 1024 and grafana_cap_net_bind_service is not true"
ansible.builtin.fail:
msg: "Trying to use a port lower than 1024 without setting grafana_cap_net_bind_service."
when:
- "grafana_ini.server.http_port | int <= 1024"
- "not grafana_cap_net_bind_service"
- name: "Fail if grafana_ini.server.socket not defined when in socket mode"
ansible.builtin.fail:
msg: "You need to configure grafana_ini.server.socket when grafana_ini.server.protocol is set to 'socket'"
when:
- "grafana_ini.server.protocol is defined and grafana_ini.server.protocol == 'socket'"
- "grafana_ini.server.socket is undefined or grafana_ini.server.socket == ''"

View file

@ -0,0 +1,28 @@
{{ ansible_managed | comment }}
# More informations:
# http://docs.grafana.org/installation/configuration
# https://github.com/grafana/grafana/blob/master/conf/sample.ini
{% for k, v in grafana_ini.items() %}
{% if v is not mapping %}
{{ k }} = {{ v }}
{% endif %}
{% endfor %}
{% for section, items in grafana_ini.items() %}
{% if items is mapping %}
[{{ section }}]
{% for sub_key, sub_value in items.items() %}
{% if sub_value is mapping %}
[{{ section }}.{{ sub_key }}]
{% for k, v in sub_value.items() %}
{{ k }} = {{ v }}
{% endfor %}
{% else %}
{{ sub_key }} = {{ sub_value }}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,39 @@
{{ ansible_managed | comment }}
# Documentation: http://docs.grafana.org/installation/ldap/
{% if 'verbose_logging' in grafana_ldap %}
verbose_logging = {{ 'true' if grafana_ldap.verbose_logging else 'false' }}
{% endif %}
[[servers]]
{% for k,v in grafana_ldap.servers.items() if k != 'attributes' %}
{% if k == 'port' %}
{{ k }} = {{ v | int }}
{% elif v in [True, False] %}
{{ k }} = {{ 'true' if v else 'false' }}
{% else %}
{{ k }} = {{ v | to_nice_json }}
{% endif %}
{% endfor %}
[servers.attributes]
{% for k,v in grafana_ldap.servers.attributes.items() %}
{{ k }} = {{ v | to_nice_json }}
{% endfor %}
{% for org in grafana_ldap.group_mappings %}
{% if 'name' in org %}
# {{ org.name }}
{% endif %}
{% for group in org.groups %}
[[servers.group_mappings]]
org_id = {{ org.id }}
{% for k,v in group.items() %}
{% if v in [True, False] %}
{{ k }} = {{ 'true' if v else 'false' }}
{% else %}
{{ k }} = "{{ v }}"
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}

View file

@ -0,0 +1,2 @@
{{ ansible_managed | comment }}
d {{ grafana_ini.server.socket | dirname }} 0775 grafana grafana

View file

@ -0,0 +1,6 @@
molecule
docker
pytest-testinfra
jmespath
selinux
passlib

View file

@ -0,0 +1,8 @@
---
grafana_package: "grafana{% if ansible_facts['architecture'] == 'armv6l' %}-rpi{% endif %}{{ (grafana_version != 'latest') | ternary('=' ~ grafana_version, '') }}"
_grafana_dependencies:
- apt-transport-https
- adduser
- ca-certificates
- libfontconfig
- gnupg2

View file

@ -0,0 +1,5 @@
---
grafana_package: "grafana{{ (grafana_version != 'latest') | ternary('-' ~ grafana_version, '') }}"
# https://unix.stackexchange.com/questions/534463/cant-enable-grafana-on-boot-in-fedora-because-systemd-sysv-install-missing
_grafana_dependencies:
- chkconfig

View file

@ -0,0 +1,6 @@
---
grafana_package: "grafana{{ (grafana_version != 'latest') | ternary('-' ~ grafana_version, '') }}"
# https://unix.stackexchange.com/questions/534463/cant-enable-grafana-on-boot-in-fedora-because-systemd-sysv-install-missing
# applies to SuSe too
_grafana_dependencies:
- insserv-compat

View file

@ -0,0 +1,72 @@
|![](https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Warning.svg/156px-Warning.svg.png) | This Ansible role is now in maintenance mode only. We recommend using the Grafana Alloy Role for future deployments and updates.
|---|---|
# Ansible Role for Grafana Agent
Ansible Role to deploy Grafana Agent on Linux hosts. Using this Role, Grafana Agent can be deployed on RedHat, Ubuntu, Debian, CentOS
and Fedora linux distributions.
## Requirements
Please ensure that `curl` is intalled on Ansible controller.
To use this role, You need a YAML file having the Grafana Agent configuration.
## Role Variables
All variables which can be overridden are stored in [./defaults/main.yaml](./defaults/main.yaml) file as well as in table below.
| Variable | Default | Description |
| :------ | :------ | :--------- |
| `grafana_agent_version` | `latest` | version of the agent to install |
| `grafana_agent_base_download_url` | `https://github.com/{{ _grafana_agent_github_org }}/{{ _grafana_agent_github_repo }}/releases/download` | base download url. Github or mirror to download from |
| `grafana_agent_install_dir` | `/opt/grafana-agent/bin` | directory to install the binary to |
| `grafana_agent_binary` | `grafana-agent` | name to use for the binary |
| `grafana_agent_config_dir` | `/etc/grafana-agent` | directory to store the configuration files in |
| `grafana_agent_config_filename` | `config.yaml` | name of the configuration file for the agent |
| `grafana_agent_env_file` | `service.env` | name of the environment file loaded by the system unit file |
| `grafana_agent_service_extra` | "" | dictionary of additional custom settings for the systemd service file |
| `grafana_agent_local_tmp_dir` | `/tmp/grafana-agent` | temporary directory to create on the controller/localhost where the archive will be downloaded to |
| `grafana_agent_data_dir` | `/var/lib/grafana-agent` | the data directory to create for the wal and positions |
| `grafana_agent_wal_dir` | `"{{ grafana_agent_data_dir }}/data"` | wal directory to use, should be a sub-folder of grafana_agent_data_dir, will automatically be created when the agent starts |
| `grafana_agent_positions_dir` | `"{{ grafana_agent_data_dir }}/data"` | positions directory to use, should be a sub-folder of grafana_agent_data_dir, will automatically be created when the agent starts |
| `grafana_agent_mode` | `static` | mode to run Grafana Agent in. Can be "flow" or "static", [Flow Docs](https://grafana.com/docs/agent/latest/flow/) |
| `grafana_agent_user` | `grafana-agent` | os user to create for the agent to run as |
| `grafana_agent_user_group` | `grafana-agent` | os user group to create for the agent |
| `grafana_agent_user_groups` | `[]` | Configurable user groups that the Grafana agent can be put in so that it can access logs |
| `grafana_agent_user_shell` | `/usr/sbin/nologin` | the shell for the user |
| `grafana_agent_user_createhome` | `false` | whether or not to create a home directory for the user |
| `grafana_agent_local_binary_file` | `""` | full path to the local binary if already downloaded or built on the controller, this should only be set, if ansible is not downloading the binary and you have manually downloaded the binary |
| `grafana_agent_flags_extra` | see [./defaults/main.yaml](./defaults/main.yaml) | dictionary of additional command-line flags, run grafana-agent --help for a complete list. [Docs](https://grafana.com/docs/agent/latest/configuration/flags/) |
| `grafana_agent_env_vars` | `{}` | sets Environment variables in the systemd service configuration. |
| `grafana_agent_env_file_vars` | `{}` | dictionary of key/pair values to write to the environment file that is loaded by the service, with the flag `--config.expand-env=true` any generated config files will support the expansion of environment variables at runtime by referencing ${ENVVAR}. be aware of boolean values, when output they will result in the proper-cased string "True" and "False" |
| `grafana_agent_provisioned_config_file` | `""` | path to a config file on the controller that will be used instead of the provided configs below if specified. |
| `grafana_agent_server_config` | see [./defaults/main.yaml](./defaults/main.yaml) | Configures the server of the Agent used to enable self-scraping, [Docs](https://grafana.com/docs/agent/latest/configuration/server-config/) |
| `grafana_agent_metrics_config` | see [./defaults/main.yaml](./defaults/main.yaml) | Configures metric collection, [Docs](https://grafana.com/docs/agent/latest/configuration/metrics-config/) |
| `grafana_agent_logs_config` | see [./defaults/main.yaml](./defaults/main.yaml) | Configures logs collection, [Docs](https://grafana.com/docs/agent/latest/configuration/logs-config/) |
| `grafana_agent_traces_config` | see [./defaults/main.yaml](./defaults/main.yaml) | Configures traces collection, [Docs](https://grafana.com/docs/agent/latest/configuration/traces-config/) |
| `grafana_agent_integrations_config` | see [./defaults/main.yaml](./defaults/main.yaml) | Configures integrations for the agent, [Docs](https://grafana.com/docs/agent/latest/configuration/integrations/) |
## OS Support
The Grafana Agent role has been tested on below Operating Systems
- Ubuntu 22.10, Ubunutu 22.04 LTS, Ubunutu 20.04 LTS, Ubunutu 18.04 LTS
- Fedora 37, Fedora 36
- Debian 11, Debian 10, Debian 9
- CentOS 9 Stream, CentOS 8 Stream, CentOS 7
- AlmaLinux 9, AlmaLinux 8
- RockyLinux 9, RockyLinux 8
## Example Playbooks
See [examples](../../examples)
## License
See [LICENSE](https://github.com/grafana/grafana-ansible-collection/blob/main/LICENSE)
## Author Information
- [Grafana Labs](https://github.com/grafana)
- [Ishan Jain](https://github.com/ishanjainn)
- [Aaron Benton](https://github.com/bentonam)
- [Vitaly Zhuravlev](https://github.com/v-zhuravlev)

View file

@ -0,0 +1,196 @@
---
# version of the agent to install
grafana_agent_version: latest
# base download url. Github or mirror to download from
grafana_agent_base_download_url: "https://github.com/{{ _grafana_agent_github_org }}/{{ _grafana_agent_github_repo }}/releases/download"
# directory to install the binary to
grafana_agent_install_dir: /opt/grafana-agent/bin
# name to use for the binary
grafana_agent_binary: grafana-agent
# directory to store the configuration files in
grafana_agent_config_dir: /etc/grafana-agent
# name of the configuration file for the agent
grafana_agent_config_filename: config.yaml
# name of the environment file loaded by the system unit file
grafana_agent_env_file: service.env
# dictionary of additional custom settings for the systemd service file
grafana_agent_service_extra: {}
# temporary directory to create on the controller/localhost where the archive will be downloaded to
grafana_agent_local_tmp_dir: /tmp/grafana-agent
# data directory to create for the wal and positions
grafana_agent_data_dir: /var/lib/grafana-agent
# wal directory to use, should be a sub-folder of grafana_agent_data_dir, will automatically be created when the agent starts
grafana_agent_wal_dir: "{{ grafana_agent_data_dir }}/wal"
# positions directory to use, should be a sub-folder of grafana_agent_data_dir, will automatically be created when the agent starts
grafana_agent_positions_dir: "{{ grafana_agent_data_dir }}/positions"
# mode to run Grafana Agent in. Can be "flow" or "static".
# Docs: https://grafana.com/docs/agent/latest/flow/
grafana_agent_mode: static
# os user to create for the agent to run as
grafana_agent_user: grafana-agent
# os user group to create for the agent
grafana_agent_user_group: grafana-agent
# Configurable user groups that the grafana agent can be put in so that it can access logs
# (See https://github.com/grafana/grafana-ansible-collection/issues/40)
grafana_agent_user_groups: []
# the shell for the user
grafana_agent_user_shell: /usr/sbin/nologin
# whether or not to create a home directory for the user
grafana_agent_user_createhome: false
# full path to the local binary if already downloaded or built on the controller
# this should only be set, if ansible is not downloading the binary and you have
# manually downloaded the binary
grafana_agent_local_binary_file: ""
# dictionary of additional command-line flags, run grafana-agent --help for a complete list
# Docs: https://grafana.com/docs/agent/latest/static/configuration/flags/
grafana_agent_flags_extra:
config.expand-env: 'true'
config.enable-read-api: 'false'
server.register-instrumentation: 'true'
server.http.address: 127.0.0.1:12345
server.grpc.address: 127.0.0.1:12346
# sets Environment variables in the systemd service configuration.
grafana_agent_env_vars: {}
# dictionary of key/pair values to write to the environment file that is loaded by the service, with the flag --config.expand-env=true
# any generated config files will support the expansion of environment variables at runtime by referencing ${ENVVAR}.
# be aware of boolean values, when output they will result in the proper-cased string "True" and "False"
grafana_agent_env_file_vars: {}
# path to a config file on the controller that will be used instead of the provided configs below if specified.
grafana_agent_provisioned_config_file: ""
#################################################################################################
# Configures the server of the Agent used to enable self-scraping #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/static/configuration/server-config/
# the entire dictionary value for this object is copied to the server: block in the config file
grafana_agent_server_config:
# Log only messages with the given severity or above. Supported values [debug,
# info, warn, error]. This level affects logging for all Agent-level logs, not
# just the HTTP and gRPC server.
#
# Note that some integrations use their own loggers which ignore this
# setting.
log_level: info
#################################################################################################
# Configures metric collection #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/static/configuration/metrics-config/
# the entire dictionary value for this object is copied to the metrics: block in the config file
grafana_agent_metrics_config:
# Configure values for all Prometheus instances
# Docs: https://grafana.com/docs/agent/latest/static/configuration/metrics-config/#global_config
global:
# How frequently should Prometheus instances scrape.
scrape_interval: 1m
# How long to wait before timing out a scrape from a target.
scrape_timeout: 10s
# A dictionary of key/pair static labels to add for all metrics.
external_labels: {}
# Default set of remote_write endpoints. If an instance doesn't define any
# remote_writes, it will use this list.
# Docs: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write
remote_write: []
# The list of Prometheus instances to launch with the agent.
# Docs: https://grafana.com/docs/agent/latest/static/configuration/metrics-config/#metrics_instance_config
configs: []
# - name: name-of-scrape-job
# # Docs: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
# scrape_configs: []
# # Optional list of remote_write targets, if not specified metrics.global.remote_write is used
# # Docs: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write
# remote_write: []
# Configure the directory used by instances to store their WAL.
#
# The Grafana Agent assumes that all folders within wal_directory are managed by
# the agent itself.
wal_directory: "{{ grafana_agent_wal_dir }}"
# Configures how long ago an abandoned (not associated with an instance) WAL
# may be written to before being eligible to be deleted
wal_cleanup_age: 12h
# Configures how often checks for abandoned WALs to be deleted are performed.
# A value of 0 disables periodic cleanup of abandoned WALs
wal_cleanup_period: 30m
#################################################################################################
# Configures logs collection #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/static/configuration/logs-config/
# the entire dictionary value for this object is copied to the logs: block in the config file
grafana_agent_logs_config:
# Directory to store Loki Promtail positions files in. Positions files are
# required to read logs, and are used to store the last read offset of log
# sources. The positions files will be stored in
# <positions_directory>/<logs_instance_config.name>.yml.
#
# Optional only if every config has a positions.filename manually provided.
#
# This directory will be automatically created if it doesn't exist.
positions_directory: "{{ grafana_agent_positions_dir }}"
# Configure values for all Loki Promtail instances.
global:
# Docs: https://grafana.com/docs/agent/latest/static/configuration/logs-config/#logs_instance_config
clients: []
#################################################################################################
# Configures traces collection #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/static/configuration/traces-config/
# the entire dictionary value for this object is copied to the traces: block in the config file
grafana_agent_traces_config:
# Docs: https://grafana.com/docs/agent/latest/static/configuration/traces-config/#traces_instance_config
configs: []
#################################################################################################
# Configures integrations for the agent #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/static/configuration/integrations/
# the entire dictionary value for this object is copied to the integrations: block in the config file
grafana_agent_integrations_config:
# Automatically collect metrics from enabled integrations. If disabled,
# integrations will be run but not scraped and thus not remote_written. Metrics
# for integrations will be exposed at /integrations/<integration_key>/metrics
# and can be scraped by an external process.
scrape_integrations: true
# Controls the Agent integration
agent:
# Enables the Agent integration, allowing the Agent to automatically
# collect and send metrics about itself.
enabled: true
# Allows for relabeling labels on the target.
relabel_configs: []
# Relabel metrics coming from the integration, allowing to drop series
# from the integration that you don't care about.
metric_relabel_configs: []
# Controls the node_exporter integration
# Docs: https://grafana.com/docs/agent/latest/static/configuration/integrations/node-exporter-config/
node_exporter:
enabled: true

View file

@ -0,0 +1,12 @@
---
- name: Restart Grafana Agent
become: true
ansible.builtin.systemd:
name: grafana-agent
state: restarted
daemon_reload: true
listen: "restart grafana-agent"
- name: Check Grafana Agent is started properly
ansible.builtin.include_tasks: ga-started.yaml
listen: "restart grafana-agent"

View file

@ -0,0 +1,25 @@
---
dependencies: []
galaxy_info:
role_name: grafana_agent
author: Ishan Jain
description: Ansible Role to deploy Grafana Agent on Linux hosts.
license: "GPL-3.0-or-later"
min_ansible_version: "2.11"
platforms:
- name: Fedora
versions:
- "all"
- name: Debian
versions:
- "all"
- name: Ubuntu
versions:
- "all"
- name: EL
versions:
- "all"
galaxy_tags:
- grafana
- observability

View file

@ -0,0 +1,56 @@
---
# these tasks are ran in both install and configure, as directories could have changed
- name: Configure directories
ansible.builtin.import_tasks: install/directories.yaml
- name: Create a symbolic link
ansible.builtin.file:
src: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}"
dest: "/usr/local/bin/{{ grafana_agent_binary }}"
owner: root
group: root
state: link
- name: Overwrite/Create Grafana Agent service
ansible.builtin.template:
src: grafana-agent.service.j2
dest: "{{ _grafana_agent_systemd_dir }}/{{ _grafana_agent_systemd_unit }}"
owner: root
group: root
mode: 0644
notify: "restart grafana-agent"
- name: Create the Service Environment file
ansible.builtin.template:
src: EnvironmentFile.j2
dest: "{{ grafana_agent_config_dir }}/{{ grafana_agent_env_file }}"
owner: root
group: "{{ grafana_agent_user_group }}"
mode: 0640
notify: "restart grafana-agent"
- name: Create Grafana Agent config
ansible.builtin.template:
src: config.yaml.j2
dest: "{{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}"
force: true
owner: root
group: "{{ grafana_agent_user_group }}"
mode: 0640
notify: "restart grafana-agent"
when: grafana_agent_provisioned_config_file | length == 0
- name: Create Grafana Agent River Config if flow mode for Grafana Agent
ansible.builtin.shell: "AGENT_MODE=flow {{ grafana_agent_install_dir }}/grafana-agent convert -f static {{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }} -o {{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}"
notify: "restart grafana-agent"
when: grafana_agent_provisioned_config_file | length == 0
- name: Copy Grafana Agent config
ansible.builtin.copy:
src: "{{ grafana_agent_provisioned_config_file }}"
dest: "{{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}"
owner: root
group: "{{ grafana_agent_user_group }}"
mode: 0640
notify: "restart grafana-agent"
when: grafana_agent_provisioned_config_file | length > 0

View file

@ -0,0 +1,29 @@
---
- name: Health check Grafana Agent
ansible.builtin.uri:
url: "{{ _grafana_agent_healthcheck_endpoint }}"
follow_redirects: none
method: GET
register: _result
failed_when: false
until: _result.status == 200
retries: 3
delay: 5
changed_when: false
when: not ansible_check_mode
- name: Check system logs if Grafana Agent is not started
when: not ansible_check_mode and _result.status != 200
block:
- name: Run journalctl
ansible.builtin.shell:
cmd: "journalctl -u grafana-agent -b -n20 --no-pager"
register: journal_ret
changed_when: false
- name: Output Grafana agent logs
ansible.builtin.debug:
var: journal_ret.stdout_lines
- name: Rise alerts
ansible.builtin.assert:
that: false
fail_msg: "Service grafana-agent hasn't started."

View file

@ -0,0 +1,19 @@
---
# user and group creation
- name: Configure user groups
ansible.builtin.import_tasks: install/user-group.yaml
# directory creation
- name: Configure directories
ansible.builtin.import_tasks: install/directories.yaml
# download and install agent
- name: Download and install Grafana Agent
ansible.builtin.import_tasks: install/download-install.yaml
when: (grafana_agent_local_binary_file is not defined) or (grafana_agent_local_binary_file | length == 0)
# local install of agent
- name: Local install of Grafana Agent
ansible.builtin.import_tasks:
file: install/local-install.yaml
when: __grafana_agent_local_install

View file

@ -0,0 +1,26 @@
---
- name: Create install directories
block:
- name: Create Grafana Agent install directory
ansible.builtin.file:
path: "{{ grafana_agent_install_dir }}"
state: directory
owner: root
group: "{{ grafana_agent_user_group }}"
mode: 0770
- name: Create Grafana Agent conf directory
ansible.builtin.file:
path: "{{ grafana_agent_config_dir }}"
state: directory
owner: root
group: "{{ grafana_agent_user_group }}"
mode: 0770
- name: Create Grafana Agent data directory
ansible.builtin.file:
path: "{{ grafana_agent_data_dir }}"
state: directory
owner: root
group: "{{ grafana_agent_user_group }}"
mode: 0775

View file

@ -0,0 +1,48 @@
---
- name: Download Grafana Agent binary to controller (localhost)
block:
- name: Create Grafana Agent temp directory
become: false
ansible.builtin.file:
path: "{{ grafana_agent_local_tmp_dir }}"
state: directory
mode: 0751
delegate_to: localhost
check_mode: false
run_once: true
- name: Download Grafana Agent archive to local folder
become: false
ansible.builtin.get_url:
url: "{{ _grafana_agent_download_url }}"
dest: "{{ grafana_agent_local_tmp_dir }}/grafana-agent_{{ _grafana_agent_cpu_arch }}_{{ grafana_agent_version }}.zip"
mode: 0664
register: _download_archive
until: _download_archive is succeeded
retries: 5
delay: 2
delegate_to: localhost
check_mode: false
run_once: true
- name: Extract grafana-agent.zip
become: false
ansible.builtin.unarchive:
src: "{{ grafana_agent_local_tmp_dir }}/grafana-agent_{{ _grafana_agent_cpu_arch }}_{{ grafana_agent_version }}.zip"
dest: "{{ grafana_agent_local_tmp_dir }}"
remote_src: false
delegate_to: localhost
run_once: true
- name: Set local path
ansible.builtin.set_fact:
__grafana_agent_local_binary_file: "{{ grafana_agent_local_tmp_dir }}/{{ grafana_agent_binary }}"
- name: Propagate downloaded binary
ansible.builtin.copy:
src: "{{ grafana_agent_local_tmp_dir }}/{{ _grafana_agent_download_binary_file }}"
dest: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}"
mode: 0755
owner: root
group: root
when: not ansible_check_mode

View file

@ -0,0 +1,10 @@
---
- name: Install from local
block:
- name: "Propagate local binary {{ grafana_agent_local_binary_file }}"
ansible.builtin.copy:
src: "{{ grafana_agent_local_binary_file }}"
dest: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}"
mode: 0755
owner: root
group: root

View file

@ -0,0 +1,38 @@
---
- name: Grafana Agent group creation
block:
- name: "Check if the group exists ({{ grafana_agent_user_group }})"
ansible.builtin.getent:
database: group
key: "{{ grafana_agent_user_group }}"
fail_key: false
- name: Set whether not the user group exists
ansible.builtin.set_fact:
__grafana_agent_user_group_exists: "{{ ansible_facts.getent_group[grafana_agent_user_group] is not none }}"
- name: Add user group "{{ grafana_agent_user_group }}"
ansible.builtin.group:
name: "{{ grafana_agent_user_group }}"
system: true
state: present
when: not __grafana_agent_user_group_exists and grafana_agent_user_group != 'root'
- name: Grafana Agent user group exists
ansible.builtin.debug:
msg: |-
The user group \"{{ grafana_agent_user_group }}\" already exists and will not be modified,
if modifying permissions please perform a separate task
when: __grafana_agent_user_group_exists
- name: Grafana Agent user creation
block:
- name: Add user "{{ grafana_agent_user }}"
ansible.builtin.user:
name: "{{ grafana_agent_user }}"
comment: "Grafana Agent account"
groups: "{{ [ grafana_agent_user_group ] + grafana_agent_user_groups }}"
system: true
shell: "{{ grafana_agent_user_shell }}"
createhome: "{{ grafana_agent_user_createhome }}"
when: grafana_agent_user != 'root'

View file

@ -0,0 +1,48 @@
---
- name: Preflight tasks
ansible.builtin.include_tasks:
file: preflight.yaml
apply:
become: true
tags:
- grafana_agent_install
- grafana_agent_configure
- grafana_agent_run
tags:
- grafana_agent_install
- grafana_agent_configure
- grafana_agent_run
- name: Install tasks
ansible.builtin.include_tasks:
file: install.yaml
apply:
become: true
tags:
- grafana_agent_install
tags:
- grafana_agent_install
- name: Configuration tasks
ansible.builtin.include_tasks:
file: configure.yaml
apply:
become: true
tags:
- grafana_agent_configure
tags:
- grafana_agent_configure
- name: Flush handlers
ansible.builtin.meta: flush_handlers
- name: Ensure Grafana Agent is started and enabled on boot
become: true
ansible.builtin.systemd:
name: grafana-agent
enabled: true
state: started
tags:
- grafana_agent_install
- grafana_agent_configure
- grafana_agent_run

View file

@ -0,0 +1,12 @@
---
- name: Preflight variable checks
ansible.builtin.import_tasks: preflight/vars.yaml
- name: Systemd variable checks
ansible.builtin.import_tasks: preflight/systemd.yaml
- name: Install variable checks
ansible.builtin.import_tasks: preflight/install.yaml
- name: Download variable checks
ansible.builtin.import_tasks: preflight/download.yaml

View file

@ -0,0 +1,36 @@
---
- name: Get Grafana Agent version from Github
when: grafana_agent_version == 'latest' and not __grafana_agent_local_install
block:
- name: Get the latest published Grafana Agent # noqa command-instead-of-module
ansible.builtin.shell: |
curl -s https://api.github.com/repos/{{ _grafana_agent_github_org }}/{{ _grafana_agent_github_repo }}/releases/latest \
| grep -m 1 tag_name \
| cut -d '"' -f 4 | cut -c 2-
changed_when: false
run_once: true
delegate_to: localhost
become: false
register: _grafana_agent_version_request
- name: Fail if cannot get Grafana Agent Version
ansible.builtin.fail:
msg: Issue getting the Grafana Agent Version
when: _grafana_agent_version_request == ""
- name: Set the Grafana Agent version
ansible.builtin.set_fact:
grafana_agent_version: "{{ _grafana_agent_version_request.stdout }}"
- name: Grafana Agent version to download
ansible.builtin.debug:
var: grafana_agent_version
- name: Set the Grafana Agent download URL
ansible.builtin.set_fact:
_grafana_agent_download_url: |-
{{ grafana_agent_base_download_url }}/v{{ grafana_agent_version }}/{{ _grafana_agent_download_archive_file }}
- name: Grafana Agent download URL
ansible.builtin.debug:
var: _grafana_agent_download_url

View file

@ -0,0 +1,72 @@
---
- name: Default to non-local install
ansible.builtin.set_fact:
__grafana_agent_local_install: false
- name: Fail when grafana_agent_local_binary_file is defined but the file doesn't exist
when: grafana_agent_local_binary_file is defined and grafana_agent_local_binary_file | length > 0
block:
- name: Check if grafana_agent_local_binary_file exists
ansible.builtin.stat:
path: "{{ grafana_agent_local_binary_file }}"
register: __grafana_agent_local_binary_check
become: false
delegate_to: localhost
check_mode: false
- name: Fail when the grafana_agent_local_binary_file does not exist
ansible.builtin.fail:
msg: "The grafana_agent_local_binary_file ({{ grafana_agent_local_binary_file }}) was specified but does not exist"
when: not __grafana_agent_local_binary_check.stat.exists
- name: Change to local install
ansible.builtin.set_fact:
__grafana_agent_local_install: true
- name: Fail when grafana_agent_provisioned_config_file is defined but the file doesn't exist
when: grafana_agent_provisioned_config_file is defined and grafana_agent_provisioned_config_file | length > 0
block:
- name: Check if grafana_agent_provisioned_config_file exists
ansible.builtin.stat:
path: "{{ grafana_agent_provisioned_config_file }}"
register: __grafana_agent_provisioned_config_file_check
become: false
delegate_to: localhost
check_mode: false
- name: Fail when the grafana_agent_provisioned_config_file does not exist
ansible.builtin.fail:
msg: "The grafana_agent_provisioned_config_file ({{ grafana_agent_provisioned_config_file }}) was specified but does not exist"
when: not __grafana_agent_provisioned_config_file_check.stat.exists
- name: Check if grafana_agent is already installed on the host
ansible.builtin.stat:
path: "{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }}"
register: __grafana_agent_is_installed
check_mode: false
- name: Is Grafana Agent already installed on the host
ansible.builtin.debug:
var: __grafana_agent_is_installed.stat.exists
- name: Install checks
when: __grafana_agent_is_installed.stat.exists and not __grafana_agent_local_install
block:
- name: Gather currently installed grafana-agent version from the host
ansible.builtin.shell:
cmd: |
{{ grafana_agent_install_dir }}/{{ grafana_agent_binary }} --version | \
head -n 1 | \
awk '{ print $3; }' | \
cut -d 'v' -f 2
changed_when: false
register: __grafana_agent_current_version_output
check_mode: false
- name: Set Grafana Agent installed version for the host
ansible.builtin.set_fact:
__grafana_agent_installed_version: "{{ __grafana_agent_current_version_output.stdout }}"
- name: Grafana Agent installed version on host
ansible.builtin.debug:
var: __grafana_agent_installed_version

View file

@ -0,0 +1,28 @@
---
- name: Assert usage of systemd as an init system
ansible.builtin.assert:
that: ansible_facts['service_mgr'] == 'systemd'
msg: This role only works with systemd
- name: Get systemd version # noqa command-instead-of-module
ansible.builtin.command: systemctl --version
changed_when: false
check_mode: false
register: __systemd_version
- name: Set systemd version fact
ansible.builtin.set_fact:
grafana_agent_systemd_version: "{{ __systemd_version.stdout_lines[0] | regex_replace('^systemd\\s(\\d+).*$', '\\1') }}"
- name: Fail when _grafana_agent_systemd_dir the directory doesn't exist
block:
- name: Check if _grafana_agent_systemd_dir exists
ansible.builtin.stat:
path: "{{ _grafana_agent_systemd_dir }}"
register: ___grafana_agent_systemd_dir_check
check_mode: false
- name: Fail when the _grafana_agent_systemd_dir directory does not exist
ansible.builtin.fail:
msg: "The _grafana_agent_systemd_dir ({{ _grafana_agent_systemd_dir }}) does not exist"
when: not ___grafana_agent_systemd_dir_check.stat.exists

View file

@ -0,0 +1,72 @@
---
# Performs initial variable validation
- name: Fail when variables are not defined
ansible.builtin.fail:
msg: "The {{ item }} property must be set"
when: ( vars[item] is not defined )
with_items:
- grafana_agent_version
- grafana_agent_install_dir
- grafana_agent_binary
- grafana_agent_config_dir
- grafana_agent_config_filename
- grafana_agent_env_file
- grafana_agent_local_tmp_dir
- grafana_agent_wal_dir
- grafana_agent_positions_dir
- grafana_agent_mode
- _grafana_agent_systemd_dir
- _grafana_agent_systemd_unit
- grafana_agent_user
- grafana_agent_user_group
- grafana_agent_user_shell
- grafana_agent_user_createhome
- grafana_agent_local_binary_file
- grafana_agent_flags_extra
- grafana_agent_env_vars
- grafana_agent_env_file_vars
- grafana_agent_provisioned_config_file
- grafana_agent_metrics_config
- grafana_agent_logs_config
- grafana_agent_traces_config
- grafana_agent_integrations_config
- name: Fail when variables do not have a length
ansible.builtin.fail:
msg: "The {{ item }} property must be valued"
when: ( vars[item] | string | length == 0 )
with_items:
- grafana_agent_version
- grafana_agent_install_dir
- grafana_agent_binary
- grafana_agent_config_dir
- grafana_agent_config_filename
- grafana_agent_env_file
- grafana_agent_local_tmp_dir
- grafana_agent_wal_dir
- grafana_agent_positions_dir
- grafana_agent_mode
- _grafana_agent_systemd_dir
- _grafana_agent_systemd_unit
- grafana_agent_user
- grafana_agent_user_group
- grafana_agent_user_shell
- grafana_agent_user_createhome
- name: Fail when variables are not a number
ansible.builtin.fail:
msg: "The {{ item }} property must be number"
when: ( vars[item] is defined and vars[item] is not number)
with_items: []
- name: Fail when flags are not a boolean
ansible.builtin.fail:
msg: "The {{ item }} property must be a boolean true or false"
when: ( vars[item] | bool | string | lower ) not in ['true', 'false']
with_items:
- grafana_agent_user_createhome
- name: Fail when the agent mode is not "flow" or "static"
ansible.builtin.fail:
msg: "The grafana_agent_mode property must be a boolean 'flow' or 'static'"
when: grafana_agent_mode not in ['flow', 'static']

View file

@ -0,0 +1,9 @@
{{ ansible_managed | comment }}
# Grafana Agent Environment File
AGENT_MODE={{ grafana_agent_mode }}
GOMAXPROCS={{ ansible_facts['processor_vcpus']|default(ansible_facts['processor_count']) }}
{% for key, value in grafana_agent_env_file_vars.items() %}
{{key}}={{value}}
{% endfor %}

View file

@ -0,0 +1,39 @@
---
{{ ansible_managed | comment }}
#################################################################################################
# Configures the server of the Agent used to enable self-scraping #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/configuration/server-config/
server:
{{ grafana_agent_server_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }}
#################################################################################################
# Configures metric collection #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/configuration/metrics-config/
metrics:
{{ grafana_agent_metrics_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }}
#################################################################################################
# Configures logs collection #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/configuration/logs-config/
logs:
{{ grafana_agent_logs_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }}
{% if grafana_agent_mode == 'static' %}
#################################################################################################
# Configures traces collection #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/configuration/traces-config/
traces:
{{ grafana_agent_traces_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }}
{% endif %}
#################################################################################################
# Configures integrations for the agent #
#################################################################################################
# Docs: https://grafana.com/docs/agent/latest/configuration/integrations/
integrations:
{{ grafana_agent_integrations_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2) }}

View file

@ -0,0 +1,65 @@
{{ ansible_managed | comment }}
[Unit]
Description=Grafana Agent
Documentation=https://grafana.com/docs/agent/latest/
After=network-online.target
[Service]
Type=simple
User={{ grafana_agent_user }}
Group={{ grafana_agent_user_group }}
WorkingDirectory={{ grafana_agent_data_dir }}
{% for key, value in grafana_agent_env_vars.items() %}
Environment={{key}}={{value}}
{% endfor %}
EnvironmentFile={{ grafana_agent_config_dir }}/{{ grafana_agent_env_file}}
{% for key, value in grafana_agent_service_extra.items() %}
{{ key }}={{ value }}
{% endfor %}
{% if grafana_agent_mode == 'flow' %}
ExecStart={{ grafana_agent_install_dir }}/{{ grafana_agent_binary }} run \
{% for flag, flag_value in grafana_agent_flags_extra.items() %}
{% if not flag_value %}
--{{ flag }} \
{% elif flag_value is string %}
--{{ flag }}={{ flag_value }} \
{% elif flag_value is sequence %}
{% for flag_value_item in flag_value %}
--{{ flag }}={{ flag_value_item }} \
{% endfor %}
{% endif %}
{% endfor %}
{{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}
{% else %}
ExecStart={{ grafana_agent_install_dir }}/{{ grafana_agent_binary }} \
{% for flag, flag_value in grafana_agent_flags_extra.items() %}
{% if not flag_value %}
--{{ flag }} \
{% elif flag_value is string %}
--{{ flag }}={{ flag_value }} \
{% elif flag_value is sequence %}
{% for flag_value_item in flag_value %}
--{{ flag }}={{ flag_value_item }} \
{% endfor %}
{% endif %}
{% endfor %}
--config.file={{ grafana_agent_config_dir }}/{{ grafana_agent_config_filename }}
{% endif %}
SyslogIdentifier=grafana-agent
Restart=always
{% if grafana_agent_systemd_version | int >= 232 %}
ProtectSystem=strict
ProtectControlGroups=true
ProtectKernelModules=true
ProtectKernelTunables=yes
{% else %}
ProtectSystem=full
{% endif %}
ReadWritePaths=/tmp {{ grafana_agent_data_dir }} {{ grafana_agent_positions_dir }} {{ grafana_agent_wal_dir }}
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,29 @@
---
_grafana_agent_github_org: grafana
_grafana_agent_github_repo: agent
# set the go cpu arch
_download_cpu_arch_map:
i386: '386'
x86_64: amd64
aarch64: arm64
armv7l: armv7
armv6l: armv6
_grafana_agent_cpu_arch: "{{ _download_cpu_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}"
# set the go os family
_grafana_agent_os_family: "{{ ansible_facts['system'] | lower }}"
# set the name of the archive file to download
_grafana_agent_download_archive_file: "grafana-agent-{{ _grafana_agent_os_family }}-{{ _grafana_agent_cpu_arch }}.zip"
# set the name of the binary file
_grafana_agent_download_binary_file: "grafana-agent-{{ _grafana_agent_os_family }}-{{ _grafana_agent_cpu_arch }}"
# systemd info
_grafana_agent_systemd_dir: /lib/systemd/system/
_grafana_agent_systemd_unit: grafana-agent.service
# Server http address, used in self health check after start
_grafana_agent_healthcheck_endpoint: "http://{{ grafana_agent_flags_extra['server.http.address'] if grafana_agent_flags_extra['server.http.address'] is defined else '127.0.0.1:12345' }}/-/ready"

View file

@ -0,0 +1,247 @@
# Ansible role - Loki
[![License](https://img.shields.io/github/license/grafana/grafana-ansible-collection)](LICENSE)
The Ansible Loki Role allows you to effortlessly deploy and manage [Loki](https://grafana.com/oss/loki/), the log aggregation system. This role is tailored for operating systems such as **RedHat**, **Rocky Linux**, **AlmaLinux**, **Ubuntu**, and **Debian**.
**🔑 Key Features**
- **📦 Out-of-the-box Deployment**: Get Loki up and running quickly with default configurations.
- **🧹 Effortless Uninstall**: Easily remove Loki from your system setting the "loki_uninstall" variable.
- **🔔 Example Alerting Rules**: Benefit from the included sample Ruler configuration. Utilize the provided example alerting rules as a reference guide for structuring your own rules effectively.
## Table of Content
- [Requirements](#requirements)
- [Role Variables](#role-variables)
- - [Default Variables - `defaults/main.yml`](#default-variables---defaultsmainyml)
- - [Alerting Rules Variables](#alerting-rules-variables)
- - [Additional Config Variables for `/etc/loki/config.yml`](#additional-config-variables-for-etclokiconfigyml)
- [Playbook](#playbook)
## Requirements
- Ansible 2.10+
## Role Variables
- 📚 Official Loki configuration [documentation](https://grafana.com/docs/loki/latest/configuration/)
- 🏗️ Upgrading Loki [documentation](https://grafana.com/docs/loki/latest/upgrading/)
### **Default Variables - `defaults/main.yml`**
```yaml
loki_version: "latest"
```
The version of Loki to download and deploy. Supported standard version "3.0.0" format or "latest".
```yaml
loki_uninstall: "false"
```
If set to `true` will perfom uninstall instead of deployment.
```yaml
loki_http_listen_port: 3100
```
The TCP port on which Loki listens. By default, it listens on port `3100`.
```yaml
loki_http_listen_address: "0.0.0.0"
```
The address on which Loki listens for HTTP requests. By default, it listens on all interfaces.
```yaml
loki_expose_port: false
```
By default, this is set to `false`. It supports only simple `firewalld` configurations. If set to `true`, a firewalld rule is added to expose the TCP `loki_http_listen_port`. If set to `false`, the system ensures that the rule is not present. If the `firewalld.service` is not active, all firewalld tasks are skipped.
```yaml
loki_download_url_rpm: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki-{{ loki_version }}.{{ __loki_arch }}.rpm"
```
The default download URL for the Loki rpm package from GitHub.
```yaml
loki_download_url_deb: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki_{{ loki_version }}_{{ __loki_arch }}.deb"
```
The default download URL for the Loki deb package from GitHub.
```yaml
loki_working_path: "/var/lib/loki"
```
⚠️ Avoid using `/tmp/loki` as the working path. This role removes the /tmp/loki directory and replaces it with the specified working path to ensure a permanent configuration.
```yaml
loki_ruler_alert_path: "{{ loki_working_path }}/rules/fake"
```
The variable defines the location where the `ruler` configuration `alerts` are stored.
⚠️ Please note that the role currently does not support multi-tenancy for alerting, so there is no need to modify this variable for different tenants.
```yaml
loki_auth_enabled: false
```
Enables authentication through the X-Scope-OrgID header, which must be present if `true`. If `false`, the OrgID will always be set to `fake`.
```yaml
loki_target: "all"
```
A comma-separated list of components to run. The default value 'all' runs Loki in single binary mode.
Supported values: `all`, `compactor`, `distributor`, `ingester`, `querier`, `query-scheduler`, `ingester-querier`, `query-frontend`, `index-gateway`, `ruler`, `table-manager`, `read`, `write`.
```yaml
loki_ballast_bytes: 0
```
The amount of virtual memory in bytes to reserve as ballast in order to optimize garbage collection.
```yaml
loki_server:
http_listen_address: "{{ loki_http_listen_address }}"
http_listen_port: "{{ loki_http_listen_port }}"
grpc_listen_port: 9096
```
Configures the `server` of the launched module(s). [All possible values for `server`](https://grafana.com/docs/loki/latest/configuration/#server)
```yaml
loki_common:
instance_addr: 127.0.0.1
path_prefix: "{{ loki_working_path }}"
storage:
filesystem:
chunks_directory: "{{ loki_working_path }}/chunks"
rules_directory: "{{ loki_working_path }}/rules"
replication_factor: 1
ring:
kvstore:
store: inmemory
```
Common configuration to be shared between multiple modules. If a more specific configuration is given in other sections, the related configuration within this section will be ignored. [All possible values for `common`](https://grafana.com/docs/loki/latest/configuration/#common)
```yaml
loki_query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
```
The `query_range` block configures the query splitting and caching in the Loki query-frontend. [All possible values for `query_range`](https://grafana.com/docs/loki/latest/configuration/#query_range)
```yaml
loki_schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
```
Configures the chunk index schema and where it is stored. [All possible values for `schema_config`](https://grafana.com/docs/loki/latest/configuration/#schema_config)
```yaml
loki_ruler:
storage:
type: local
local:
directory: "{{ loki_working_path }}/rules"
rule_path: "{{ loki_working_path }}/rules_tmp"
ring:
kvstore:
store: inmemory
enable_api: true
enable_alertmanager_v2: true
alertmanager_url: http://localhost:9093
```
The `ruler` block configures the Loki ruler. [All possible values for `ruler`](https://grafana.com/docs/loki/latest/configuration/#ruler)
```yaml
loki_analytics:
reporting_enabled: false
```
Enable anonymous usage reporting. Disabled by default.
### **Alerting Rules Variables**
(not set by default)
```yaml
---
loki_ruler_alerts:
- name: Logs.Nextcloud
rules:
- alert: NextcloudLoginFailed
expr: |
count by (filename,env,job) (count_over_time({job=~"nextcloud"} | json | message=~"Login failed.*" [10m])) > 4
for: 0m
labels:
severity: critical
annotations:
summary: "{% raw %}On {{ $labels.job }} in log {{ $labels.filename }} failed login detected.{% endraw %}"
- name: Logs.sshd
rules:
- alert: SshLoginFailed
expr: |
count_over_time({job=~"secure"} |="sshd[" |~": Failed|: Invalid|: Connection closed by authenticating user" | __error__="" [15m]) > 15
for: 0m
labels:
severity: critical
annotations:
summary: "{% raw %}SSH authentication failure (instance {{ $labels.instance }}).{% endraw %}"
```
Example alerting rule configuration. You can add multiple alerting rules to suit your requirements. Please note that the alerting rules are not templated by default
### **Additional Config Variables for `/etc/loki/config.yml`**
(not set by default)
Below variables allow you to extend Loki configuration to fit your needs. Always refer to official [Loki configuration](https://grafana.com/docs/loki/latest/configuration/) to obtain possible configuration parameters.
| Variable Name | Description
| ----------- | ----------- |
| `loki_distributor` | Configures the `distributor`. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#distributor)
| `loki_querier` | Configures the `querier`. Only appropriate when running all modules or just the querier. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#querier)
| `loki_query_scheduler` | The `query_scheduler` block configures the Loki query scheduler. When configured it separates the tenant query queues from the query-frontend. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#query_scheduler)
| `loki_frontend` | The `frontend` block configures the Loki query-frontend. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#frontend)
| `loki_ingester_client` | The `ingester_client` block configures how the distributor will connect to ingesters. Only appropriate when running all components, the distributor, or the querier. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#ingester_client)
| `loki_ingester` | The `ingester` block configures the ingester and how the ingester will register itself to a key value store. 📚 configuration [documentation](https://grafana.com/docs/loki/latest/configuration/#ingester)
| `loki_index_gateway` | The `index_gateway` block configures the Loki index gateway server, responsible for serving index queries without the need to constantly interact with the object store. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#index_gateway)
| `loki_storage_config` | The `storage_config` block configures one of many possible stores for both the index and chunks. Which configuration to be picked should be defined in schema_config block. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#storage_config)
| `loki_chunk_store_config` | The `chunk_store_config` block configures how chunks will be cached and how long to wait before saving them to the backing store. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#chunk_store_config)
| `loki_compactor` | The `compactor` block configures the compactor component, which compacts index shards for performance. 📚 [documentation](https://grafana.com/docs/loki/latest/configure/#compactor)
| `loki_limits_config` | The `limits_config` block configures global and per-tenant limits in Loki. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#limits_config)
| `loki_frontend_worker` | The `frontend_worker` configures the worker - running within the Loki querier - picking up and executing queries enqueued by the query-frontend. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#frontend_worker)
| `loki_table_manager` | The `table_manager` block configures the table manager for retention. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#table_manager)
| `loki_memberlist` | Configuration for memberlist client. Only applies if the selected kvstore is memberlist. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#memberlist)
| `loki_runtime_config` | Configuration for `runtime config` module, responsible for reloading runtime configuration file. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#runtime_config)
| `loki_operational_config` | These are values which allow you to control aspects of Loki's operation, most commonly used for controlling types of higher verbosity logging, the values here can be overridden in the configs section of the `runtime_config` file. 📚 [documentation](https://grafana.com/docs/loki/latest/configure/#operational_config)
| `loki_tracing` | Configuration for tracing. 📚 [documentation](https://grafana.com/docs/loki/latest/configuration/#tracing)
| `loki_bloom_build` | The `bloom_build` block configures the Loki bloom planner and builder servers, responsible for building bloom filters. 📚 [documentation](https://grafana.com/docs/loki/latest/configure/#bloom_build)
| `loki_bloom_gateway` | The `bloom_gateway` block configures the Loki bloom gateway server, responsible for serving queries for filtering chunks based on filter expressions. 📚 [documentation](https://grafana.com/docs/loki/latest/configure/#bloom_gateway)
## Playbook
- playbook
```yaml
- name: Manage loki service
hosts: all
become: true
roles:
- role: grafana.grafana.loki
```
- Playbook execution example
```shell
# Deployment
ansible-playbook -i inventory/hosts playbook/function_loki_play.yml
# Uninstall
ansible-playbook -i inventory/hosts playbook/function_loki_play.yml -e "loki_uninstall=true"
```
## License
See [LICENSE](https://github.com/grafana/grafana-ansible-collection/blob/main/LICENSE)
## Author Information
- [VoidQuark](https://github.com/voidquark)

View file

@ -0,0 +1,110 @@
---
# defaults file for loki
loki_version: "latest"
loki_uninstall: false
loki_http_listen_port: 3100
loki_http_listen_address: "0.0.0.0"
loki_expose_port: false
loki_download_url_rpm: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki-{{ loki_version }}.{{ __loki_arch }}.rpm"
loki_download_url_deb: "https://github.com/grafana/loki/releases/download/v{{ loki_version }}/loki_{{ loki_version }}_{{ __loki_arch }}.deb"
loki_working_path: "/var/lib/loki"
loki_ruler_alert_path: "{{ loki_working_path }}/rules/fake"
# Default Variables for /etc/loki/config.yml
loki_auth_enabled: false
loki_target: "all"
loki_ballast_bytes: 0
loki_server:
http_listen_address: "{{ loki_http_listen_address }}"
http_listen_port: "{{ loki_http_listen_port }}"
grpc_listen_port: 9096
loki_common:
instance_addr: 127.0.0.1
path_prefix: "{{ loki_working_path }}"
storage:
filesystem:
chunks_directory: "{{ loki_working_path }}/chunks"
rules_directory: "{{ loki_working_path }}/rules"
replication_factor: 1
ring:
kvstore:
store: inmemory
loki_query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
loki_schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
loki_ruler:
storage:
type: local
local:
directory: "{{ loki_working_path }}/rules"
rule_path: "{{ loki_working_path }}/rules_tmp"
ring:
kvstore:
store: inmemory
enable_api: true
enable_alertmanager_v2: true
alertmanager_url: http://localhost:9093
loki_analytics:
reporting_enabled: false
# Alerting Rules Variables
# loki_ruler_alerts:
# - name: Logs.Nextcloud
# rules:
# - alert: NextcloudLoginFailed
# expr: |
# count by (filename,env,job) (count_over_time({job=~"nextcloud"} | json | message=~"Login failed.*" [10m])) > 4
# for: 0m
# labels:
# severity: critical
# annotations:
# summary: "{% raw %}On {{ $labels.job }} in log {{ $labels.filename }} failed login detected.{% endraw %}"
# - name: Logs.sshd
# rules:
# - alert: SshLoginFailed
# expr: |
# count_over_time({job=~"secure"} |="sshd[" |~": Failed|: Invalid|: Connection closed by authenticating user" | __error__="" [15m]) > 15
# for: 0m
# labels:
# severity: critical
# annotations:
# summary: "{% raw %}SSH authentication failure (instance {{ $labels.instance }}).{% endraw %}"
# Additional Config Variables for /etc/loki/config.yml
# loki_distributor:
# loki_querier:
# loki_query_scheduler:
# loki_frontend:
# loki_ingester_client:
# loki_ingester:
# loki_index_gateway:
# loki_storage_config:
# loki_chunk_store_config:
# loki_compactor:
# loki_limits_config:
# loki_frontend_worker:
# loki_table_manager:
# loki_memberlist:
# loki_runtime_config:
# loki_operational_config:
# loki_tracing:
# loki_bloom_build:
# loki_bloom_gateway:

View file

@ -0,0 +1,10 @@
---
# handlers file for loki
- name: Restart loki
listen: "restart loki"
ansible.builtin.systemd:
daemon_reload: true
name: loki.service
state: restarted
enabled: true
when: not ansible_check_mode

View file

@ -0,0 +1,28 @@
---
galaxy_info:
role_name: loki
author: voidquark
description: Manage Grafana Loki Application
license: "GPL-3.0-or-later"
min_ansible_version: "2.10"
platforms:
- name: EL
versions:
- "8"
- "9"
- name: Fedora
versions:
- all
- name: Debian
versions:
- all
- name: Ubuntu
versions:
- all
galaxy_tags:
- loki
- grafana
- logging
- monitoring
dependencies: []

View file

@ -0,0 +1,5 @@
---
- name: Converge
hosts: all
roles:
- role: grafana.grafana.loki

View file

@ -0,0 +1,20 @@
---
dependency:
name: galaxy
options:
ignore-errors: true
driver:
name: docker
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: converge.yml

View file

@ -0,0 +1,150 @@
---
# tasks file for loki deployment
- name: Obtain the latest version from the Loki GitHub repo
when: loki_version == "latest"
block:
- name: Scrape Github API endpoint to obtain latest Loki version
ansible.builtin.uri:
url: "https://api.github.com/repos/grafana/loki/releases/latest"
method: GET
body_format: json
become: false
delegate_to: localhost
run_once: true
check_mode: false
register: __github_latest_version
- name: Latest available Loki version
ansible.builtin.set_fact:
loki_version: "{{ __github_latest_version.json.tag_name | regex_replace('^v?(\\d+\\.\\d+\\.\\d+)$', '\\1') }}"
- name: Verify current deployed version
block:
- name: Check if Loki binary is present
ansible.builtin.stat:
path: "/usr/bin/loki"
register: __already_deployed
- name: Obtain current deployed Loki version
ansible.builtin.command:
cmd: "/usr/bin/loki --version"
changed_when: false
register: __current_deployed_version
when: __already_deployed.stat.exists | bool
- name: Include RedHat/Rocky setup
ansible.builtin.include_tasks:
file: setup-RedHat.yml
when: ansible_facts['os_family'] in ['RedHat', 'Rocky']
- name: Include Debian/Ubuntu setup
ansible.builtin.include_tasks:
file: setup-Debian.yml
when: ansible_facts['os_family'] == 'Debian'
- name: Check if Loki default dir is present
ansible.builtin.stat:
path: "/tmp/loki/boltdb-shipper-active"
register: __default_structure
- name: Default structure cleanup
when: __default_structure.stat.exists | bool
block:
- name: Ensure that Loki is stopped before default cleanup
ansible.builtin.systemd:
name: loki.service
state: stopped
- name: Remove default configuration from "/tmp/loki" directory
ansible.builtin.file:
path: "/tmp/loki"
state: absent
- name: Ensure that Loki working path exists
ansible.builtin.file:
path: "{{ loki_working_path }}"
state: directory
owner: "loki"
group: "root"
mode: "0755"
- name: Template Loki config - /etc/loki/config.yml
ansible.builtin.template:
src: "config.yml.j2"
dest: "/etc/loki/config.yml"
owner: "root"
group: "root"
mode: "0644"
validate: "/usr/bin/loki --verify-config -config.file %s"
notify: restart loki
- name: Ensure that Loki rule path exists
ansible.builtin.file:
path: "{{ loki_ruler_alert_path }}"
state: directory
owner: "loki"
group: "root"
mode: "0750"
when:
- loki_ruler_alert_path is defined
- loki_ruler is defined
- name: Template Loki Rule File
ansible.builtin.template:
src: "rules.yml.j2"
dest: "{{ loki_ruler_alert_path }}/rules.yml"
owner: "loki"
group: "root"
mode: "0644"
notify: restart loki
when:
- loki_ruler_alerts is defined
- loki_ruler_alert_path is defined
- loki_ruler is defined
- name: Get firewalld state
ansible.builtin.systemd:
name: "firewalld"
register: __firewalld_service_state
- name: Enable firewalld rule to expose Loki tcp port {{ loki_http_listen_port }}
ansible.posix.firewalld:
immediate: true
permanent: true
port: "{{ loki_http_listen_port }}/tcp"
state: enabled
when:
- __firewalld_service_state.status.ActiveState == "active"
- loki_expose_port | bool
- name: Ensure that Loki firewalld rule is not present - tcp port {{ loki_http_listen_port }}
ansible.posix.firewalld:
immediate: true
permanent: true
port: "{{ loki_http_listen_port }}/tcp"
state: disabled
when:
- __firewalld_service_state.status.ActiveState == "active"
- not loki_expose_port | bool
- name: Flush handlers after deployment
ansible.builtin.meta: flush_handlers
- name: Ensure that Loki is started
ansible.builtin.systemd:
name: loki.service
state: started
when: not ansible_check_mode
- name: Verify that Loki URL is responding
ansible.builtin.uri:
url: "http://{{ loki_http_listen_address }}:{{ loki_http_listen_port }}/ready"
method: GET
register: loki_verify_url_status_code
retries: 5
delay: 8
until: loki_verify_url_status_code.status == 200
when:
- loki_expose_port | bool
- not ansible_check_mode

View file

@ -0,0 +1,15 @@
---
# tasks file for loki
- name: Include OS specific variables
ansible.builtin.include_vars:
file: "{{ ansible_facts['os_family'] }}.yml"
- name: Deploy Loki service
ansible.builtin.include_tasks:
file: "deploy.yml"
when: not loki_uninstall
- name: Uninstall Loki service
ansible.builtin.include_tasks:
file: "uninstall.yml"
when: loki_uninstall

View file

@ -0,0 +1,7 @@
---
- name: APT - Install Loki
ansible.builtin.apt:
deb: "{{ loki_download_url_deb }}"
state: present
notify: restart loki
when: __current_deployed_version.stdout is not defined or loki_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,8 @@
---
- name: DNF - Install Loki from remote URL
ansible.builtin.dnf:
name: "{{ loki_download_url_rpm }}"
state: present
disable_gpg_check: true
notify: restart loki
when: __current_deployed_version.stdout is not defined or loki_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,51 @@
---
# tasks file for loki uninstall
- name: Stop Loki service
ansible.builtin.systemd: # noqa ignore-errors
name: loki
state: stopped
ignore_errors: true
- name: Uninstall Loki rpm package
ansible.builtin.dnf:
name: "loki"
state: absent
autoremove: true
when: ansible_facts['os_family'] in ['RedHat', 'Rocky']
- name: Uninstall Loki deb package
ansible.builtin.apt:
name: "loki"
state: absent
purge: true
when: ansible_facts['os_family'] == 'Debian'
- name: Ensure that Loki firewalld rule is not present - tcp port {{ loki_http_listen_port }}
ansible.posix.firewalld: # noqa ignore-errors
immediate: true
permanent: true
port: "{{ loki_http_listen_port }}/tcp"
state: disabled
ignore_errors: true
- name: Remove Loki directories"
ansible.builtin.file:
path: "{{ remove_me }}"
state: absent
loop:
- "/etc/loki"
- "{{ loki_working_path }}"
loop_control:
loop_var: remove_me
- name: Remove the Loki system user
ansible.builtin.user:
name: "loki"
force: true
state: absent
- name: Remove Loki system group
ansible.builtin.group:
name: "loki"
state: absent

View file

@ -0,0 +1,107 @@
{% if loki_target is defined %}
target: {{ loki_target }}
{% endif %}
{% if loki_auth_enabled is defined %}
auth_enabled: {{ loki_auth_enabled }}
{% endif %}
{% if loki_ballast_bytes is defined %}
ballast_bytes: {{ loki_ballast_bytes }}
{% endif %}
server:
{{ loki_server | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% if loki_distributor is defined %}
distributor:
{{ loki_distributor | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_querier is defined %}
querier:
{{ loki_querier | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_query_scheduler is defined %}
query_scheduler:
{{ loki_query_scheduler | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_frontend is defined %}
frontend:
{{ loki_frontend | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_query_range is defined %}
query_range:
{{ loki_query_range | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_ruler is defined %}
ruler:
{{ loki_ruler | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_ingester_client is defined %}
ingester_client:
{{ loki_ingester_client | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_ingester is defined %}
ingester:
{{ loki_ingester | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_index_gateway is defined %}
index_gateway:
{{ loki_index_gateway | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_storage_config is defined %}
storage_config:
{{ loki_storage_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_chunk_store_config is defined %}
chunk_store_config:
{{ loki_chunk_store_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_schema_config is defined %}
schema_config:
{{ loki_schema_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_compactor is defined %}
compactor:
{{ loki_compactor | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_limits_config is defined %}
limits_config:
{{ loki_limits_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_frontend_worker is defined %}
frontend_worker:
{{ loki_frontend_worker | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_table_manager is defined %}
table_manager:
{{ loki_table_manager | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_memberlist is defined %}
memberlist:
{{ loki_memberlist | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_runtime_config is defined %}
runtime_config:
{{ loki_runtime_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_operational_config is defined %}
operational_config:
{{ loki_operational_config | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_tracing is defined %}
tracing:
{{ loki_tracing | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_analytics is defined %}
analytics:
{{ loki_analytics | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_common is defined %}
common:
{{ loki_common | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_bloom_build is defined %}
bloom_build:
{{ loki_bloom_build | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}
{% if loki_bloom_gateway is defined %}
bloom_gateway:
{{ loki_bloom_gateway | to_nice_yaml(indent=2,sort_keys=False) | indent(2, False) }}
{% endif %}

View file

@ -0,0 +1,3 @@
---
groups:
{{ loki_ruler_alerts | to_nice_yaml(indent=2,sort_keys=False) | indent(2,False) }}

View file

@ -0,0 +1,8 @@
---
__loki_arch_map:
x86_64: 'amd64'
armv6l: 'arm'
armv7l: 'arm'
aarch64: 'arm64'
__loki_arch: "{{ __loki_arch_map[ansible_facts['architecture']] | default(ansible_facts['architecture']) }}"

View file

@ -0,0 +1,2 @@
---
__loki_arch: "{{ ansible_facts['architecture'] }}"

View file

@ -0,0 +1,109 @@
# Ansible role - Mimir
[![License](https://img.shields.io/github/license/grafana/grafana-ansible-collection)](LICENSE)
This role installs and configures a [Mimir](https://grafana.com/docs/mimir/latest/)
standalone application.
## Testing with Molecule
To be able to test this collection locally, we use Molecule. Molecule is an Ansible test tool that enable us to run our roles inside containers. In our case, we are using Podman as a container runtime. To be able to run the Molecule test, you need to have the following installed on your machine:
- Podman
- Ansible
- Python3
### First Time Setup
To install all the dependencies, use the following commands:
```sh
# Create a virtual environment
python -m venv .venv
# On MacOS, WSL, Linux
source .venv/bin/activate
# On Windows
.\.venv\Scripts\activate
# Install dependencies
pip3 install ansible-core==2.16 'molecule-plugins[docker]' pytest-testinfra jmespath selinux passlib
# Create molecule network
docker network create molecule
```
### Run Minio for local S3
To be able to run Mimir using an object store backend, run the following command
```sh
docker run -d \
-p 9000:9000 \
-p 9001:9001 \
--name minio-mimir \
--network molecule \
-e "MINIO_ROOT_USER=testtest" \
-e "MINIO_ROOT_PASSWORD=testtest" \
-e "MINIO_DEFAULT_BUCKETS=mimir" \
bitnami/minio:latest
```
### Testing the changes
To test the changes in a role run:
```sh
molecule converge
## example: molecule converge
```
When Ansible has succesfully ran, you can run assertions against your infrastructure using.
```sh
molecule verify
## example: `molecule verify`
```
You can also run commands like `molecule destroy`, `molecule prepare`, and `molecule test`. See Molecule documentation for more information
## Role Variables
---
| Name | Type | Default | Description |
| --------------------------------------- | ---- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mimir_working_path | str | /usr/share/mimir | Used to specify the directory path where Mimir, a component of the Grafana Agent, stores its working files and temporary data. |
| mimir_uninstall | bool | false | If set to `true` will perfom uninstall instead of deployment. |
| mimir_ruler_alert_path | str | /data/ruler | Used to specify the directory path where the Mimir ruler component of the Grafana Agent stores its alert files. |
| mimir_http_listen_port | str | 8080 | Used to specify the port number on which the Mimir component of the Grafana Agent listens for incoming HTTP requests. |
| mimir_http_listen_address | str | 0.0.0.0 | Used to specify the network address on which the Mimir component of the Grafana Agent listens for incoming HTTP requests. |
| mimir_ruler.rule_path | str | /data/ruler | Used to specify the directory path where the Mimir ruler component of the Grafana Agent looks for rule files. |
| mimir_ruler.alertmanager_url | str | http://127.0.0.1:8080/alertmanager | Used to specify the URL or address of the Alertmanager API that the Mimir ruler component of the Grafana Agent should communicate with. |
| mimir_ruler.ring.heartbeat_period | str | 2s | Used to specify the interval at which the Mimir ruler component of the Grafana Agent sends heartbeat signals to the ring. |
| mimir_ruler.heartbeat_timeout | str | 10s | Used to specify the maximum duration of time that the Mimir ruler component of the Grafana Agent will wait for a heartbeat signal from other components in the ring. |
| mimir_alertmanager.data_dir | str | /data/alertmanager | sed to specify the directory path where the Mimir Alertmanager component of the Grafana Agent stores its data files. |
| mimir_alertmanager.fallback_config_file | str | /etc/alertmanager-fallback-config.yaml | Used to specify the path to a fallback configuration file for the Mimir Alertmanager component of the Grafana Agent. |
| mimir_alertmanager.external_url | str | http://localhost:9009/alertmanager | Used to specify the external URL or address at which the Mimir Alertmanager component of the Grafana Agent can be accessed. |
| mimir_memberlist.join_members | [] | List of members for the Mimir cluster |
## **Additional Config Variables for `/etc/mimir/config.yml`**
Below variables allow you to extend Mimir configuration to fit your needs.
Always refer to official [Mimir configuration](https://grafana.com/docs/mimir/latest/configure/about-configurations/)
to obtain possible configuration parameters.
> [!NOTE]
> These variables are not set by default.
| Name | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mimir_blocks_storage` | Configures the `blocks_storage` component. 📚 [documentation](https://grafana.com/docs/mimir/latest/configure/configuration-parameters/#blocks_storage) |
| `mimir_ruler_storage` | Configures the `ruler_storage` component. 📚 [documentation](https://grafana.com/docs/mimir/latest/configure/configuration-parameters/#ruler_storage) |
| `mimir_alertmanager_storage` | Configures the `alertmanager_storage` component. 📚 [documentation](https://grafana.com/docs/mimir/latest/configure/configuration-parameters/#alertmanager_storage) |
| `mimir_distributor` | Configures the `distributor` component. 📚 [documentation](https://grafana.com/docs/mimir/latest/configure/configuration-parameters/#distributor) |
| `mimir_ingester` | Configures the `ingester` component. 📚 [documentation](https://grafana.com/docs/mimir/latest/configure/configuration-parameters/#ingester) |
| `mimir_querier` | Configures the `querier` component. 📚 [documentation](https://grafana.com/docs/mimir/latest/configure/configuration-parameters/#querier) |
For extra configuration samples refer to [`examples` directory](../../examples).

View file

@ -0,0 +1,31 @@
---
# defaults file for mimir
mimir_version: "latest"
mimir_uninstall: false
__mimir_arch: "{{ arch_mapping[ansible_facts['architecture']] | default('amd64') }}"
mimir_download_url_rpm: "https://github.com/grafana/mimir/releases/download/mimir-{{ mimir_version }}/mimir-{{ mimir_version }}_{{ __mimir_arch }}.rpm"
mimir_download_url_deb: "https://github.com/grafana/mimir/releases/download/mimir-{{ mimir_version }}/mimir-{{ mimir_version }}_{{ __mimir_arch }}.deb"
mimir_working_path: "/var/lib/mimir"
mimir_ruler_alert_path: "{{ mimir_working_path }}/ruler"
mimir_http_listen_port: 8080
mimir_http_listen_address: "0.0.0.0"
arch_mapping:
x86_64: amd64
aarch64: arm64
armv7l: armhf
i386: i386
ppc64le: ppc64le
mimir_server:
http_listen_port: "{{ mimir_http_listen_port }}"
http_listen_address: "{{ mimir_http_listen_address }}"
mimir_ruler:
rule_path: "{{ mimir_working_path }}/ruler"
alertmanager_url: "http://localhost:{{ mimir_http_listen_port }}/alertmanager"
mimir_alertmanager:
data_dir: "{{ mimir_working_path }}/alertmanager"
external_url: "http://localhost:{{ mimir_http_listen_port }}/alertmanager"

View file

@ -0,0 +1,6 @@
---
- name: Restart mimir
ansible.builtin.systemd:
name: mimir.service
state: restarted

View file

@ -0,0 +1,37 @@
---
# meta/main.yml
galaxy_info:
author: "Grafana"
role_name: mimir
description: "Grafana - platform for analytics and monitoring"
license: "MIT"
min_ansible_version: "2.9"
platforms:
- name: Ubuntu
versions:
- bionic
- xenial
- name: Debian
versions:
- stretch
- buster
- name: EL
versions:
- "7"
- "8"
- name: Fedora
versions:
- "30"
- "31"
galaxy_tags:
- grafana
- dashboard
- alerts
- alerting
- presentation
- monitoring
- metrics
dependencies: []
allow_duplicates: true

View file

@ -0,0 +1,33 @@
---
- name: Converge mimir
hosts: all
collections:
- grafana.grafana
vars:
mimir_storage:
storage:
backend: s3
s3:
endpoint: minio-mimir.:9000
access_key_id: testtest
secret_access_key: testtest
insecure: true
bucket_name: mimir
# Blocks storage requires a prefix when using a common object storage bucket.
mimir_blocks_storage:
storage_prefix: blocks
tsdb:
dir: "{{ mimir_working_path}}/ingester"
# Use memberlist, a gossip-based protocol, to enable the 3 Mimir replicas to communicate
mimir_memberlist:
join_members:
- molecule-grafana-mimir01.:7946
- molecule-grafana-mimir02.:7946
- molecule-grafana-mimir03.:7946
tasks:
- name: "Run Grafana mimir collection"
include_role:
name: mimir

View file

@ -0,0 +1,60 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: molecule-grafana-mimir01
image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-"/sbin/init"}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
network: molecule
network_mode: "molecule"
published_ports:
- 7946
- 9019:9009
- 9096
- name: molecule-grafana-mimir02
image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-"/sbin/init"}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
network: molecule
network_mode: "molecule"
published_ports:
- 7946
- 9029:9009
- 9096
- name: molecule-grafana-mimir03
image: "geerlingguy/docker-${MOLECULE_DISTRO:-rockylinux8}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-"/sbin/init"}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
privileged: true
pre_build_image: true
network: molecule
network_mode: "molecule"
published_ports:
- 7946
- 9039:9009
- 9096
provisioner:
name: ansible
env:
ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles
verifier:
name: testinfra
lint: |
set -e
yamllint .
ansible-lint .

View file

@ -0,0 +1,36 @@
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/mimir",
"/var/lib/mimir",
]
files = [
"/etc/mimir/config.yml"
]
for directory in dirs:
d = host.file(directory)
assert d.is_directory
assert d.exists
for file in files:
f = host.file(file)
assert f.exists
assert f.is_file
def test_service(host):
s = host.service("mimir")
assert s.is_running
def test_packages(host):
p = host.package("mimir")
assert p.is_installed

View file

@ -0,0 +1,111 @@
---
# tasks file for Mimir deployment
- name: Obtain the latest version from the Mimir GitHub repo
when: mimir_version == "latest"
block:
- name: Scrape Github API endpoint to obtain latest Mimir version
ansible.builtin.uri:
url: "https://api.github.com/repos/grafana/mimir/releases/latest"
method: GET
body_format: json
become: false
delegate_to: localhost
run_once: true
register: __github_latest_version
- name: Latest available Mimir version
ansible.builtin.set_fact:
mimir_version: "{{ __github_latest_version.json.tag_name | regex_replace('^v?(\\d+\\.\\d+\\.\\d+)$', '\\1') }}"
- name: Verify current deployed version
block:
- name: Check if Mimir binary is present
ansible.builtin.stat:
path: "/usr/bin/mimir"
register: __already_deployed
- name: Obtain current deployed Mimir version
ansible.builtin.command:
cmd: "/usr/bin/mimir --version"
changed_when: false
register: __current_deployed_version
when: __already_deployed.stat.exists | bool
- name: Include RedHat/Rocky setup
ansible.builtin.include_tasks:
file: setup-Redhat.yml
when: ansible_facts['os_family'] in ['RedHat', 'Rocky']
- name: Include Debian/Ubuntu setup
ansible.builtin.include_tasks:
file: setup-Debian.yml
when: ansible_facts['os_family'] == 'Debian'
- name: Check if Mimir default dir is present
ansible.builtin.stat:
path: "/tmp/mimir/boltdb-shipper-active"
register: __default_structure
- name: Default structure cleanup
when: __default_structure.stat.exists | bool
block:
- name: Ensure that Mimir is stopped before default cleanup
ansible.builtin.systemd:
name: mimir.service
state: stopped
- name: Remove default configuration from "/tmp/mimir" directory
ansible.builtin.file:
path: "/tmp/mimir"
state: absent
- name: Ensure that Mimir working path exists
ansible.builtin.file:
path: "{{ mimir_working_path }}"
state: directory
owner: "mimir"
group: "mimir"
mode: "0755"
- name: Ensure that Mimir rule path exists
ansible.builtin.file:
path: "{{ mimir_ruler_alert_path }}"
state: directory
owner: "mimir"
group: "mimir"
mode: "0755"
when:
- mimir_ruler_alert_path is defined
- mimir_ruler is defined
- name: Template Mimir config - /etc/mimir/config.yml
ansible.builtin.template:
src: "config.yml.j2"
dest: "/etc/mimir/config.yml"
owner: "mimir"
group: "mimir"
mode: "0644"
validate: "mimir -modules --config.file=%s"
environment:
PATH: "/usr/local/bin:{{ ansible_env.PATH }}"
notify:
- Restart mimir
- name: Ensure restart has completed
ansible.builtin.meta: flush_handlers
- name: Ensure that Mimir is started
ansible.builtin.systemd:
name: mimir.service
state: started
enabled: true
- name: Verify that Mimir URL is responding
ansible.builtin.uri:
url: "http://{{ mimir_http_listen_address }}:{{ mimir_http_listen_port }}/ready"
method: GET
register: mimir_verify_url_status_code
retries: 5
delay: 8
until: mimir_verify_url_status_code.status == 200

View file

@ -0,0 +1,9 @@
- name: Deploy Mimir service
ansible.builtin.include_tasks:
file: "deploy.yml"
when: not mimir_uninstall
- name: Uninstall Mimir service
ansible.builtin.include_tasks:
file: "uninstall.yml"
when: mimir_uninstall

View file

@ -0,0 +1,7 @@
---
- name: APT - Install Mimir
ansible.builtin.apt:
deb: "{{ mimir_download_url_deb }}"
state: present
notify: Restart mimir
when: __current_deployed_version.stdout is not defined or mimir_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,8 @@
---
- name: DNF - Install Mimir from remote URL
ansible.builtin.dnf:
name: "{{ mimir_download_url_rpm }}"
state: present
disable_gpg_check: true
notify: Restart mimir
when: __current_deployed_version.stdout is not defined or mimir_version not in __current_deployed_version.stdout

View file

@ -0,0 +1,51 @@
---
# tasks file for Mimir uninstall
- name: Stop Mimir service
ansible.builtin.systemd: # noqa ignore-errors
name: mimir
state: stopped
ignore_errors: true
- name: Uninstall Mimir rpm package
ansible.builtin.dnf:
name: "mimir"
state: absent
autoremove: true
when: ansible_facts['os_family'] in ['RedHat', 'Rocky']
- name: Uninstall Mimir deb package
ansible.builtin.apt:
name: "mimir"
state: absent
purge: true
when: ansible_facts['os_family'] == 'Debian'
- name: Ensure that Mimir firewalld rule is not present - tcp port {{ mimir_http_listen_port }}
ansible.posix.firewalld: # noqa ignore-errors
immediate: true
permanent: true
port: "{{ mimir_http_listen_port }}/tcp"
state: disabled
ignore_errors: true
- name: Remove Mimir directories"
ansible.builtin.file:
path: "{{ remove_me }}"
state: absent
loop:
- "/etc/mimir"
- "{{ mimir_working_path }}"
loop_control:
loop_var: remove_me
- name: Remove the Mimir system user
ansible.builtin.user:
name: "mimir"
force: true
state: absent
- name: Remove Mimir system group
ansible.builtin.group:
name: "mimir"
state: absent

View file

@ -0,0 +1,67 @@
# Run Mimir in single process mode, with all components running in 1 process.
target: all,alertmanager,overrides-exporter
# Configure Mimir to use Minio as object storage backend.
common:
{% if mimir_storage is defined %}
{{ mimir_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
# Blocks storage requires a prefix when using a common object storage bucket.
{% if mimir_blocks_storage is defined %}
blocks_storage:
{{ mimir_blocks_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
# Blocks storage requires a prefix when using a common object storage bucket.
{% if mimir_ruler_storage is defined %}
ruler_storage:
{{ mimir_ruler_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
# Alertmanager storage requires a prefix when using a common object storage bucket.
{% if mimir_alertmanager_storage is defined %}
alertmanager_storage:
{{ mimir_alertmanager_storage | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
# Use memberlist, a gossip-based protocol, to enable the 3 Mimir replicas to communicate.
{% if mimir_memberlist is defined %}
memberlist:
{{ mimir_memberlist | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_ruler is defined %}
ruler:
{{ mimir_ruler | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_alertmanager is defined %}
alertmanager:
{{ mimir_alertmanager | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_server is defined %}
server:
{{ mimir_server | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_limits is defined %}
limits:
{{ mimir_limits | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_distributor is defined %}
distributor:
{{ mimir_distributor | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_ingester is defined %}
ingester:
{{ mimir_ingester | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}
{% if mimir_querier is defined %}
querier:
{{ mimir_querier | to_nice_yaml(indent=2, sort_keys=False) | indent(2, False)}}
{% endif %}

Some files were not shown because too many files have changed in this diff Show more