Всем привет! Меня зовут Виктор, я DevOps‑инженер компании Nixys, которая помогает другим компаниям внедрять в их IT‑решения передовые практики DevOps, MLOps и DevSecOps. Сегодня я приглашаю вас вместе со мной пройти путь «от незнания к best practices» в работе с Terraform. Этот материал подготовлен для серии наших одноименных видеороликов на YouTube, но мы решили дополнить его и предложить вам более детальное описание процесса в этой статье. Не забывайте следить за нашими обновлениями на YouTube, Habr и подписывайтесь на наш Telegram‑канал DevOps FM — мы всегда рады новым друзьям. Начнём? Ответ однозначный — да! Если у вашего провайдера есть возможность работы с этим инструментом, обязательно используйте его. Почему? В первую очередь, Terraform — это автоматизация. А всё, что можно автоматизировать, надо автоматизировать. Да‑да, тавтология, но, надеюсь, так лучше закрепится мысль. Вы же не задаетесь вопросом: «а зачем конвейерные линии на производстве ставят», верно? Вот и здесь принцип тот же. Да, вам нужен оператор этой линии (в нашем случае толковый DevOps‑инженер с нужными скиллами), но просто вдумайтесь, сколько проблем вы этим решаете! Предоставление инфраструктуры и решение долгих рутинных задач. Наверняка перед вами когда‑то возникала проблема, что нужно обеспечить несколько сред для разработки своего продукта, так как параллельно у вас 20 разрабов сражаются за то, чтобы запустить свой код на единственном сервере (а в большинстве случаев они просто будут ждать пока под них не настроят окружение). Вы же в это время получаете простой в разработке, неконкурентоспособность на рынке и т. д. и т. п. Если это про вас, тогда вам определенно нужен Terraform, ведь он автоматизирует процесс создания и предоставления ресурсов инфраструктуры — таких, как серверы, сети, балансировщики нагрузки и базы данных. И вуаля — вам не нужно тратить кучу времени на новое окружение.  Снижение человеческого фактора. У вас было такое, что в процессе работы проекта, возникали ошибки по вине человека? Это может быть что угодно: опечатка в названии сервиса при обращении к нему, или же просто кто‑то мог поторопиться, мискликнуть и случайно удалить не ту машину. Вот 100% что было, все мы люди. Тогда опишите свою инфраструктуру кодом, вы снизите шанс подобного к минимуму. Не до нуля, нет, но на несколько порядков меньше — это факт. Обновление. Ох, когда‑нибудь сталкивались с тем, что на рабочем проекте нужно обновить софт? И ладно если это что‑то несущественное, а вдруг надо прям куб обновлять, да ещё через несколько мажорных версий? В общем, Terraform и тут поможет: потратьте время на описание своей инфраструктуры в коде и вы забудете эту боль. Это не снимет с вас обязанность продумать все нюансы. Но во‑первых, Terraform изначально заставляет вас организовать инфраструктуру особым образом, и с такой организацией легче обновляться, а во‑вторых — запускаете две команды, и вы подняли полный клон своей инфраструктуры — можете там ломать, откатывать, переделывать всё, что вашей душе угодно. И если у вас получилось обновиться, с вероятностью, крайне близкой к 100%, вы без проблем обновите и свой прод. Стандартизация. Если у вас команда из двух и более человек, работающих с инфраструктурой, то вы наверняка сталкивались с тем, что они работают по‑разному. Да есть нормы, но они разнятся от компании к компании, и в регламенты всё не затолкаешь, и по итогу получаете вроде бы одну инфраструктуру, но часть поднята одним способом, часть — другим. Это может быть что угодно — от нестандартизированных имён сервисов до использования разных ОС для однотипных хостов. С Terraform вы это решаете, так или иначе. Даже если у вас нет регламента к написанию кода, всё устроено таким образом, что инженерам всё равно придётся работать по одному стандарту. Очевидность. Нанимали нового человека? Сколько времени ему нужно на то, чтобы влиться в работу? С инфраструктурой, которая поднята руками, всё ещё сложнее — там может быть столько неочевидных вещей, что обнаружить их можно будет только когда что‑то уже сломалось, и инженеру приходится носом землю рыть в поисках неисправности. Если вы описали всё с помощью кода, это означает, что больше нет кучи мест, где может быть реализован тот или иной функционал — всё здесь, в манифестах. Да, они могут быть сложны и запутанны, но, по крайней мере, это не какой‑нибудь пользовательский cron на 101-м сервере ходит в прод и забивает весь пул соединений — если он есть, то описан в коде, и мы знаем где его искать. Есть один универсальный источник — это код: ознакомился с ним, значит ознакомился со всей инфраструктурой. Работа в команде. Здесь всё просто — система контроля версий. Если есть код, мы и работаем с ним, как с кодом. Заливаем его в GitHub, GitLab или где вы привыкли работать — и всё, мы обеспечили необходимый функционал для нормальной работы в команде. Если у вас всё руками поднималось, забудьте про такие плюшки. В целом, Terraform — это мощный инструмент, который решает множество проблем как IT‑отдела в частности, так и, соответственно, бизнеса в целом. Terraform — это инструмент, который позволяет описывать инфраструктуру в виде кода и автоматизировать её развёртывание, модификацию и удаление. Он поддерживает множество провайдеров — таких, как AWS, Google Cloud, Azure, Yandex Cloud и других. Одним из основных преимуществ Terraform является возможность описать желаемое состояние инфраструктуры в виде кода, и затем он автоматически приводит её в это состояние, а вы не заботитесь о ручном управлении каждым ресурсом. Чтобы начать эффективно работать с этим инструментом, важно понимать основные концепции — такие, как манифесты, провайдеры и команды. Далее в статье мы рассмотрим их, а также принципы работы с Terraform. Кроме того, я расскажу про best practices, которые помогут вам создавать качественный и надежный код. Итак, Terraform использует манифесты, чтобы описать состояние инфраструктуры, которую вы хотите создать или управлять. Манифесты Terraform — это файлы с расширением.tf, которые содержат описание ресурсов, их атрибутов и зависимостей. Для описания манифестов используется HashiCorp Configuration Language (HCL). HCL является удобным и понятным языком, который позволяет описывать ресурсы, зависимости между ними и указывать значения атрибутов. После того, как манифесты написаны, Terraform может использовать их для управления ресурсами в провайдере. Это позволяет вам автоматизировать процесс работы с инфраструктурой, улучшать ее надежность и снижать вероятность возникновения ручных ошибок. Terraform использует несколько основных элементов для описания инфраструктуры: Providers: провайдеры — это интерфейсы для взаимодействия с ресурсами, такими, как AWS, Google Cloud, Microsoft Azure и т. д. Resources: ресурсы — это объекты, которые вы хотите создать, удалить или изменить. Например, виртуальная машина, балансировщик нагрузки, база данных. Variables: переменные — это значения, которые можно передать в Terraform для конфигурирования вашей инфраструктуры. Data Sources: источники данных — это инструменты, которые позволяют Terraform получать данные из внешних источников, таких как API, базы данных или другие службы. Outputs: выводы — это результаты, которые Terraform выдает после выполнения конфигурации, например, IP‑адреса, идентификаторы ресурсов и т. д. Дополнительные элементы Terraform, которые стоит упомянуть: Модули. Позволяют повторно использовать код и упрощают управление сложными инфраструктурами. Варианты окружения. Terraform позволяет управлять различными версиями инфраструктуры в зависимости от окружения. Выражения. Terraform использует выражения для вычисления значений и проверки условий. Итак, мы понимаем, что с помощью Terraform можно описать инфраструктуру в коде, этот код содержится в файлах, что логично🙂, так давайте поговорим с вами о них. Основные типы файлов, которые вы можете использовать в Terraform, включают в себя: Файлы конфигурации (.tf файлы) — это основные файлы Terraform, которые содержат описание ресурсов, которые вы хотите создать или управлять. Файлы переменных (.tfvars файлы) — это файлы, которые содержат значения переменных, использующихся в файлах конфигурации. Файл состояния (.tfstate файл): это файл, который хранит текущее состояние вашей инфраструктуры. Он обновляется каждый раз, когда вы запускаете apply (конечно, если вы что‑то изменили в коде). Также, стоит отдельно рассказать про файл состояния ‑.tfstate. Terraform сохраняет информацию о текущем состоянии вашей инфраструктуры в файле состояния. Этот файл содержит информацию о ресурсах, которые уже были созданы в вашей инфраструктуре, и их текущих свойствах. Terraform использует этот файл, чтобы определить, какие ресурсы необходимо изменить, какие создать, а какие необходимо удалить, когда вы запускаете команду apply. Грубо говоря, он помогает Terraform определять, какие изменения необходимо внести в инфраструктуру, чтобы ее состояние соответствовало описанному в конфигурационных файлах. Файл состояния может храниться локально или удаленно, например, в объектном хранилище. Это позволяет вам хранить общую копию файла состояния и управлять им с нескольких хостов. Важно отметить, что файл состояния является важным аспектом управления вашей инфраструктурой, поэтому необходимо обеспечить его блокировку на время работы с ним. Также, нужно понимать, что файл состояния является конфиденциальным, и его необходимо хранить в безопасном месте, так как он содержит важную информацию о всей вашей инфраструктуре. Кроме всего этого, хочу упомянуть, что если вам пришлось вручную залезть в tfstate‑файл, то, как говорит мой коллега: «Вы где‑то что‑то сделали не так в этой жизни» 🙂 Теперь давайте поговорим с вами о том, на какие best practices стоит обратить внимание: Не храните всю инфраструктуру в одном месте, разбейте её на части; Изолируйте несвязанные компоненты друг от друга. Разделяйте логически свою инфраструктуру, сеть, сервисы, мониторинг и т. д.; Не храните файл состояния локально; Блокируйте файл состояния при работе; Пишите код так, чтобы потом его можно было использовать в другом месте, используйте модули. Пользуйтесь переменными — удобнее использовать переменные для управления конфигурацией инфраструктуры. Пишите документацию, она всегда пригодится. Когда вы пишите код своей инфраструктуры, достаточно написать всё в одном файле, к примеру в файле main.tf: В нём будет всё, и объявление провайдеров, и ресурсы, и data sources, и outputs:  Terraform это «прожуёт» и исполнит код. Всё бы ничего, если вы описали один ресурс, но их может быть два, а может и пару десятков. В таком файле будет уже сложнее разбираться и вносить изменения в инфраструктуру. Хорошо, мы можем под каждый ресурс создавать отдельный файл, например gitlab.tf, mongodb.tf, rebbitmq.tf и т. д.: Уже лучше, теперь мы знаем в каком файле необходимо изменить значение или дописать параметр. Но возникает ещё одна проблема — Terraform воспринимает все файлы в рабочем каталоге как один, а это значит, что и при запуске plan или apply он пойдёт перепроверять всю вашу инфраструктуру. Это долго и никому не нужно. Нет необходимости перепроверять ресурсы для кластера k8s, если изменения вносились в подсеть. Какой тогда выход? Всё просто, мы создадим отдельный рабочий каталог под каждый ресурс или связанную логически группу ресурсов: Теперь у нас имеется несколько манифестов Terraform, каждый из которых описывает свою часть общей инфраструктуры, но всё же у нас остаётся проблема с тем, что всё свалено в одну кучу. Описание того же кластера k8s достаточно большое, и работать в одном файле совсем не удобно. Решение следующее — мы разбиваем конфигурационные файлы по типу описываемых компонентов, т. е. есть главный файл main.tf, где мы объявим провайдера и пропишем доступы к облаку. Далее, мы логически распределим код в отдельные файлы. Выглядит это следующим образом: Таким образом, мы и разделяем конфигурацию логически и с легкостью разберемся в инфраструктуре, если видим её впервые. Конечно, можно придумывать ещё много разных усовершенствований, но иногда стоит просто остановиться и работать дальше) Итак, теперь мы примерно понимаем, что к чему, и как нам организовать нашу конфигурацию. Давайте теперь перейдём к делу и начнём создание межрегионального кластера kubernetes с автоскейлингом worker-нод с помощью Terraform в Yandex Cloud. В этом кластере мы поднимем мониторинг посредством kube-prometheus-stack. Части про установку Terraform и yc (консольной утилиты Yandex Cloud) я снова пропускаю, вы можете самостоятельно ознакомиться с этой информацией по приведённым ссылкам.  Также я не буду останавливаться на создании сервисного аккаунта и организации доступа к облаку, об этом я рассказывал в демонстрационном ролике – DevOps с Nixys | Знакомство с Terraform - Tutorial для начинающих #1. Начнём мы, как это водится, с документации. Согласно ей, код по созданию межрегионального кластера будет выглядеть так:  А чтобы развернуть ещё и группы узлов, понадобится новый ресурс:  Это согласно инструкции Яндекса + документации Terraform. Если хотим больше групп — давайте сделаем по одной в каждой зоне и в конечном итоге, с остальными ресурсами мы получим такую картину:  В принципе, если не париться, можно запускать и так. Всё заведётся и будет работать. Мы получим отказоустойчивый кластер k8s с рабочими узлами в разных зонах доступности, да ещё и с автоскейлингом. Но сможем ли мы оставить всё как есть? Конечно же нет, давайте сделаем из этого франкенштейна аккуратные, красивые, а главное функциональные манифесты, с которыми в дальнейшем можно будет жить с чистой совестью. Работать мы будем в отдельной директории my_project.  Для того, чтобы создать кластер k8s нам потребуется сеть, подсеть и сервисный аккаунт. Поэтому, в соответствии с упомянутыми ранее рекомендациями, мы начнём создавать инфраструктуру с сети и её компонентов в отдельном рабочем каталоге network: Далее мы создадим файл main.tf, в нем пропишем провайдера и доступ к облаку:  Всё, что здесь описано, должно быть знакомо по предыдущему ролику, поэтому не будем останавливаться на этом и перейдем к описанию сети и подсети, их мы опишем в файле vpc.tf:  Это согласно инструкции Яндекса. Давайте на примере кода в main.tf и vpc.tf проведём ряд усовершенствований. Для начала вынесем имя сети и ещё пару параметров в переменные. Создадим файлы terraform.tfvars и variables.tf. Теперь наш рабочий каталог выглядит следующим образом: В файле variables.tf укажем, что хотим создать четыре переменные:  Тут мы можем указать пояснение к переменной и указать тип данных. Также, визуально для эстетичности разграничим содержимое. Далее, в файле terraform.tfvars мы присвоим значение этим переменным:  И в конце концов перепишем в файлах main.tf и vpc.tf соответствующие параметры:   Отлично, с переменными разобрались, теперь перейдём к циклу — зачем нам описывать три подсети, по сути одинаковых ресурса, если можно обойтись одним блоком кода, верно? Добавим в variables.tf описание переменной subnets, только здесь мы обозначим не один параметр, а их набор. У Terraform есть тип map, который нам отлично подходит под задачу (ещё бы, для этого и создавался):  Тут же, исключительно ради примера, делаю проверку на корректность указываемых значений с помощью блока validation, где в condition пишу условие, а в параметре error_message текст ошибки. Можно делать проверки на всё подряд, но это отдельная и трудоемкая задача. Остановимся на том, что просто упомянули о такой возможности. Далее, в файле terraform.tfvars мы просто укажем значения:  Как вы можете видеть, я добавил ещё 3 подсети для воркер-нод, чтобы всё разграничить. А теперь создадим файл local.tf, со следующим содержимым:  Выражение перебирает входную переменную var.subnets и с помощью встроенной функции flatten сглаживает полученный вложенный список в единый одномерный список объектов. Каждый объект в результирующем списке имеет три атрибута: name, zone и cidr. При этом, оставляя значение для ключа cidr в списке, что очень важно. Ведь далее в ресурсе нам нужно указывать именно список. Таким образом, мы можем переписать наш код в vpc.tf:  Здесь мы используем цикл for_each, в котором сопоставляем уникальный идентификатор с каждым элементом списка local.subnet_array. В данном случае, уникальным идентификатором является “name”. Таким образом, сейчас для Terraform это выглядит как 6 ресурсов, и он создаст отдельные подсети с уникальным именем. Всё, у нас есть 6 подсетей 🙂 Что ещё. А, ну давайте создадим статические IP-адреса, сколько бы их ни было. Действуем по тому же сценарию, в variables.tf добавляем новую переменную:  После этого в terraform.tfvars добавляем значения. Сделаю один, но мы сразу закладываем возможность создания нескольких:  Создаём локальную переменную external_ips_array в locals.tf:  Ну и, наконец, создадим файл static-ip.tf:  Всё как и с подсетями. Что ещё нам потребуется? Конечно же связность подсетей и безопасность, а значит пора заняться описанием security group. По уже сложившейся традиции, создадим файл security-group.tf, где опишем группы безопасности для мастеров и воркеров:  Лично я считаю такую организацию групп безопасности более верной для кластера:  internal, для связности мастеров и воркеров, здесь разрешено всё в рамках этой самой группы.   k8s_master исключительно под мастеров где разрешён доступ к 443 порту по переменной, которой мы должны передать вайтлист и служебное правило для балансировщиков.  k8s_worker группа безопасности для воркеров, где мы разрешим вообще всё.  Создадим переменную для вайтлиста и пропишем значение для неё.   С network закончили, переносим сюда ключи от сервисного аккаунта и от бакета. Рабочий каталог, после всех наших манипуляций, должен выглядеть следующим образом: Можно запускать terraform init, затем terraform apply. Всё по аналогии: создадим файлы main.tf, variables.tf и terraform.tfvars, а также перенесём наши ключи. Кроме того, создадим ещё три конфигурационных файла cluster.tf, node_group.tf и service_account.tf: Переносим ресурсы для групп узлов в node_group.tf, описание кластера в cluster.tf, и всё, что относится к сервисным аккаунтам в service_account.tf, сюда же положим и ключ шифрования.    Так же, как и в network, создадим переменные:  Присвоим им значения:  И перепишем main.tf  Не забываем сменить имя файла состояния в параметре key. Итак, давайте переписывать. Начинаем с service_account.tf, у нас имя сервисного аккаунта было задано через locals, мы это переделаем. Нам нужно имя сервисного аккаунта и ключа шифрования задать через переменные, для этого создаём их:  И определяем:  Переписываем service_account.tf:  Что ж, вот мы и можем, наконец, приступить к оптимизации кода кластера. Давайте подставим наши значения в cluster, попутно прописав переменные для некоторых из них:  Как вы можете видеть, у двух переменных, а именно master_public_ip и network_policy_provider, уже задано дефолтное значение. Нам нужно в terraform.tfvars указать остальные:  Вот мы и дошли до важнейшего момента - нам необходимо указать id сети, но её мы поднимали другим манифестом Terraform'а, а значит, из этого манифеста Terraform не может увидеть созданные там ресурсы. как же быть? Для того, чтобы получить какие-либо данные извне, есть data sources. С помощью data мы можем указать терраформ источник, откуда ему следует взять данные.  Создадим файл data.tf:  Источник данных указывается очень просто:  data, и прописываем откуда брать данные, а нужна нам сеть с именем mynet. Теперь нам достаточно сослаться на этот источник в описании ресурса, обязательно указав что именно нам нужно оттуда забрать - data.yandex_vpc_network.mynet.id: Можем оставить так, но что, если имя сети поменяется? Переписали мы конфигурацию сети, нам же придётся и в каталоге k8s в data.tf поменять параметр, а ведь мы переписываем код для того, чтобы не лазить больше в конфигурационные файлы. Мы можем вынести этот параметр в переменные и менять его там - скажете вы. А что, если вообще не заморачиваться над тем, как назван ресурс? Что, если мы укажем id сети так, что нам нигде не придётся ничего переписывать при любых изменениях в манифесте network? Для этого нужно ответить на один вопрос - а где ещё хранятся данные о созданных ресурсах любого манифеста? Верно, в файле состояния. Вот мы и укажем Terraform'у сходить и прочитать другой tfstate файл. Создадим другой источник данных и в нем укажем путь и доступы к чужому файлу состояния.   Как видите, здесь используются переменные, давайте их создадим. И присвоим значения - их мы возьмём из main.tf в каталоге network. Расположил я их после переменных для main.   Теперь мы можем читать другой файл состояния и считывать оттуда значения, однако, их там пока нет. Для того, чтобы они появились, нам нужно вернуться в каталог network и оформить outputs. Итак, создаём outputs.tf и описываем то, что хотим вывести:  А нужно нам id сети. Вот и всё. Обязательно запустите terraform apply, чтобы данные записались в файл состояния. Теперь, для того, чтобы нам сослаться на какой-либо ресурс из network, потребуется постоянно указывать конструкцию data.terraform_remote_state.network.outputs. и имя того output’а который нам нужен. Например: Давайте используем locals, чтобы упростить себе жизнь. Итак, создаём файл locals.tf тут мы создадим переменную:  Теперь можем всегда указывать просто: Думаю так гораздо удобнее. Давайте для групп безопасности тоже выведем id, т.к. они нам также понадобятся:  Но также нам нужны зоны и id подсетей. Здесь уже сложнее, ведь мы не можем просто указать эти значения, так же, как и для других, поскольку подсети у нас создаются циклом в network и писать output под каждую подсеть мы не можем.  Для нашей задачи мы действительно используем output, но для этого напишем небольшое условие:  Как вы помните, мы создали 6 подсетей, три под мастеров и три под воркеров, вот я и хочу получить их id и zone разными output»ами: первый — под наши требования для кластера, а второй — заранее сделаем для воркеров.  Здесь мы фильтруем значение переменной subnets, чтобы бралась информация только по необходимым подсетям(в данном случае для мастеров). Далее с помощью функции zipmap мы создаём список, где присваиваем ключам subnet_id и zone соответствующие атрибуты из переменной, и так для каждой подсети в k8s_masters. Аналогично и для подсетей в k8s_workers.  Перезапускаем terraform apply. Окей, теперь переписываем конфигурацию на переменные, а ID групп безопасности указываем две, internal и k8s_master из output»ов предыдущего манифеста:  Для того, чтобы указать подсети и зоны, мы используем dynamic блок с циклом for_each, указываем, откуда будем брать значения (а берем мы их из нашего нового output»a). Далее мы описываем content, и сюда пишем те параметры, которые нужны этому блоку, а нужны зона и ID, теперь указываем откуда брать значения, а их мы берем из того цикла, который мы передали, т. е. location.value и указываем какой элемент нужен. Запускаем terraform init и terraform apply в каталоге k8s. Ну вот, у нас получилось поднять инфраструктуру с помощью двух разных манифестов Terraform. Бежим в Яндекс и проверяем — кластер на месте. Нам осталось решить, как описать группы узлов. Они у нас так же, как и подсети до этого, указаны тремя ресурсами. Давайте исправлять. Как вы наверное уже догадались, делать мы это будем через тот же for_each. Но сначала, создадим переменную:  И присвоим значение этой переменной в terraform.tfvars:  Тут мы указываем всё то, что хотим видеть в параметрах ресурса, главное потом это правильно присвоить. Как вы могли заметить, блоки отличаются, а именно для node‑group‑a указан auto_scale, а для остальных групп fixed_scale. Таким образом, я хочу добавить возможность менять тип групп через переменные. Также, в yandex_kubernetes_node_group нам потребуется указать id подсетей. Вспоминаем о нашем ранее созданном output»е — k8s_workers_subnet_info, но там передаётся не только id, но и зона. В чем проблема? — спросите вы. Ведь можно переписать output, чтобы он выдавал только id. Но нет, в таком случае у нас пропадёт привязка к зоне, ведь мы просто получим набор id, и понять какой из них принадлежит определённой зоне не получится. Да, они у нас сейчас по порядку, первый id будет относится к зоне ru‑cenral1-a, второй — к b и третий — к c. Но это мы в манифесте network нашей переменной так значения по порядку указали, а вот если их поменять местами, получим путаницу с таким подходом, и поднимется у нас, к примеру, группа с именем node‑group‑a в зоне b. Поэтому output мы не трогаем, а сделаем преобразование данных через locals:  Создаём переменную worker_subnet_list, тут мы создадим список, где каждой зоне присвоится id. Возвращаемся к нашему ресурсу в файл node_group.tf:  Конечно же, меняем имя ресурса в Terraform'е. Объявляем, что хотим использовать for_each и указываем откуда циклу брать значения (а лежат они в переменной node_groups). name, берет значение из each.key, это у нас имя объекта: node‑group‑a, node‑group‑b и node‑group‑c.  Далее присваиваем значения остальным параметрам, но делаем мы это по условию, для этого используем функция lookup, синтаксис у неё очень прост, мы указываем где искать, что искать, и что присвоить если ничего не нашла (значение по умолчанию). subnet_ids ставим источник local.worker_subnet_list, указываем что брать, а забирать мы будем id той зоны, которая указана в переменной node_groups, поэтому ставим each.value[«zone»]. Дефолтное значение убираем, т.к. сюда мы по умолчанию ничего не сможем поставить.  security_group_ids — группы безопасности мы указываем без всяких условий, просто указываем нужные output»ы.  scale_policy — как я уже говорил, нужна возможность выбора, либо фиксированная группа узлов, либо с автоскейлингом — для этого добавляем два соответствующих dynamic блока, в них описываем цикл, но, просто указать источник значений не получится, у нас в переменной вложенный список для этих блоков, чтобы вытащить необходимые значения, нужно этот список вытащить из другого, для этого воспользуемся функцией flatten, которая выравнивает вложенные списки. А далее можем использовать lookup — отличие лишь в том, что для блока fixed_scale на месте дефолтного значения мы ставим условие, которое проверяет наличие параметра auto_scale и если он есть, оставляет этот блок fixed_scale без значения, а если нет, то выставляет один фиксированный узел.  В content для этих блоков указываем использовать значения из соответствующего источника: для auto_scale — auto_scale.value.что_берем, для fixed_scale — fixed_scale.value.size allocation policy: без всяких lookup»ов ссылаемся на нужное значение. Получившаяся конфигурация должна поднимать для нас региональный кластер Kubernetes, с автоскейлингом нод в зоне ru‑central1-a, а в остальных зонах с 1 фиксированный нодой.  Однако, в процессе подготовки тестового стенда я столкнулся с проблемой, что инфраструктура поднималась и работала с этим кодом, а уже на следующий день — нет. Все необходимые узлы создавались, но не имели связи с мастерами. После проверки, я понял, что правила для группы безопасности не отрабатывают так, как это задумано. Пришлось переписать группу internal:  Поясню свою мысль. Правило, где мы выставляем predefined_target = «self_security_group», должно разрешать всё в рамках этой самой группы, т. е. хосты, к которым привязана группа internal (а в нашем случае это все мастеры и воркеры), должны свободно общаться друг с другом. Но этого не происходит — по информации от техподдержки, они перерабатывают механизм работы этого правила, и пока оно может «работать неожидаемым образом, когда эндпоинты находятся в разных зонах». Поэтому, мне пришлось добавить другое, которое открывает доступ всем узлам, находящимся в перечисленных в ней подсетях.  Как работает правило:  Нам нужны cidr наших подсетей, они у нас в переменной subnets. Однако, нам не нужно собирать этот параметр со всех подсетей, мы же можем прописать создание и других, не только для кластера и воркеров, как сейчас, — у нас могут появиться другие подсети, и мы не можем позволить трафику с других хостов, которые не относятся к кластеру, ходить к нему. Поэтому отфильтруем только то, что относится к кластеру, — это мастеры и воркеры: Но, таким образом, мы получили два списка. Не проблема, вызовем функцию concat, которая объединяет несколько списков в один:  Теперь, нужно вытащить только cidr, а у нас в переменной помимо этого ключа ещё name и zone, поэтому напишем условие, что в список мы собираем только значения ключа cidr: Мы получили нужные параметры, но, cidr у нас представлен в переменной, как вложенный список, с помощью flatten мы выравниваем этот список: Вот теперь, можем запускаем terraform apply и любуемся на нашу инфраструктуру. 🙂 Если есть необходимость в настройке дополнительных параметров, как доступ по ssh к воркер‑нодам, вы это сможете сделать уже самостоятельно, почитав документацию. Можно было ещё много параметров описать, но цель статьи в другом — показать разные методы по написанию кода инфраструктуры и научить способам работы с компонентами Terraform»а. Надеюсь вам это помогло.  Самые внимательные, наверное, заметили, что я говорил о том, что не нужно дублировать ресурсы, если их можно описать одним блоком, но при этом, сам создал подряд три группы безопасности. Так вот, предлагаю вам написать в комментариях, как бы вы описали эти ресурсы.  Спасибо, что дочитали до конца. Постараемся выложить следующую часть, как можно скорее. Благодарю всех за внимание, до скорого!)  DevOps