Как стать автором
Обновить

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

Уровень сложностиСредний
Время на прочтение19 мин
Количество просмотров10K

Вступление

Не задумывались ли вы когда-нибудь над тем, чтобы знать о каждом входе на ваши сервера? Меня охватила такая же паранойя: а вдруг, когда я сплю, на мой сервер заходит домовой и творит там ужасы? Хотя логин на наши сервера и запрещен по паролю, а 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 (много скринов)
Подключение к источнику данных по localhost!
Подключение к источнику данных по localhost!

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

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

Настраиваем сущности 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!

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

Теги:
Хабы:
Всего голосов 15: ↑15 и ↓0+15
Комментарии26

Публикации

Истории

Работа

Ближайшие события

Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область