Как многократно развертывать инфраструктуру с помощью CloudFormation

Я никогда больше не буду развертывать инфраструктуру в AWS без использования CloudFormation (инфраструктура как код) для этого. Раньше я рылся в графическом интерфейсе пользователя, чтобы настроить инфраструктуру или сервисы в AWS, и нелегко запомнить каждый шаг от одного проекта к другому. Теперь я просматриваю документацию и использую простой текстовый редактор для реализации своих приложений.

В этом блоге я объясню, почему я так сильно отношусь к инфраструктуре как к коду (IaC). Затем я опишу, как с помощью CloudFormation создать простое бессерверное приложение, показанное ниже. Это приложение состоит из лямбда-функции, которая имеет разрешения на чтение и запись в таблицу DynamoDB и запись в поток CloudWatch Logs.

Почему CloudFormation?

Несомненно, я мог бы развернуть одну таблицу DynamoDB через графический интерфейс быстрее, чем через CloudFormation. Но что произойдет, если я поставлю стол не в то место и мне придется его переместить? Что происходит, когда мне нужно развернуть вторую или третью таблицу? Как я могу задокументировать свои действия, чтобы другие могли следить за моей работой или копировать ее?

Я решил впервые использовать CloudFormation в большом проекте. Я создавал бота, который использовал несколько ресурсов AWS. После создания бота мне пришлось развернуть вторую копию бота для взаимодействия с другим API. Развертывание второго бота с CloudFormation заняло около 10 минут - значительно быстрее, чем кто-либо мог бы сделать через веб-консоль, - потому что все было определено в коде. Спустя несколько месяцев другая команда взяла мой код и развернула свою собственную копию моего бота с небольшим опытом работы с AWS - именно так должен работать IaC!

Некоторые из моих любимых преимуществ CloudFormation:

  • CloudFormation развертывает ресурсы в правильном порядке. Я могу определить их в файле шаблона в любом порядке, и CloudFormation это поймет. Он знает, что база данных должна быть создана до роли IAM, которая разрешает доступ к базе данных, и что роль IAM должна быть создана до ресурса, который ее использует. Иногда он может правильно не понимать все зависимости, но я никогда этого не видел.
  • CloudFormation отлично уничтожает созданные им ресурсы. Удаление всех компонентов устаревшего приложения не только утомительно, но и чревато ошибками. Очень легко забыть роль или политику IAM или журнал CloudWatch. Со временем эти потерянные ресурсы вызывают беспорядок в моем аккаунте. Хуже того, некоторые потерянные ресурсы могут стоить денег (например, оставить запущенным ненужный инстанс EC2).
  • CloudFormation предоставляется бесплатно. Я должен платить за ресурсы, которые он развертывает, но за сам CloudFormation плата не взимается. Учитывая, что он отслеживает мой файл шаблона и запоминает каждый созданный им ресурс, бесплатно - это отличная цена.
  • Файл шаблона CloudFormation служит для моей команды документацией о том, что требуется для развертывания каждого приложения. Если я пишу свои шаблоны в YAML, я даже могу добавлять комментарии к файлу.
  • Я могу добавить свой шаблон CloudFormation в репозиторий моего проекта на GitHub (или другой механизм управления версиями), чтобы включить отслеживание изменений и историю версий.

Начало работы с CloudFormation

Шаблоны CloudFormation можно писать в YAML или JSON. Я использую YAML, так как он более компактный, чем JSON, и позволяет оставлять комментарии. Я не хочу углубляться в эту статью, не углубляясь сразу в развертывание чего-либо. Ниже приведен полный шаблон CloudFormation, который просто развертывает одну таблицу DynamoDB.

Если приведенный выше код сохранен как файл mystack.yml, я могу использовать AWS CLI для развертывания этого очень простого стека с помощью этой команды:

aws cloudformation create-stack --stack-name medium --template-body file://mystack.yml

Также могу сразу проверить статус создания:

aws cloudformation describe-stacks --stack-name medium

Если я наберу указанную выше команду достаточно быстро, я получу ответ, в котором указано, что статус - CREATE_IN_PROGRESS, но если я подожду 60 секунд, я увижу вместо этого CREATE_COMPLETE.

{
    "StackId": "arn:aws:cloudformation:...:stack/medium/...",
    "StackName": "medium",
    "Description": "demo CloudFormation for medium.com",
    "CreationTime": "2021-07-21T02:48:57.657000+00:00",
    "RollbackConfiguration": {},
    "StackStatus": "CREATE_IN_PROGRESS",
    "DisableRollback": false,
    "NotificationARNs": [],
    "Tags": [],
    "EnableTerminationProtection": false,
    "DriftInformation": {
        "StackDriftStatus": "NOT_CHECKED"
    }
}

Точно так же я могу сделать это через веб-интерфейс, перейдя в CloudFormation, выбрав «Создать стек с новыми ресурсами (стандарт)», а затем указав его на этот файл YAML.

Этот стек довольно прост (без входов и без ролей IAM), поэтому, назвав его, я могу просто нажимать «Далее» на каждом этапе пользовательского интерфейса.

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

Далее я расскажу об анатомии самого шаблона CloudFormation, а также об используемых нами параметрах.

Анатомия шаблона CloudFormation

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

  • Версия формата (необязательно). Хотя формат файла CloudFormation остался неизменным на 2010-09-09, рекомендуется указать его на тот случай, если формат изменится в будущем. Я хочу быть уверен, что AWS правильно интерпретирует мой файл в будущем.
  • Описание (необязательно). Рекомендуется включать описание стека, чтобы моим коллегам не приходилось гадать о назначении стека, основываясь исключительно на присвоенном ему имени.
  • Ресурсы (обязательные). В этот раздел входит определение каждого объекта или службы, которые будут реализованы. Пример: таблица DynamoDB, экземпляр EC2, лямбда-функция и даже лямбда-функция псевдоним. Все они называются ресурсами в шаблоне CloudFormation. На данный момент я определил только один ресурс: таблицу DynamoDB.

Параметры DynamoDB

Вот снова шаблон CloudFormation (идентичный тому, который был опубликован выше):

Полная документация для ресурса DynamoDB находится здесь, но есть несколько элементов, которые следует выделить в шаблоне выше.

  • Текст MyProductTable - это понятное имя ресурса, имеющее отношение только к CloudFormation. Его можно использовать в шаблоне для ссылки на эту таблицу, и на самом деле я буду использовать его позже для назначения разрешений IAM.
  • DeletionPolicy определяет, удаляется ли таблица при удалении стека. В производственной среде я устанавливаю Retain, чтобы мои данные сохранялись, даже если я случайно удалю стек. Во время разработки я люблю комментировать это или устанавливать значение Delete.
  • TableName не является обязательным. Если он не указан, CloudFormation присвоит таблице имя в форме [StackName]-[ResourceName]-[random characters]. Я мог бы жестко привязать его к любой строке, но это вызовет ошибку, если я когда-нибудь решу развернуть второй стек на основе того же шаблона, поскольку AWS не позволяет двум таблицам иметь одно и то же имя. Я мог бы использовать специальный синтаксис CloudFormation, например !Sub "${AWS::StackName}-products", чтобы назвать таблицу, но я опущу параметр в этом примере и позволю CloudFormation выбрать имя за меня.

Погружение в более сложное

Вот весь шаблон CloudFormation для приложения, которое я обещал в начале этой статьи. Это расширенный набор первого файла шаблона, который включал только таблицу DynamoDB (строки 1–16 идентичны).

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

aws cloudformation update-stack --stack-name medium --template-body file://step2.yml --capabilities CAPABILITY_NAMED_IAM

Независимо от того, выполняю ли я обновление через интерфейс командной строки, как показано выше, или через веб-консоль, я должен подтвердить AWS, что я понимаю, что этот шаблон создаст новую роль IAM. Я делаю это с помощью флага CAPABILITY_NAMED_IAM в интерфейсе командной строки или с помощью флажка на последнем экране операции обновления в веб-консоли.

Роль IAM

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

  1. настраиваемая политика, которая разрешает GetItem, Scan и UpdateItem разрешения для таблицы DynamoDB
  2. настраиваемая политика, которая разрешает CreateLogStream и PutLogEvents в мою учетную запись, что позволяет создавать и регистрировать события в журналах CloudWatch.

Полная документация ресурса IAM Role находится здесь, но следует выделить несколько моментов.

  • RoleName - необязательный параметр, и, как и DynamoDB TableName, я опущу его и позволю CloudFormation называть этот ресурс. CloudFormation назовет роль IAM в формате [StackName]-[ResourceName]-[random characters].
  • AssumeRolePolicyDocument позволяет использовать эту роль IAM лямбда-функцией (о которой я еще не упоминал).
  • PolicyName для каждой политики требуется, но не обязательно должен быть уникальным в глобальном масштабе, поскольку это просто встроенная политика.

Обратите внимание, что определение роли IAM относится к таблице DynamoDB как !GetAtt MyProductTable.Arn. Язык шаблонов CloudFormation позволяет мне указывать ARN для ресурса перед его созданием!

Мне не нужно знать имя таблицы, чтобы создать относящуюся к ней роль IAM. CloudFormation понимает это за меня!

Лямбда-функция

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

Полная документация к ресурсу Лямбда-функция находится здесь, но следует выделить несколько моментов.

  • Свойство FunctionName является необязательным, как и предыдущие ресурсы в этом шаблоне.
  • Я могу указать код, который Lambda выполняет в файле шаблона (как показано выше), но на самом деле он не масштабируется. Лучший способ развернуть Lambda-функции в CloudFormation - указать S3Bucket и S3Key, содержащие пакет развертывания (zip-файл) для Lambda.
  • К лямбда-выражению можно добавить несколько Environment переменных. В примере показано только одно: имя таблицы DynamoDB. Код Lambda в примере показывает, как получить доступ к этой переменной среды, позволяя писать код, который обращается к ресурсу, еще до того, как ресурс будет создан.

Код лямбда-функции можно указать прямо в файле шаблона.

CloudWatch LogGroup

Создавать группу журналов не требуется. Я добавляю его в свои шаблоны CloudFormation по двум причинам:

  1. Я могу указать свойство RetentionInDays на другое значение, кроме неопределенного.
  2. Когда я удаляю стек, удаляются и журналы. Я не люблю журналы, которые больше не нужно загромождать мой аккаунт.

Полная документация по ресурсу CloudWatch LogGroup находится здесь, но включение ресурса в мой файл шаблона совершенно необязательно.

Запустите лямбда-функцию

На вкладке «Ресурсы» стека CloudFormation показаны все ресурсы, созданные шаблоном CloudFormation. Я могу щелкнуть лямбда-функцию, чтобы перейти к ней и выполнить ее вручную.

Код Python, который я указал в файле шаблона CloudFormation, уже заполнен лямбда-функцией.

Тестируя лямбда-функцию, я вижу, что в выходных данных отображается имя таблицы DynamoDB, как и ожидалось.

Удалить стек

Проект завершен, и теперь пора удалить весь стек. Это довольно просто. На самом деле это очень просто.

aws cloudformation delete-stack --stack-name medium

Что произойдет, если я удалю неправильный стек?

CloudFormation запоминает детали даже удаленных стопок. Я могу просто перейти к стекам CloudFormation, выбрать «Удалено» и по-прежнему иметь возможность отозвать весь свой файл шаблона. Если бы я выбрал Retain для DynamoDB DeletionPolicy, тогда у меня все еще были бы мои данные, а CloudFormation сохранила мою конфигурацию. Неплохо для бесплатного сервиса!

Что теперь?

В этой статье я показал вам, как развернуть очень простой стек, обновить его и удалить.

Лучший способ начать работу с CloudFormation - создать очень простой файл шаблона (как я сделал в начале этой статьи) и развернуть его. Для меня проще всего было внести дополнительные изменения и выполнить update операцию для проверки моего синтаксиса. Наконец, я часто обращался к документации, всегда проверяя наличие необходимых свойств для каждого ресурса.

Надеюсь, вы согласитесь, что развертывание инфраструктуры с помощью CloudFormation дает огромные преимущества.

Больше контента на plainenglish.io