Два вопроса возникают у любого сервиса, как только он выходит за пределы «работает на моём ноутбуке»: где хранить секреты (пароли к базе, ключи, токены) и как понять, что происходит внутри, когда что-то идёт не так. Первое — про безопасность, второе — про наблюдаемость. В Azure под каждую задачу есть сервис. Доступ ко всем ним выдаётся через Entra ID и RBAC.

managed identity приложение права на чтение Key Vault секреты, ключи, сертификаты по RBAC Log Analytics структурированные логи, платно за объём Application Insights время ответа, ошибки, трассы группа действий кому и как звонить

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

Обязательно

Key Vault: хранилище секретов и ключей

Key Vault — управляемое хранилище секретов, ключей шифрования и сертификатов. Идея простая: секреты не живут в коде, в конфигах и в git, а лежат в Key Vault, откуда приложение забирает их по правам своей managed identity.

Зачем так, а не «положить пароль в переменную окружения и забыть»:

  • секрет в одном месте, а не размазан по десяти конфигам и репозиториям;
  • доступ к нему — по правам RBAC, и можно увидеть, кто его читал. «Можно» здесь буквальное: журнал обращений к Key Vault по умолчанию не пишется, диагностические параметры хранилища надо заранее направить в Log Analytics — иначе в день разбора инцидента читать будет нечего;
  • секрет обновляют в одном месте, а не переклеивают по всем сервисам вручную. Только автоматику тут не обещают: сам Key Vault меняет по политике лишь сертификаты и ключи учётной записи хранилища, а смену пароля базы вы собираете сами — событие о скором истечении плюс функция, которая меняет пароль и в базе, и в хранилище. И приложение обязано перечитывать секрет: значение, прочитанное один раз на старте, смены в хранилище не заметит;
  • здесь же хранят ключи шифрования (которыми шифруют диски, хранилища, базы) и TLS-сертификаты.

Правило новичку: любой пароль или ключ, попавший в git, считается скомпрометированным. Даже если удалить его следующим коммитом — он останется в истории. Key Vault снимает соблазн «просто захардкодить».

Два способа управлять правами на хранилище секретов

Решение, которое принимают при создании хранилища и потом жалеют: модель прав. Их две, и они несовместимы.

Управление доступом на основе ролей (RBAC) — тот же механизм, что у остальных ресурсов Azure: роли (Key Vault Secrets User, Key Vault Secrets Officer, Key Vault Administrator) назначаются на хранилище или на подписку, видны в общем списке назначений, наследуются по иерархии.

Политики доступа (access policies) — старая модель, своя только для хранилищ секретов: список участников с отметками, что каждому можно (читать секреты, писать ключи, перечислять сертификаты). Не связана с ролями, не наследуется, не видна в общем аудите прав.

Что выбирать: роли, если нет причины поступить иначе. Причина обычно одна — унаследованная инфраструктура, где всё настроено политиками. Смешивать нельзя: у хранилища активна одна модель.

И то, из-за чего это стоит решить заранее: переключение модели ломает доступ. Хранилище, созданное с политиками, при переводе на роли теряет все прежние разрешения — приложения получают отказ, пока не назначены роли. Операция выполнимая, но требует окна и списка всех, кто ходит в хранилище.

Как секрет попадает в приложение

Три рабочих способа, и выбор зависит от того, где работает приложение.

Ссылка на хранилище прямо в настройках сервиса. У App Service и Container Apps значение настройки может быть не текстом, а ссылкой на секрет в хранилище:

@Microsoft.KeyVault(SecretUri=https://kv-orders-prod.vault.azure.net/secrets/db-password/)

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

Хранилище как источник настроек приложения. Надстройка для вашего фреймворка подключает секреты как обычные свойства конфигурации — разбор есть в статье про подключение SDK. Удобно, когда секретов много и хочется видеть их как настройки.

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

И главная оговорка, общая для всех трёх: значение читается при старте и живёт в памяти. Смена секрета не доезжает до работающего приложения сама — нужен перезапуск, либо механизм обновления настроек, либо чтение в момент использования с кэшем на минуты. Поэтому смену пароля базы делают с двумя действующими значениями: сначала выкатывают приложение с новым, потом отзывают старое. Это правило одинаково во всех облаках.

Azure Monitor и Log Analytics

Когда сервис работает в облаке, да ещё в нескольких копиях, смотреть логи через вход на каждую машину невозможно. Azure Monitor — единая система наблюдаемости. Логи собираются в Log Analytics, где их ищут и фильтруют языком запросов KQL. Метрики — загрузка процессора, задержка, число ошибок — собираются в дашборды. А алерты — правила вида «если ошибок больше стольки-то за минуту — разбудить дежурного».

Практический совет: пишите логи структурированно (с полями — уровень, идентификатор запроса, сервис), а не сплошным текстом. По структурированным логам можно искать и строить фильтры. И ключевая мысль: наблюдаемость нужна до аварии, а не после — метрики и алерты, настроенные заранее, превращают «пользователи жалуются, а мы не понимаем почему» в «нам пришло уведомление, и мы уже чиним». Общие правила логирования не зависят от облака — их разбирает Observability style guide.

И про деньги, потому что это главная практическая ловушка Azure Monitor: платят за объём принятых логов. Совет «пишите всё структурированно», понятый как «пишите всё», даёт счёт, сопоставимый со стоимостью самих вычислений, — поэтому уровни логирования настраивают осознанно, а самые болтливые места прореживают. Обратная сторона той же медали: в Application Insights по умолчанию включена выборка (sampling) — часть телеметрии отбрасывается ради экономии. «В логах нет моего запроса» — чаще всего не поломка, а именно она.

Application Insights

Application Insights — часть Azure Monitor, заточенная под само приложение (APM, application performance monitoring): она показывает время ответа по эндпоинтам, частоту ошибок, зависимости (запросы к базе и внешним сервисам) и трассировки запросов сквозь сервисы. Это переход от «сервер жив» к «конкретный запрос пользователя тормозит вот на этом вызове базы».

Чтобы это заработало, в приложение надо что-то добавить. Для Java код обычно не трогают вовсе: к процессу подключают агент автоинструментирования параметром запуска (-javaagent:applicationinsights-agent.jar), и он сам видит входящие HTTP-запросы, вызовы базы и обращения к соседним сервисам. Для Node и Python есть дистрибутивы Azure Monitor на OpenTelemetry (@azure/monitor-opentelemetry, azure-monitor-opentelemetry), которые включают одной строкой при старте; для Go официальной сборки нет, и берут OpenTelemetry SDK с экспортёром в Azure Monitor. Общий второй путь для всех — OpenTelemetry, если телеметрию вы уже собираете через него. Без одного из двух «покажет, где теряется время» остаётся обещанием.

Если заводите ресурс сами: классический Application Insights выведен, новые ресурсы создают на основе рабочей области Log Analytics — телеметрия приложения ложится туда же, где остальные логи.

Как связать записи одного запроса

Логи, метрики и трассировки по отдельности отвечают на разные вопросы, а наблюдаемость появляется, когда их можно связать. Механизм простой: сквозной идентификатор.

В Azure это устроено так. Библиотека Application Insights (или сборщик OpenTelemetry) добавляет к каждому исходящему запросу стандартный заголовок трассировки (traceparent), а на входе читает его и продолжает ту же трассу. Все записи получают два поля: operation_Id — идентификатор всей операции, сквозной через сервисы, и operation_ParentId — идентификатор родительского шага.

Что это даёт на практике: запрос, на который жалуется пользователь, находится по идентификатору, а дальше одним запросом собирается всё, что с ним связано, — записи журнала, зависимости, исключения, причём из разных сервисов:

union traces, requests, dependencies, exceptions
| where operation_Id == "8f3e2b1c4d5a6e7f"
| order by timestamp asc
| project timestamp, itemType, name, message, resultCode, duration

Чтобы это работало, нужны три вещи. Библиотека подключена во всех сервисах цепочки (иначе трасса обрывается на первом, где её нет). Идентификатор попадает в логи приложения — он берётся из контекста и добавляется в формат вывода, чтобы записи собственного журнала тоже находились по нему. И идентификатор возвращается клиенту (например, в заголовке ответа) — тогда пользователь или служба поддержки называют его, и разбор начинается с точного места, а не с поиска по времени.

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

Где это применяется

Эта связка — секреты в Key Vault, логи и метрики в Azure Monitor, трассировки в Application Insights — базовый гигиенический минимум любого боевого сервиса. Без неё сервис вроде бы работает, но при первой же проблеме вы оказываетесь слепы.

Типичные ошибки новичков:

  • хранят пароли в коде или конфигах вместо Key Vault;
  • логируют бессистемно — сплошным текстом, без идентификатора запроса, и потом ничего не находят;
  • настраивают наблюдаемость после первой аварии, а не до неё;
  • логируют секреты и персональные данные — и утечка переезжает в логи.
Дополнительно: при первом чтении можно пропустить

Глубже: мягкое удаление: почему нельзя пересоздать хранилище с тем же именемрасширенное

Первая боль при работе с хранилищем секретов выглядит так: удалили хранилище (или секрет), пытаетесь создать с тем же именем — и получаете отказ «имя уже используется».

Причина — мягкое удаление, которое сегодня включено по умолчанию и не отключается. Удалённое хранилище переходит в состояние «удалено, но восстановимо» и хранится так заданное число дней (обычно 90). Имя при этом остаётся занятым.

Что делать:

az keyvault list-deleted -o table                          # что лежит в удалённых
az keyvault recover --name kv-orders-prod                   # восстановить
az keyvault purge --name kv-orders-prod                      # вычистить окончательно

И вторая настройка, которая это усложняет: защита от вычистки (purge protection). Если она включена, вычистить досрочно нельзя вообще — придётся ждать окончания срока хранения. Это сделано намеренно: злоумышленник с правами администратора не сможет уничтожить секреты и ключи безвозвратно.

Практические выводы. Для боевых хранилищ защиту от вычистки включают — это защита от самого плохого сценария. Для стендов, которые пересоздаются в конвейере, её не включают, иначе повторное развёртывание с тем же именем упрётся в занятое имя на три месяца; а в имена стендов добавляют что-то уникальное (номер сборки, хеш ветви), чтобы конфликтов не было вовсе. То же касается ключей шифрования: удалённый ключ с защитой от вычистки продолжает существовать, и данные, зашифрованные им, не становятся недоступными — это как раз то, чего от неё и хотят.

Глубже: оповещения: кто их получает и почему выключаетрасширенное

Правило, которое находит проблему, бесполезно без адресата. В Azure за это отвечает группа действий (action group) — список получателей и способов: почта, сообщение, вызов вебхука, запуск функции, создание обращения в системе поддержки.

az monitor action-group create -g rg-ops -n ag-oncall \
  --action email oncall ops@example.com --action webhook chat https://hooks.example.com/ops

Дальше правило оповещения ссылается на эту группу — и вот здесь начинается то, о чём стоит сказать честно.

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

Что делает оповещения полезными:

Сигналят на симптом, а не на причину. «Доля ошибок у пользователей выше 2 % пять минут» — симптом, и на него надо реагировать. «Загрузка процессора 85 %» — причина, которая может быть совершенно нормальной. Первое будят дежурного, второе показывают на графике.

У каждого оповещения есть действие. Правило, на которое нельзя ответить конкретным шагом, — не оповещение, а информация; ей место в отчёте, а не в звонке ночью.

Пороги по длительности, а не по мгновенному значению. Условие «дольше N минут» убирает почти весь шум от коротких всплесков.

Разные каналы по важности. Будить ночью — только то, что требует немедленного действия; остальное — в рабочий чат или в утренний отчёт. В Azure это разные группы действий, и это самая простая правка, которая уменьшает выгорание дежурных.

Раз в квартал — разбор оповещений. Сколько раз сработало, сколько из них потребовали действия, какие надо убрать или переписать. Без этого набор правил только растёт.

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

Microsoft Defender for Cloud оценивает состояние безопасности: находит открытые наружу ресурсы, незашифрованные хранилища, слабые настройки и подсказывает, что исправить. А Activity Log пишет журнал действий над инфраструктурой: кто создал ресурс, кто поменял права, кто удалил базу — незаменимо при разборе инцидентов («откуда взялся этот публичный контейнер?»). Помните про срок: сам по себе Activity Log хранится около 90 дней. Вчерашнее событие вы найдёте, а событие полугодовой давности — уже нет, если заранее не настроили экспорт журнала в Log Analytics.

Глубже: поиск по логам и что в них не должно попадатьрасширенное

Логи, метрики и трассировки в Azure складываются в рабочую область аналитики, и искать по ним — это отдельный язык запросов. Пара примеров, чтобы было видно, как это выглядит:

// ошибки за час с группировкой по типу
exceptions
| where timestamp > ago(1h)
| summarize count() by type, outerMessage
| order by count_ desc

// медленные запросы к зависимостям
dependencies
| where timestamp > ago(30m) and duration > 1000
| project timestamp, name, target, duration, resultCode

Два практических приёма: искать по идентификатору операции (см. раздел выше) и группировать по полю, а не читать записи глазами. Оплата — за объём принятых данных и за хранение, поэтому запросы ограничивают периодом.

А теперь про то, что в логи попадать не должно. Персональные данные в журнале — это не мелочь, а нарушение, и в облаке оно тяжелее, чем на своём сервере: рабочая область аналитики доступна всем, у кого есть права на чтение (а их обычно выдают широко), хранится месяцами и попадает в резервные копии и в выгрузки.

Что именно нельзя писать: адреса электронной почты и телефоны, имена и фамилии, адреса доставки, номера документов и карт, содержимое запросов и ответов целиком (там всё это и оказывается), значения заголовков авторизации и токены.

Что делают вместо: в журнал идут идентификаторы (customerId, orderId), а не значения; для диагностики значения маскируют (u***@example.com) одной общей функцией; тело запроса не логируют вовсе на путях, где есть личные данные; а адрес запроса пишут шаблоном (GET /api/customers/{id}), а не фактической строкой — иначе идентификаторы и параметры поиска окажутся в журнале.

Отдельно стоит помнить про трассировки и метрики: это тот же канал. Атрибут спана с адресом электронной почты уезжает в ту же рабочую область; метка метрики с идентификатором пользователя, кроме утечки, даёт ещё и взрыв числа временных рядов. В обоих случаях правило то же: идентификаторы, а не данные.

И организационная часть, без которой техническая не работает: у рабочей области есть срок хранения (по умолчанию месяц-три, настраивается) и список тех, у кого есть доступ. Оба стоит посмотреть один раз: год хранения плюс доступ у всей компании означает, что любая утечка в журнал — это утечка на год для всех. Подробный разбор темы — в статье про персональные данные и секреты.

Коротко

  • Key Vault держит секреты, ключи шифрования и сертификаты в одном месте с правами RBAC; журнал обращений включают диагностическими параметрами, а секреты приложений он сам не меняет.
  • Модель прав выбирают при создании и не смешивают: роли по умолчанию, политики доступа только в унаследованном; переключение ломает доступ.
  • Мягкое удаление не отключается, а защита от вычистки запрещает досрочную очистку: в бою её включают, на пересоздаваемых стендах нет, и имя удалённого хранилища занято до конца срока.
  • Секрет попадает в приложение ссылкой в настройках сервиса, как свойство через надстройку или вызовом клиента; в двух первых случаях значение живёт в памяти до перезапуска.
  • Azure Monitor собирает логи в Log Analytics, метрики и оповещения; платят за объём принятого, поэтому структурированно не значит всё подряд.
  • Application Insights это APM поверх Log Analytics: время ответа, ошибки, зависимости; для Java агент автоинструментирования параметром запуска.
  • Связь записей даёт сквозной идентификатор через traceparent во всех сервисах цепочки: он попадает в логи и возвращается в ответе.
  • Оповещение бесполезно без группы действий и вредно, если шумит: сигналят на симптом, с действием, по длительности, разными каналами по важности и разбирают раз в квартал.
  • В логи, трассы и метрики не пишут персональные данные и секреты: идентификаторы вместо значений, маскирование одной функцией, срок хранения и список доступа рабочей области.

Что почитать дальше