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


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


Обратите внимание: я намеренно не использую реальные примеры кода с некоторыми конкретными поставщиками, такими как AWS или Google для простоты понимания.


Модули Terraform


Вы уже пишете модули


Даже если вы не создаете модуль намеренно, если вы используете Terraform, вы уже пишете модуль — так называемый «корневой» модуль.


Любой файл конфигурации Terraform (.tf) в каталоге, даже один, образует модуль.


Что делает модуль?


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


Предположим, у нас есть виртуальный сервер с некоторыми функциями, размещенными в облаке. Какой набор ресурсов может описывать этот сервер? Например:


  • сама виртуальная машина, созданная из некоторого образа


  • подключенное блочное устройство указанного размера для дополнительного хранилища


  • статический общедоступный IP-адрес, сопоставленный с виртуальным сетевым интерфейсом сервера


  • набор правил брандмауэра, который будет прикреплен к серверу


  • другие вещи, такие как другое блочное устройство, дополнительный сетевой интерфейс и т. д.




Теперь предположим, что вам нужно создать этот сервер с набором ресурсов много раз. Именно здесь модули действительно полезны. Вы же не хотите повторять один и тот же код конфигурации снова и снова, не так ли?


Вот пример, показывающий, как может быть вызван наш «серверный» модуль.


«Вызвать модуль» означает использовать его в файле конфигурации.


Здесь мы создаем 5 экземпляров «сервера», используя единый набор конфигураций (в модуле):


module "server" {

    count         = 5

    source        = "./module_server"
    some_variable = some_value
}

Terraform поддерживает "счетчик" для модулей, начиная с версии 0.13.


Организация модуля: дочерний и корневой


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


  • сеть, подобная виртуальному частному облаку (VPC)


  • хостинг статического контента (т.е. bucket)


  • балансировщик нагрузки и связанные с ним ресурсы


  • конфигурация журналирования


  • или что-то еще, что вы считаете отдельным логическим компонентом инфраструктуры



Допустим, у нас есть два разных модуля: «серверный» модуль и «сетевой» модуль. В модуле под названием «сеть» мы определяем и настраиваем нашу виртуальную сеть и размещаем в ней серверы:


module "server" {
    source        = "./module_server"
    some_variable = some_value
}

module "network" {  
    source              = "./module_network"
    some_other_variable = some_other_value
}

Два разных дочерних модуля, вызываемые в корневом модуле


Когда у нас есть несколько настраиваемых модулей, мы можем называть их «дочерними» модулями. А файл конфигурации, в котором мы вызываем дочерние модули, относится к корневому модулю.



Дочерний модуль может быть получен из нескольких мест:


  • локальные пути


  • официальный реестр Terraform — если вы знакомы с другими реестрами, такими как реестр Docker, то вы уже понимаете идею


  • репозиторий Git (пользовательский или GitHub/BitBucket)


  • HTTP URL-адрес архива .zip с модулем



Но как передать сведения о ресурсах между модулями?


В нашем примере серверы должны быть созданы в сети. Итак, как мы можем сказать «серверному» модулю создавать виртуальные машины в сети, которая была создана в модуле «сеть»?


Вот тут и появляется инкапсуляция.


Инкапсуляция модуля


Инкапсуляция в Terraform состоит из двух основных концепций: области модуля и явного доступа к ресурсам.


Scope (область видимости) модуля


Все экземпляры ресурсов, имена и, следовательно, видимость ресурсов изолированы в области действия модуля. Например, модуль «A» по умолчанию не видит и не знает о ресурсах в модуле «B».


Видимость ресурсов, иногда называемая изоляцией ресурсов, гарантирует, что ресурсы будут иметь уникальные имена в пространстве имен модуля. Например, с нашими 5 экземплярами модуля «сервер»:


module.server[0].resource_type.resource_name
module.server[1].resource_type.resource_name
module.server[2].resource_type.resource_name
...

Адреса ресурсов модуля, созданные с помощью мета-аргумента count


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


module "server-alpha" {    
    source        = "./module_server"
    some_variable = some_value
}
module "server-beta" {
    source        = "./module_server"
    some_variable = some_value
}

Обратите внимание на аргумент источника — он остается прежним, это тот же исходный модуль


В этом случае имя или адрес ресурсов будет следующим:


module.server-alpha.resource_type.resource_name

module.server-beta.resource_type.resource_name

Явное раскрытие ресурсов


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


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



Поэтому мы должны объявить значение output в «сетевом» модуле, чтобы экспортировать его ресурс или атрибут ресурса в другие модули.


Модуль «сервер» должен объявить variable, которая будет использоваться позже в качестве входных данных:



Имена output и variable могут отличаться, но для ясности я предлагаю использовать одни и те же имена.


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


Затем, когда мы вызываем дочерний модуль «сервер» в корневом модуле, мы должны назначить вывод из «сетевого» модуля переменной модуля «сервер»:


network_id = module.network.network_id

Обратите внимание на выходной адрес 'network_id' здесь — мы указываем, где он находится


Вот как будет выглядеть окончательный код для вызова наших дочерних модулей:


module "server" {
    count         = 5
    source        = "./module_server"
    some_variable = some_value
    network_id    = module.network.network_id
}

module "network" {  
    source              = "./module_network"
    some_other_variable = some_other_value
}

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


Подведение итогов


Теперь вы должны понять, что такое модули и для чего они нужны.


Если вы находитесь в самом начале пути к Terraform, вот несколько советов по дальнейшим действиям.


Я рекомендую вам ознакомиться с этим коротким руководством от HashiCorp, создателя Terraform, о модулях: "Organize Configuration".


Кроме того, есть отличное комплексное учебное пособие, охватывающее все, от новичка до продвинутых понятий о Terraform: "Study Guide — Terraform Associate Certification".


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


Если вам понравилась статья, подпишитесь на меня в Twitter (@vasylenko), где я иногда делюсь своими выводами и советами по Terraform, AWS, Ansible и другим технологиям, связанным с DevOps.