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

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

Нажмите создать и скачайте файл в формате 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:AzamatKomaev/habr-terraform-ansible-logs.git
$ cd ./habr-terraform-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. В консоли облака должны будут появиться сервера:
Photo

В терминале должны отобразиться 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. Общая схема такая:

Роль агентов будет выполнять 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:AzamatKomaev/habr-terraform-ansible-logs.git
$ cd ./habr-terraform-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:
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 (много скринов)




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

Как можно увидеть на последнем скриншоте, список меток пуст. Это связано с тем, что 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"}
В логах вы должны увидеть содержимое файла. При этом вы сами можете указать с какого сервера хотите получить логи и какой файл смотреть:
Photo

На этом моменте мы прощаемся с 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: как по мне это проще всего. Создадим бота и добавим его в нашу группу:
Много скриншотов


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

Я буду настраивать все компоненты через 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:
Много скриншотов


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

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

Составляем Loki query
Прежде чем настраивать уведомления, давайте зайдем в Explore и поэкспериментируем с запросами. Получим список всех логов, которые содержат информацию о входе на все сервера:
{filename="/var/log/auth.log"} |= `Accepted publickey`
Но так мы увидим только список логов, который ничего нам не даст для алертов... Давайте добавим функцию count_over_time
, которая будет подсчитывать количество логов за опрёделенный промежуток времени:
(count_over_time({filename="/var/log/auth.log"} |= `Accepted publickey` [1s]))
Теперь у нас есть появится график, отображающий кол-во логов за заданный период. Также вы можете увидеть какие метки есть у логов:

Отлично, мы можем использовать эти метки в содержимом уведомления. Но чего-то не хватает... Информации о коннекте! Давайте воспользуемся операцией pattern
:
(count_over_time({filename="/var/log/auth.log"} |= `Accepted publickey` | pattern `<_> sshd[<_>]: <status> publickey for <user> from <client_ip> port <client_port> <_>` [1s]))
Снова взглянем на метки:

Появились дополнительные метки, которые будут сообщать более подробную информацию про удачное подключение.
Давайте также сформируем запрос для неудачных подключении:
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 и нажимаем создать алерт. Заполним поля следующим образом:
Много скриншотов

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

Идём дальше. Создадим фолдер, а также Evaluation group. Интервал выберем 10s. Это означает, что Grafana будет каждые 10 секунд проверять статус уведомления. Если условие отправки уведомления выполняется, то оно переходит в статус Pending
. Через время, указанное в "Pending period" уведомление переходит в статус Firing
и отправляется в Telegram/Email/другой Contact Point.

Summary и Description опциональны - можем их не указывать. В Labels и Notifications важно указать валидные метки ранее созданной политики уведомлении. Нажмём на Preview Routing, чтобы убедиться, что уведомления будут отправляться по валидному Contact Point:

Это алерт для успешных входов. Сделаем дупликат для неудачных коннектов и заменим Loki Query.
Получаем алерты
Давайте тестировать!
Попробуем выполнить логин по SSH на один из серверов. После этого алерт перёшел в состояние Pending:

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

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

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

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

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