Terraform: инфраструктура на уровне кода — страница 42 из 65

Еще одним способом выпуска модулей является их публикация в реестре Terraform. Публичный реестр модулей Terraform находится по адресу registry.terraform.io и содержит сотни универсальных, открытых и поддерживаемых сообществом модулей для AWS, Google Cloud, Azure и многих других провайдеров. Для публикации в нем своих модулей необходимо соблюсти несколько требований53.

• Модуль должен находиться в репозитории GitHub.

• Репозиторий должен иметь название вида terraform--, где PROVIDER — это провайдер, на которого рассчитан модуль (например, aws), а NAME — имя модуля (скажем, vault).

• Модулю нужна определенная структура файлов: код 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. Это выражение со следующим синтаксисом: