Ни одно внедрение платформ для запуска контейнеризованных приложений в продуктивном контуре не должно обходиться без настройки логирования происходящих событий. В нашей платформе для управления контейнерами «Штурвал» для этих целей используется модуль OpenSearch.
На одном из проектов мне понадобилось настроить алертинг, чтобы администраторы k8s получали по электронной почте оповещение, если происходят те или иные события. Например, когда назначается ClusterRole с высоким уровнем доступа, при попытке запуска привилегированного контейнера или изменении конфигурации узла.
Изначально в кластере была настроена Audit Policy, определяющая, какие события должны записываться в журнал аудита и какие данные они должны содержать, а также Fluentbit Operator, отправляющий всё в OpenSearch. Кстати, подробнее о том, как мы настраиваем политику аудита в «Штурвале», я буду рассказывать 5 июня на конференции БеКон.
Казалось бы, дело за малым — изучить документацию и настроить алертинг, который доступен непосредственно в интерфейсе OpenSearch. Но этой статьи бы не было, если бы не дьявол, спрятавшийся в деталях.
И тут важная деталь — я аналитик, не сильно погруженный в дебри OpenSearch. С настройкой SMTP действительно не возникло никаких проблем, алертинг на нужные ресурсы тоже настраивался нативно. Но когда понадобилось вытащить данные из тела запроса и добавить их в отправляемое письмо, OpenSearch сказал, что «у него лапки». В открытых источниках я нашла множество тикетов без ответов или с сомнительными советами, которые не помогали решить проблему. Возможно, где-то ответы и есть, но запрятаны так глубоко, что я не докопала.
Вооружившись терпением, тестовым кластером и поминанием не самыми добрыми словами документации OpenSearch, я засела упражняться. В итоге алертинг я настроила и попутно сделала инструкцию. Надеюсь, она будет для вас полезной. Поехали!
Шаг 1. Настройка отправителя
Для настройки алертинга в интерфейсе OpenSearch переходим в меню и в разделе Management кликаем на страницу Notifications:
В подразделе Channels создаем новый канал. Для него нужно задать имя, выбрать тип канала Email, а также тип отправителя SMTP sender:
Далее создаем SMTP-отправителя и группу получателей и добавляем в нее почтовые адреса. Не забываем сохранить канал.
Шаг 2. Настройка мониторов
Чтобы настроить отправку алертов по созданному каналу, переходим в левом меню в раздел OpenSearch Plugins на страницу Alerting:
Шаг 3. Отправка алертов по KubeAudit-логам
Так как нет необходимости агрегировать запросы (нам нужно получить сведения из каждого события как можно быстрее), выбираем тип монитора: Per query monitor (Per query monitors run a query and generate alerts based on trigger criteria that match query results).
В визуальном редакторе нет возможности вытащить данные лога в отправляемое сообщение, поэтому выбираем Monitor defining method: Extraction query editor. Далее переходим в расписание (Schedule) и задаем частоту, равную 1 минуте. Лог будет проверяться каждую минуту на наличие новых событий, соответствующих фильтрам (о них расскажем чуть позже).
В источнике данных (Data source) выбираем индекс. В выпадающем списке будут только индексы на конкретный день. Чтобы задать индекс на все дни, пропишите название индекса и поставьте постфикс -* (в таком виде он недоступен в выпадающем списке), в качестве timefield выбираем timestamp. Например, для KubeAudit-логов из «Штурвала» мы автоматически создаем индекс kube-audit-myclustername.
Для конфигурации Query необходимо обратиться к JSON, так как для нас принципиальна вложенность объектов. Пример аудит-лога с уровнем записи Request на этапе ResponseCompete для события назначения роли в неймспейсе:
{
"_index": "kube-audit-shturval-client-2024.04.17",
"_id": "m7Jz7I4BpPBuL85FZZaj",
"_version": 1,
"_score": null,
"_source": {
"@timestamp": "2024-04-17T14:26:12.611Z",
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Request",
"auditID": "dbe66fe0-ebcc-4f30-ba26-2e7fb5f0f73b",
"stage": "ResponseComplete",
"requestURI": "/apis/rbac.authorization.k8s.io/v1/namespaces/habr/rolebindings",
"verb": "create",
"user": {
"username": "kubernetes-admin",
"groups": [
"system:masters",
"system:authenticated"
]
},
"sourceIPs": [
"10.11.111.11"
],
"userAgent": "manager/v0.0.0 (linux/amd64) kubernetes/$Format",
"objectRef": {
"resource": "rolebindings",
"namespace": "habr",
"name": "shturval:habr-view-k.kazakov",
"apiGroup": "rbac.authorization.k8s.io",
"apiVersion": "v1"
},
"responseStatus": {
"metadata": {},
"code": 201
},
"requestObject": {
"kind": "RoleBinding",
"apiVersion": "rbac.authorization.k8s.io/v1",
"metadata": {
"name": "shturval:habr-view-k.kazakov",
"namespace": " habr",
"creationTimestamp": null
},
"subjects": [
{
"kind": "User",
"apiGroup": "rbac.authorization.k8s.io",
"name": "shturval:k.kazakov"
}
],
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "view"
}
},
"requestReceivedTimestamp": "2024-04-17T14:26:12.607375Z",
"stageTimestamp": "2024-04-17T14:26:12.611117Z",
"annotations": {
"authorization.k8s.io/decision": "allow",
"authorization.k8s.io/reason": ""
}
},
"fields": {
"requestReceivedTimestamp": [
"2024-04-17T14:26:12.607Z"
],
"stageTimestamp": [
"2024-04-17T14:26:12.611Z"
],
"@timestamp": [
"2024-04-17T14:26:12.611Z"
]
},
"highlight": {
"objectRef.resource": [
"@opensearch-dashboards-highlighted-field@rolebindings@/opensearch-dashboards-highlighted-field@"
]
},
"sort": [
1713363972611
]
}
В query в примере запрашивается успешное создание ресурса rolebindings в любом неймспейсе кластера:
{
"size": 1,
"query": {
"bool": {
"filter": [
{
"range": {
"@timestamp": {
"from": "{{period_end}}||-1m",
"to": "{{period_end}}",
"include_lower": true,
"include_upper": true,
"format": "epoch_millis",
"boost": 1
}
}
},
{
"match_phrase": {
"objectRef.resource": {
"query": "rolebindings",
"slop": 0,
"zero_terms_query": "NONE",
"boost": 1
}
}
},
{
"match_phrase": {
"verb": {
"query": "create",
"slop": 0,
"zero_terms_query": "NONE",
"boost": 1
}
}
},
{
"term": {
"responseStatus.code": {
"value": 201,
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
"docvalue_fields": [
{
"field": "stageTimestamp",
"format": "date_time"
},
{
"field": "_source.auditID"
},
{
"field": "_source.objectRef.namespace"
}
]
}
Здесь size указывает на то, сколько раз данные лога будут вставлены в отправляемое сообщение. Если size равен 0 (значение по умолчанию), то даже при правильной настройке данные лога не попадут в отправляемое сообщение.
"@timestamp": {
"from": "{{period_end}}||-1m"
-1m указывает на время, в течение которого необходимо отреагировать на полученное событие. Таким образом, за одну минуту проверки мы отправим найденное событие один раз. Если указать, например, 10m, то событие будет десять раз продублировано (тоже один раз в минуту).
После настройки запускаем проверку получения данных, нажав Run в правой части экрана Query. Если за прошедшую минуту у вас было событие в логах, подходящее под запрос, то правая часть экрана будет содержать его данные лога. Если не было, но вы настроили запрос верно, то будут примерно такие данные:
{
"_shards": {
"total": 1,
"failed": 0,
"successful": 1,
"skipped": 0
},
"hits": {
"hits": [],
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null
},
"took": 1,
"timed_out": false
}
Для того чтобы прописать, какие поля мы хотим добавить в отправленное сообщение, нужно добавить в каждое желаемое поле блок docvalue_fields. Это дает большую вариативность для настройки, т. к. на разные виды событий вы можете вытаскивать в алерт разный состав данных.
Обратите внимание: если поле лежит не на первом уровне, нужно указать полный путь для него. Например, при отправке значения лога в field необходимо указать "_source.log".
Далее мы увидим, сколько срабатываний за время жизни монитора найдено:
Следующий этап — настройка триггера для отправки сообщения.
Триггер должен иметь название на английском языке и без пробелов. Указываем уровень важности события (для одного триггера можно задать один уровень важности). Также можно создать разные триггеры. В нашем случае триггер срабатывает, когда есть хотя бы одно событие:
ctx.results[0].hits.total.value > 0
Если в запрашиваемый период есть события, то Preview condition response покажет найденные данные.
Далее настраиваем Action на отправку сообщения:
Задаем Action name.
Выбираем один или несколько каналов из выпадающего списка с ранее созданными каналами.
Вводим заголовок, который будет отображаться в теме письма. Например, мы использовали следующий: «Назначены права доступа в пространстве имён».
В блоке Message необходимо сконфигурировать данные, которые мы хотим отправить. По умолчанию создается такое сообщение:
Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.
- Trigger: {{ctx.trigger.name}}
- Severity: {{ctx.trigger.severity}}
- Period start: {{ctx.periodStart}}
- Period end: {{ctx.periodEnd}}
Здесь нужно понимать, что начало и окончание периода приходят по UTC (получается -3 часа от реального времени в нашем случае) — для пользователя такой формат будет неудобным. Поэтому мы вытащили дату и время из лога. Чтобы подставить данные лога, нужно добавить открывающий {{#ctx.results.0.hits.hits}} и закрывающий {{/ctx.results.0.hits.hits}} теги, содержащие путь до лога. Внутрь тегов помещаем только те данные, которые тащим из лога.
Далее вытаскиваем поля из лога в сообщение:
Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.
- Trigger: {{ctx.trigger.name}}
- Уровень важности: {{ctx.trigger.severity}}
{{#ctx.results.0.hits.hits}}
- Дата события: {{_source.stageTimestamp}}
- Идентификатор события: {{_source.auditID}}
- Пространство имён: {{_source.objectRef.namespace}}
{{/ctx.results.0.hits.hits}}
Алерт будет выглядеть так:
Если вы нажмете Preview message или Send test message, то не сможете увидеть данные лога. Чтобы проверить, корректно ли приходят данные, воспроизводим действие на стенде. Стоит понимать, что алерт может приходить с лагом. В случае настройки с параметрами, указанными выше, допускается лаг не более 1 минуты. После завершения нажимаем Create. Теперь всё должно получиться.
Если вам интересно больше узнать о настройке алертинга и пообщаться с нашими экспертами, приходите к нам на демонстрацию возможностей платформы «Штурвал» — всё расскажем и покажем :) Записаться на демо можно по ссылке.
Алиса Кириченко
Ведущий аналитик