Два вопроса возникают у любого сервиса, как только он выходит за пределы «работает на моём ноутбуке»: где хранить секреты (пароли к базе, ключи, токены) и как понять, что происходит внутри, когда что-то идёт не так. Первое — про безопасность, второе — про наблюдаемость. В Azure под каждую задачу есть сервис. Доступ ко всем ним выдаётся через Entra ID и RBAC.
Секреты и наблюдаемость закрывают четыре сервиса, и ко всем приложение ходит по управляемому удостоверению. Свяжет логи, метрики и трассы сквозной идентификатор запроса, а оповещение без группы действий никого не разбудит.
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 во всех сервисах цепочки: он попадает в логи и возвращается в ответе.
- Оповещение бесполезно без группы действий и вредно, если шумит: сигналят на симптом, с действием, по длительности, разными каналами по важности и разбирают раз в квартал.
- В логи, трассы и метрики не пишут персональные данные и секреты: идентификаторы вместо значений, маскирование одной функцией, срок хранения и список доступа рабочей области.
Что почитать дальше
- Entra ID и RBAC — как приложение получает доступ без ключей.
- Подключение SDK — как читать секреты и настраивать повторы из кода.
- Оптимизация затрат в Azure — журналы как статья расхода.
- Наблюдаемость и секреты в облаке — та же модель без привязки к провайдеру.