golang

Мониторинг SSL-сертификатов в oVirt Engine: как мы научились спать спокойно благодаря Go и Promethe…

  • четверг, 11 сентября 2025 г. в 00:00:06
https://habr.com/ru/companies/hostkey/articles/945776/

Авторы статьи: Артем Зубков, Junior администратор отдела DevOps.

В современных распределённых системах надёжность и безопасность инфраструктуры напрямую зависят от корректного функционирования криптографических компонентов, в частности — SSL/TLS-сертификатов. Одним из критически важных аспектов эксплуатации таких систем является своевременный мониторинг срока действия сертификатов, поскольку их просрочка может привести к нарушению работы сервисов, недоступности API, сбоям в аутентификации и даже компрометации безопасности соединений. В рамках экосистемы oVirt Engine, выступающего центральным узлом управления виртуальной инфраструктурой, особое значение имеют сертификаты, обеспечивающие защищённое взаимодействие между компонентами системы и конечными пользователями.

Серверы для мониторинга

Закажите сервер с предустановленным ПО для мониторинга: Grafana, Zabbix, Prometheus и другими.

Узнать больше

В данной статье мы рассмотрим практику реализации автоматизированного мониторинга срока действия двух ключевых сертификатов: apache.cer и websocket-proxy.cer, размещённых на каждом oVirt Engine в директории /etc/pki/ovirt-engine/certs/

Сертификат apache.cer используется веб-сервером Apache, который обслуживает веб-интерфейс и REST API oVirt Engine, обеспечивая шифрование и аутентификацию клиентских подключений. В свою очередь, websocket-proxy.cer применяется для защиты WebSocket-соединений, необходимых для передачи консольных сессий виртуальных машин через браузер. Несвоевременное обновление этих сертификатов может привести к недоступности управления виртуальными машинами и административного интерфейса, что делает их мониторинг приоритетной задачей.

Для решения этой задачи мы разработали специализированный экспортер — cert_checker, размещаемый непосредственно на каждом oVirt Engine в каталоге /opt/cert_checker. Для тех, кто не знает, oVirt Engine — это центральный сервер управления, который контролирует все ноды виртуализации, общие дисковые ресурсы и виртуальные сети.

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

Для корректной работы экспортера требуется наличие на хосте установленной версии Go (golang) не ниже той, которая использовалась при его компиляции, что гарантирует стабильность и совместимость исполняемого файла. Далее в статье будет подробно описана архитектура решения, процесс развёртывания, формат выдаваемых метрик и рекомендации по интеграции в инфраструктуру мониторинга.

Создание сервиса

Для работы экспортера так же необходимо создать сервис systemd. Для это идем в каталог /etc/systemd/system/ и создаем systemd-unit:

Description=oVirt cert cheker service
ConditionPathExists=/opt/cert_cheker
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/cert_cheker
ExecStart=/usr/local/go/bin/go run main.go //указать путь установки go и способ выполнения main.go
User=root
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target

После чего сделать systemctl daemon-reload, systemctl start cert_cheker.service и проверить статус работы сервиса systemctl status cert_cheker.service. В случае успеха вывод статуса должен выглядеть примерно так:

И наконец, нужно убедиться, что нужный нам порт (порт 1337), куда будут отдаваться метрики, не закрыт фаерволлом.

Для этого нужно ввести, например, команду 

netstat -na | grep 1337

Если вывод имеет следующий вид.:

Тогда необходимо открыть порт вручную, введя команду

firewall-cmd --add-port 1337/tcp

Проверяем результат:

Если порт открыт, следует проверить, тянется ли метрика. Для этого нужно ввести новую команду

curl http://localhost:1337/metrics 

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

Создание алерта Prometheus

Первым делом создаем алерт. Все примеры мы будем показывать ссылась на наш gitlab и расположение Ansible плейбуков для развертывания инфраструктуры в нем. В случае Prometheus, его алерты можно найти по по пути /devops/ansible-playbooks/prometheus_playbook/ files/alerts

Выбираем раздел меню New file и создаем файл в формате yml:

Затем пишем алерт, где указываем имя самого алерта, нужный для мониторинга атрибут + значение, на которое будет срабатывать алерт, время обновления, тип алерта (в нашем примере warning), а также описание. Формат следующий:

groups:
- name: ovirt_engine_apache_cert_expiry
  rules:
    - alert: ovr_apache_cert_expiry
      expr: ovirt_engine_apache_cert_expiry < 14
      for: 45s
      labels:
        severity: warning
      annotations:
        summary: "Certificate for {{ $labels.engine }} will expire soon"
        description: "The certificate for {{ $labels.engine }} will expire in {{ $value }} days. Please renew it."
- name: ovirt_engine_ws_proxy_cert_expiry
  rules:
    - alert: ovr_ws_proxy_cert_expiry
      expr: ovirt_engine_ws_proxy_cert_expiry < 14
      for: 45s
      labels:
        severity: warning
      annotations:
        summary: "Certificate for {{ $labels.engine }} will expire soon"
        description: "The certificate for {{ $labels.engine }} will expire in {{ $value }} days. Please renew it."

Сохраняем и переходим в каталог /devops/ansible-playbooks/prometheus_playbook/group_vars. Нас интересует файл federation_lang.yaml. Открываем его через web IDE и в верхней части документа, рядом с остальными таргетами, ниже создаем свой:

В таргете прописаны имя таргета, путь на каталог на HTTP, поднятом ранее на 1337 порту. Далее, после static_configs, прописываем целевые адреса за портом 1337. Затем, после labels, название сервиса и компонент virtualization.

Так же необходимо в самом начале документа, после поля rule_files указать имя написанного алерта:

После этого переходим к Jenkins задаче conf_prometheus.dsl и запускаем обновление fideration_lang:

После успешного обновления следует перейти по URL (http://<ip>:9090/) и проверить отработку таргета, введя имя искомого атрибута, например ovirt_engine_apache_cert_expiry. Если проблем не возникло, то вывод будет примерно таким:

Вывод алерта на дэшборд Grafana

Если предыдущие шаги выполнены успешно, алерт будет выведен на общий дэшборд. Необходимо перенести его на дэшборд oVirt. Делается это следующим образом:

Заходим в настройки панели, выбрав пункт меню Edit:

Вписываем название алерта в формате job!="cert_cheker". Обязательно после должна быть запятая. Если название вписывается в середине поля, то запятую ставим с обеих сторон:

Далее идем в дэшборд Prometheus AlertManager - Ovirt checks:

На дэшборде oVirt снова заходим в настройке панели:

Жмем + Query :

В появившемся поле вписываем имя алерта опять в формате job="cert_cheker".

После чего нажимаем Save в верхней части страницы:

Как следствие, после выполненных действий алерт будет выводиться на дэшборд oVirt, и настройку можно считать оконченной.

Описание работы кода экспортера

Для начала импортируются необходимые пакеты, включая crypto/x509 для работы с сертификатами.  Пакет crypto/x509 имеет ключевое значение, поскольку предоставляет функции для парсинга сертификатов X.509 и проверки срока действия сертификатов.

Также необходимы пакеты encoding/pem для декодирования PEM-закодированных данных, io для работы с вводом-выводом, log для логирования, net/http для работы с HTTP-запросами, os для работы с операционной системой и time для работы со временем.

Еще необходимо импортировать пакеты из внешних библиотек:

  • github.com/prometheus/client_golang/prometheus для работы с Prometheus;

  • github.com/prometheus/client_golang/prometheus/promhttp для обработки HTTP-запросов Prometheus:

package main
import (
    "crypto/x509"
    "encoding/pem"
    "io"
    "log"
    "net/http"
    "os"
    "time"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

Дальше определяются две метрики: apacheCertExpiry и wsProxyCertExpiry, которые представляют собой измерители (gauges) в Prometheus. Они используются для отслеживания количества дней до истечения срока действия сертификатов Apache и WebSocket Proxy соответственно:

var (
    apacheCertExpiry = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "ovirt_engine_apache_cert_expiry",
        Help: "Number of days until the Apache certificate expires",
    })
    wsProxyCertExpiry = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "ovirt_engine_ws_proxy_cert_expiry",
        Help: "Number of days until the WebSocket Proxy certificate expires",
    })
)

В функции init() эти метрики регистрируются в Prometheus с помощью функции prometheus.MustRegister():

func init() {
    prometheus.MustRegister(apacheCertExpiry)
    prometheus.MustRegister(wsProxyCertExpiry)
}

В функции main() запускается HTTP-сервер (в данном случае на порту 1337), который будет принимать метрики в каталог /metrics, в формате который поддерживает Prometheus:

func main() {
    log.Println("Starting HTTP server on :1337")
    http.Handle("/metrics", promhttp.Handler())
    go func() {
        log.Fatal(http.ListenAndServe(":1337", nil))
    }()

Далее в бесконечном цикле каждый час проверяется срок действия сертификатов Apache и WebSocket Proxy с помощью функции checkCertExpiry(). Эта функция принимает путь к файлу сертификата в качестве аргумента и возвращает количество дней до истечения срока действия сертификата. Если количество дней до истечения срока действия сертификата больше или равно 0, значение метрики обновляется с помощью функции Set() :

for {
        apacheDaysUntilExpiry := checkCertExpiry("/etc/pki/ovirt-engine/certs/apache.cer")
        if apacheDaysUntilExpiry >= 0 {
            apacheCertExpiry.Set(float64(apacheDaysUntilExpiry))
        }
        wsProxyDaysUntilExpiry := checkCertExpiry("/etc/pki/ovirt-engine/certs/websocket-proxy.cer")
        if wsProxyDaysUntilExpiry >= 0 {
            wsProxyCertExpiry.Set(float64(wsProxyDaysUntilExpiry))
        }
        time.Sleep(1 * time.Hour)
    }
}

Если срок действия сертификата не удалось определить, тогда возвращается -1:

func checkCertExpiry(certFile string) int {
    log.Printf("Checking certificate %s\n", certFile)
    file, err := os.Open(certFile)
    if err != nil {
        log.Printf("Failed to open certificate %s: %v", certFile, err)
        return -1
    }
    defer file.Close()
    certData, err := io.ReadAll(file)
    if err != nil {
        log.Printf("Failed to read certificate %s: %v", certFile, err)
        return -1
    }
    block, _ := pem.Decode(certData)
    if block == nil {
        log.Printf("Failed to decode PEM block for %s", certFile)
        return -1
    }
    cert, err := x509.ParseCertificate(block.Bytes)
    if err != nil {
        log.Printf("Failed to parse certificate %s: %v", certFile, err)
        return -1
    }
    daysUntilExpiry := int(cert.NotAfter.Sub(time.Now()).Hours() / 24)
    log.Printf("Certificate %s expires in %d days\n", certFile, daysUntilExpiry)
    return daysUntilExpiry
}

В функции checkCertExpiry() сначала открывается файл сертификата с помощью функции os.Open(). Потом данные сертификата читаются из файла с помощью функции io.ReadAll(). Далее данные сертификата декодируются из формата PEM с помощью функции `pem.Decode(). Затем данные сертификата парсятся в структуру x509.Certificate с помощью функции x509.ParseCertificate().

После этого вычисляется количество дней до истечения срока действия сертификата путем вычитания текущего времени из времени истечения срока действия сертификата с помощью функции Sub() и преобразования результата в дни. Количество дней до истечения срока действия сертификата возвращается функцией checkCertExpiry().

Итог

В ходе реализации мониторинга SSL/TLS-сертификатов в экосистеме oVirt мы создали надёжное и автоматизированное решение на основе самописного экспортера cert_checker. Этот инструмент позволяет в режиме реального времени отслеживать сроки истечения ключевых сертификатов — apache.cer и websocket-proxy.cer, — предотвращая потенциальные простои в работе веб-интерфейса и консольных подключений к виртуальным машинам. 

Интеграция с Prometheus и Grafana обеспечила нам централизованное наблюдение, а настройка алертинга позволила оперативно реагировать на приближающееся окончание срока действия сертификатов — за 14 дней до истечения, а не в последний момент. 

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

С учетом нескольких месяцев эксплуатации, пока «причесывалась» эта статья, можно констатировать следующее: решение оказалось простым, но эффективным: благодаря использованию стандартных библиотек Go и экосистемы Prometheus, мы получили гибкий и легко поддерживаемый компонент, который можно быстро адаптировать под другие типы сертификатов или системы. Важно, что теперь мониторинг стал проактивным — вместо реагирования на инциденты мы можем предотвращать их заранее.


А как вы организуете мониторинг сертификатов в своей инфраструктуре?

Серверы для мониторинга

Закажите сервер с предустановленным ПО для мониторинга: Grafana, Zabbix, Prometheus и другими.

Узнать больше