Привет всем,

В данный момент телеграмм используется как основной источник сбора всех уведомлений/нотификаций по проблемам в инфраструктуре. И от обилия систем, на выходе имеем нехилый такой набор различных чатов.

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

Начнем от самого простого этапа к сложному.

Создание Канала/Топика

Тут думаю все просто, сначала создаем приватный чат, в настройках канала включаем поддержку топиков. alertmanager-to-tg1.png

alertmanager-to-tg2.png

Внутри чата создадим наш первый топик, в который и будет в последующем слать уведомления. alertmanager-to-tg3.png

Далее обратимся к @BotFather для создания собственнго бота. Наш бот будет неким посредников в отправке сообщений из alertmanager в телеграмм.

Процесс создания бота можно пропустить, на выходе у вас должен быть API token от созданного бота.

И наконец не забываем добавить бота в группу, в качестве простого пользователя.

Отправка через Curl

Для отправки сообщения в топик через утилиту curl, для начала нужно получить данные группы и топика. Как правило это chat_id и message_thread_id.

Что бы определить эти данные, в топик пишем рандомное сообщение при этом тегаем нашего бота: alertmanager-to-tg5.png

Далее в браузере открываем такой линк:

https://api.telegram.org/bot{INSERT_BOT_TOKEN}/getUpdates

В значение INSERT_BOT_TOKEN подставляем токен от бота.

Браузер отобразит массив, из которого мы находим chat_id и message_thread_id. alertmanager-to-tg4.png

Затем открываем командной строку и экспортируем в переменные окружения переменную со значением API токена от бота:

export TG_BOT={INSERT_BOT_TOKEN}

Опять же в значение INSERT_BOT_TOKEN подставляем токен.

И после при помощи курл отправляем первое сообщение:

curl -X POST -H 'Content-Type: application/json' \
  -d '{"reply_to_message_id": "4", "chat_id": "-1002124751773", "text": "This is a test message cUrl"}' \
  https://api.telegram.org/bot$TG_BOT/sendMessage

В консоле ответом отобразится результат запроса, и если все корретно то прилетит сообщение: alertmanager-to-tg6.png

Отправка через Alertmanager

На текущий момент Alertmanager не умеет отправлять сообщения в топики. Для получения этой фишки, скачаем его исходный код, внесем в него изменения. Далее соберем новый билд менеджера.

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

[root@mon ~]# yum install go nmp git make 

Клонируем репозиторий алерт менеджера:

[root@mon opt]# git clone https://github.com/prometheus/alertmanager.git

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

[root@mon opt]# cd alertmanager

Затем в редакторе открываем файл config/notifiers.go, находим структуру TelegramConfig, и добавляем новое поле:

[root@mon alertmanager]# vim config/notifiers.go
---
type TelegramConfig struct {
        NotifierConfig `yaml:",inline" json:",inline"`

        HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

        APIUrl               *URL   `yaml:"api_url" json:"api_url,omitempty"`
        BotToken             Secret `yaml:"bot_token,omitempty" json:"token,omitempty"`
        BotTokenFile         string `yaml:"bot_token_file,omitempty" json:"token_file,omitempty"`
        ChatID               int64  `yaml:"chat_id,omitempty" json:"chat,omitempty"`
        Message              string `yaml:"message,omitempty" json:"message,omitempty"`
        DisableNotifications bool   `yaml:"disable_notifications,omitempty" json:"disable_notifications,omitempty"`
        ParseMode            string `yaml:"parse_mode,omitempty" json:"parse_mode,omitempty"`
        //  Добавил новое поле - ReplyToMessageID
        MessageThreadId      int64  `yaml:"message_thread_id,omitempty" json:"message_thread_id,omitempty"`
}

Сохраняем и выходим.

Далее идем в notify/telegram/telegram.go внутри файла находим функцию Notify, и добавляем:

[root@mon alertmanager]# vim notify/telegram/telegram.go
---
func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, error) {
        var (
                err  error
                data = notify.GetTemplateData(ctx, n.tmpl, alert, n.logger)
                tmpl = notify.TmplText(n.tmpl, data, &err)
        )

        if n.conf.ParseMode == "HTML" {
                tmpl = notify.TmplHTML(n.tmpl, data, &err)
        }

        key, ok := notify.GroupKey(ctx)
        if !ok {
                return false, fmt.Errorf("group key missing")
        }

        messageText, truncated := notify.TruncateInRunes(tmpl(n.conf.Message), maxMessageLenRunes)
        if truncated {
                level.Warn(n.logger).Log("msg", "Truncated message", "alert", key, "max_runes", maxMessageLenRunes)
        }

        n.client.Token, err = n.getBotToken()
        if err != nil {
                return true, err
        }

        message, err := n.client.Send(telebot.ChatID(n.conf.ChatID), messageText, &telebot.SendOptions{
                // Добавил ReplyTo в SendOptions
                ReplyTo:               &telebot.Message{ID: int(n.conf.MessageThreadId)},
                DisableNotification:   n.conf.DisableNotifications,
                DisableWebPagePreview: true,
        })
        if err != nil {
                return true, err
        }
        level.Debug(n.logger).Log("msg", "Telegram message successfully published", "message_id", message.ID, "chat_id", message.Chat.ID)

        return false, nil
}

В данном случаи внесли изменения только в метод Send:

message, err := n.client.Send(telebot.ChatID(n.conf.ChatID), messageText, &telebot.SendOptions{
        // Добавил ReplyTo в SendOptions
        ReplyTo:               &telebot.Message{ID: int(n.conf.MessageThreadId)},
        DisableNotification:   n.conf.DisableNotifications,
        DisableWebPagePreview: true,
})

Сохраняемся и выходим.

Итак мы внеслм изменения в двух местах, теперь собирем новый билд для проверки работы нового функционала.

Внутри репозитория запускаем сборку, командой:

[root@mon alertmanager]# make build

В процессе сборки, если сборка будет ломаться на этапе с реактом, то достаточно обновить npm на более современную версию. По крайней мере мне помог этот вариант. Для обновления достаточно выполнить команды:

[root@mon alertmanager]# yum module reset nodejs -y 
[root@mon alertmanager]# yum module enable nodejs:18 -y 
[root@mon alertmanager]# yum update npm -y 

Также для запуска сервиса потребуется конфигурационный alertmanager, напишем его:

[root@mon alertmanager]# vim /etc/alertmanager/alertmanager.yml
---
route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 5m
  receiver: 'telegram-api'
receivers:
  - name: 'telegram-api'
    telegram_configs:
    - bot_token: '<СЮДА ВСТАВЛЯЕМ API TOKEN>'
      api_url: 'https://api.telegram.org'
      chat_id: 'СЮДА CHAT_ID'
      parse_mode: ''
      message_thread_id: ' СЮДА TOPIC_ID'
      message: "🚨 Alertmanager 🚨\n--\n🔺 Alertname: {{ .GroupLabels.alertname}}\n🔺 Severity: {{ .CommonLabels.severity }}\n📌 {{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}"

Ну и запускаем алерт менеджера:

[root@mon alertmanager]# ./alertmanager --config.file=/etc/alertmanager/alertmanager.yml

Попробуем отправить тестовый алерт, на порт alertmanager через curl:

[root@mon ~]# curl -w '\n' -i --header 'Content-Type: application/json' --data '[{"labels":{"alertname":"test"},"endsAt":"2024-10-5T00:10:53-03:00"}]' http://localhost:9093/api/v1/alerts

Теперь если в браузере открыть UI алерт менеджера, то увидем новый алерт в очереди: alertmanager-to-tg7.png

И соответвенно этот алерт прилетит в наш ранее созданный топик: alertmanager-to-tg8.png

Новый бинарь закидываем сюда /usr/local/bin/:

[root@mon alertmanager]# mv alertmanager /usr/local/bin/

Затем создадим новый systemd-юнит для запуска алертменеджера как сервис:

[root@mon alertmanager]# vim /etc/systemd/system/alertmanager.service
---
[Unit]
Description=Alertmanager
Wants=network-online.target
After=network-online.target

[Service]
User=alertmanager
Group=alertmanager
Type=simple
WorkingDirectory=/etc/alertmanager/
ExecStart=/usr/local/bin/alertmanager \
    --config.file=/etc/alertmanager/alertmanager.yml
[Install]
WantedBy=multi-user.target

Перечитываем конфиг systemd, и запускаем сервис:

[root@mon alertmanager]# systemctl daemon-reload
[root@mon alertmanager]# systemctl enable --now alertmanager

Ну и на этом все, задача решена =)