HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>При описании инфраструктуры в Terraform для различных проектов можно заметить, что определенные комбинации ресурсов часто повторяются. Например, когда мы создаем кластер баз данных, мы обычно создаем вместе с ним и базу пользователей. А когда мы создаем несколько серверов, мы часто хотим объединить их в одну виртуальную сеть.</p>
1 <p>При описании инфраструктуры в Terraform для различных проектов можно заметить, что определенные комбинации ресурсов часто повторяются. Например, когда мы создаем кластер баз данных, мы обычно создаем вместе с ним и базу пользователей. А когда мы создаем несколько серверов, мы часто хотим объединить их в одну виртуальную сеть.</p>
2 <p>Terraform позволяет описывать наборы взаимосвязанных ресурсов как модули. Модули Terraform близки по своей идее к импортируемым ролям в Ansible. Мы можем подключать их в свой проект, передавать им параметры и на выходе получать комплексное инфраструктурное решение.</p>
2 <p>Terraform позволяет описывать наборы взаимосвязанных ресурсов как модули. Модули Terraform близки по своей идее к импортируемым ролям в Ansible. Мы можем подключать их в свой проект, передавать им параметры и на выходе получать комплексное инфраструктурное решение.</p>
3 <p>В этом уроке мы разберемся, зачем нужны модули Terraform и как их применять.</p>
3 <p>В этом уроке мы разберемся, зачем нужны модули Terraform и как их применять.</p>
4 <h2>Какую проблему решают модули</h2>
4 <h2>Какую проблему решают модули</h2>
5 <p>Модули помогают отвлечься от особенностей описания ресурсов и зависимостей конкретного облака. Мы импортируем модуль, передаем в него параметры, а дальше уже сам модуль создает все необходимые ресурсы.</p>
5 <p>Модули помогают отвлечься от особенностей описания ресурсов и зависимостей конкретного облака. Мы импортируем модуль, передаем в него параметры, а дальше уже сам модуль создает все необходимые ресурсы.</p>
6 <p>Так модуль с хорошей документацией экономит время, которое мы тратим на изучение ресурсов и зависимостей конкретного провайдера. Модуль помогает быстрее добиться нужного результата.</p>
6 <p>Так модуль с хорошей документацией экономит время, которое мы тратим на изучение ресурсов и зависимостей конкретного провайдера. Модуль помогает быстрее добиться нужного результата.</p>
7 <p>Возьмем для примера модуль<a>terraform-yc-postgresql</a>. Он описывает создание в облаке Yandex кластера БД PostgreSQL, а также помещение в него баз данных и пользователей.</p>
7 <p>Возьмем для примера модуль<a>terraform-yc-postgresql</a>. Он описывает создание в облаке Yandex кластера БД PostgreSQL, а также помещение в него баз данных и пользователей.</p>
8 <p>Ранее мы описывали кластер, его базу и пользователей с помощью обычных ресурсов. Попробуем сделать то же, но с помощью модуля.</p>
8 <p>Ранее мы описывали кластер, его базу и пользователей с помощью обычных ресурсов. Попробуем сделать то же, но с помощью модуля.</p>
9 <h2>Подключаем в инфраструктуру модуль</h2>
9 <h2>Подключаем в инфраструктуру модуль</h2>
10 <p>Возьмем<a>пример описания модуля</a>для простого single-node кластера, уберем лишние настройки и подправим некоторые поля:</p>
10 <p>Возьмем<a>пример описания модуля</a>для простого single-node кластера, уберем лишние настройки и подправим некоторые поля:</p>
11 <p>Модуль обозначается ключевым словом module. Далее мы даем модулю имя как обычным ресурсам Terraform. Мы назовем модуль<em>yandex-postgresql</em>. Так будет понятно, что он делает.</p>
11 <p>Модуль обозначается ключевым словом module. Далее мы даем модулю имя как обычным ресурсам Terraform. Мы назовем модуль<em>yandex-postgresql</em>. Так будет понятно, что он делает.</p>
12 <p>В обязательном для модуля поле source мы указали путь к<a>коду модуля</a>на Github с указанием используемой версии в ref. Так можно ссылаться на любые модули, которые хранятся на Github, в том числе на свои.</p>
12 <p>В обязательном для модуля поле source мы указали путь к<a>коду модуля</a>на Github с указанием используемой версии в ref. Так можно ссылаться на любые модули, которые хранятся на Github, в том числе на свои.</p>
13 <p>В network_id и subnet_id мы добавили ссылки на ресурсы сети и подсети, которые создаем в том же проекте. Также добавили в кластер вторую базу<em>hexlet-test</em>, чтобы проверить, как модуль работает с несколькими ресурсами. Еще добавили дополнительного пользователя<em>guest</em>.</p>
13 <p>В network_id и subnet_id мы добавили ссылки на ресурсы сети и подсети, которые создаем в том же проекте. Также добавили в кластер вторую базу<em>hexlet-test</em>, чтобы проверить, как модуль работает с несколькими ресурсами. Еще добавили дополнительного пользователя<em>guest</em>.</p>
14 <p>Вместо того чтобы описывать пять отдельных ресурсов Terraform, мы компактно описали все взаимозависимые ресурсы в одном модуле.</p>
14 <p>Вместо того чтобы описывать пять отдельных ресурсов Terraform, мы компактно описали все взаимозависимые ресурсы в одном модуле.</p>
15 <p>Мы технически можем добавить еще один модуль<em>yandex-postgresql-2</em>с тем же source, описать в нем другой набор параметров и создать два полноценных кластера БД со всем содержимым в одном проекте. Так с помощью модулей мы можем управлять инфраструктурой на более высоком уровне абстракции.</p>
15 <p>Мы технически можем добавить еще один модуль<em>yandex-postgresql-2</em>с тем же source, описать в нем другой набор параметров и создать два полноценных кластера БД со всем содержимым в одном проекте. Так с помощью модулей мы можем управлять инфраструктурой на более высоком уровне абстракции.</p>
16 <p>Выполним terraform init, чтобы загрузить модуль:</p>
16 <p>Выполним terraform init, чтобы загрузить модуль:</p>
17 <p>Initializing modules... Initializing the backend... Initializing provider plugins... - Finding yandex-cloud/yandex versions matching "&gt;= 0.89.0"... - Finding hashicorp/local versions matching "&gt; 2.2.0"... - Finding hashicorp/random versions matching "&gt; 3.3.0"... - Installing yandex-cloud/yandex v0.97.0... - Installed yandex-cloud/yandex v0.97.0 (unauthenticated) - Installing hashicorp/local v2.4.0... - Installed hashicorp/local v2.4.0 (unauthenticated) - Installing hashicorp/random v3.5.1... - Installed hashicorp/random v3.5.1 (unauthenticated) ... Terraform has been successfully initialized!</p>
17 <p>Initializing modules... Initializing the backend... Initializing provider plugins... - Finding yandex-cloud/yandex versions matching "&gt;= 0.89.0"... - Finding hashicorp/local versions matching "&gt; 2.2.0"... - Finding hashicorp/random versions matching "&gt; 3.3.0"... - Installing yandex-cloud/yandex v0.97.0... - Installed yandex-cloud/yandex v0.97.0 (unauthenticated) - Installing hashicorp/local v2.4.0... - Installed hashicorp/local v2.4.0 (unauthenticated) - Installing hashicorp/random v3.5.1... - Installed hashicorp/random v3.5.1 (unauthenticated) ... Terraform has been successfully initialized!</p>
18 <p>Помимо провайдера<em>yandex-cloud</em>дополнительно установились провайдеры<em>local</em>и<em>random</em>. Это произошло, так как наш модуль<em>terraform-yc-postgresql</em>использует их, чтобы генерировать случайные пароли и сохранять их в файлы.</p>
18 <p>Помимо провайдера<em>yandex-cloud</em>дополнительно установились провайдеры<em>local</em>и<em>random</em>. Это произошло, так как наш модуль<em>terraform-yc-postgresql</em>использует их, чтобы генерировать случайные пароли и сохранять их в файлы.</p>
19 <p>Если инициация прошла успешно, мы сможем увидеть код используемого модуля в рабочей папке .terraform/modules внутри проекта. Заглянем внутрь и посмотрим, из чего состоит модуль.</p>
19 <p>Если инициация прошла успешно, мы сможем увидеть код используемого модуля в рабочей папке .terraform/modules внутри проекта. Заглянем внутрь и посмотрим, из чего состоит модуль.</p>
20 <h2>Знакомимся со структурой модуля</h2>
20 <h2>Знакомимся со структурой модуля</h2>
21 <p>Модуль по сути является вложенным проектом Terraform, который содержит все стандартные компоненты: источники данных, ресурсы, переменные, outputs. Модуль принимает из проекта<a>значения переменных</a>и использует их, чтобы параметризировать<a>ресурсы</a>.</p>
21 <p>Модуль по сути является вложенным проектом Terraform, который содержит все стандартные компоненты: источники данных, ресурсы, переменные, outputs. Модуль принимает из проекта<a>значения переменных</a>и использует их, чтобы параметризировать<a>ресурсы</a>.</p>
22 <p>Главное отличие модуля от проекта - отсутствие блока provider, поскольку модуль не предназначен для самостоятельного использования. При этом в модуле<a>есть раздел required_providers</a>, который определяет, какие версии провайдеров понадобится доустановить при подключении модуля в проект.</p>
22 <p>Главное отличие модуля от проекта - отсутствие блока provider, поскольку модуль не предназначен для самостоятельного использования. При этом в модуле<a>есть раздел required_providers</a>, который определяет, какие версии провайдеров понадобится доустановить при подключении модуля в проект.</p>
23 <p>Модуль Terraform может храниться в обычном git-репозитории и версионироваться с помощью тегов git либо использовать для хранения специальный инфраструктурный registry, реализация которого есть, к примеру, в Gitlab.</p>
23 <p>Модуль Terraform может храниться в обычном git-репозитории и версионироваться с помощью тегов git либо использовать для хранения специальный инфраструктурный registry, реализация которого есть, к примеру, в Gitlab.</p>
24 <p>Сейчас внутри модуля нас интересуют outputs. Модуль создаст кластер, базы, пользователей и сгенерит для них пароли. Нам нужно извлечь эти данные, чтобы передать их ресурсу виртуальной машины.</p>
24 <p>Сейчас внутри модуля нас интересуют outputs. Модуль создаст кластер, базы, пользователей и сгенерит для них пароли. Нам нужно извлечь эти данные, чтобы передать их ресурсу виртуальной машины.</p>
25 <h2>Обращаемся к полям модуля</h2>
25 <h2>Обращаемся к полям модуля</h2>
26 <p>Заглянем в<em>outputs.tf</em>подключенного модуля:</p>
26 <p>Заглянем в<em>outputs.tf</em>подключенного модуля:</p>
27 <p>В<em>cluster_fqdns_list</em>мы сможем прочитать хост, который раньше получали из поля ресурса кластера yandex_mdb_postgresql_cluster.dbcluster.host.0.fqdn. В<em>owners</em>во вложенной структуре хранятся пользователи и пароли БД, в<em>databases</em>- список баз данных.</p>
27 <p>В<em>cluster_fqdns_list</em>мы сможем прочитать хост, который раньше получали из поля ресурса кластера yandex_mdb_postgresql_cluster.dbcluster.host.0.fqdn. В<em>owners</em>во вложенной структуре хранятся пользователи и пароли БД, в<em>databases</em>- список баз данных.</p>
28 <p>К полям модулей можно обращаться так же, как к полям ресурсов. Outputs являются полями самого модуля, поэтому мы в общем виде можем получить их значения, если пропишем module.&lt;modulename&gt;.&lt;outputname&gt;.</p>
28 <p>К полям модулей можно обращаться так же, как к полям ресурсов. Outputs являются полями самого модуля, поэтому мы в общем виде можем получить их значения, если пропишем module.&lt;modulename&gt;.&lt;outputname&gt;.</p>
29 <p>Пропишем в ресурсе виртуальной машины значения, которые возвращает модуль:</p>
29 <p>Пропишем в ресурсе виртуальной машины значения, которые возвращает модуль:</p>
30 <p>Поскольку outputs в этом модуле представляют собой списки, в ресурсе<em>vm</em>мы обращаемся к отдельным элементам списка, чтобы извлечь строковые значения:</p>
30 <p>Поскольку outputs в этом модуле представляют собой списки, в ресурсе<em>vm</em>мы обращаемся к отдельным элементам списка, чтобы извлечь строковые значения:</p>
31 <ul><li>Берем module.yandex-postgresql.cluster_fqdns_list[0].0, чтобы извлечь строку с хостом кластера</li>
31 <ul><li>Берем module.yandex-postgresql.cluster_fqdns_list[0].0, чтобы извлечь строку с хостом кластера</li>
32 <li>Берем module.yandex-postgresql.databases[0], чтобы извлечь имя первой базы данных, созданной в модуле - это будет hexlet</li>
32 <li>Берем module.yandex-postgresql.databases[0], чтобы извлечь имя первой базы данных, созданной в модуле - это будет hexlet</li>
33 <li>Берем module.yandex-postgresql.owners_data[0].user и module.yandex-postgresql.owners_data[0].password, чтобы получить у первого и единственного owner-пользователя имя и пароль для подключения к БД</li>
33 <li>Берем module.yandex-postgresql.owners_data[0].user и module.yandex-postgresql.owners_data[0].password, чтобы получить у первого и единственного owner-пользователя имя и пароль для подключения к БД</li>
34 </ul><p>Также установим зависимость, чтобы виртуальная машина не создавалась до тех пор, пока модуль не развернет кластер БД:</p>
34 </ul><p>Также установим зависимость, чтобы виртуальная машина не создавалась до тех пор, пока модуль не развернет кластер БД:</p>
35 <p>На этом наша инфраструктура готова к запуску.</p>
35 <p>На этом наша инфраструктура готова к запуску.</p>
36 <h2>Запускаем инфраструктуру</h2>
36 <h2>Запускаем инфраструктуру</h2>
37 <p>Попробуем запустить terraform apply и посмотрим, как Terraform обработает запрос на создание инфраструктуры с подключенным модулем:</p>
37 <p>Попробуем запустить terraform apply и посмотрим, как Terraform обработает запрос на создание инфраструктуры с подключенным модулем:</p>
38 <p># module.yandex-postgresql.random_password.password["guest"] will be created + resource "random_password" "password" { ... } # module.yandex-postgresql.random_password.password["me"] will be created + resource "random_password" "password" { ... } # module.yandex-postgresql.yandex_mdb_postgresql_cluster.this will be created + resource "yandex_mdb_postgresql_cluster" "this" { ... }</p>
38 <p># module.yandex-postgresql.random_password.password["guest"] will be created + resource "random_password" "password" { ... } # module.yandex-postgresql.random_password.password["me"] will be created + resource "random_password" "password" { ... } # module.yandex-postgresql.yandex_mdb_postgresql_cluster.this will be created + resource "yandex_mdb_postgresql_cluster" "this" { ... }</p>
39 <p>На этапе подтверждения видим, что Terraform трансформирует параметры, переданные модулю в отдельные ресурсы, и присваивает им имена вида module.yandex-postgresql.*. Когда мы изменяем параметры, передаваемые модулю, мы будем изменять ресурсы, которые стоят за ним.</p>
39 <p>На этапе подтверждения видим, что Terraform трансформирует параметры, переданные модулю в отдельные ресурсы, и присваивает им имена вида module.yandex-postgresql.*. Когда мы изменяем параметры, передаваемые модулю, мы будем изменять ресурсы, которые стоят за ним.</p>
40 <p>Применим изменения и понаблюдаем, как создается инфраструктура:</p>
40 <p>Применим изменения и понаблюдаем, как создается инфраструктура:</p>
41 <p>module.yandex-postgresql.random_password.password["guest"]: Creating... module.yandex-postgresql.random_password.password["me"]: Creating... module.yandex-postgresql.random_password.password["me"]: Creation complete after 0s [id=none] module.yandex-postgresql.random_password.password["guest"]: Creation complete after 0s [id=none] yandex_vpc_network.net: Creating... yandex_vpc_network.net: Creation complete after 0s [id=enpn9tfh3hd8s8hara9m] yandex_vpc_subnet.subnet: Creating... yandex_vpc_subnet.subnet: Creation complete after 1s [id=e9bk60o99g5i2r01ia47] module.yandex-postgresql.yandex_mdb_postgresql_cluster.this: Creating... module.yandex-postgresql.yandex_mdb_postgresql_cluster.this: Still creating... [10s elapsed] ... module.yandex-postgresql.yandex_mdb_postgresql_cluster.this: Creation complete after 6m3s [id=c9q7s2022m4rife6o7bt] module.yandex-postgresql.yandex_mdb_postgresql_user.owner["me"]: Creation complete after 37s [id=c9q7s2022m4rife6o7bt:me] module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet"]: Creating... module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet-test"]: Creating... module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet-test"]: Creation complete after 40s [id=c9q7s2022m4rife6o7bt:hexlet-test] module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet"]: Creation complete after 1m16s [id=c9q7s2022m4rife6o7bt:hexlet] module.yandex-postgresql.yandex_mdb_postgresql_user.user["guest"]: Creating... module.yandex-postgresql.yandex_mdb_postgresql_user.user["guest"]: Creation complete after 54s [id=c9q7s2022m4rife6o7bt:guest] yandex_compute_instance.vm: Creating... yandex_compute_instance.vm: Provisioning with 'remote-exec'... yandex_compute_instance.vm (remote-exec): (output suppressed due to sensitive value in config) yandex_compute_instance.vm: Creation complete after 1m27s [id=fhm60f9q8b2ach07atnm]</p>
41 <p>module.yandex-postgresql.random_password.password["guest"]: Creating... module.yandex-postgresql.random_password.password["me"]: Creating... module.yandex-postgresql.random_password.password["me"]: Creation complete after 0s [id=none] module.yandex-postgresql.random_password.password["guest"]: Creation complete after 0s [id=none] yandex_vpc_network.net: Creating... yandex_vpc_network.net: Creation complete after 0s [id=enpn9tfh3hd8s8hara9m] yandex_vpc_subnet.subnet: Creating... yandex_vpc_subnet.subnet: Creation complete after 1s [id=e9bk60o99g5i2r01ia47] module.yandex-postgresql.yandex_mdb_postgresql_cluster.this: Creating... module.yandex-postgresql.yandex_mdb_postgresql_cluster.this: Still creating... [10s elapsed] ... module.yandex-postgresql.yandex_mdb_postgresql_cluster.this: Creation complete after 6m3s [id=c9q7s2022m4rife6o7bt] module.yandex-postgresql.yandex_mdb_postgresql_user.owner["me"]: Creation complete after 37s [id=c9q7s2022m4rife6o7bt:me] module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet"]: Creating... module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet-test"]: Creating... module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet-test"]: Creation complete after 40s [id=c9q7s2022m4rife6o7bt:hexlet-test] module.yandex-postgresql.yandex_mdb_postgresql_database.database["hexlet"]: Creation complete after 1m16s [id=c9q7s2022m4rife6o7bt:hexlet] module.yandex-postgresql.yandex_mdb_postgresql_user.user["guest"]: Creating... module.yandex-postgresql.yandex_mdb_postgresql_user.user["guest"]: Creation complete after 54s [id=c9q7s2022m4rife6o7bt:guest] yandex_compute_instance.vm: Creating... yandex_compute_instance.vm: Provisioning with 'remote-exec'... yandex_compute_instance.vm (remote-exec): (output suppressed due to sensitive value in config) yandex_compute_instance.vm: Creation complete after 1m27s [id=fhm60f9q8b2ach07atnm]</p>
42 <p>При использовании логики модуля Terraform сгенерировал случайные пароли для пользователей с помощью провайдера random, создал кластер PostgreSQL, добавил в него базы и пользователей. Лог работы провижнера, создающего контейнер с приложением, оказался скрыт, так как в нем применялись сенситивные переменные.</p>
42 <p>При использовании логики модуля Terraform сгенерировал случайные пароли для пользователей с помощью провайдера random, создал кластер PostgreSQL, добавил в него базы и пользователей. Лог работы провижнера, создающего контейнер с приложением, оказался скрыт, так как в нем применялись сенситивные переменные.</p>
43 <h2>Выводы</h2>
43 <h2>Выводы</h2>
44 <p>Модуль Terraform - это инструмент для описания комплексных инфраструктурных решений, которые состоят из нескольких взаимосвязанных ресурсов. Модуль можно подключить в свой проект, передать ему набор переменных, и Terraform создаст новые ресурсы на основе логики модуля.</p>
44 <p>Модуль Terraform - это инструмент для описания комплексных инфраструктурных решений, которые состоят из нескольких взаимосвязанных ресурсов. Модуль можно подключить в свой проект, передать ему набор переменных, и Terraform создаст новые ресурсы на основе логики модуля.</p>
45 <p>Модули упрощают описание сложных инфраструктурных решений и позволяют переиспользовать инфраструктурный код в разных проектах.</p>
45 <p>Модули упрощают описание сложных инфраструктурных решений и позволяют переиспользовать инфраструктурный код в разных проектах.</p>