О Terraform

Thank you for reading this post, don't forget to subscribe!

Terraform дела­ет толь­ко одну зада­чу. И дела­ет её хоро­шо. Зада­ча эта: создать, собрать и настро­ить и вверг­нуть во тьму ресур­сы. Любые ресур­сы, кото­рые мож­но опи­сать в виде набо­ра свойств, понят­ных про­вай­де­ру этих самых ресур­сов. В первую оче­редь речь идёт о ресур­сах наших люби­мых вычис­ли­тель­ных обла­ков. AWS и Azure. И мно­же­ства других.

Terraform — это не есть еди­ное API для всех облаков.

Зато опи­са­ния ресур­сов будут в тек­сто­вом виде. В фай­лах фор­ма­та HCL (это нечто сред­нее меж­ду JSON и YAML). В вашей систе­ме кон­тро­ля вер­сий. Это и назы­ва­ет­ся Infrastructure as Code.

Terraform напи­сан на Go. Так что вся уста­нов­ка сво­дит­ся к рас­па­ков­ке един­ствен­но­го бинар­но­го фай­ла из архи­ва, про­пи­сы­ва­нию его в PATH и настрой­ке авто­до­пол­не­ния в вашей shell.

Поеха­ли по терминологии.

Кон­фи­гу­ра­ция. Configuration. Это набор *.tf фай­лов в теку­щем ката­ло­ге, то есть где вы запу­сти­ли terraform. В этих фай­лах вы опи­сы­ва­е­те то, что вам нуж­но создать и под­дер­жи­вать. Не важ­но, как вы эти фай­лы назо­вё­те. Не важ­но, в каком поряд­ке в них всё опи­ше­те. Есть толь­ко неболь­шое согла­ше­ние, что долж­ны быть фай­лы: main.tf — типа заглав­ные опре­де­ле­ния, variables.tf — все вход­ные пере­мен­ные, outputs.tf — все выход­ные пере­мен­ные дан­ной конфигурации.

Про­вай­де­ры. Providers. Пла­ги­ны, отдель­ные бинар­ни­ки, кото­рые опре­де­ля­ют весь осталь­ной набор того, что мож­но задать в кон­фи­гу­ра­ции. Они ска­чи­ва­ют­ся при ини­ци­а­ли­за­ции кон­фи­гу­ра­ции. Ини­ци­а­ли­за­ция дела­ет­ся коман­дой terraform init.

Вот как может выгля­деть опи­са­ние про­вай­де­ра для ама­зо­но­во­го обла­ка (AWS):

Кон­струк­ции с фигур­ны­ми скоб­ка­ми — это interpolations. В дан­ном слу­чае берут­ся зна­че­ния двух вход­ных переменных.

Пере­мен­ные. Variables. Вход­ные зна­че­ния для вашей кон­фи­гу­ра­ции. Их надо явно описать:

Пере­мен­ные быва­ют трёх типов: стро­ка, спи­сок, map. В дан­ном слу­чае тип не ука­зан, и под­ра­зу­ме­ва­ет­ся стро­ка. С под­держ­кой вло­жен­ных типов, типа спис­ка mapов, всё настоль­ко неопре­де­лён­но, что луч­ше счи­тать, что вло­жен­ных типов нет.

Зна­че­ния для пере­мен­ных, как и поло­же­но, мож­но пере­да­вать кучей раз­ных спо­со­бов. Через пере­мен­ные окру­же­ния типа TF_VAR_aws_profile. Через пара­мет­ры команд­ной стро­ки типа -var 'aws_profile="terraform"'. Через фай­лы *.tfvars, кото­рые нуж­но явно пере­да­вать в команд­ной стро­ке: -var-file ../test.tfvars. Через фай­лы *.auto.tfvars в теку­щем ката­ло­ге, кото­рые под­гру­жа­ют­ся авто­ма­ти­че­ски. Понят­ное дело, есть пра­ви­ла пере­опре­де­ле­ния пере­мен­ных, а mapы даже мержатся.

Есть ещё выход­ные пере­мен­ные. Это неко­то­рые зна­че­ния, кото­рые явля­ют­ся выхо­дом вашей кон­фи­гу­ра­ции. Эти зна­че­ния потом мож­но лег­ко полу­чить коман­дой terraform output.

Ресур­сы. Resources. То, ради чего всё это и зате­ва­ет­ся. Ресур­сы, под­дер­жи­ва­е­мые про­вай­де­ром, кото­рые нуж­но создать, или моди­фи­ци­ро­вать, или удалить.

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

Вот как может выгля­деть, напри­мер, созда­ние S3 бакета:

Атри­бу­ты одних ресур­сов могут быть ука­за­ны как interpolations при созда­нии дру­гих ресур­сов. Тогда Terraform пони­ма­ет, что ресур­сы име­ют зави­си­мость, и созда­ёт их в пра­виль­ном порядке.

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

Напри­мер, Security Groups в AWS. Часто нуж­но, что­бы одна Security Group ссы­ла­лась на дру­гую, а та ссы­ла­лась на эту. В кон­со­ли это про­блем не вызы­ва­ет, созда­ём обе, а потом моди­фи­ци­ру­ем пра­ва досту­па, что­бы они ссы­ла­лись друг на друга.

Terraform так не уме­ет. Он счи­та­ет, что все ресур­сы долж­ны быть созда­ны за один при­сест, без мно­го­ша­го­вых моди­фи­ка­ций, в поряд­ке, соглас­но их зави­си­мо­стям (а если нет зави­си­мо­стей, то даже парал­лель­но). А здесь полу­ча­ет­ся цик­ли­че­ская зави­си­мость, кото­рая Terraform не устраивает.

Для реше­ния про­бле­мы при­ду­ма­ли «псев­до­ре­сурс» aws_security_group_rule, кото­рый фак­ти­че­ски явля­ет­ся шагом моди­фи­ка­ции «насто­я­ще­го» ресур­са aws_security_group. Цик­ли­че­ская зави­си­мость раз­ры­ва­ет­ся. Насто­я­щие ресур­сы созда­ют­ся в несколь­ко шагов.

Обра­ти­те вни­ма­ние на пере­да­чу спис­ков. Поче­му такие воль­но­сти, и поче­му кавыч­ки обя­за­тель­ны, я не знаю.


Источ­ни­ки дан­ных. Data Sources. Про­вай­де­ры уме­ют не толь­ко созда­вать ресур­сы, но и запра­ши­вать атри­бу­ты уже име­ю­щих­ся ресур­сов. Напри­мер, что­бы вста­вить какие-нибудь свой­ства в ваши ресур­сы. Для это­го и нуж­ны источ­ни­ки данных.

Но есть и более инте­рес­ные при­ме­не­ния. Напри­мер, про­вай­дер template поз­во­ля­ет читать локаль­ные фай­лы-шаб­ло­ны, под­став­ляя туда пере­мен­ные. Син­так­сис шаб­ло­нов пол­но­стью повто­ря­ет син­так­сис interpolations. Что доволь­но убо­го. Цик­лов, напри­мер, туда не завезли.

Но есть очень инте­рес­ный спо­соб «доба­вить» цик­лы в шаб­ло­ны. Дело в том, что и у ресур­сов, и у источ­ни­ков дан­ных есть аргу­мент count. По умол­ча­нию он равен одно­му, и созда­ёт­ся ров­но один ресурс или источ­ник дан­ных. (Если поста­вить нуль, то ниче­го созда­но не будет). А если поста­вить боль­ше еди­ни­цы, то будет созда­но несколь­ко ресур­сов или источ­ни­ков дан­ных. И все атри­бу­ты этих ресур­сов или источ­ни­ков дан­ных мож­но полу­чить в виде спис­ка. Это общая прак­ти­ка в Terraform: по спис­кам аргу­мен­тов фор­ми­ро­вать спис­ки ресур­сов. А уж пре­вра­тить спи­сок зна­че­ний в тот же мас­сив JSON — дело техники.

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

  • var.var_name — зна­че­ние переменной
  • resource_type.resource_name.attribute_name — зна­че­ние атри­бу­та ресур­са, опре­де­лён­но­го в дан­ной конфигурации
  • resource_type.resource_name.*.attribute_name — спи­сок зна­че­ний атри­бу­тов ресур­са, если count был боль­ше единицы
  • data.data_source_type.data_source_name.attribute_name — зна­че­ние атри­бу­та источ­ни­ка данных
  • data.data_source_type.data_source_name.*.attribute_name — спи­сок зна­че­ний атри­бу­тов источ­ни­ка дан­ных, если count был боль­ше единицы
  • count.index — индекс теку­щей «ите­ра­ции» «цик­ла», если count боль­ше единицы
  • path.module — путь (в фай­ло­вой систе­ме) теку­ще­го модуля
  • module.module_name.module_output — зна­че­ние выход­ной пере­мен­ной модуля

Почти со все­ми эти­ми выра­же­ни­я­ми мож­но интер­ак­тив­но поиг­рать, если выпол­нить коман­ду terraform console.

Моду­ли. Modules. Любая кон­фи­гу­ра­ция в Terraform может рас­смат­ри­вать­ся как модуль. Теку­щая кон­фи­гу­ра­ция, в теку­щем ката­ло­ге, назы­ва­ет­ся root module. Любые дру­гие кон­фи­гу­ра­ции, в дру­гих ката­ло­гах фай­ло­вой систе­мы, из реест­ра моду­лей от HashiCorp, из репо­зи­то­рия на GitHub или Bitbucket, про­сто доступ­ные по HTTP, мож­но под­клю­чить к теку­щей кон­фи­гу­ра­ции и использовать.

При под­клю­че­нии моду­лю даёт­ся имя. Под этим име­нем он будет здесь досту­пен. Нуж­но ука­зать место­по­ло­же­ние моду­ля. Одно и то же место­по­ло­же­ние моду­ля мож­но под­клю­чать несколь­ко раз под раз­ны­ми име­на­ми. Так что думай­те об опуб­ли­ко­ван­ных кон­фи­гу­ра­ци­ях как о клас­сах, кото­рые мож­но пере­ис­поль­зо­вать. А кон­крет­ное под­клю­че­ние здесь мож­но счи­тать экзем­пля­ром клас­са. Насле­до­ва­ния толь­ко нет 🙂 Зато деле­ги­ро­ва­ние — пожа­луй­ста. Моду­ли могут исполь­зо­вать дру­гие модули.

Кро­ме име­ни, моду­лю при под­клю­че­нии нуж­но задать аргу­мен­ты. Это те самые вход­ные пере­мен­ные. Внут­ри моду­ля они будут исполь­зо­вать­ся как ${var.var_name}. А резуль­тат рабо­ты модуль предо­став­ля­ет в виде выход­ных пере­мен­ных. На них в дан­ной кон­фи­гу­ра­ции мож­но ссы­лать­ся как ${module.module_name.var_name}. В общем-то, почти ана­ло­гич­но исполь­зо­ва­нию ресурсов.

Резю­ми­ру­ем. Име­ем набор *.tf фай­лов в теку­щем ката­ло­ге. Это — кон­фи­гу­ра­ция. Или root module. Кото­рый может под­клю­чать дру­гие моду­ли из раз­ных мест. Дру­гие моду­ли — это такие же *.tf фай­лы. Они при­ни­ма­ют пере­мен­ные и воз­вра­ща­ют пере­мен­ные. По ходу дела опи­сы­ва­ют ресур­сы и исполь­зу­ют источ­ни­ки данных.

Име­ем пол­ное декла­ра­тив­ное опи­са­ние того, что мы хотим полу­чить. Как это работает?

Состо­я­ние. State. Вто­рое, после кон­фи­гу­ра­ции, клю­че­вое поня­тие Terraform. Состо­я­ние хра­нит состо­я­ние ресур­сов. Как оно обсто­ит на самом деле в этом обла­ке. По умол­ча­нию состо­я­ние — это один боль­шой (не силь­но боль­шой) JSON файл terraform.tfstate, кото­рый созда­ёт­ся в теку­щем каталоге.

Этот файл состо­я­ния мож­но хра­нить в систе­ме кон­тро­ля вер­сий. Но, если силь­но мно­го раз­ра­бот­чи­ков будут пра­вить кон­фи­гу­ра­цию Terraform, состо­я­ние тоже будет часто менять­ся. И при­дёт­ся посто­ян­но пра­вить кон­флик­ты, не гово­ря уже о том, что не сто­ит забы­вать делать pull.

Поэто­му луч­ше исполь­зо­вать remote state. Состо­я­ние мож­но хра­нить в Consul. А, в слу­чае AWS, в S3. Тоже будет один фай­лик, но в обла­ке, и с вер­си­о­ни­ро­ва­ни­ем. И будет всем досту­пен, и посто­ян­но будет из обла­ка дёр­гать­ся. Норм.

Поеха­ли.

Сна­ча­ла нашу кон­фи­гу­ра­цию нуж­но про­и­ни­ци­а­ли­зи­ро­вать. Коман­да terraform init выка­чи­ва­ет плу­ги­ны (про­вай­де­ры, бэкен­ды для remote state), моду­ли (с GitHub, напри­мер), и сва­ли­ва­ет это в скры­тый под­ка­та­лог .terraform. Этот под­ка­та­лог не нуж­но хра­нить в систе­ме кон­тро­ля вер­сий. Но он нужен для нор­маль­ной рабо­ты Terraform. Поэто­му terraform init -input=false — обя­за­тель­ный шаг, что­бы запус­кать Terraform из CI.

Вто­рая коман­да для исполь­зо­ва­ния Terraform в обыч­ной жиз­ни: terraform apply. На самом деле она выпол­ня­ет несколь­ко шагов.

Шаг пер­вый. Refresh. Terraform срав­ни­ва­ет теку­щее извест­ное состо­я­ние с реаль­ным состо­я­ни­ем ресур­сов в обла­ке. Про­вай­дер про­из­во­дит кучу запро­сов на чте­ние через облач­ное API. И состо­я­ние обнов­ля­ет­ся. При пер­вом запус­ке состо­я­ние явля­ет­ся пустым. Зна­чит, обнов­лять нече­го, и Terraform счи­та­ет, что ни одно­го ресур­са не существует.

Шаг вто­рой. Plan. Terraform срав­ни­ва­ет теку­щее извест­ное (и толь­ко что обнов­лён­ное) состо­я­ние с кон­фи­гу­ра­ци­ей. Если в состо­я­нии ресур­са нет, а в кон­фи­гу­ра­ции он есть, ресурс будет создан. Если в состо­я­нии ресурс есть, а в кон­фи­гу­ра­ции его нет, ресурс будет уда­лён. Если изме­ни­лись аргу­мен­ты ресур­са, то, если это воз­мож­но, ресурс будет обнов­лён на месте. Или же ресурс будет уда­лён, а на его месте будет создан новый ресурс того же типа, но с новы­ми аргументами.

План будет пред­став­лен поль­зо­ва­те­лю. С пол­ным ука­за­ни­ем того, что будет созда­но, уда­ле­но, или изме­не­но. В той мере, насколь­ко это извест­но до нача­ла насто­я­ще­го выпол­не­ния. (Иден­ти­фи­ка­то­ры ресур­сов, напри­мер, как пра­ви­ло назна­ча­ют­ся слу­чай­но, и ста­но­вят­ся извест­ны лишь после насто­я­ще­го созда­ния ресур­са.) Надо отве­тить «yes», что­бы перей­ти к сле­ду­ю­ще­му шагу.

Шаг тре­тий. Apply. Соб­ствен­но, при­ме­не­ние пла­на, создан­но­го на преды­ду­щем шаге. Соглас­но зави­си­мо­стям. При этом сно­ва изме­ня­ет­ся состо­я­ние, туда запи­сы­ва­ют­ся все насто­я­щие свой­ства создан­ных ресурсов.

Ну вот и всё. Всё про­сто. Све­ря­ем­ся, срав­ни­ва­ем, высчи­ты­ва­ем раз­ни­цу, нака­ты­ва­ем изме­не­ния. В отли­чие от Ansible, свер­ка состо­я­ния дела­ет­ся один раз перед постро­е­ни­ем пла­на и для всей кон­фи­гу­ра­ции сразу.

А теперь — нюансы.

Terraform — прост и упрям. И он не дела­ет rollback.

К сожа­ле­нию, в боль­шин­стве слу­ча­ев созда­ние даже одно­го ресур­са — не ато­мар­но. Даже созда­ние S3 bucket — это с деся­ток раз­ных вызо­вов, в основ­ном, что­бы отдель­но выяс­нить раз­ные свой­ства это­го баке­та (get запро­сы). Если какой-то запрос не выпол­нил­ся (а в моём слу­чае наи­бо­лее частой при­чи­ной оши­бок был недо­ста­ток прав на отдель­ные опе­ра­ции), Terraform счи­та­ет, что созда­ние ресур­са провалилось.

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

Кро­ме того, Terraform совсем не в кур­се ресур­сов, создан­ных авто­ма­ти­че­ски при вызо­ве API обла­ка (при­мер: Elastic Network Interface в AWS созда­ют­ся неяв­но). И не в кур­се ресур­сов, не опи­сан­ных в кон­фи­гу­ра­ции, но от кото­рых могут зави­сеть его ресур­сы (при­мер: Security Group в AWS не полу­чит­ся уда­лить, пока хоть кто-то её исполь­зу­ет, но вот кто этот кто, Terraform знать не все­гда может).

Но Terraform упрям. Так что после прав­ки прав досту­па, прав­ки оши­бок в кон­фи­гу­ра­ции, и несколь­ких запус­ков terraform apply, обла­ко таки неумо­ли­мо перей­дёт в жела­е­мое состо­я­ние. Плюс может остать­ся немно­го мусо­ра — ненуж­ных ресур­сов, кото­рые были созда­ны, но не были уда­ле­ны. Мусор при­дёт­ся под­чи­стить ручками.

Потом, пола­гаю, при­дёт куль­ту­ра всё делать через Terraform, и сра­зу уга­ды­вать нуж­ные пра­ва. И мусо­ра будет меньше.

Мож­но ли пере­не­сти инфра­струк­ту­ру, наби­тую руч­ка­ми в кон­со­ли, в Terraform? Можно.

Пише­те кон­фи­гу­ра­цию. Луч­ше добав­лять один ресурс за одним. Дела­е­те terraform import. По сути, вы сопо­став­ля­е­те име­на ресур­сов в кон­фи­гу­ра­ции (resource_type.resource_name) с реаль­ны­ми иден­ти­фи­ка­то­ра­ми суще­ству­ю­щих ресур­сов (они раз­ные для раз­ных типов ресур­сов). Terraform пыта­ет­ся про­чи­тать атри­бу­ты ресур­сов и запи­сать их в состо­я­ние. Дела­е­те terraform plan и смот­ри­те, не пыта­ет­ся ли Terraform что-то попра­вить. Если пыта­ет­ся, пра­ви­те кон­фи­гу­ра­цию, и сно­ва смот­ри­те на план. В иде­а­ле Terraform ска­жет, что всё ок, и ниче­го пра­вить не будет. В слу­чае поху­же он всё-таки что-нибудь пересоздаст.

Ана­ло­гич­ным обра­зом, толь­ко исполь­зуя terraform refresh, мож­но при­ве­сти кон­фи­гу­ра­цию в соот­вет­ствие с теми изме­не­ни­я­ми, кото­рые кто-то сде­лал сво­и­ми шалов­ли­вы­ми руч­ка­ми. (И руч­ки потом оторвать).

Импорт не все­гда рабо­та­ет иде­аль­но. Совсем не рабо­та­ет для «псев­до­ре­сур­сов», о кото­рых я гово­рил ранее. Слож­но­сти воз­ни­ка­ют со слож­ны­ми ресур­са­ми, с мно­же­ством вло­жен­ных сущ­но­стей, вро­де тех же Security Group или Route Table в AWS. Но после terraform apply и пере­тря­хи­ва­ния внут­рен­но­стей ресур­сов в уго­ду Terraform, всё устаканивается.

Рефак­то­ринг. Рефак­то­ринг кон­фи­гу­ра­ции Terraform. Он воз­мо­жен. Нуж­но толь­ко дей­ство­вать аккуратно.

Пере­име­но­ва­ние ресур­са. Ско­рее все­го Terraform пред­ло­жит уда­лить ста­рый ресурс, и создать новый, с новым име­нем. Если это непри­ем­ле­мо, мож­но попро­бо­вать уда­лить ресурс из состо­я­ния коман­дой terraform state rm, а потом сде­лать terraform import. Есть ещё спе­ци­аль­ная коман­да terraform state mv, кото­рая вро­де как спе­ци­аль­но пред­на­зна­че­на для это­го рефак­то­рин­га. Но я с помо­щью state mv как-то добил­ся ста­биль­но­го крэ­ша Terraform. С тех пор остерегаюсь.

Раз­би­е­ние одной боль­шой кон­фи­гу­ра­ции на несколь­ко малень­ких. На это есть несколь­ко причин.

При­чи­на пер­вая. terraform apply выпол­ня­ет­ся не силь­но быст­ро. И чем боль­ше ресур­сов име­ет­ся в кон­фи­гу­ра­ции, тем мед­лен­нее. Ему же надо про­ве­рить состо­я­ние каж­до­го ресур­са, даже если в кон­фи­гу­ра­ции этот ресурс и не менял­ся. Име­ет смысл выде­лить части кон­фи­гу­ра­ции исхо­дя из часто­ты изме­не­ний и «охва­та тер­ри­то­рии». Ска­жем, VPC вам при­дёт­ся настра­и­вать лишь один раз, и потом почти нико­гда не менять. ECS кла­стер вы буде­те созда­вать каж­дый раз, когда будет появ­лять­ся новое окру­же­ние, и потом опять без изме­не­ний. А сер­ви­сы нуж­но под­нов­лять каж­дый инди­ви­ду­аль­но почти при каж­дом деплое.

При­чи­на вто­рая. Terraform пока пло­хо рабо­та­ет с вер­си­о­ни­ру­е­мы­ми ресур­са­ми. Типич­ный при­мер: ECS Task Definition. Этот ресурс нель­зя уда­лить, толь­ко поме­тить как неак­тив­ный. Этот ресурс нель­зя изме­нить, толь­ко создать новую реви­зию. Поэто­му на каж­дый terraform apply будет «уда­лён» ста­рый Task Definition, и создан новый Task Definition, даже если в кон­фи­гу­ра­ции ниче­го не меня­лось. И обнов­лён ECS Service, кото­рый этот Task Definition использует.

Это при­ве­дёт к тому, что этот Service обно­вит и пере­за­пу­стит свои тас­ки. То есть про­изой­дёт насто­я­щий реде­п­лой. Это хоро­шо, пото­му что мож­но делать реде­п­лой ECS сер­ви­сов с помо­щью Terraform. Это пло­хо, пото­му что я не хочу реде­п­ло­ить все два десят­ка моих сер­ви­сов одно­вре­мен­но по terraform apply одной боль­шой кон­фи­гу­ра­ции. Сер­ви­сы нуж­но выно­сить в отдель­ные кон­фи­гу­ра­ции Terraform.

Хоро­шо. Создать ещё одну папоч­ку с *.tf фай­ла­ми рядом — не про­бле­ма. Пере­не­сти туда управ­ле­ние ресур­са­ми с помо­щью terraform import тоже мож­но. Но ведь наши сер­ви­сы долж­ны кое-что знать о кла­сте­ре: имя кла­сте­ра, те же Security Groups, ещё пара­мет­ров по мелочи.

Кон­фи­гу­ра­ции зави­сят от дру­гих кон­фи­гу­ра­ций. Из более общих кон­фи­гу­ра­ций нуж­но пере­дать пара­мет­ры более кон­крет­ным кон­фи­гу­ра­ци­ям. Кла­стер дол­жен знать о VPC, сер­ви­сы долж­ны знать о кластере.

Мож­но пере­дать всё, что нуж­но, горст­ку нуж­ных иден­ти­фи­ка­то­ров, руч­ка­ми. Как вход­ные пере­мен­ные нашей кон­фи­гу­ра­ции. Будет рабо­тать. Если вы пра­виль­но ско­пи­ру­е­те эти идентификаторы.

Но есть более спор­тив­ный спо­соб. Если у нас есть remote state, его мож­но под­клю­чить как источ­ник дан­ных, и про­чи­тать выход­ные пере­мен­ные совер­шен­но дру­гой кон­фи­гу­ра­ции, чьё состо­я­ние мы подключили.

 

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

Terraform не заме­ня­ет Ansible, а допол­ня­ет его. Terraform созда­ёт ресур­сы (вир­ту­аль­ные маши­ны, где угод­но), а Ansible настра­и­ва­ет их (через ssh). Дру­гое дело, что если ресур­сы — Docker кон­тей­не­ры, то их и настра­и­вать (изнут­ри) не нужно.