Блог

Почему Terraform работает нормально, когда ваше облако не работает

Это было во вторник днём. План Terraform прошел без ошибок. Никаких изменений. Я перепроверил ещё раз, потому что последние несколько развёрток прошли не гладко, и я хотел убедиться. Всё по-прежнему в порядке. Поэтому я слиял PR, взял кофе и пошёл на встречу в 15:00, посвящённую приоритетам дорожной карты на третий квартал, на которой мне, честно говоря, не нужно было присутствовать.

Через сорок пять минут меня ждали пять сообщений в Slack.

Наш API возвращал ошибки 403 на определенном эндпоинте. Журналы службы не помогли, как это всегда бывает, когда что-то действительно не так (много шума, нет сигнала). Потребовалось два часа, чтобы отследить проблему до политики S3-бакета. Во время инцидента три недели назад кто-то вручную ужесточил политику в консоли AWS, чтобы предотвратить потенциальную утечку. Инцидент закрыли, тикет закрыли, и в ветке Slack наступила тишина. 

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

С точки зрения Terraform, политика была точно такой, как заявлено. С точки зрения реального мира, она была другой в течение трех недель, и новое развертывание наконец-то нарушило ее.

Это не страшилка. Это обычный вторник.

Как файл состояния теряет синхронизацию

Состояние Terraform — это не текущая запись. Это моментальный снимок, документ JSON, фиксирующий, как выглядела инфраструктура после последнего terraform apply успешного запуска. На сайте env zero есть подробная статья о том, что именно содержит этот файл и почему это важно, и суть заключается именно в формулировке: «моментальный снимок того, как выглядела ваша инфраструктура после последнего применения». А не того, как она выглядит сейчас.

Это различие имеет большее значение, чем кажется. IaC — это декларация намерений. Вы заявляете, что должно существовать. Terraform сопоставляет эту декларацию с тем, что действительно существовало на момент вашего последнего применения. Но облако продолжает развиваться после этого.

Вот как на самом деле выглядит файл состояния Terraform для политики корзины S3 (сокращенно; реальные файлы также включают terraform_version, lineageресурс modeи ссылку на провайдера):

Серийный номер 47. Это счетчик записей состояния, о которых знает Terraform: применения, terraform apply -refresh-only запуски, terraform state команд. Каждая из них отслеживается. Все, что произошло вне операций Terraform: не отслеживается. Если политика корзины была изменена вручную после серийного номера 47, этот файл по-прежнему отражает представление мира на момент серийного номера 47.

Возникает очевидный вопрос: а как же terraform refresh? (Он был признан устаревшим в Terraform 0.15; текущий эквивалент — terraform apply -refresh-only.) По умолчанию terraform plan также обновляет состояние из провайдера перед сравнением. Он улавливает дрифт по ресурсам, о которых Terraform уже знает. Но он ничего не делает с бакетом, который кто-то создал вручную три месяца назад, или с ролью IAM, добавленной одноразово во время инцидента. У Terraform нет записей о них, поэтому ему нечего обновлять. Разрыв — это не просто устаревшие данные по известным ресурсам. Это ресурсы, которые вообще никогда не попадали в файл состояния.

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

Очевидной причиной являются ручные изменения: исправления, примененные в 2 часа ночи, эксперименты, которые были частично убраны, изменения в консоли, которые никто не вернулся документировать. Но более тонкая проблема — это дрейф, управляемый сервисами. AWS Auto Scaling изменяет количество ваших инстансов. RDS автоматически масштабирует хранилище, когда достигается пороговое значение. ECS Application Auto Scaling корректирует желаемое количество задач сервиса в ответ на нагрузку, при этом Terraform об этом не знает. Ничто из этого не проходит через Terraform. Это не человеческая ошибка. Это облако делает именно то, на что вы его настроили, способами, которые ваш файл состояния никогда не был предназначен отслеживать. Интеграции сторонних разработчиков, инструменты принудительного применения политик и оптимизаторы затрат добавляют к этому еще один слой.

У нас была строгая дисциплина в конвейере благодаря env zero: последовательные запуски, принудительное соблюдение политик, контроль на уровне команды. Но любой инструмент развертывания знает только о том, что проходит через него. Он сообщает вам о ресурсах, управляемых через ваши конвейеры. Он ничего не говорит о корзине S3, которая была ограничена в 23:00 в четверг.

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

И опасность заключается не только в том, что вы не знаете об этом отклонении. Она в том, что ваш следующий terraform apply будет действовать в соответствии с той версией мира, в которую он верит. Инженер, который ужесточил политику вашего S3-бакета в 2 часа ночи, чтобы предотвратить утечку? Ваш следующий развертыватель тихонько открывает его обратно. Terraform делает именно то, что вы ему велели. Просто он не знал, что произошло между применениями.

Проблема с «просто соблюдайте конвейер»

Ладно, значит, решение очевидно, верно? Просто не вносите изменения вручную. Принудительно применяйте конфигурацию. Если это не прошло через Terraform, значит, этого не было.

Конечно. Удачи с этим в масштабе.

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

Теперь добавьте учетные записи. Тестовая среда, производственная среда, аварийное восстановление, избыточность по регионам. Добавьте приобретенную команду со своей собственной организацией AWS, своими собственными соглашениями об именовании, своим собственным набором ресурсов, развернутых вручную, которые предшествуют любой дисциплине IaC. Теперь вы поддерживаете десятки файлов состояния. Консоль AWS показывает вам по одной учетной записи за раз. Консоль GCP показывает вам по одному проекту за раз. Скрипты работают, пока не перестают, и их нужно писать, прежде чем вы поймете, что искать. В этом и заключается проблема. Отклонения не объявляют о себе.

Археология консоли: открытие учетных записей одна за другой, попытка составить ментальную картину, переходя по EC2, S3, IAM, RDS. Это нормально для пары учетных записей. Совершенно невыносимо при десяти. Я заканчивал проверку одной учетной записи и сразу терял уверенность в первой, которую проверил. Однажды я провел пятничное послеобеденное время, вручную сравнивая группы безопасности на трех учетных записях с тем, что описывалось в файлах состояния. Я обнаружил два несоответствия. Кроме того, я не мог избавиться от ощущения, что пропустил еще три.

Этап скриптов boto3: написать скрипт для перечисления ресурсов, сбросить в CSV, сравнить с состоянием. У меня был один, который работал нормально, пока мы не превысили размер страницы по умолчанию, и он начал незаметно пропускать инстансы. DescribeInstances Он использует пагинацию, и если не реализовать цикл пагинации правильно, он просто возвращает первую страницу и останавливается. Без ошибки. Исправил это, и скрипт перечисления S3 перестал работать по несвязанным причинам. В итоге у меня оказалась небольшая коллекция скриптов, каждый из которых охватывал отдельный сервис, и каждый требовал обслуживания по графику, не имеющему никакого отношения к тому, когда они мне действительно были нужны.

Ручные проверки: просьба к руководителям команд составить перечень того, что запускают их команды. В результате получились списки того, о чем люди думали регулярно. В них отсутствовало все, что стало невидимым из-за привычности.

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

Запрос о том, что на самом деле работает

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

CloudQuery синхронизирует фактическое состояние вашего облака в таблицы SQL: то, что существует в ваших учетных записях AWS прямо сейчас, а не то, что Terraform зафиксировал в последний раз. Вы подключаете его к своим учетным записям, запускаете синхронизацию и запрашиваете инфраструктуру так же, как вы запрашивали бы базу данных. Вот как это выглядело для проблемы с тегами, с которой я имел дело:

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

Вы можете писать подобные запросы для всего, что вас интересует: корзины S3 с открытым доступом, группы безопасности с открытыми правилами входящего трафика, роли IAM с * разрешениями, экземпляры RDS без шифрования. В CloudQuery hub есть готовые запросы для наиболее распространенных случаев, если вы не хотите начинать с нуля.

Файл состояния сообщает вам, что, по мнению Terraform, работает. А это показывает, что работает на самом деле.

«Файл состояния показывает, что, по мнению Terraform, работает. А это показывает, что работает на самом деле».

Если вы обнаружите отклонение, способ исправления зависит от того, в какую сторону оно произошло. Если Terraform управляет ресурсом, которого больше нет в облаке, terraform state rm он удаляет его из состояния, не уничтожая ничего. Если в облаке есть ресурс, который должен находиться под управлением Terraform, terraform import он добавляет его. В Terraform 1.5+ добавлена -generate-config-out автоматическую генерацию начальной конфигурации, хотя вам все равно придется ее проверить и очистить (это каркас, а не готовый файл). Ни один из этих путей не является привлекательным. Но понимание того, в какой ситуации вы находитесь, и обнаружение ее до того, как она приведет к инциденту, составляет большую часть работы.

Отклонение, о котором вы не знали, что нужно искать

Однако здесь есть ограничение этого подхода: он все равно требует, чтобы вы знали, о чем спрашивать.

В большинстве случаев это нормально. Я знаю, какие вопросы меня интересуют. Я могу написать запросы для известных шаблонов рисков и запускать их по расписанию. Это работает.

Более сложной проблемой является отклонение, о котором я не знал, что нужно искать. Политика корзины S3, которая была ужесточена во время инцидента. Роль IAM, которую кто-то «временно» расширил во время сеанса отладки и так и не вернул в прежние рамки. Экземпляр EC2, который должен был быть выключен после эксперимента, но не был. Ни один из этих сбоев не дает громкого сигнала. Они находятся в промежутке между заявленным и фактическим состоянием, невидимые, пока что-то не сломается из-за них.

Я постоянно возвращаюсь к мысли о том, как бы это выглядело, если бы инструмент имел собственное мнение, а не просто данные, выявляя аномалии самостоятельно, а не ожидая вашего запроса. Ресурсы, которые существуют в облаке, но отсутствуют в состоянии IaC. Конфигурация, изменившаяся с момента последней синхронизации. Паттерны, которые выглядят подозрительно в разных аккаунтах. Уровень запросов покрывает риски, о которых вы знаете. Мне нужно что-то с достаточным контекстом, чтобы само по себе подсказывать, на что стоит обратить внимание.

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

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

Если вы имеете дело с этим у более чем одного поставщика облачных услуг (а если вы прошли через слияние или поглощение, то, вероятно, это так), проблема отклонений становится значительно сложнее. Я написал об этом отдельно в статье «Мультиоблачность случилась с нами случайно».

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

Подписывайтесь на наш канал в Телеграм

Подписаться
Terraform