Облако Terraform AWS: разумное управление инфраструктурой

Опубликовано: 2022-03-11

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

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

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

Представляем Терраформ

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

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

Цель

Я объясню, что такое Terraform, как он подходит для всей экосистемы и как он сравнивается с другими подобными инструментами. Затем я покажу вам шаги, необходимые для настройки нескольких сред и готовой установки Terraform для команды. Я объясню основы написания конфигурации Terraform — как справиться со сложностью и дублированием кода с помощью совместно используемых модулей .

Все примеры будут посвящены одному облачному провайдеру: Amazon Web Services (AWS). Это всего лишь облако, с которым у меня больше всего опыта, но вся информация должна относиться и к другим облакам.

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

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

Управление инфраструктурой — это сложно

Существует несколько шагов, связанных с настройкой среды для приложения в облаке. Если вы не запишете их все в виде подробных контрольных списков и не будете постоянно им следовать, вы все время будете совершать ошибки; мы люди, в конце концов. Этими шагами трудно поделиться. Вам нужно задокументировать множество ручных процедур, а документы могут быстро устареть. Теперь умножьте все это на общее количество сред для одного приложения: dev , test/qa , stage и prod . Также нужно подумать о безопасности для каждого из них.

Разве не у каждого инструмента есть пользовательский интерфейс, который я могу использовать и забыть о сложности?

У них есть пользовательские интерфейсы — ярким примером является Консоль управления AWS. Но эти инструменты делают многое под капотом. Один щелчок по пользовательскому интерфейсу может фактически вызвать каскад изменений, которые трудно понять. Обычно нет возможности отменить то, что вы сделали в пользовательском интерфейсе (обычных подсказок «Вы уверены?» часто недостаточно). Кроме того, всегда полезно иметь вторую пару глаз для проверки изменений, но когда мы используем пользовательский интерфейс, нам нужно сидеть с этим человеком вместе или проверять наши изменения после их внесения, что больше похоже на аудит, чем на проверку. рассмотрение. У каждого облачного провайдера есть собственный пользовательский интерфейс, который вам необходимо освоить. Пользовательские интерфейсы спроектированы так, чтобы быть простыми в использовании (не обязательно простыми!) Такое ручное нажатие также очень сложно автоматизировать.

Как насчет инструментов командной строки?

Они были бы лучше, чем инструменты пользовательского интерфейса для наших вариантов использования. Тем не менее, вы по-прежнему склонны вносить изменения вручную или писать сценарии bash, которые могут легко выйти из-под контроля. Более того, у каждого провайдера есть свои инструменты CLI. С помощью этих инструментов вы не сможете увидеть свои изменения, пока не зафиксируете их. К счастью, это не новая проблема, и есть инструменты, которые помогают с этим.

Оркестрация против управления конфигурацией

Я определю некоторые категории инструментов и методов, которые используются для управления инфраструктурой. Это оркестровка и управление конфигурацией. Вы можете рассматривать их примерно как модели декларативного и императивного программирования (подумайте о Прологе и Python). У каждого есть свои плюсы и минусы, но лучше знать их все и применять лучший инструмент для конкретной работы. Кроме того, оркестровка обычно требует более высокого уровня абстракции, чем управление конфигурацией.

Оркестровка

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

Вот где подходит Terraform. Вы используете его для управления частью ИТ-инфраструктуры: вы говорите ему, что нужно развернуть, а Terraform связывает все это вместе и выполняет все необходимые вызовы API. В этом пространстве есть аналогичные инструменты; одним из самых известных является AWS Cloud Formation. У него лучше поддержка восстановления и откатов в случае ошибок, чем у Terraform, но и, на мой взгляд, более крутая кривая обучения. Он также не зависит от облака: он работает только с AWS.

Управление конфигурацией

Дополнительным аспектом этих практик является подход к управлению конфигурацией. В этой парадигме вы указываете точные шаги, которые инструмент должен сделать, чтобы получить заданную желаемую конфигурацию. Сами шаги могут быть небольшими и поддерживать несколько операционных систем, но вам нужно активно продумывать порядок их выполнения. Они не осознают текущее состояние и окружение (если вы не запрограммируете их на это) и поэтому будут слепо выполнять любые действия, которые вы им даете. Это может привести к проблеме, известной как дрейф конфигурации , когда ваши ресурсы будут медленно десинхронизироваться с тем, что они изначально должны были представлять, особенно если вы внесли в них некоторые изменения вручную. Они отлично справляются с управлением и предоставлением услуг на отдельных экземплярах. Примерами инструментов, которые превосходно справляются с этим рабочим процессом, являются Chef, Puppet, Ansible и Salt.

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

Мем телешоу IT Crowd с их культовым слоганом: Вы пробовали выключить и снова включить?

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

Ansible (и другие инструменты CM) можно использовать для управления инфраструктурой AWS, но это требует много работы и более подвержено ошибкам, особенно когда инфраструктура часто меняется и усложняется.

Важно помнить, что подходы к оркестровке и управлению конфигурацией не противоречат друг другу. Они совместимы. Совершенно нормально иметь группу экземпляров EC2 (VPS) в группе AutoScaling, управляемую Terraform, но работающую с образом приложения AWS (AMI), который представляет собой моментальный снимок диска, подготовленный с обязательными шагами, например, с помощью Ansible. . В Terraform даже есть концепция «провайдеров», которые позволяют вам запускать внешние инструменты инициализации после загрузки машины.

Документация по Terraform прекрасно объясняет это и помогает разместить Terraform во всей экосистеме.

Что такое терраформ?

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

Имя HashiCorp должно звонить в колокольчик — они также производят Nomad, Vault, Packer, Vagrant и Consul. Если вы использовали любой из этих инструментов, вы уже знаете качество документации, активное сообщество и полезность, которую вы можете ожидать от их решений.

Инфраструктура как код

Terraform не зависит от платформы; вы можете использовать его для управления серверами без операционной системы или облачными серверами, такими как AWS, Google Cloud Platform, OpenStack и Azure. На жаргоне Terraform они называются провайдерами . Вы можете получить представление о масштабах, прочитав полный список поддерживаемых провайдеров. Несколько провайдеров могут использоваться одновременно, например, когда провайдер А настраивает для вас виртуальные машины, а провайдер Б настраивает и делегирует записи DNS.

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

Основные части установки Terraform

  1. Сам бинарный файл Terraform, который вам нужно установить
  2. Файлы исходного кода, т. е. ваша конфигурация
  3. Состояние (локальное или удаленное), представляющее ресурсы, которыми управляет Terraform (подробнее об этом позже).

Написание конфигурации Terraform

Вы пишете код конфигурации Terraform в файлах *.tf , используя язык HCL. Существует возможность использовать формат JSON ( *.tf.json ), но он предназначен для машин и автоматической генерации, а не для людей. Я рекомендую вам придерживаться HCL. Я не буду углубляться в синтаксис языка HCL; официальные документы отлично описывают, как писать HCL и как использовать переменные и интерполяции. Я упомяну только тот минимум, который необходим для понимания примеров.

Внутри файлов Terraform вы в основном имеете дело с ресурсами и источниками данных . Ресурсы представляют собой компоненты вашей инфраструктуры, например экземпляр AWS EC2, экземпляр RDS, DNS-запись Route53 или правило в группе безопасности. Они позволяют вам предоставлять и изменять их внутри облачной архитектуры.

Предполагая, что вы настроили Terraform, если вы выполните terraform apply , приведенный ниже код создаст полнофункциональный экземпляр EC2 (показаны только определенные свойства):

 resource "aws_instance" "bastion" { ami = "ami-db1688a2" # Amazon Linux 2 LTS Candidate AMI 2017.12.0 (HVM), SSD Volume Type - ami-db1688a2 instance_type = "t2.nano" key_name = "${var.key_name}" subnet_ vpc_security_group_ids = ["${aws_security_group.bastion.id}"] monitoring = "false" associate_public_ip_address = "true" disable_api_termination = "true" tags = { Name = "${var.project_tag}-bastion-${var.env}" Env = "${var.env}" Application ApplicationRole = "Bastion Host" Project = "${var.project_tag}" } }

С другой стороны, есть источники данных, которые позволяют читать данные о заданных компонентах, не изменяя их. Вы хотите получить AWS ID (ARN) сертификата, выданного ACM? Вы используете источник данных. Разница в том, что источники данных имеют префикс data_ при ссылке на них в файлах конфигурации.

 data "aws_acm_certificate" "ssl_cert" { domain = "*.example.com" statuses = ["ISSUED"] }

Вышеуказанное относится к выданному сертификату ACM SSL, который можно использовать вместе с AWS ALB. Прежде чем вы все это сделаете, вам нужно настроить свою среду.

Структура папок

Среды Terraform (и их состояния) разделены каталогами. Terraform загружает все файлы *.tf в каталоге в одно пространство имен, поэтому порядок не имеет значения. Я рекомендую следующую структуру каталогов:

 /terraform/ |---> default_variables.tf (1) /stage/ (2) |---> terraform.tfvars (3) |---> default_variables.tf (4) |---> terraform.tf (5) |---> env_variables.tf (6) /prod/ /<env_name>/
  1. default_variables.tf — определяет все переменные верхнего уровня и, возможно, их значения по умолчанию. Их можно повторно использовать в каждой среде (вложенный каталог) с символическими ссылками.
  2. /stage/ — каталог, содержащий конфигурацию для целой отдельной среды (здесь он называется stage , но может быть любым). Любые изменения, сделанные в этой папке, полностью независимы от других сред (env, таких как prod ), что вам нужно, чтобы не испортить производственную среду изменениями, внесенными в stage !
  3. terraform.tfvars — определяет значения переменных. Файлы .tfvars аналогичны файлам .env в том смысле, что они содержат пары key=val для определенных переменных. Например, это указывает profile AWS, имя key_path AWS и key_name AWS, которые я использую. Его можно игнорировать в Git.
  4. default_variables.tf — это симлинк на файл (2), который позволяет нам делиться независимыми от окружения переменными, не повторяясь.
  5. terraform.tf — это основная конфигурация каждого окружения; он содержит блок terraform {} , который настраивает серверную часть. Здесь я также настраиваю провайдеров .
  6. env_variables.tf — этот файл содержит переменные, специфичные для env. Я помечаю все ресурсы тегом Env=<env_name> в AWS, поэтому этот файл обычно определяет только одну переменную: env .

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

Внутренняя конфигурация

Я уже упоминал состояние Terraform. Это неотъемлемая часть рабочего процесса Terraform. Вы можете задаться вопросом, действительно ли требуется состояние. Разве Terraform не может постоянно запрашивать API AWS, чтобы получить фактическое состояние инфраструктуры? Что ж, если подумать, Terraform необходимо поддерживать сопоставление между тем, чем он управляет в декларативных файлах конфигурации, и тем, чему эти файлы фактически соответствуют (в среде облачного провайдера). Обратите внимание, что при написании файлов конфигурации Terraform вам не нужны идентификаторы, например, отдельных экземпляров EC2 или ARN, которые будут созданы для публикуемых вами групп безопасности. Однако внутри Terraform необходимо знать, что данный блок ресурсов представляет собой конкретный ресурс с идентификатором/ARN. Это необходимо для обнаружения изменений. Кроме того, состояние используется для отслеживания зависимостей между ресурсами (о чем вам обычно не нужно думать!). Они используются для построения графа, который можно (обычно) распараллелить и выполнить. Как всегда, я рекомендую вам прочитать прекрасную документацию по состоянию Terraform и его назначению.

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

По умолчанию Terraform сохраняет состояние в файле на диске, расположенном в текущем рабочем каталоге (каждой среды) в виде файла terraform.tfstate . Это нормально, если вы знаете, что будете единственным разработчиком на работе или только изучаете и экспериментируете с Terraform. Технически вы могли бы заставить его работать в команде, потому что вы можете передать состояние в репозиторий VCS. Но тогда вам нужно будет убедиться, что все всегда работают с последней версией состояния и что никто не вносит изменения одновременно! Как правило, это большая головная боль, и я настоятельно не рекомендую этого делать. Кроме того, если кто-то присоединится к вашей работе с одним разработчиком, вам все равно придется настроить альтернативное место для состояния.

К счастью, это проблема с хорошими решениями, встроенными в Terraform: так называемое удаленное состояние . Чтобы удаленное состояние работало, вам необходимо настроить серверную часть с помощью одного из доступных внутренних поставщиков. Следующий серверный пример будет основан на AWS S3 и AWS DynamoDB (база данных AWS NoSQL). Вы можете использовать только S3, но тогда вы потеряете механизм блокировки состояния и проверки согласованности (не рекомендуется). Если вы ранее использовали только локальное состояние, при настройке удаленной серверной части вам будет предложено перенести свое состояние в первый раз, чтобы вы ничего не потеряли. Вы можете прочитать больше о конфигурации бэкенда здесь.

К сожалению, существует проблема курицы и яйца: корзину S3 и таблицу DynamoDB необходимо создавать вручную. Terraform не может создавать их автоматически, так как еще нет состояния! Что ж, есть некоторые решения, такие как https://github.com/gruntwork-io/terragrunt, которые автоматизируют это с помощью интерфейса командной строки AWS, но я не хочу отклоняться от основной темы этой записи в блоге.

Важные вещи, которые нужно знать о конфигурации серверной части S3 и DynamoDB:

  1. Включите управление версиями в корзине S3, чтобы избежать человеческих ошибок и закона Мерфи.
  2. Таблица DynamoDB имеет ограничение скорости чтения и записи (называемое емкостью). Если вы вносите много изменений в удаленное состояние, обязательно включите DynamoDB AutoScaling для этой таблицы или настройте достаточно высокие ограничения на чтение и запись. В противном случае Terraform будет получать ошибки HTTP 400 от AWS API при выполнении большого количества вызовов.

Подводя итог, можно добавить следующую внутреннюю конфигурацию в terraform.tf для настройки удаленного состояния на S3 и DynamoDB.

 terraform { # Sometimes you may want to require a certain version of Terraform required_version = ">= 0.11.7" # Stores remote state, required for distributed teams # Bucket & dynamoDB table have to be created manually if they do not exist # See: https://github.com/hashicorp/terraform/issues/12780 backend "s3" { bucket = "my-company-terraform-state" key = "app-name/stage" region = "eu-west-1" # 5/5 R/W Capacity might not be enough for heavy, burst work (resulting in 400s). Consider enabling Auto Scaling on the table. # See: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html dynamodb_table = "terraform-state-lock-table" } }

Это много, чтобы принять сразу, но помните, вы делаете это один раз для каждого окружения, а затем можете забыть об этом. Если вам нужен еще больший контроль над блокировкой состояния, есть HashiCorp Terraform Enterprise, но я не буду его здесь описывать.

Провайдеры

Чтобы этот бэкенд был доступен и вообще имел возможность общаться с нашим облачным провайдером, нам нужно настроить так называемый провайдер . В файл terraform.tf можно поместить следующий блок (для каждой среды):

 provider "aws" { profile = "${var.profile}" region = "${var.region}" version = "~> 1.23.0" }

Переменные для profile и region хранятся в файле terraform.tfvars , который можно игнорировать. Переменная profile относится к именованному профилю, содержащему учетные данные безопасности для облака AWS с использованием стандартного файла учетных данных. Обратите внимание, что я также устанавливаю некоторые ограничения версии. Вы не хотите, чтобы Terraform обновлял плагины вашего провайдера без вашего ведома при каждой инициализации серверной части. Тем более, что существует версия 2.x провайдера AWS, требующая тщательного обновления.

Внутренняя инициализация

Каждая внутренняя конфигурация требует шага инициализации в начале и каждый раз, когда в нее вносятся изменения. Инициализация также настраивает модули Terraform (подробнее о них позже), поэтому, когда вы добавляете их, вам также необходимо повторно запустить этап инициализации. Эту операцию безопасно выполнять несколько раз. Обратите внимание, что во время инициализации серверной части не все переменные могут быть прочитаны Terraform для настройки состояния, а также по соображениям безопасности (например, секретные ключи). Чтобы преодолеть это и, в нашем случае, использовать профиль AWS, отличный от используемого по умолчанию, вы можете использовать параметр -backend-config с допускающими k=v пар переменных. Это называется частичной конфигурацией.

 terraform init -backend-config=profile=<aws_profile_name>

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

Рабочий процесс

Общий рабочий процесс при работе с кодом Terraform выглядит следующим образом:

  1. Напишите конфигурацию для вашей инфраструктуры.
  2. Посмотрите, какие фактические изменения он внесет ( terraform plan ).
  3. При желании выполните именно те изменения, которые вы видели на шаге 2 ( terraform apply ).
  4. ПЕРЕЙТИ К 1

План терраформирования

Команда Terraform plan предоставит вам список изменений, которые будут внесены в вашу инфраструктуру после выполнения команды apply . Безопасно выдавать plan несколько раз, так как сам по себе он ничего не меняет.

Как читать план

Объекты в Terraform (ресурсы и источники данных) легко идентифицировать по их полным именам.

  1. В случае ресурсов идентификатор может выглядеть так: <resource_type>.<resource_name> — например, aws_ecs_service.this .
  2. В случае ресурсов внутри модулей у нас есть дополнительное имя модуля: module.<module_name>.<resource_type>.<resource_name> — например, module.my_service_ecs_service_task.aws_ecs_service.this .
  3. Источники данных (внутри и снаружи модуля): (module.<module_name>).data.<resource_type>.<resource_name> — например, module.my_service_ecs_service_task.data.aws_ecs_task_definition.this .

Тип ресурса специфичен для данного провайдера и обычно включает его имя ( aws_… ). Весь список доступных ресурсов для AWS можно найти в документации.

Есть пять действий, которые план покажет для заданных ресурсов:

  1. [+] Добавить – Будет создан новый ресурс.
  2. [-] Уничтожить — Ресурс будет полностью уничтожен.
  3. [~] Изменить на месте — ресурс будет изменен, и один или несколько параметров будут изменены. Это вообще безопасно. Terraform покажет вам, какие параметры будут изменены и как (если это возможно).
  4. [- / +] — Ресурс будет удален, а затем создан заново с новыми параметрами. Это происходит, если было внесено изменение в параметр, который нельзя изменить на месте. Terraform покажет вам, какие изменения вызывают воссоздание ресурса, со следующим красным комментарием: (forces new resource) . Это потенциально опасно, так как есть период, в котором ресурс вообще не будет существовать. Это также может нарушить другие связанные зависимости. Я бы рекомендовал обходить такие изменения, если только вы не знаете, какими будут последствия, или вас не волнует время простоя.
  5. [<=] — будет прочитан источник данных. Это операция только для чтения.

Terraform AWS — пример «плана»

Выше приведен пример плана. Изменения, которые я сделал, были:

  • Изменен instance_type первого экземпляра бастиона.
  • Добавлен второй экземпляр бастиона
  • Изменено имя группы безопасности

Обратите внимание, что последнее изменение — это правило группы безопасности, которое автоматически обнаруживает изменение имени родительской группы. На мой взгляд, весь план очень читаем. Значения некоторых параметров отображаются как <computed> — это не означает, что они будут изменены, а скорее то, что их нельзя получить и представить на данном этапе (например, имя SG, которое еще не создано). Помните, что во время выполнения команды plan будут показаны только измененные ресурсы. Все, что пропущено, не будет затронуто.

Обычно команды Terraform принимают дополнительные параметры. Наиболее важным параметром команды plan является опция -out , которая сохранит ваш план на диске. Затем этот сохраненный план может быть выполнен (точно так же, как сохранен) с помощью команды применения. Это очень важно, так как в противном случае могут быть изменения, внесенные, например, коллегами в промежутке между выдачей вами plan и выдачей apply . Пример:

  1. Выпустите plan и убедитесь, что он выглядит хорошо.
  2. Кто-то меняет состояние без вашего ведома.
  3. Выпуск apply и — упс, он сделал что-то не то, что планировалось!

К счастью, этот рабочий процесс был улучшен в Terraform v0.11.0. Начиная с этой версии, команда apply автоматически представляет вам план, который вы должны затем утвердить (явно набрав yes ). Преимущество в том, что если вы примените этот план, он будет выполнен точно так, как представлено.

Еще одна полезная опция -destroy , которая покажет вам план, который приведет к уничтожению всех ресурсов, которыми управляет Terraform. Вы также можете указать определенные ресурсы для уничтожения с помощью опции -target .

Применить

Когда мы применяем данный план, Terraform выходит и блокирует наше состояние, чтобы обеспечить эксклюзивность. Затем он переходит к изменению ресурсов и, в конце концов, отправляет обновленное состояние. Следует отметить, что некоторые ресурсы требуют больше времени для завершения, чем другие. Например, создание экземпляра AWS RDS может занять более 12 минут, и Terraform будет ждать его завершения. Очевидно, что Terraform достаточно умен, чтобы не блокировать этим любую другую операцию. он создает ориентированный граф запрошенных изменений и, если нет взаимозависимостей, использует параллелизм для ускорения выполнения.

Импорт ресурсов

Часто Terraform и другие решения «конфигурация как код» внедряются постепенно, в уже существующую среду. Переход действительно легкий. По сути, все, что не определено внутри Terraform, остается неуправляемым и неизменным. Terraform заботится только о том, чем он управляет. Конечно, возможно, что это вызовет проблемы — например, если Terraform полагается на какую-то конечную точку, которая существует за пределами его конфигурации, а затем вручную уничтожается. Terraform об этом не знает и поэтому не может пересоздать этот ресурс при изменении состояния, что приведет к ошибкам от API при выполнении плана. Это не то, о чем я бы беспокоился; преимущества внедрения Terraform намного перевешивают недостатки.

Чтобы немного упростить процесс знакомства, Terraform включает команду import . Он используется для введения уже существующих внешних ресурсов в состояние Terraform и позволяет ему управлять этими ресурсами. К сожалению, Terraform не может автоматически генерировать конфигурации для этих импортированных модулей, по крайней мере, на момент написания. Есть планы на эту функциональность, но сейчас вам нужно сначала написать определение ресурса в Terraform, а затем импортировать этот ресурс, чтобы сообщить Terraform, чтобы он начал управлять им. После импорта в состояние (и во время планирования исполнения) вы увидите все отличия между тем, что вы написали в .tf файлах, и тем, что реально существует в облаке. Таким образом, вы можете дополнительно настроить конфигурацию. В идеале никаких изменений не должно появляться, что означает, что конфигурация Terraform отражает то, что уже находится в облаке 1:1.

Модули

Модули — это неотъемлемая часть конфигурации Terraform, и я рекомендую вам принять их и использовать почаще. Они предоставляют вам возможность повторно использовать определенные компоненты. Примером может служить кластер AWS ECS, который используется для запуска контейнеров Docker. Для работы такого кластера нужно настроить множество отдельных ресурсов: сам кластер, конфигурацию запуска, которая бы управляла отдельными инстансами EC2, контейнерные репозитории для образов, группу и политику автомасштабирования и так далее. Обычно вам нужно иметь отдельные кластеры для отдельных сред и/или приложений.

Один из способов обойти это — скопировать и вставить конфигурацию, но это явно недальновидное решение. Вы будете делать ошибки, выполняя простейшие обновления.

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

Локальные и удаленные модули

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

Это, однако, имеет ограничения. Сложно создавать версии этих модулей и делиться ими. Управление версиями важно, потому что вы можете захотеть использовать версию 1.0.0 вашего модуля ECS в рабочей среде, но хотели бы поэкспериментировать с версией 1.1.0 в промежуточной среде. Если бы модуль хранился вместе с вашим кодом, каждое изменение в коде модуля отражалось бы в каждой среде (после запуска apply ), что обычно нежелательно.

Один удобный подход к управлению версиями модулей — поместить их все в отдельный репозиторий, например, your-company/terraform-modules. Затем при ссылке на эти модули внутри вашей конфигурации Terraform вы можете использовать ссылку VCS в качестве источника:

 module "my-module" { source = "[email protected]:your-company/terraform-modules.git//modules/my-module?ref=v1.1.0" ... }

Здесь я ссылаюсь на v1.1.0 моего модуля (конкретный путь), который я могу протестировать независимо от других версий того же модуля в разных средах.

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

Использование модулей

На модули можно легко ссылаться в средах Terraform, определив специальный модульный блок. Вот пример такого блока для гипотетического модуля ECS:

 module "my_service_ecs_cluster" { source = "../modules/ecs_cluster" cluster_name = "my-ecs-service-${var.env}" repository_names = [ "my-ecs-service-${var.env}/api", "my-ecs-service-${var.env}/nginx", "my-ecs-service-${var.env}/docs", ] service_name = "my-ecs-service-${var.env}" ecs_instance_type = "t2.nano" min_size = "1" max_size = "1" use_autoscaling = false alb_target_group_arn = "${module.my_alb.target_group_arn}" subnets = "${local.my_private_subnets}" security_groups = "${aws_security_group.my_ecs.id}" key_name = "${var.key_name}" env_tag = "${var.env}" project_tag = "${var.project_tag}" application_tag = "${var.api_app_tag}" asg_tag = "${var.api_app_tag}-asg" }

Все передаваемые параметры (кроме некоторых глобальных, таких как source ) были определены внутри конфигурации этого модуля как входные данные (переменные).

Реестр модулей

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

Раньше вам приходилось либо писать собственные модули с нуля (и учиться на своих ошибках), либо использовать модули, опубликованные на GitHub и в других местах, без каких-либо гарантий относительно их поведения (кроме чтения кода!)

Обмен данными между средами

В идеале среды должны быть полностью разделены, даже если используются разные учетные записи AWS. In reality, there are cases when one Terraform environment might use some information in another environment. This is especially true if you are gradually converting your architecture to use Terraform. One example might be that you have a global env that provides certain resources to other envs.

Let's say env global shares data with stage . For this to work, you can define outputs at the main level of the environment like so:

 output "vpc_id" { value = "${module.network.vpc_id}" }

Then, in the stage environment, you define a datasource that points to the remote state of global :

 data "terraform_remote_state" "global" { backend = "s3" config { bucket = "my-app-terraform-state" key = "terraform/global" region = "${var.region}" dynamodb_table = "terraform-state-lock-table" profile = "${var.profile}" } }

Now, you can use this datasource as any other and access all the values that were defined in global 's outputs:

 vpc_

Words of Caution

Terraform has a lot of pros. I use it daily in production environments and consider it stable enough for such work. Having said that, Terraform is still under active development. Thus, you will stumble on bugs and quirks.

Where to Report Issues and Monitor Changes

First of all, remember: Terraform has a separate core repo and repositories for each provider (eg, AWS). If you encounter issues, make sure to check both the core repo and the separate provider repositories for issues and/or opened pull requests with fixes. GitHub is really the best place to search for bugs and fixes as it is very active and welcoming.

This also means that provider plugins are versioned separately, so make sure you follow their changelogs as well as the core one. Most of the bugs I have encountered were resolved by upgrading the AWS provider which already had a fix.

Can't Cheat Your Way out of Cloud Knowledge

You cannot use Terraform to configure and manage infrastructure if you have no knowledge of how a given provider works. I would say this is a misconception and not a downside, since Terraform has been designed to augment and improve the workflow of configuration management and not to be some magic dust that you randomly sprinkle around and—poof! Environments grow! You still need a solid knowledge of a security model of each cloud, how to write, eg, AWS policies, what resources are available, and how they interact.

Prefer Separate Resources That Are Explicitly linked

There are certain resources—for example, the AWS security group or AWS route table—that allow you to configure the security rules and routes respectively, directly inside their own block. This is tempting, as it looks like less work but in fact will cause you trouble. The problems start when you are changing those rules on subsequent passes. The whole resource will be marked as being changed even if only one route/security rule is being introduced. It also gives implicit ordering to those rules and makes it harder to follow the changes. Thankfully, mixing those both approaches is not allowed now (see the note).

Best-practice example, with explicitly linked resources:

 resource "aws_security_group" "my_sg" { name = "${var.app_tag}-my-sg" ... } resource "aws_security_group_rule" "rule_one" { security_group_ ... } resource "aws_security_group_rule" "rule_two" { security_group_ ... }

Terraform plan Doesn't Always Detect Issues and Conflicts

I already mentioned this in the case where you were managing resources with Terraform that were relying on other, unmanaged infrastructure. But there are more trivial examples—for example, you will get an error if your EC2 instance has Termination Protection enabled, even though plan would show you it's OK to destroy it. You can argue that this is what Termination Protection has been designed for, and I agree, but there are more examples of things you can do in theory/on plan but when executed will deadlock or error out. For example, you cannot remove a network interface if something is using it—you get a deadlock without an option to gracefully recover.

Syntax Quirks

There are also quirks related to how HCLv1 (the syntax language Terraform uses) has been designed. It has a couple of frustrating quirks. There is work underway to provide an improved version of the parser for HCLv2. The best way to read on the current limitations and the plan to overcome them is this fantastic blog series. In the meantime, there are workarounds for most of those issues. They are not pretty and they will fail once v0.12 comes out, but hey, it is what it is.

When State Update Fails

It sometimes happens that Terraform is not able to correctly push an updated state. This is usually due to underlying network issues. The solution is to retry the state update instead of running apply again, which will fork the state .

Another issue might happen when state lock (the synchronization primitive that prevents multiple users to update the same state) fails to be taken down by Terraform. This involves running terraform force-unlock with the lock ID to take it down manually.

Thankfully, in case of such problems, Terraform provides you with a good description and steps you need to make to fix it.

Not Everything Is Fun to Manage Through Terraform

There are certain cases where Terraform is not my tool of choice. For example, configuring AWS CodePipeline and CodeBuild projects (AWS equivalent of CI/CD pipeline) is cumbersome when done through Terraform. You need to define each step through very verbose configuration blocks and things like “Login via GitHub” are a lot more complicated than using the UI. Of course, it's still possible if you prefer to have it codified. Well, I guess it's a good candidate for a well-written module!

Same thing goes for managing AWS API Gateway endpoints. In this case, using a dedicated serverless framework would be a better option.

When configuring AWS resources with Terraform, you will find yourself writing a lot of policies. Policies that would otherwise often be auto-generated for you (when using the UI). For those, I'd recommend the AWS Visual Editor and then copying the resulting policy JSON into Terraform.

Заключение

Using Terraform has been fun and I'll continue doing so. Initial steps can be a bumpy ride, but there are more and more resources that help to ease you in.

I'd definitely recommend taking Terraform for a spin and simply playing with it. Remember, though—be safe and test it out on a non-essential account. If you are eligible for AWS Free Tier, use it as a 12-month free trial. Just be aware it has limitations as to what you can provision. Otherwise, just make sure you spin the cheapest resources, like t3.nano instances.

I highly recommend extensions for Terraform support in various code editors. For Visual Studio Code, there is one with syntax highlighting, formatting, validation and linting support.

It's always valuable to learn new things and evaluate new tools. I found that Terraform helped me immensely in managing my infrastructure. I think working with Terraform will only get easier and more fun, especially once v0.12.0 ships with a major upgrade to the HCL syntax and solve most of the quirks. The traction and community around Terraform are active and vibrant. You can find a lot of great resources on things I didn't manage to cover in a single blogs post, eg, a detailed guide on how to write modules.

Related: Terraform vs. CloudFormation: The Definitive Guide