Заметка про опыт работы с Terraform

Доброго времени суток коллеги! Этим летом я выступал с на митапе с докладом про Terraform.

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

Внимание! Уважаемые читатели — прошу принять во внимание, что когда готовился этот доклад, актуальной версией Terraform была v0.11.

Буквально за день до первого прочтения увидела свет версия v0.12, с новинками которой можно ознакомиться в анонсе

Мой доклад базируется на 0.11 версии и думаю по прежнему будет представлять ценность, так как он основан не только на технических моментах работы с Terraform но и на практиках. Поехали!

Доклад посвящается опыту моей команды с использованием Terraform.

О чем же этот доклад?

  • Я вкратце обрисую, что такое Terraform, потому что наверняка среди читателей есть люди, которые с ним не знакомы или знакомы очень мало. Поэтому стоит по верхам пробежаться и рассказать об этом инструменте, и рассказать о том, как мы его используем, для чего и к каким методам, практикам пришли, исходя из нашего опыта.
  • Я опишу технические практики  и приемы которые мы выработали в ходе работы с ним — эдакие best practices.
  • И расскажу про кое-какие организационные приемы и практики

Итак, Terraform — это еще одна очень популярная утилита компании Hashicorp, появившаяся в 2014 году, которая позволяет вам, используя парадигму Infrastructure as a Code (Заметка Dodo Pitza на русском) написать, как вы ходите видеть вашу инфраструктуру на очень дружественном, легко читаемом декларативном языке.  Итак — единый вид ресурсов, применение практик работы с кодом, которые за долгое время уже выработаны сообществом разработчиков только теперь применительно к инфраструктуре, поддержка всех современных платформ,а так-же безопасное и предсказуемое изменение инфраструктуры. Да, для 2014 года это было очень и очень сильно. Предсказуемое изменение инфраструктуры в то время- да как так (сарказм)?

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

Небольшая ремарка: наш проект полностью находится в Amazon, на базе AWS-сервисов, и поэтому сегодня я буду говорить о применении Terraformа именно в этом ключе. Отдельно замечу, что он может применяться не только для Amazon. Он является универсальной утилитой, которая позволяет управлять ( в той или иной мере) всем, у чего есть API.

Вкратце о структуре Terraformа, из чего он, по нашему мнению, состоит:

  1. Во-первых, это сам движок Terraformа, его ядро, бинарные файлы, который вы скачиваете и устанавливаете себе на компьютер, на сервер, еще куда-нибудь. Который занимается обработкой логики, то есть это обработка синтаксиса, работа с плагинами и провайдерами, автовычисление всех ваших изменений и все прочее.
  2. Во вторых, это – сами провайдеры, то есть открытые плагины, коих на момент подготовки доклада я насчитал более ста штук, которые позволяют вам работать с тем или иным облачным сервисом, и не только с облачным. Потому что если посмотреть на официальную Wiki Hashicorp, на список этих провайдеров (https://www.terraform.io/docs/providers/), в их числе есть не только провайдеры для Amazon, для Google, для DigitalOcean и прочих. Там также присутствуют провайдеры для работы с VMware Vsphere, для работы с Docker. Я даже нашел у них в этом официальном списке провайдера, который позволяет вам управлять правилами для Cisco ASA (https://www.terraform.io/docs/providers/ciscoasa/index.html)! Вы можете помимо прочего управлять:

И так далее…

Неофициальных провайдеров еще больше. Из своего опыта – в ходе экспериментов я натыкался на GitHub на сторонний, не включенный в официальный список провайдер, который позволял работать с DNS от GoDaddy (https://github.com/n3integration/terraform-godaddy) и с ресурсами Proxmox (https://github.com/Telmate/terraform-provider-proxmox).

Вы можете в рамках одного Terraform проекта использовать разные провайдеры и соответственно ресурсы разных поставщиков услуг или технологии — скажем управлять инфраструктурой в AWS, с внешним DNS у GoDaddy. А завтра Ваша компания купила startup который хостился в DO или Azure и пока решают — мигрировать это в AWS или нет, вы также можете это поддержать с помощью того же инструмента!

3. Следующая составляющая — это ресурсы. Те сущности облака, которые вы можете создавать, используя Terraform. Но их список, синтаксис и свойства зависят от используемого провайдера, по сути — от используемого облака. Или не только облака.

4. И, наконец, последнее – это модули. То есть шаблоны, при помощи которых Terraform позволяет вам шаблонизировать вашу конфигурацию, делать некоторые темплейты, и тем самым сделать ваш код меньше, делать возможность его переиспользования, ну и просто комфортно вам с ним работать.

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

  • Во-первых, это cloud agnostic утилита, Да, сейчас нас сервис находится в Amazon, все хорошо. Но когда мы выбирали этот инструмент, то подумали: «А что будет, если завтра, послезавтра, через неделю – к нам придет менеджмент и скажет: «Ребята, а мы подумали – давайте-ка будем не только разворачиваться в Amazon, у нас есть какой-то проект, где нам нужно будет инфраструктуру завести например в Google Cloud. Или в Azure – ну, мало ли». Мы решили, что нам хотелось бы иметь инструмент, который не будет жестко привязан к какому-либо облачному сервису.
  • Во-вторых, он открытый. Opensource, код на GitHub-е (https://github.com/hashicorp/terraform). Опять же, на момент создания этого доклада я посмотрел, что у репозитория рейтинг больше 16 тысяч звезд, и это, неплохое подтверждение репутации проекта. Плюс не раз и не два сталкивались с тем, что в некоторых версиях бывают баги и не совсем понятное поведение, и наличие открытого репозитория позволяет найти информацию о том, что это действительно баг и его можно исправить просто обновив движок. Или что это баг, но «ребята, подождите, буквально через два дня выйдет новая версия и мы его пофиксим». Или – да, это что-то непонятное, странное, с ним разбираются, но товарищ нашел некоторый work-around,  и там написано как применить его. Это очень удобно.
  • Следующее: то, что Terraform, как утилита, находится полностью под вашим контролем. Он может быть установлен на ваш ноутбук, на ваш сервер, он может быть легко встроен в ваш пайплайн, который может быть сделан на базе любого инструмента. Мы, например, используем для этого GitLab CI.
  • Terraform умеет, и хорошо это делает, проверку состояния вашей инфраструктуры. Что здесь имеется в виду? Положим, вы начали использовать Terraform в своей команде, решили на него перейти. Создаете описание какого-то ресурса в Amazon, например Security Groups, применяете, она у вас создается, все хорошо. И тут – бац! — ваш коллега, который вчера вернулся из отпуска и еще не в курсе, что вы тут все так красиво устроили, или вообще коллега из другого отдела, который тоже имеет доступ к вашему аккаунту и что-то там может делать, который вообще не в курсе про Infrastructure as code, про применение Terraformа — заходит, и настройки этой Security-группы меняет ручками. И не встретившись с ним, не пообщавшись, или не напоровшись потом на некую проблему, вы об этом в обычной ситуации никогда не узнаете. Но, если вы используете Terraform, то даже прогон плана вхолостую по этому ресурсу покажет вам, что есть изменения в рабочей среде. Когда Terraform просматривает ваш код, он параллельно с этим  обращается к API облачного провайдера, получает от него состояние объектов и сравнивает: «А сейчас там то же самое, что я делал до этого, о чем я помню?» Потом сравнивает это с кодом, смотрит, что нужно еще изменить. И, например, если в его стейте, в его памяти, и в вашем коде всё одинаково, но там есть изменения – он вам покажет, и предложит его откатить. Ну или как-то еще с этим разобраться. На мой взгляд, тоже очень хорошее свойство. Таким образом, это еще один шаг, лично для нас, к тому, чтобы получить иммутабельную инфраструктуру.
  • Еще одна очень важная фича – это модули, о которых я упоминал, и counts. Об этом я чуть попозже расскажу. Когда буду сравнивать с инструментами. 
  • А также вишенка на торте: у Terraformа есть довольно большой список встроенных функций (https://www.terraform.io/docs/configuration/functions.html) , которые, несмотря на декларативный, то есть совсем не язык программирования, позволяют нам реализовать некоторую, не сказать, чтобы программную, но логику. Некоторые авто-вычисления, split строк есть, приведение к нижнему и верхнему кейсу, удаление каких-то символов этой строки. Мы это довольно активно используем. Не весь, конечно. Но они сильно облегчают жизнь, особенно, когда вы пишите модуль, который потом должен быть пере использован в очень разных окружениях.

Если мы говорим про Terraform в Amazone. То один из популярнейших вопросов, которые встречаются в интернете при любых обсуждениях, это его сравнение с CloudFormation. Мы тоже этим вопросом задавались, когда выбирали его, как инструмент, и здесь я привожу краткое сравнение тех пунктов, по которым мы решили, что это нам важно. Сравнили, и решили, что выбираем Terraform.

  • Во-первых, как я уже и говорил – это Multi cloud support. Terraform, за счет использования различных провайдеров, сиречь плагинов, может работать с любым крупным облачным провайдером. CloudFormation у нас привязан только к Амазону. Не считаю, что это плохо для него, как для инструмента, но для нас это не подходит.
  • State reconciliation. Это то, про что я сейчас рассказывал – если у вас есть какое-то изменение, сделанное не Terraform, на ресурсе, который он создал, и он отслеживает – вы об этом узнаете. У CloudFormation это появилось недавно. Исходя из их блога, который я просматривал при подготовке презентации, они анонсировали эту фичу только в ноябре 2018 года (https://aws.amazon.com/ru/blogs/aws/new-cloudformation-drift-detection/). То есть, к тому моменту мы уже очень активно пользовались Terraform, соответственно, в момент выбора этой фичи не было – тоже минус в сторону CloudFormation.
  • Поддержка условий. Здесь Terraform пальму первенства отдает. В CloudFormation это реализовано, у Terraformа есть только в виде тернарных операторов. Нам этого, к сожалению, очень сильно не хватает. В конце, где я буду перечислять некоторые минусы, я об этом чуть подробнее расскажу.
  • Поддержка шаблонов. У Terraformа есть, это называется модули, по сути это и есть шаблоны. И это очень здорово помогает в работе. У CloudFormation такого нет.
  • Место, где размещаются ваши файлы, хранящие состояния. Terraform позволяет здесь выбрать несколько видов backend, например локально на вашей машине (это поведение по умолчанию); на файловой шаре; в S3 и где-нибудь еще. Это порой бывает полезно, потому что tfstate Terraform выглядит в виде большого текстового файла json  подобной структурой, и бывает порой полезно в него залезть, почитать, и как минимум иметь возможность сделать его backup, потому что мало ли что. Лично мне, например, спокойнее от того, что это находится в каком-то контролируемом мной месте. Может быть, немножко паранойя. У CloudFormation стейт вашего окружения хранится «где-то в Amazone». Не могу назвать это минусом, но мне лично это не нравится.
  • По поводу информации плана изменений. Когда вы собираетесь Terraformу сделать какое-то изменение (вы написали новый код или поменяли свой код), и запускаете команду «Terraform план», что происходит: он выводит вам, во-первых, список ресурсов, которые будут изменены – созданы, удалены, перенастроены, пересозданы. А также для каждого ресурса он выводит подробный список тех его свойств, которые он изменит, и он старается максимально подробно показать, что будет сделано. Скажем – у секьюрити группы какие правила будут добавлены, убраны – с портами, айпишниками, с чем-то. Там, где он не может этого сделать, бывают такие ситуации – скажем, потому что это зависит от IP-провайдера, он не знает, какой ему будет выдан ID ресурса – он честно напишет, что свойства этого объекта будут calculated. Но, тем не менее, это очень подробная структура. И когда мы тестируем то, что собираемся выкатить, это очень сильно помогает. У CloudFormation я, честно, не нашел. Я увидел из всех сравнений, из всех демонстраций, что всё, что он может – это только показать, что он будет эти ресурсы трогать. Он создаст, он удалит, он изменит. Что он с ними сделает – не очевидно.
  • И последняя, наверное, одна из самых важных фич – это возможность импортировать ресурсы. Предположим. У вас уже уже некоторое настроенное состояние инфраструктуры в AWS. Она управлялся руками или чем-то еще – неважно. Вы решили перейти на Terraform, и хотите теперь всей инфраструктурой управлять при помощи него. Так вот, Terraform позволяет вам то, что у вас уже было, взять под свой контроль. Вам достаточно написать код, который будет характеризовать этот объект, либо, если его достаточно много, есть такая утилита, которая называется  Terraforming (https://github.com/dtan4/terraforming). Она ходит в тот же Amazone, забирает оттуда информацию о стейте окружения и потом вываливает в виде кода. Он машинно-сгенерированный, не оптимизированный, но это хороший первый шаг чтобы начать миграцию. А потом вы просто даете команду: Terraform import, кусок кода, который описывает ресурс и айдишник ресурса. Terraform сравнит, заведет это в свое состояние окружение, и теперь он им управляет. CloudFormation такого не умеет. Если у вас что-то было сделано до этого руками, вы либо это грохнете и пересоздайте с помощью CloudFormation, либо живите так дальше. К сожалению, без вариантов.

Теперь – как начать работать с Terraform? Это крайне просто. 

  1. Нулевой шаг опционален, но я настоятельно рекомендую его сделать. Это – создать Git-репозиторий и сразу же начать хранить там все ваши изменения, эксперименты, вообще – всё.
  2. Первый шаг: прочитайте Getting-started guide (https://learn.hashicorp.com/terraform/getting-started/build). Он маленький, простенький, довольно подробный, хорошо описывает то, как вам начать работать с этой утилитой. 
  3. Напишите немного демонстрационного, рабочего кода. Можно даже скопировать какой то пример чтобы потом с ним “поиграться”. Для эксперимента достаточно сделать буквально вот такой кусок кода в виде одного файлика. Однако учтите, что делать вот такую настройку провайдера как на слайде не стоит нигде кроме как при первом эксперименте.

Мы, например, пришли к тому, что передаем эти параметры Terraformу, как переменные окружения. Когда мы готовимся что-то выполнить, мы запускаем скрипт, который генерирует для нас временный ключ, secret и id, заносит их нам в переменные окружения сессии, и дальше погнали. Ну и описание какого-то простого ресурса, который будет создан. Terraform plan посмотрели, что он хочет сделать, Terraform apply, нажали Y – ресурс создан. На мой взгляд – крайне просто.

Отлично! Разобрались с тем, что это такое и зачем. Теперь расскажу о масштабах применения этого инструмента у нас. Наш проект – это 4 Amazone-аккаунта, 9 VPC и больше 30 окружений (Devlopment окружения, stages, регрессии, user acceptance testing и production).

Более конкретные цифры такие:

  • более 1000 EC2 instance суммарно,
  • 2000 DNS записей,
  • Volumes,
  • Networks,
  • Security Groups,
  • load balancers,
  • ElasticIP адреса,
  • RT53 зоны.

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

Помимо этого мы управляем:

  • настройками VPC;
  • мы управляем IAM-политиками и ролями;
  • мы управляем таблицами маршрутизации,
  • сертификатами,
  • сетевыми ACL;
  • мы управляем настройками нашего web application firewall,
  • S3 бакетами,
  • SQS-очередями
  • и всем, что наш сервис в Amazone может использовать.

Нет такой, как мне кажется, я пока не встречал фичи, чтобы у Amazone это было и это нельзя было Terraformом с точки зрения инфраструктуры описать.

Отлично. Теперь – самая интересная часть доклада, это то, к чему мы пришли, столь долгое время работая с этим инструментом. Некоторые выработанные нами практики.

Я разбил их на три большие группы:

  • практики, которые связаны с работой с исходниками;
  • интеграция Terraform с Git;
  • некоторые вещи, которые относятся к более организационным процессам.

Начнем с исходников.  Вы начали Ваш первый проект и храните все в одном большом main.tf файле. Вот типовой пример (честно взял первое попавшееся с github): https://github.com/mdb/terraform-example/tree/master/terraform

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

Core репозиторий

Первое, что я крайне рекомендую, и наша команда крайне рекомендует делать: выделить так называемый core-репозиторий, или core-стейт вашего проекта, вашего окружения. Как только вы начнете создавать инфраструктуру при помощи Terraformа, или ее импортировать – вы сразу столкнетесь с тем, что у вас есть некоторые сущности, которые будучи один раз развернутыми, настроенными, крайне редко меняются. Например, это настройки VPC, или сам VPC. Например, это сети, некоторые базовые, общие Security-groups типа ssh-access – можно собрать довольно большой список.

Нет смысла держать это в том же репозитории, что и сервисы, которые вы меняете часто. Выделите их в отдельный репозиторий и состыкуйте через такую фичу Terraformа, как remote state. Чем это хорошо? Во-первых, вы уменьшаете кодовую базу того участка проекта, с которым вы часто работаете непосредственно. Во-вторых, таким образом вы получаете вместо одного большого tfstate файла, который хранит в себе описание состояния вашей инфраструктуры два файла меньшего размера, и в конкретный момент времени вы работаете с одним из них.

В чем тут хитрость? Когда Terraform строит план, то есть обсчитывает, калькулирует то, что он должен изменить, применить – он пересчитывает полностью этот стейт, сверяется с кодом, сверяется с состоянием в AWS. Чем ваш стейт больше – тем план будет дольше строиться. Мы пришли к этой практике тогда, когда у нас построение плана на весь environment в продакшене стал занимать 20 минут. За счет того, что мы вытащили в  отдельную кору всё, что мы не меняем часто, мы сократили это вдвое. У нас есть еще идея, как это можно сократить дальше, разбив уже не только на core и non-core, но еще и по подсистемам, потому что они у нас связаны и обычно меняются вместе. Тем самым мы, скажем, 10 минут превратим в 3. Но мы пока в процессе этого.

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

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

Выделение кор-репозитория происходит буквально в три шага: сделали отдельную репу, в ней весь код, отдельно — и описываете те сущности, которые должны быть пере-использованы в стороннем репозитории при помощи такого вот output. Скажем, мы создаем ресурс aws subnet, в котором описываем, где он располагается, какая зона доступности, адресное пространство. А потом говорим, что мы в аутпут отправляем id этого объекта. Можно сделать по output на каждый параметр, который вам необходим.

В чем здесь хитрость? Когда вы описываете таким образом это значение, Terraform отдельно сохраняет его в tfstate коры, и когда вы будете к нему обращаться, ему не нужно будет синхронизировать, пересчитать, он сможет сразу из этого стейта вам это дело отдать. Дальше, в репозитории, который не-кор, вы описываете такую связь с удаленным state: у вас есть remote state такой-то, он лежит – (мы используем s3 для хранения), то есть он у нас лежит в S3-bucket таком-то, такой-то ключ и регион.

А дальше, когда мне нужно развернуть новый сетевой интерфейс для инстанса в какой-то конкретной подсетке, что я указываю? Я говорю, что data remote state, в нем найди имя этого стейта, в нем найди вот этот вот параметр, который, собственно, совпадает, вот с  этим именем. И когда я буду строить план изменений в моем не кор-репозитории, вот это значение он просто возьмет, сразу вычитает из стейта, положит себе в память, и не будет пытаться как-то синхронизировать и проверять. Оно для него будет являться для него некоторой константой. Если вы захотите его изменить – придется делать это в репозитории вот этого конечно, core. Но так как это меняется редко, то это особо вас не тревожит.

Модули

Следующее: использование шаблонов или как их называют в терминологии Terraform — модули.

Модуль — самодостаточная конфигурация состоящая из одного или более связанных ресурсов, управляемая как группа:

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

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

Например: когда мы разворачиваем новый EC2 инстанс, мы делаем для него сетевой интерфейс и attachment(присоединение сетевого интерфейса), мы часто делаем для него Elastic IP-адрес, мы делаем route-53 запись, и что-то еще. То есть, как минимум уже 4 сущности. Каждый раз описывать их четырьмя разрозненными кусками кода неудобно. При этом они довольно типовые. Напрашивается действие – сделай шаблон, и потом просто обращайся к этому шаблону, передавая в него параметры: какое-нибудь имя, в какую сетку запихнуть, какую на него навесить sucurity group. Это очень удобно.

Более того, из-за того, что в Terraform есть фича, называемая Count, это позволяет еще сильнее сократить ваш стейт. Можно одним куском кода описать большую пачку инстансов. Скажем, мне нужно развернуть 20 однотипных машин. Я не буду писать 20 кусков кода даже из шаблона, я напишу 1 кусочек кода, укажу в нем Count и число – сколько мне нужно сделать.

Выглядит это таким вот образом. Здесь у нас есть некоторые модули, которые ссылаются на шаблон (о том, как это происходит – будет в следующем слайде). Я передаю только специфические параметры: ID subnet; AMI, с которой развернуть; тип инстанса; настройки секьюрити-групп; что-нибудь еще, и указываю, сколько мне таких штук сделать. Отлично, взял их, развернул. Завтра ко мне приходят разработчики и говорят: «Слушай, мы хотим поэкспериментировать с нагрузкой, дай нам, пожалуйста, еще два таких». Что мне нужно сделать: я вот здесь цифру меняю на 5, говорю «apply», он входит, смотрит «ага, у тебя три уже есть, а сейчас тебе еще два сделаю». И делает. Такие же. Объем кода остается ровно тот же самый.

Условно можно модули разделить на два типа — ресурсные и инфраструктурные. С точки зрения кода отличия нет, это скорее более высокоуровневые понятия, которые вводит сам оператор. Такие абстракции для облегчения себе задачи управления.

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

  • Указываем путь к модулю — источник его конфигурации, через директиву Source
  • Указываем версию — да да, эксплуатация по принципу “latest and greatest” тут не лучший вариант. Вы же не включаете каждый раз в свой проект последнюю версию библиотеки? Но об этом чуть позже.
  • Передаем в него аргументы

Зачем мы привязываемся к версии модуля берем просто последнюю — инфраструктура должна быть версионная так как ресурсы не могут быть версионными а код может. Ресурс может быть создан удален или пересоздан. Все! Так же мы должны четко знать какой версии у нас создан каждый кусок инфраструктуры. Подробнее о том как это делать и примеры почему отсутствие версионирования могут все сломать — далее по тексту.

Инфраструктурные модули — довольно просты:

  • Состоят из ресурсных
  • Включают некие стандарты компании — например теги, списки стандартных значений, принятые дефолты и тп.

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

Рекомендации касательно модулей:

  1. Если можете не писать а использовать готовые- не пишите. Особенно если в этом вы новичок. Доверьтесь готовым модулям или хотя бы посмотрите как это уже сделали до вас. Однако если у вас все же есть необходимость писать свое — не используйте внутри обращение к провайдерам и будьте аккуратны с провиженерами.
    • Проверьте что Terraform Registry (https://registry.terraform.io/) не содержит уже готовый ресурсный модуль
    • Пишите свой модуль — спрячьте специфику под капот. Конечный пользователь, которым можете выступать и Вы не должен волноваться о том что и как вы реализуете внутри
    • Делайте input параметров и output значений из вашего модуля. И лучше  если это будут отдельные файлы. Так удобней
  2. Если пишите свои модули  — храните их в репозитории и версионируйте. Лучше отдельный репозиторий под модуль (далее будет понятно почему так на примерах)
  3. Не используйте локальные модули — они не версионируемые и не пере используемые.   
  4. Избегайте
    • Использования описания провайдеров в модуле —  потому что подключение credentials может быть настроено и применяться по разному у разных людей. Кто-то использует переменные окружения для этого, а кто то подразумевает хранение своих ключей и секретов в файлах с прописыванием путей для них. Это надо указывать уровнем выше. Может на уровне проекта
    • Осторожно используйте local provisioner. Он исполняется локально, на той машине, на которой запускается terraform, но среда исполнения у разных пользователей может быть разная. До тех пор пока вы не встроите это в CI ( об этом так же дальше в статье), вы можете натыкаться на различные артефакты —  например local exec и запуск ansible — а у кого то другой дистрибутив, другой shell, другая версия ansible, или вообще Windows (тут нужна картинка “вот это поворот!”)

Признаки хорошего модуля (http://bit.ly/common-traits-in-terraform-modules):

  1. Хорошие модули имеют документацию и описание примеров. Если они оформлены каждый в виде отдельного репозитория это легче сделать.
  2. Не имеют жестко  заданных значений параметров (hardcoded) — например регион AWS
  3. Используют разумные значения по умолчанию, оформленные в виде defaults. Например модуль для EC2 инстанса по умолчанию не будет создавать вам виртуальную машину с типом m5d.24xlarge (https://aws.amazon.com/ec2/instance-types/) а использует для этого что то из минимальных t2 или t3 типов.
  4. Код “чист” — структурирован, снабжен комментариями, не запутан излишне, оформлен в едином стиле
  5. Очень желательно чтобы он был снабжен тестами. Хоть это и сложно. К этому мы к сожалению еще сами не пришли.

Тегирование

Следующая маленькая практика – это тегирование. Теги – это важно. Почему? Во-первых, это биллинг. У AWS есть инструменты, которые позволяют вам посмотреть, сколько денег вы тратите на свою инфраструктуру. И нашему менеджменту очень хотелось иметь инструмент, чтобы они могли это посмотреть довольно детерминировано. Например, сколько денег потребляют такие-то компоненты, или такая-то подсистема, такая-то команда, такое-то окружение. Как-нибудь, строя разные отчеты.

Во-вторых, тегирование – это документирование вашей системы. Вы упрощаете себе поиск. Даже просто в AWS-консоли если эти теги аккуратненько вывести себе на экран – вам становится проще понимать, к чему относится тот или иной тип инстанса. Если приходят новые коллеги, вам проще это объяснить, показав: «Смотри – это вот сюда относится».

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

Это:

  1. Team — какая команда использует ресурс
  2. Department — аналогично только какой департамент
  3. Environment — это чисто наша фишка — ресурсы бьются по тн “окружениям” но вы например можете заменить его на проект или что то подобное.

Мы для себя решили, что прибавим к этому еще 4: 

  1. Subsystem — подсистема к которой относится компонент. Зачем? Компоненты могут относиться к одной подсистеме. Например, мы хотим посмотреть, сколько у нас эта подсистема, ее сущности, стали потреблять. Вдруг она, допустим, за предыдущий месяц сильно выросла. Нам нужно прийти к разработчикам и сказать: «Ребята, дорого стоит. Бюджет вот уже впритык, давайте уже как-то оптимизировать или как-то логику».
  2. Type — тип компонента- балансировщик, хранилище, приложение или база данных
  3. Component — собственно сам компонент, его название во внутренней нотации
  4. Termination date — время когда он должен быть удален. В формате даты. Если его удаление не предвидится, ставим “Permanent”. Мы ввели его, потому что в Dev окружениях, и даже в некоторых Stages – у нас есть Stage для стресс-тестирования, который поднимается на стресс-сессии, то есть мы не держим эти машинки регулярно – мы указываем дату, когда ресурс должен быть уничтожен. Дальше к этому можно прикрутить автоматизацию на базе AWS Lambda, каких-то внешних скриптов, которые работают через AWS Command Line Interface, которые будут по крону уничтожать эти ресурсы автоматически.

Например нам надо посмотреть сколько у нас обходится в месяц вся наша инфраструктура  сбора логов. А это logstash-и, elasticksearch masters, elasticsearch slaves, kibana — уже 4 типа компонент но одна подсистема. Вот мы и помечаем их разными тегами Component и одинаковым Subsystem.

Теперь – как тегировать. Начинали мы с варианта вот с такого варианта.

Мы решили, что будем делать для каждого компонента свою tag-map, в которой будем перечислять все указанные теги — когда его терминировать, к чему относится. Очень быстро поняли, что это неудобно. Потому что кодовая база у нас растет из-за этого очень сильно, поскольку у нас больше 30 компонент, и 30 таких кусков кода – неудобно, нужно что-то поменять, бегаешь меняешь.

Поэтому мы пришли к более централизованному варианту. 

В Terraform есть такая сущность locals (https://www.terraform.io/docs/configuration/locals.html), в которой вы можете перечислить подмножество, и потом их друг с другом использовать. Например, мы вынесли некоторые common-теги вот в такую структуру, и дальше – специфичные, по подсистемам. Мы говорим: «Возьми вот этот блок и в него добавь, например, subsystem 1. А для подсистемы 2 добавь subsystem 2». Как это получается в виде уже непосредственно кода, используемого в модуле?

А вот так. Мы говорим: теги, возьми, пожалуйста, общие и к ним добавь Type, application,  имя, Component и кто такой». Получается очень кратко, наглядно и централизованное изменение, если вдруг это потребуется.

Контроль Версий

Ваши модули-шаблоны, если будете их использовать, должны где-то храниться. Самый простой путь, с которого, скорее всего, все начинают – это хранение их локально, вот таким вот образом. То есть – в  том же каталоге, просто некоторый подкаталог, в котором вы описываете, например, шаблон для какого-то вида сервиса. Это не очень хороший путь. Да. Это удобно, это можно быстро поправить и быстро протестировать, но это сложно потом пере-использовать и сложно контролировать.

Предположим, к вам пришли разработчики и сказали: «Так, нам нужна такая-то сущность в такой-то конфигурации, в нашей инфраструктуре». Вы это написали, сделали в виде локального модуля в репозитории их проекта. Развернули – отлично. Они потестили, сказали: «Пойдет! Пошли в Production». Приходим в Stage, стресс-тестирование, Production. Каждый раз это что? Ctrl-C, Ctrl-V; Ctrl-C, Ctrl-V. Пока мы добрались до Prod, наш коллега – потому что его другая команда попросила – взял, скопировал из того лабораторного окружения, перенес в другое и там поменял, или поменял в том же. И у нас получается уже несогласованное состояние. При горизонтальном масштабировании, когда у вас столько лабораторных окружений, сколько у нас, это просто адище.

Поэтому хороший путь – заводить под каждый ваш модуль отдельный Git-репозиторий, и потом просто на него ссылаться. Опять же – это один источник правды. Меняем всё в одном месте — хорошо, удобно, контролируемо.

Упреждая вопрос — “Как же ваш код доезжает до production” — создается отдельный проект который пере использует подготовленные и проверенные модули. Нет мы НЕ используем Terraform Workspaces и чуть ниже я опишу почему.

Но тут нас поджидает маленький подводный камень. Положим, мы сделали так. Отлично, у нас один источник истины, централизованно меняется. Я взял, написал, подготовил и поставил себе, что завтра с утра иду разворачивать в production. Построил план, протестировал – отлично, идем. Или даже уже выкатил это в production, но мне нужно потом для этой сущности внести какие-то изменения. В этот момент мой коллега, руководствуясь исключительно благими побуждениями, пошел и что-то оптимизировал, добавил в этот модуль. И так получилось, что эти изменения ломают обратную совместимость.

Например, он добавил необходимых параметров, которые обязан передать, иначе модуль не соберется. Или он поменял названия этих параметров. Я прихожу с утра, у меня время для изменений строго ограничено, начинаю строить план, и Terraform подтягивает state-модули с Гита, начинает строить план и говорит: «Хоп! Не могу. Не хватает у тебя, ты переименовал». Я такой: «Да я же этого не делал, как с этим быть?» А если это ресурс, который был создан давно, то после подобных изменений придется пробегать по всем окружениям, как-то менять и приводить к одному виду. Это неудобно.

Как можно это поправить? Это можно поправить, используя Git tags. Мы для себя решили, что будем использовать SemVer (https://semver.org/) нотацию и выработали простое правило: как только конфигурация нашего модуля достигает некоего стабильного состояния, то есть мы можем это использовать, мы на этот commit вешаем тег. Если мы вносим изменения и они не ломают обратную совместимость, мы у тега меняем минорный номер; если ломают – меняем мажорный номер.

Таким образом можно вот так в адресе source привязаться к конкретному тегу, и хотя бы обеспечить, что то, что у вас собиралось раньше – будет собираться всегда. Пусть версия модуля уехала вперед, но в нужный момент мы придем, и когда нам это действительно нужно – применяем. А то, что и до этого было работающим, хотя бы не сломается. Это удобно. Вот так примерно это выглядит у нас в Gitlab.

А вот так это выглядит в плане истории изменений. Dvelopment ветка, мастер-ветка, вносим,  merge, вешаем теги.

Использование веток, еще одна тоже очень важная практика. Мы для себя выработали правило, что изменения на инфраструктуру ты должен применять только из мастера, но любое изменение, которое ты хочешь сделать и протестировать – сделай, пожалуйста, отдельную ветку, поиграйся с ней, поэскпериментируйпоэкспериментируй, построй планы, посмотри, как собирается. А потом сделай merge-request, передай на твоего коллегу, и пусть он поможет тебе посмотреть на твой код.

Зачем это нужно? Мы сталкивались с тем, что, например, что человек торопится или он только пришел к нам в команду, разобрался в том, как работает Terraform, но код он пишет, скажем, не очень удобно, можно было бы написать это лучше. И пусть это кто-то посмотрит. Почему бы нет? Code review, по-моему, великолепная практика. Поэтому, что мы делаем? Ветка под фичу, merge-request, анализ коллеги, возможно, более опытного, разделить с ним ответственность и применить и применить из master.

Хранение tfstate

Теперь что касается не технических, а больше, наверное, организационных вещей. Где хранить ваш tfstate. Так как я рассказываю это в применении к AWS, здесь будет AWS-специфика, но, вероятно, это работает и для того же Google, у которого тоже есть объектное хранилище типа S3.

Не стоит хранить ваш state локально. Не стоит хранить ваш state в Git. Мы на этом обожглись, когда у кого-то, при раскатывании веток не-мастера, у него получается свой tfstate, в котором сохранено состояние, потом он это вмерживает включает через merge, кто-то вмерживает свой, получаются merge-конфликты. Или получается без них, но несогласованное состояние, потому что «у него уже есть, у меня еще нет», и потом все это сидеть исправлять – это неприятная практика.

Поэтому мы решили, что будем хранить это в надежном месте, версионируемом, но это будет вне Git-а. Под это отлично подходит S3 (https://www.terraform.io/docs/backends/types/s3.html): он доступен, у него HA, насколько я помню четыре девятки точно, может быть, пять. Из коробки он дает версионированость, если даже вы свой tfstate сломаете — всегда можно откатиться. И еще он дает очень важную вещь в сочетании с DynamoDB, этому Terraform научился, по-моему, с версии 0.8. В DynamoDB вы заводите табличку, в которой Terraform записывает информацию о том, что он делает lock state.

То есть, предположим, я хочу внести какие-то изменения. Начинаю строить план или начинаю его применять, Terraform идет в DynamoDB и говорит, что он в этой табличке вносит информацию о том, что этот state заблокирован; пользователь такой-то, компьютер такой-то, в такой-то момент. В этот момент мой коллега, который работает удаленно (ну, из дома человек захотел поработать) или, может быть, за 2 места от меня, но сосредоточен на работе и не видит, что я делаю, тоже решил, что нужно что-то изменить. Он строит план, но запускает его чуть позже.

Terraform идет в DynamoDB, видит – Lock, обламывается, сообщает пользователю: «Извини, tfstate заблокирован тем-то», Коллега видит, что я сейчас работаю, может ко мне подойти и сказать: «Слушай, у меня чендж важнее, уступи мне, пожалуйста». Я говорю: «Хорошо», отменяю построение плана, снимаю блок, скорее даже, он автоматически снимается, если вы это делаете корректно, не прерывая по Ctrl-C. Коллега идет и делает. Тем самым мы страхуем себя от ситуации, когда вы вдвоем что-то меняете.

Про merge-request я вкратце уже сказал, повторюсь, что обязательно мы используем ветвление в Git — это уже с точки зрения процесса, а не технической части. Обязательно мы назначаем наши merge-request-ы на коллег. Более того, в Gitlab мы используем практически все доступные нам инструменты для совместной работы, для merge-request-ов или даже просто каких-то пулов – это обсуждение вашего кода, его ревью, выставление in-progress или issue, еще чего-то подобного. Это очень полезно, это помогает в работе, делайте это.

Плюс, в этом случае rollback тоже получается легче, можно от вернуться на предыдущий commit или, если вы, скажем, решили, что будете не только из мастера применять изменения (ай нехорошие), можно просто переключиться на стабильную ветку. Например, вы сделали ветку с фичей и решили, что будете вносить изменения сначала из фичей ветки, а потом уже изменения, после того, как все хорошо сработало, вносить в мастер. Скажем, стабилизация после. Получается, для вас rollback будет – переключиться…Вот вы в своей ветке применил изменения, поняли, что что-то не то; переключились на мастер — никаких изменений нет, сказали apply — он вернулся. Тоже вполне себе практика, почему бы нет.

Внедрите CI

Еще одна практика, от нас рекомендуемая – использовать Terraform в CI режиме, использование его в pipeline. Чуть позже поясню, почему это очень полезно. Когда будем говорить о минусах системы, это как бы – решение, забегая вперед.

Мы для себя решили, что будет два вида pipeline: pipeline для branch по фичам (не мастер) и pipeline для мастера. Что делает branch pipeline? Как только мой comit прилетает в ветку, у нас Gitlab используется, у вас может быть другое решение; запускает, во-первых, автоматическую верификацию кода – тупо проверка на опечатки, на то, что скобочку не закрыл или что-то еще. А потом запускает построение плана. И коллега, который будет смотреть ваш merge-request, сразу может открыть построившийся план и увидеть не только код – что вы добавляете, но еще и как это ляжет на вашу инфраструктуру. Наглядно и полезно.

В мастере сюда добавляется еще один шаг. Отличие в том, что у вас план не просто генерируется, он еще и сохраняется в виде артефакта. Это еще одна очень полезная фича Terraformа, в том, что план можно сохранить в виде файла, и потом применить именно его. Скажем, вы сделали merge-request и его отложили. Через месяц про него вспомнили и решили вернуться. У вас код уже далеко уехал вперед. За счет того, что вы храните у себя артефакт плана, вы можете применить именно его и на то, что вы хотели в тот момент.

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

Вот как это выглядит в GitLab. Здесь уже всё применено. Вот это автоматически, а вот этот шаг применяется уже вручную по нажатию кнопки оператором.

Недостатки Terraform

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

Функции

Во-первых, несмотря на то, что у Terraformа довольно большое число встроенных функций, они удобные и полезные, не все из них так хороши, как нам хотелось бы думать. Есть некоторые функции, которые не совсем удобны. Например, есть функция «element», у которой в некоторых ситуациях, при нехватке опыта, поведение может быть не совсем тем, которое вы ожидали.

Например, вы используете модуль, в модуль передается count – сколько развернуть инстансов, и передается, скажем, список подсетей, разбитых по availability-зонам. Передали, применили, увеличили каунт, еще применили. А теперь вы решили передать в него увеличенный список подсетей. У вас появилась сетка, вы еще одну AZ решили задействовать. У вас меняется вторая часть списка, а count с этим списком сопоставляется через элемент.

И так получится, что Terraform первые – скажем, у вас было 4 AZ до этого и 5 инстансов, а потом вы добавили еще одну AZ – он первые 4, которые были в тех, которые оставались по порядку, оставит. А пятую, которую он до этого вносил в первую, потому что список заканчивался и он шел с начала, а теперь у вас появилась дополнительная, он скажет: «А сейчас я ее пересоздам». А вы не хотели. Вы хотели, чтобы у вас только новые приезжали. Это нужно немножко держать в голове. Это все происходит из за особенностей работы Terraform со списками — из за структуры списка (индексов) если мы добавляем или убираем элемент где то в середине списка то все ресурсы которые создавались после него ( использовавшие этот список) будут пере создаваться тк индекс изменился.

Тернарный оператор

Следующее, как раз то, про что я говорил: условие – только тернарный оператор (https://www.terraform.io/docs/configuration/expressions.html#conditional-expressions). Получается такое неприятное ограничение. Нам действительно не хватает условий. Всё, что мы можем сделать – использовать подобную конструкцию. Хотелось бы все-таки какие-то более привычные if, else Жаль, что их нет. Возможно, подвезут.

Необходимость в автоматизации внесения изменений

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

Например, Terraform имеет такое свойство: скажем, у вас с коллегой были одинаковые версии на машинах, потом коллега у себя обновил на единичку версию, сходил – его Terraform…достаточно даже просто ему потрогать Tfstate, чтобы у него обновилась информация, что он был сгенерирован версией такой-то. Вы на следующий день приходите, начинаете вносить изменения, Terraform идет свериться, видит, что в Tfstate требуемая версия Terraformа выше и говорит: «Нет, не могу, обнови меня». Когда у тебя маленькое окно для внесения изменений с утра, допустим, полчаса – увидеть, что тебе сначала нужно обновить утилиту – неприятно.

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

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

Большое окружение, вам будет каждый раз со своего места лень ждать, пока построится план на все окружение. Вы придете к тому, что будете стараться строить через опцию target, применение только на то, что вы изменили. Например, вы добавили некоторый инстанс и говорите: «Terraform apply target instance», или security group или что-то такое. Позволяет вам сделать быстрее. Все мы люди, ленимся.

Но! В таком случае, если, например, у вас что-то сломалось – скажем, устарела какая-то конфигурация, и в случае построения полного плана вы бы это увидели, что она сломалась, и план у вас не строиться. И вам придется тратить довольно много сил и времени на то, чтобы привести это в актуальное состояние. Не нужно до такого доводить. Если есть CI – в нем мы просто принудительно говорим, что Terraform будет строить план полностью, вы запушили изменения, и пусть он свой план строит, вы пошли и занялись чем-то еще. Он построил, вы увидели, он у вас есть в виде артефакта, и вы пошли его применять. Но это дисциплинирует.

Он не серебряная пуля

И напоследок еще: Terraform, несмотря на все его плюсы, это не серебряная пуля. Что он не позволит вам сделать: во-первых, Terraform не позволяет вам описать зависимость между модулями, если они логически на одном уровне. С чем, например, столкнулись мы: когда у нас есть модуль, описывающий набор некоторых инстансов со всеми сопутствующими параметрами, и есть модуль, который описывает балансировщик – когда мы хотим одно с другим состыковать, то на вход модуля, описывающего балансировщик, подаем из первого модуля с инстансами генерируемый список id-ов.

Так вот, пока вы не создали инстанс – этот список еще не появился в Tfstate, и модуль с балансировщиками не сможет собраться, потому что ему нечего будет к себе стыковать. То есть вам придется разворачивать это в два прохода. Сделать зависимость «сначала разверни этот модуль, а потом этот модуль» – не получается. Мы сейчас пытаемся это реализовать за счет того, что разбиваем нашу инфраструктуру по подсистемам и пишем модуль, во-первых, для ресурса, а во-вторых, модуль для подсистемы — как раз те самые инфраструктурные модули, в которых уже модули как бы стыкуем на одном уровне. Это получается обойти, но пока рано чем-то таким хвастаться, мы буквально на днях начали пытаться решить это таким work-around-ом. Пока не могу ничем похвастаться, мы лишь обкатываем это решение в Lab и первое с чем столкнулись из неприятного — сложно разрешаемые зависимости версий.

Следующий момент — Terraform может успешно построить план, но этот план успешно не применяется. И Terraform в этом не виноват. Почему? Потому что, например, он не отслеживает число свободных IP в ваших подсетях, которые у вас остались. Он не может его вынуть. Например, он не отслеживает, что в некоторых AZ у вас нет каких-то инстанс-типов. Скажем, мы используем North Virginia, и там сейчас есть 6 Availability-zones. В одной из них точно доступны не все типы инстансов. Мы с этим столкнулись, выясняли с техподдержкой, они сказали: «Да, это временное явление». Но до какого момента это будет – непонятно. Опять же – план у нас при этом строится, всё хорошо, Terraform ничего об этом не знает.

Terraform ничего не знает про ваши лимиты в AWS. Скажем, у вас лимит – 200 машин, из них уже 198 развернули, хотите развернуть еще 5. План он вам построит. Но при выполнении плана он сделает две, а еще на три вернет вам ошибку от API AWS. Увы.

И последний пункт, наконец. Что он тоже не может сделать – он не может учесть, что некоторые имена должны быть уникальными. Например, вы хотите сделать S3 bucket. Это глобальный сервис на регион, и даже если в вашем аккаунте вы не создавали сервис с таким именем – не факт, что его не создал кто-то другой. И когда вы будете его создавать с помощью Terraform – он прекрасно построит план, начнет его создавать, а AWS скажет: «Извини, у кого-то это уже есть». Заранее этого не предусмотреть никак. Только если руками пытаться заранее как-то это сделать, хотя это идет вразрез с практикой.

Тем не менее, это – лучшее, что есть сейчас на…не сказать – рынке, но  — на горизонте, и мы продолжаем это использовать, он очень сильно нам помогает. Собственно, на этом всё.

Дополнение

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

Интеграции Terraform

Интеграция TF с чем то еще это не  задача TF. Его задача — создать инфраструктуру. А то что будет потом запущено на этой инфраструктуре — не его головная боль. То, как будет подготовленная почта для инфраструктуры ( например заготовлены AMI) — вопрос вне рамок. Например что сейчас используем мы:

  • Подготовка AMI образом для дальнейшего использования — Packer + Ansible
  • Использование AMI при разворачивании инфраструктуры — Terraform
  • Последующая настройка гостевых ОС, доставка кода и конфигурации — ansible

 

Чего следует избегать при эксплуатации Terraform

  1. Не передавайте переменные как аргументы в командной строке. Выносим их в tfvars либо в переменные окружения, либо используйте defaults. Лучше всего конечно tfvars (https://learn.hashicorp.com/terraform/getting-started/variables.html#from-a-file). То есть никаких “terraform plan -var 1… -var 2… — var 3…” — пусть var1-3 будут в tfvars файле — это версионируемо, эти параметры можно также включить в git и все будет прозрачно.
  2. Используйте опцию «-target»  только если действительно надо. Или вLAB для эксперимента. На постоянной основе для работы с production — нет. Почему? Оно маскирует и проблемы зачастую. Если вы хотите уменьшить область изменений и уменьшить время на построение плана, тогда вместо этого лучше разбейте ваше окружение на меньшие части как например с core.
  3.  Использование “-parallelism” — не играйтесь с изменением параллелизма. По умолчанию 10 но увеличение его до 100, для ускорения применения изменений —  это плохая идея потому что что-то может пойти не так. Например вы запинаете api Amazon, и получите отлуп.

Почему мы не используем Terraform Worskaces

Потому что  это решение пришло не из пользовательского опыта оно пришло из terraform enterprise. И плохо ложится в саму парадигму IaC ( на наш взгляд). Лучше лишний раз сложить в отдельную папку / репозиторий ваш проект,  зато вы всегда будете уверены что текущий код описывает только инфраструктуру этого проекта, этого ресурса и ничего более. Даже в документации TF-а это указано (https://www.terraform.io/docs/state/workspaces.html):

 «When Terraform is used to manage larger systems, teams should use multiple separate Terraform configurations that correspond with suitable architectural boundaries within the system so that different components can be managed separately and, if appropriate, by distinct teams. Workspaces alone are not a suitable tool for system decomposition, because each subsystem should have its own separate configuration and backend, and will thus have its own distinct set of workspaces.

 ….

 Instead, use one or more re-usable modules to represent the common elements, and then represent each instance as a separate configuration that instantiates those common elements in the context of a different backend.»

Тестирование

Тестирование нашего кода и наших модулей в TF. Нет мы еще не внедрили у себя это и не совсем понятно как это хорошо сделать. Чтобы внедрить тестирование, надо ответить на ряд вопросов.

Что тестировать? — есть куча ограничений и зависимостей у разных провайдеров. Поэтому все очень относительно для разных провайдеров. Да, мы используем только AWS и нам проще, этот вопрос отпадает для нас но актуален для других.

Если мы говорим про aws — там есть разные регионы с разным набором фич. Есть разные аккаунты с разными лимитами. Есть даже в одном регионе разные az с разной поддержкой фич (был открыт раньше) — мы например столкнулись с тем что в us-east-1 в одной AZ нам доступны не все типы инстансов

Есть библиотека Terratest (https://github.com/gruntwork-io/terratest), но нам пока не довелось обратить на нее пристальное внимание.

Инструменты

И напоследок хочу порекомендовать несколько инструментов для работы с Terraform:

Terraforming https://github.com/dtan4/terraforming

Terraform landscape https://github.com/coinbase/terraform-landscape

Tfswitch https://github.com/warrensbox/terraform-switcher