Найти в Дзене
Linux | Network | DevOps

Ansible + Grafana Loki: автоматическое уведомление о входе по SSH

Не задумывались ли вы когда-нибудь над тем, чтобы знать о каждом входе на ваши сервера? В этой статье мы развёрнем через Terraform несколько серверов в Yandex.Cloud, а затем при помощи Ansible настроим необходимый софт на каждом сервере. У нас будет основной сервер, где будет развёрнут Loki (система агрегирования логов) и Grafana (инструмент для визуализации данных), на серверах, которые мы хотим отслеживать, будет установлен Promtail (агент для сбора и отправки логов). Мы разберёмся с тем, как отслеживать входы на сервер, а затем в удобном формате отправлять об этом уведомления в чат с помощью вышеуказанных сервисов. Помимо этого, вы можете использовать Grafana не только для отслеживания коннектов к вашим серверам. Вы также можете развернуть Node-Exporter(-s)+Prometheus для мониторинга, чтобы отслеживать производительность серверов. Ссылка на репозиторий GitHub Чаще всего в инфраструктуре бывает стадо из множества серверов, поэтому давайте создадим несколько серверов. С этим нам пом
Оглавление

Вступление

Не задумывались ли вы когда-нибудь над тем, чтобы

знать о каждом входе на ваши сервера?

В этой статье мы развёрнем через Terraform несколько серверов в Yandex.Cloud, а затем при помощи Ansible настроим необходимый софт на каждом сервере. У нас будет основной сервер, где будет развёрнут Loki (система агрегирования логов) и Grafana (инструмент для визуализации данных), на серверах, которые мы хотим отслеживать, будет установлен Promtail (агент для сбора и отправки логов). Мы разберёмся с тем, как отслеживать входы на сервер, а затем в удобном формате отправлять об этом уведомления в чат с помощью вышеуказанных сервисов.

Помимо этого, вы можете использовать Grafana не только для отслеживания коннектов к вашим серверам. Вы также можете развернуть Node-Exporter(-s)+Prometheus для мониторинга, чтобы отслеживать производительность серверов.

Ссылка на репозиторий GitHub

Разворчиваем инфраструктуру

Чаще всего в инфраструктуре бывает стадо из множества серверов, поэтому давайте создадим несколько серверов. С этим нам поможет Terraform.

Для начала создадим сервисный аккаунт в облаке с ролью admin на всё облако:

-2

Далее создадим авторизированный ключ, чтобы Terraform мог управлять инфраструктурой:

-3

Нажмите создать и скачайте файл в формате JSON.

Зайдем в терминал и настроим доступ к облаку в yc. Для начала создадим

профиль. Профили служат для хранения конфигурации для доступа к

различным облакам если у вас их несколько:

$ yc config profiles create yc-compute-logs

Profile 'yc-compute-logs' created and activated

Назначим service-account-key (путь до authorized-key.json, который мы установили ранее), cloud-id и folder-id:

$ yc config set cloud-id <your_cloud_id>

$ yc config set folder-id <your_folder_id>

$ yc config set service-account-key <your_path_to_authorized_key>

Убедимся, что настроили доступ к облаку в yc корректно. Попробуем получить список всех сервисных аккаунтов:

$ yc iam service-account list

+----------------------+-----------+

| ID | NAME |

+----------------------+-----------+

| ajevk0p06h138i651oje | terraform |

+----------------------+-----------+

Перед созданием виртуальных машин сгенерируем несколько SSH ключей для доступа к ним:

ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f ~/.ssh/habr-logs/grafana

ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f ~/.ssh/habr-logs/node1

ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f ~/.ssh/habr-logs/node2

ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f ~/.ssh/habr-logs/node3

Теперь необходимо склонировать репозитории. Сделаем это, перейдём в директорию terraform, запустим set_env.sh скрипт и выполним terraform plan:

$ git clone git@github.com:neuxunil/ansible-logs.git

$ cd ./ansible-logs/terraform

$ . ./set_env.sh

You are using yc-compute-logs!

Profile 'yc-compute-logs' activated

$ Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

+ create

Terraform will perform the following actions:

<output was hidden>

Plan: 12 to add, 0 to change, 0 to destroy.

Changes to Outputs:

+ grafana_ip_address = (known after apply)

+ node1_ip_address = (known after apply)

+ node2_ip_address = (known after apply)

+ node3_ip_address = (known after apply)

Terraform покажет вам какие ресурсы будут созданы после команды terraform apply. После выполнения этой команды будут созданы следующие ресурсы:

  • Сеть default, подсеть в зоне ru-central1-a
  • 4 диска (10ГБ, SSD)
  • 4 виртуальных машин (5% CPU code fraction, 2 CPU cores, 2 RAM)

Выполним terraform apply. В консоли облака должны будут появиться сервера:

-4

В терминале должны отобразиться IP-адреса серверов:

Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

Outputs:

grafana_ip_address = "158.160.43.15"

node1_ip_address = "51.250.71.111"

node2_ip_address = "158.160.110.84"

node3_ip_address = "158.160.100.121"

Они нам понадобятся в дальнейшем.

Деплоим нужные компоненты через Ansible

Передаём эстафету Ansible. С помощью Ansible мы можем развёрнуть один или несколько сервисов сразу на нескольких серверах. Меньше слов, больше практики: приступим к разворачиванию Grafana и Loki!

Grafana - это инструмент для визуализации данных. При помощи Grafana можно просматривать метрики из различных источников данных, создавать дашборды с информацией, гибко настраивать уведомления в чат или на почту. Loki - система агрегирования логов, которая будет служить источником данных для Grafana. Общая схема такая:

-5

Роль агентов будет выполнять Promtail, его мы будем ставить на сервера

node1, node2 и node3. Promtail на разных виртуальных машинах будет

отслеживать содержимое заданных файлов, а затем отправлять их в

единственный инстанс Loki. Затем мы сможем просматривать эти логи при

помощи Grafana. Помимо этого, у Grafana есть возможность настраивать

алерты из коробки, поэтому почему бы не воспользоваться такой

замечательной функциональностью?

Установка Ansible и первоначальная конфигурация

Установить Ansible проще простого, это можно сделать различными путями, я выбрал pip:

$ python3 -m pip install --user ansible

Хотя Ansible считается "agentless" (вам не требуется устанавливать агент на каждую ВМ), нам потребуется установленный python на каждой виртуальной машине.

Далее нам необходим репозиторий из GitHub,

если вы пропустили шаг с поднятием нескольких ВМ в Yandex.Cloud, то

склонируйте этот самый репозитории и перейдите в директорию ansible:

$ git clone git@github.com:neuxunil/ansible-logs.git

$ cd ./ansible-logs/ansible

Тут вы можете найти множество файлов и директории, давайте остановимся на inventory.ini.

В этом файле вы должны указать IP-адреса ваших серверов, на которых

Ansible будет выполнять заданные вами команды. Для гибкости адреса можно

сгруппировать по названию. В файле я указал IP-адреса моих серверов,

замените их на ваши:

inventory.ini

[grafana]

158.160.43.15 ansible_user=admin

[nodes]

51.250.71.111 ansible_user=admin logging_label=node1

158.160.110.84 ansible_user=admin logging_label=node2

158.160.100.121 ansible_user=admin logging_label=node3

С помощью ansible_user мы можем указать от какого пользователя выполнять команды

logging_level является кастомной переменной, она нам пригодится в дальнейшем

Попробуем совершить тестовое подключение к серверам. Выполним ping:

$ ansible -m ping all -i ./inventory.ini

Output

158.160.43.15 | SUCCESS => {

"ansible_facts": {

"discovered_interpreter_python": "/usr/bin/python3"

},

"changed": false,

"ping": "pong"

}

51.250.71.111 | SUCCESS => {

"ansible_facts": {

"discovered_interpreter_python": "/usr/bin/python3"

},

"changed": false,

"ping": "pong"

}

158.160.100.121 | SUCCESS => {

"ansible_facts": {

"discovered_interpreter_python": "/usr/bin/python3"

},

"changed": false,

"ping": "pong"

}

158.160.110.84 | SUCCESS => {

"ansible_facts": {

"discovered_interpreter_python": "/usr/bin/python3"

},

"changed": false,

"ping": "pong"

}

Всё в порядке! Идём дальше...

В текущей директорий ansible есть несколько YAML файлов. Каждый из них

содержит Ansible Playbook - список задач, которые Ansible должен

выполнить на опредёленных серверах.

Одна задача - одно действие. Для задачи указывается название модуля. Модуль - это небольшая программа, выполняющаяся на сервере. Список всех модулей можно посмотреть в документации Ansible.

Мы будем использовать модули ansible.builtin.*, их хватит для наших

задач. Вместо полного названия, для этой группы модулей можно

использовать короткие алисасы.

Деплоим Loki

Начнём с установки Loki. Для этого я создал отдельный файл loki-installation-playbook.yml с Ansible Playbook, отвечающего за установку и запуск Loki. Loki и

Grafana будут установлены на один сервер. Содержимое всех дальнейших

конфигурационных файлов и пояснения к ним будут спрятаны в спойлере:

loki-installation-playbook.yml

- name: Install Loki

hosts: grafana

tasks:

- name: Install unzip for unpacking archives

apt:

name: unzip

become: true

- name: Install loki using wget and unzip it

shell:

chdir: /home/admin

cmd: |

wget https://github.com/grafana/loki/releases/download/v2.9.4/loki-linux-amd64.zip

unzip "loki-linux-amd64.zip"

chmod a+x "loki-linux-amd64"

sudo mv ./loki-linux-amd64 /usr/local/bin/loki

- name: Install default loki configuration yaml

shell:

chdir: /home/admin

cmd: wget https://raw.githubusercontent.com/grafana/loki/main/cmd/loki/loki-local-config.yaml

- name: Copy loki.service (unit for systemd) to /etc/systemd/system folder

copy:

src: ./resources/loki.service

dest: /etc/systemd/system

become: true

- name: Load loki unit and start it

systemd:

name: loki

state: started

daemon_reload: true

enabled: true

become: true

В самом начале мы указываем название Playbook, а также название группы

хостов из inventory.ini на которых будут выполняться задачи. Затем

следует список задач.

Так как некоторые задачи требует привилегированного доступа, для таких задач задаётся поле become со значением true: become: true

Install unzip for unpacking archives: Устанавливаем unzip для распаковки архива с бинарником loki. Для этого воспользуемся модулем ansible.builtin.apt.

Install loki using wget and unzip it: Устанавливаем архив с бинарником loki, распоковываем его и перемещаем в одну из директории из $PATH. Тут используем модуль ansible.builtin.shell, запускающий bash команды на удаленном сервере.

Install default loki configuration yaml: Устанавливаем конфигурационный файл для запуска loki, нам хватит значении по-умолчанию.

Copy loki.service (unit for systemd) to /etc/systemd/system folder: Для запуска loki в фоновом режиме будет использоваться systemd. Поэтому необходимо скопировать локальный файл loki.service в директорию с юнитами. Для копирования используется модуль ansible.builtin.copy.

Load loki unit and start it: После

успешного копирования юнита systemd, необходимо запустить его. Для

управления процессами systemd можно использовать модуль ansible.builtin.systemd_service.

Запустим Playbook. Для этого используется команда ansible_playbook, в которую следует передать путь до inventory.ini, а также до самого playbook:

$ ansible-playbook -i ./inventory.ini ./loki-installation-playbook.yml

Output

PLAY [Install Loki] ***********************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************

ok: [158.160.43.15]

TASK [Install unzip for unpacking archives] ***********************************************************************************************

changed: [158.160.43.15]

TASK [Install loki using wget and unzip it] ***********************************************************************************************

changed: [158.160.43.15]

TASK [Install default loki configuration yaml] ********************************************************************************************

changed: [158.160.43.15]

TASK [Copy loki.service (unit for systemd) to /etc/systemd/system folder] *****************************************************************

changed: [158.160.43.15]

TASK [Load loki unit and start it] ********************************************************************************************************

changed: [158.160.43.15]

PLAY RECAP ********************************************************************************************************************************

158.160.43.15 : ok=6 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Если выполнение команды прошло без ошибок, то перейдите по адресуhttp://<grafana_ip_address>:3100/metrics. Вы должны увидеть список всех метрик:

$ curl http://158.160.43.15:3100/metrics | head -n 10

Output

# HELP cortex_consul_request_duration_seconds Time spent on consul requests. 0

# TYPE cortex_consul_request_duration_seconds histogram

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.005"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.01"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.025"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.05"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.1"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.25"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.5"} 2436

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="1"} 2436

Loki развёрнут успешно!

Разворачиваем Grafana

Настало время для Grafana. Установить её deb пакет немного сложнее, если вы из России, чем Loki... Поэтому я воспользовался <Дядя Майор, не надо>

и поместил установленный .deb файл в директорию resources. Репозитории

GitHub не позволяет загружать более 100МБ содержимого, поэтому я добавил

его в .gitignore.

Рассмотрим содержимое grafana-installation-playbook.yml:

- name: Install Grafana

hosts: grafana

tasks:

- name: Install musl

apt:

name: musl

become: true

- name: Copy grafana deb package from local

copy:

src: ./resources/grafana-enterprise_10.3.3_amd64.deb

dest: /home/admin/

- name: Install Grafana

apt:

deb: /home/admin/grafana-enterprise_10.3.3_amd64.deb

become: true

- name: Make sure a grafana-server is running

systemd_service:

state: started

name: grafana-server

become: true

Install musl: устнавливаем необходимый пакет для Grafana.

Copy grafana deb package from local: копируем deb пакет в директорию /home/admin на удаленном сервере.

Install Grafana: устанавливаем ранее скопированный deb пакет.

Make sure a grafana-server is running: после установки запускаем systemd юнит.

Запускаем Playbook!

$ ansible-playbook -i ./inventory.ini ./grafana-installation-playbook.yml

Output

PLAY [Install Grafana] ********************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************

ok: [158.160.43.15]

TASK [Install musl] ***********************************************************************************************************************

changed: [158.160.43.15]

TASK [Copy grafana deb package from local] ************************************************************************************************

changed: [158.160.43.15]

TASK [Install Grafana] ********************************************************************************************************************

changed: [158.160.43.15]

TASK [Make sure a grafana-server is running] **********************************************************************************************

changed: [158.160.43.15]

PLAY RECAP ********************************************************************************************************************************

158.160.43.15 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

После успешного выполнения перейдем по адресу http://<grafana_ip_address>:3000. Введите в поле с логином и паролем admin. Затем перейдем во вкладку Connections, выберем источник данных Loki и подключим его:

Добавление источника данных Loki в Grafana (много скринов)

-6
-7
Подключение к источнику данных по localhost!
Подключение к источнику данных по localhost!
-9

Зайдем в Explore, выберем источник данных loki и посмотрим доступные метки:

-10

Как можно увидеть на последнем скриншоте, список меток пуст. Это связано с

тем, что Loki пока не получает никакие логи. Пора развернуть агенты на

node1, node2 и node3 сервера для сбора и доставки логов. Роль агента

будет играть Promtail.

Настраиваем Firewall

Так как в Loki будут доставляться логи из других серверов, то открывать сервис публично такая себе затея. Воспользуемся ufw - брандмауэром на Linux. По-умолчанию все порты окажутся закрытыми. Откроем 22 порт для SSH и 3000 порт для доступа к Grafana. Разрешим доступ к 3100 порту (Loki) только из приватной сети. Для всех правил ufw я создал отдельный Playbook:

firewall-setup-playbook.yml

- name: Set up firewall for grafana server

hosts: grafana

tasks:

- name: Set up firewall via ufw

shell:

cmd: |

sudo ufw allow 22

sudo ufw allow 3000

sudo ufw allow from 10.0.0.0/8 to any port 3100

sudo ufw --force enable

Запускаем Playbook:

$ ansible-playbook -i ./inventory.ini ./firewall-setup-playbook.yml

Output

PLAY [Set up firewall for grafana server] *************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************

ok: [158.160.43.15]

TASK [Set up firewall via ufw] ************************************************************************************************************

changed: [158.160.43.15]

PLAY RECAP ********************************************************************************************************************************

158.160.43.15 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Взглянем на открытые порты. Воспользуемся командой nmap:

$ nmap -Pn 158.160.43.15

Starting Nmap 7.80 ( https://nmap.org ) at 2024-02-24 19:17 MSK

Nmap scan report for 158.160.43.15

Host is up (0.11s latency).

Not shown: 998 filtered ports

PORT STATE SERVICE

22/tcp open ssh

3000/tcp open ppp

Как можно увидеть в терминале, открыты два порта. Что насчёт Loki? Давайте

зайдем на сервер node1 и попробуем выполнить curl запрос к Loki:

$ curl http://grafana:3100/metrics | head -n 3

# TYPE cortex_consul_request_duration_seconds histogram

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.005"} 7796

cortex_consul_request_duration_seconds_bucket{kv_name="ingester-ring",operation="CAS loop",status_code="200",le="0.01"} 7796

Разворачиваем Promtail

Теперь нам нужно настроить агент, который будет собирать и доставлять логи в Loki. Эту роль будет играть Promtail. Его необходимо развернуть на

каждом сервере, где мы хотим обрабатывать логи. Как и в случае с Loki,

для управления Promtail используется systemd юнит. Запускаем Playbook.

Обратите внимание, что он запустится на всех заданных в inventory.ini

IP-адресах с групой nodes.

Но прежде давайте сформируем конфигурационный файл для Promtail. В нём мы должны указать куда следует отправлять логи (clients[0].url), а также scrape_config, который будет получать изменения из /var/log/auth.log файла и отправлять их в Loki. В этом файле хранится вся информация об авторизации

пользователей. Помимо этого, мы можем добавить собственные метки.

Добавим node_name и node_ip для каждого сервера. Мы будем использовать эту метку для того, чтобы в Grafana была возможность определять откуда прилетел лог.

promtail-config.yml

server:

http_listen_port: 9080

grpc_listen_port: 0

positions:

filename: /tmp/positions.yaml

clients:

- url: http://grafana:3100/loki/api/v1/push

scrape_configs:

- job_name: system

static_configs:

- targets:

- localhost

labels:

__path__: /var/log/auth.log

node_name: $NODE_NAME

node_ip: $NODE_IP

Содержимое Playbook:

promtail-installation-playbook.yml

- name: Install Promtail

hosts: nodes

tasks:

- name: Install unzip for unpacking archives

apt:

name: unzip

become: true

- name: Install promtail using wget and unzip it

shell:

chdir: /home/admin

cmd: |

wget https://github.com/grafana/loki/releases/download/v2.8.8/promtail-linux-amd64.zip

unzip "promtail-linux-amd64.zip"

chmod a+x "promtail-linux-amd64"

sudo mv ./promtail-linux-amd64 /usr/local/bin/promtail

- name: Copy promtail configuration

copy:

src: ./resources/promtail-config.yml

dest: /home/admin/promtail-config.yml.tmp

- name: Set $NODE_NAME in promtail-config.yml

environment:

NODE_NAME: "{{ logging_label }}"

NODE_IP: "{{ ansible_host }}"

shell:

chdir: /home/admin

cmd: envsubst < promtail-config.yml.tmp > promtail-config.yml

- name: Copy promtail.service (unit for systemd) to /etc/systemd/system folder

copy:

src: ./resources/promtail.service

dest: /etc/systemd/system

become: true

- name: Load promtail unit and start it

systemd:

name: promtail

state: started

daemon_reload: true

enabled: true

become: true

Set $NODE_NAME in promtail-config.yml: подставляем вместо $NODE_NAME и $NODE_IP значения переменных, указаных в inventory.ini

$ ansible-playbook -i ./inventory.ini ./promtail-installation-playbook.yml

Output

PLAY [Install Promtail] *******************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************

ok: [51.250.71.111]

ok: [158.160.110.84]

ok: [158.160.100.121]

TASK [Install unzip for unpacking archives] ***********************************************************************************************

changed: [51.250.71.111]

changed: [158.160.100.121]

changed: [158.160.110.84]

TASK [Install promtail using wget and unzip it] *******************************************************************************************

changed: [158.160.110.84]

changed: [158.160.100.121]

changed: [51.250.71.111]

TASK [Copy promtail configuration] ********************************************************************************************************

changed: [51.250.71.111]

changed: [158.160.100.121]

changed: [158.160.110.84]

TASK [Set $NODE_NAME in promtail-config.yml] **********************************************************************************************

changed: [158.160.110.84]

changed: [51.250.71.111]

changed: [158.160.100.121]

TASK [Copy promtail.service (unit for systemd) to /etc/systemd/system folder] *************************************************************

changed: [158.160.110.84]

changed: [51.250.71.111]

changed: [158.160.100.121]

TASK [Load promtail unit and start it] ****************************************************************************************************

changed: [158.160.100.121]

changed: [158.160.110.84]

changed: [51.250.71.111]

PLAY RECAP ********************************************************************************************************************************

158.160.100.121 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

158.160.110.84 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

51.250.71.111 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Зайдем в Grafana во вкладку Explore и попробуем выполнить следующий запрос:

{node_name="node1", filename="/var/log/auth.log"}

В логах вы должны увидеть содержимое файла. При этом вы сами можете

указать с какого сервера хотите получить логи и какой файл смотреть:

-11

На этом моменте мы прощаемся с Ansible, так как все нужные компоненты развёрнуты успешно. Приступим к настройке алертов!

Настраиваем отправку уведомлении

Стоит отметить, что на моих серверах по-умолчанию выключен вход по паролю. Поэтому в ходе этой статьи мы будем отслеживать входы по SSH, хотя никто вам не мешает настроить отправку для входа по паролю.

/var/log/auth.log

Давайте подробнее расмотрим содержимое файла /var/log/auth.log. Тут полно информации, но нас интересуют подобные строки:

2024-02-25 10:16:38.858 Feb 25 07:03:47 node1 sshd[1139]: Accepted publickey for admin from <client_ip_addr> port 34638 ssh2: RSA SHA256:<hidden_content>

Такая строчка прилетает после успешного входа. Давайте попробуем войти на node1 по приватному ключу от Grafana:

$ ssh -i ~/.ssh/habr-logs/grafana admin@<node1_ip_address>

admin@51.250.71.111: Permission denied (publickey).

В логи прилетает следующее:

2024-02-25 10:44:32.126 Feb 25 07:44:31 node1 sshd[3054]: Connection closed by authenticating user admin <client_ip_add> port 48444 [preauth]

В обоих случаях мы видим информацию про IP-адрес и порт клиента, а также

название пользователя, к которому выполняется логин. Но формат логов

немного отличается, поэтому у нас будет два алерта: один алерт для

успешных коннектов, второй - для неудачных.

Настраиваем Telegram

Давайте настроим отправку уведомлении в Telegram: как по мне это проще всего. Создадим бота и добавим его в нашу группу:

Сохраним токен - он будет нужен при добавлении Contact Point
Сохраним токен - он будет нужен при добавлении Contact Point
Убедитесь, что бот имеет доступ к сообщениям
Убедитесь, что бот имеет доступ к сообщениям

Настраиваем сущности Grafana Alerting

-14
Я буду настраивать все компоненты через GUI. В репозитории
есть директория provisioning, вы её можете использовать для того,
чтобы избежать ручной настройки. Воспользуйтесь документацией: тык

Начнём с Contact Point-ов. Grafana поддерживает множество Contact Point. Они служат для отправки уведомлении различными способами. Так Grafana

поддерживает множество Contact Point-ов для разных мессенджеров и

сервисов, мы воспользуемся Telegram

Для него необходимо указать BOT API Token и Chat ID. Опционально можем указать другие параметры.

Получим Chat ID. После того как вы добавили бота в вашу группу, отправим что-нибудь в чат и выполним запрос:

$ curl https://api.telegram.org/bot<bot_api_token>/getUpdates | jq '.result[-1].message.chat.id'

-1002141029691

Создадим Contact Point в разделе Alerting. Прежде напишем собственный шаблон для уведомлении:

{{ if gt (len .Alerts) 0 }}

{{ range .Alerts }}

{{ if .Labels.node_name }}

Alertname: {{ .Labels.alertname }}

Status: {{ .Labels.status }}

Node name: {{ .Labels.node_name }}

From: {{ .Labels.client_ip }}:{{ .Labels.client_port }}

To: {{ .Labels.user }}@{{ .Labels.node_ip }}

{{ else }}

{{ range .Labels.SortedPairs }}

The name of the label is {{ .Name }}, and the value is {{ .Value }}

{{ end }}

{{ end }}

{{ end }}

{{ end }}

Далее мы составим Loki запрос, который будет содержать метки client_ip,

client_port, status и user. Метки node_name и node_ip указаны в

конфигурации Promtail и доступны по-умолчанию. Если алерт содержит метку

node_name, то будем присылать сообщение, содержащее все метрики в удобном формате. Иначе отправим просто список всех меток.

Создадим Contact Point:

-15
-16

Отправим тестовый алерт:

Photo

-17

Далее на очереди Notification policy. Он будет служить "мостом" между алертом и Contact-Point-ом. В политике указываются метки и контакт поинт:

-18

Составляем Loki query

Прежде чем настраивать уведомления, давайте зайдем в Explore и поэкспериментируем с запросами. Получим список всех логов, которые содержат информацию о входе на все сервера:

{filename="/var/log/auth.log"} |= `Accepted publickey`

Но так мы увидим только список логов, который ничего нам не даст для алертов... Давайте добавим функцию count_over_time, которая будет подсчитывать количество логов за опрёделенный промежуток времени:

(count_over_time({filename="/var/log/auth.log"} |= `Accepted publickey` [1s]))

Теперь у нас есть появится график, отображающий кол-во логов за заданный

период. Также вы можете увидеть какие метки есть у логов:

-19

Отлично, мы можем использовать эти метки в содержимом уведомления. Но чего-то не хватает... Информации о коннекте! Давайте воспользуемся операцией pattern:

(count_over_time({filename="/var/log/auth.log"} |= `Accepted publickey` | pattern `<_> sshd[<_>]: <status> publickey for <user> from <client_ip> port <client_port> <_>` [1s]))

Снова взглянем на метки:

-20

Появились дополнительные метки, которые будут сообщать более подробную информацию про удачное подключение.

Давайте также сформируем запрос для неудачных подключении:

count_over_time({filename="/var/log/auth.log"} |= `Connection closed by authenticating` != `root` | pattern `<_> sshd[<_>]: Connection <status> by authenticating user <user> <client_ip> port <client_port> <_>` [1s])

Создаём алерты

Теперь давайте создадим два алерта с составленным выше запросом. Переходим в Alerting -> Alert Rules и нажимаем создать алерт. Заполним поля

следующим образом:

-21

Укажем название алерта. Затем укажем ранее сформированный Loki Query. Сделаем так, чтобы алерт срабатывал, если таких логов прилетело больше, чем ноль. Можно подключиться к одному из серверов и нажать на Run queries, чтобы убедиться, что всё ок:

-22

Идём дальше. Создадим фолдер, а также Evaluation group. Интервал выберем

10s. Это означает, что Grafana будет каждые 10 секунд проверять статус

уведомления. Если условие отправки уведомления выполняется, то оно

переходит в статус Pending. Через время, указанное в "Pending period" уведомление переходит в статус Firing и отправляется в Telegram/Email/другой Contact Point.

-23

Summary и Description опциональны - можем их не указывать. В Labels и

Notifications важно указать валидные метки ранее созданной политики

уведомлении. Нажмём на Preview Routing, чтобы убедиться, что уведомления

будут отправляться по валидному Contact Point:

-24

Это алерт для успешных входов. Сделаем дупликат для неудачных коннектов и заменим Loki Query.

Получаем алерты

Давайте тестировать!

Попробуем выполнить логин по SSH на один из серверов. После этого алерт перёшел в состояние Pending:

-25

Ожидаем 10 секунд и видим, что алерт перешёл в статус Alerting:

-26

Спустя несколько секунд должно прийти сообщение в Telegram чат:

-27

Можно сделать коннект сразу к двум или трём серверам. Тогда придёт такой алерт:

-28

Попробуем выполнить вход на сервер по невалидному SSH-ключу:

-29

Всё работает!

Огромный вывод

Почему огромный? Потому что статья получилась огромная! В этом туториале мы воспользовались множеством DevOps-инструментов: развернули инфраструктуру в облаке через Terraform, настроили компоненты Grafana через Ansible, сконфигурировали Loki, Promtail и Grafana и в конечном итоге смогли настроить отправку сообщений в Telegram чат после логина по SSH!

Конечно, возможно есть более лёгкие пути реализации того, что написано в заголовке. Но я опирался на количество инструментов, которые можно использовать для различных целей...

Обсудить эту статью можно в Телеграм канале: https://t.me/linautonet