Еще одним способом выпуска модулей является их публикация в реестре Terraform. Публичный реестр модулей Terraform находится по адресу registry.terraform.io и содержит сотни универсальных, открытых и поддерживаемых сообществом модулей для AWS, Google Cloud, Azure и многих других провайдеров. Для публикации в нем своих модулей необходимо соблюсти несколько требований53.
• Модуль должен находиться в репозитории GitHub.
• Репозиторий должен иметь название вида terraform-
• Модулю нужна определенная структура файлов: код Terraform должен находиться в корне репозитория, у вас должен быть файл README.md и необходимо соблюдать соглашение об именовании файлов main.tf, variables.tf и outputs.tf.
• Для выпуска кода в репозитории нужно использовать теги Git и семантическое версионирование (x.y.z).
Если ваш модуль отвечает всем этим требованиям, вы можете сделать его доступным для всех желающих. Для этого войдите в реестр модулей Terraform с помощью своей учетной записи GitHub и опубликуйте свой модуль в веб-интерфейсе (рис. 6.4).
Реестр модулей Terraform умеет анализировать входные и выходные параметры модуля, поэтому вы можете видеть их в пользовательском интерфейсе вместе с полями type и description, как показано на рис. 6.5.
Более того, Terraform поддерживает специальный синтаксис для подключения модулей из реестра. Вместо длинного адреса Git с малозаметными параметрами ref вы можете использовать в аргументе source более короткий URL-адрес реестра, указывая версию с помощью отдельного аргумента version. Вот как это выглядит:
module "
source = "
version = "
# (...)
}
NAME — это идентификатор модуля в вашем коде Terraform, OWNER — владелец репозитория в GitHub (например, в github.com/foo/bar таковым является foo), REPO — название GitHub-репозитория (скажем, в github.com/foo/bar это bar), PROVIDER — нужный вам провайдер (предположим, aws), а VERSION — версия модуля, которую вы хотите использовать. Вот пример того, как подключить модуль Vault из реестра Terraform:
module "vault" {
source = "hashicorp/vault/aws"
version = "0.12.2"
# (...)
}
Рис. 6.4. Модуль HashiCorp Vault в реестре Terraform
Рис. 6.5. Реестр Terraform автоматически анализирует и отображает ввод и вывод модуля
Если у вас есть подписка на сервис Terraform Enterprise от HashiCorp, вы можете делать то же самое с закрытым реестром модулей Terraform, который находится в ваших закрытых Git-репозиториях и доступен только вашей команде. Это отличный способ распространения модулей внутри вашей компании.
За пределами возможностей Terraform-модулей
Несмотря на то что эта книга посвящена Terraform, для построения собственной инфраструктуры промышленного уровня понадобятся и другие инструменты: Docker, Packer, Chef, Puppet и, конечно же, старый добрый bash-скрипт. Большую часть этого кода можно разместить в папке modules рядом с кодом Terraform. Например, в репозитории HashiCorp Vault, который вы видели ранее, папка modules содержит не только модули Terraform, как уже упомянутый выше vault-cluster, но и bash-скрипты: вроде run-vault (http://bit.ly/2GTCxg8), который можно выполнять на сервере Linux во время загрузки (скажем, в качестве пользовательских данных) для конфигурации и запуска Vault.
Однако иногда возникает необходимость пойти еще дальше и выполнить внешний код (например, скрипт) прямо из модуля Terraform. Временами это нужно для интеграции Terraform с другой системой (скажем, вы уже применяли Terraform для выполнения скриптов пользовательских данных на серверах EC2), а изредка это попытка обойти ограничения Terraform, вызванные нехваткой какого-то API провайдера или невозможностью реализовать сложную логику в декларативном стиле. Если поискать, в Terraform можно найти несколько «аварийных люков», которые позволяют это сделать:
• средства инициализации ресурсов;
• средства инициализации ресурсов с использованием null_resource;
• внешний источник данных.
Пройдемся по ним по очереди.
Средства инициализации ресурсов
Terraform использует средства инициализации ресурсов во время запуска для выполнения скриптов на локальном или удаленном компьютере. Обычно это связано с компоновкой/очисткой ресурсов или управлением конфигурацией. Таких средств существует несколько, включая local-exec (скрипт в локальной системе), remote-exec (скрипт на удаленном компьютере), chef (запускает Chef Client для удаленного ресурса) и file (копирует файл на удаленный ресурс)54.
Чтобы добавить в ресурс средства инициализации, можно воспользоваться блоком provisioner. Например, ниже показано, как с помощью local-exec выполнить скрипт на локальном компьютере:
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo \"Hello, World from $(uname -smp)\""
}
}
Если выполнить для этого кода команду terraformapply, она выведет текст Hello,Worldfrom, за которым следуют подробности о локальной операционной системе (полученные из команды uname):
$ terraform apply
(...)
aws_instance.example (local-exec): Hello, World from Darwin x86_64 i386
(...)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Для использования средства инициализации remote-exec потребуется чуть больше усилий. Чтобы выполнить код на удаленном ресурсе, таком как сервер EC2, вашему клиенту Terraform придется сделать следующее.
• Связаться с сервером EC2 по сети. Вы уже знаете, как разрешить это с помощью группы безопасности.
• Пройти аутентификацию на сервере EC2. Средство инициализации remote-exec поддерживает протоколы SSH и WinRM. Поскольку вы будете запускать сервер под управлением Linux (Ubuntu), нужно использовать аутентификацию по SSH. Значит, вам необходимо сконфигурировать SSH-ключи.
Для начала создадим группу безопасности, которая разрешает входящие соединения на стандартном для SSH порте под номером 22:
resource "aws_security_group" "instance" {
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
# Чтобы этот пример можно было легко попробовать, мы разрешаем все
# соединения по SSH.
# В реальных условиях вы должны принимать их только с доверенных IP-адресов.
cidr_blocks = ["0.0.0.0/0"]
}
}
Обычно для аутентификации нужно сгенерировать на своем компьютере пару SSH-ключей: открытый и секретный. Первый следует загрузить в AWS, а второй сохранить в надежном месте, откуда к нему сможет обратиться Terraform. Но, чтобы вам проще было попробовать этот пример, можете использовать ресурс tls_private_key, который сгенерирует секретный ключ автоматически:
# Чтобы этот пример можно было легко попробовать, мы генерируем секретный ключ
# в Terraform.
# В реальных условиях управление SSH-ключами следует вынести за пределы Terraform.
resource "tls_private_key" "example" {
algorithm = "RSA"
rsa_bits = 4096
}
Этот секретный ключ хранится в состоянии Terraform, что не очень хорошо для промышленного использования, но подойдет для этого примера. Теперь загрузим открытый ключ в AWS с помощью ресурса aws_key_pair:
resource "aws_key_pair" "generated_key" {
public_key = tls_private_key.example.public_key_openssh
}
Приступим к написанию кода для сервера EC2:
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
key_name = aws_key_pair.generated_key.key_name
}
Первые несколько строк этого кода должны показаться знакомыми: они развертывают Ubuntu Amazon Machine Image (AMI) на t2.micro и привязывают к этому серверу EC2 группу безопасности, которую вы создали ранее. Единственное нововведение — это использование атрибута key_name, который позволяет AWS привязать ваш открытый ключ к серверу EC2. AWS добавит этот ключ в его файл authorized_keys, благодаря чему вы сможете зайти на этот сервер по SSH с соответствующим секретным ключом.
Теперь добавим к серверу EC2 средство инициализации remote-exec:
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
key_name = aws_key_pair.generated_key.key_name
provisioner "remote-exec" {
inline = ["echo \"Hello, World from $(uname -smp)\""]
}
}
Это почти ничем не отличается от средства инициализации local-exec, только вместо одной исполняемой команды (command) здесь с помощью аргумента inline передается их список. В завершение вам нужно сконфигурировать Terraform для подключения к этому серверу EC2 по SSH при запуске remote-exec. Для этого предусмотрен блок connection:
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
key_name = aws_key_pair.generated_key.key_name
provisioner "remote-exec" {
inline = ["echo \"Hello, World from $(uname -smp)\""]
}
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = tls_private_key.example.private_key_pem
}
}
Блок connection заставляет Terraform подключиться к публичному IP-адресу сервера EC2 по SSH с помощью имени пользователя "ubuntu" (так по умолчанию называется корневой пользователь в образах AMI с Ubuntu) и автоматически сгенерированного секретного ключа. Обратите внимание на ключевое слово self, которое применяется для задания параметра host. Это выражение со следующим синтаксисом: