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

self.

Этот специальный синтаксис можно использовать исключительно в блоках connec­tion и provisioner для обращения к выходному атрибуту родительского ресурса. Если применить стандартный синтаксис вида aws_instance.example., получится ошибка циклической зависимости, так как ресурс не может ссылаться на самого себя. Таким образом, выражение self является обходным решением, которое было добавлено специально для средств инициализации ресурсов.

Если выполнить для этого кода команду terraformapply, получится следующее:

$ terraform apply

(...)

aws_instance.example: Creating...

aws_instance.example: Still creating... [10s elapsed]

aws_instance.example: Still creating... [20s elapsed]

aws_instance.example: Provisioning with 'remote-exec'...

aws_instance.example (remote-exec): Connecting to remote host via SSH...

aws_instance.example (remote-exec): Connecting to remote host via SSH...

aws_instance.example (remote-exec): Connecting to remote host via SSH...

(... repeats a few more times ...)

aws_instance.example (remote-exec): Connecting to remote host via SSH...

aws_instance.example (remote-exec): Connected!

aws_instance.example (remote-exec): Hello, World from Linux x86_64 x86_64

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.


Сравнение средств инициализации ресурсов и пользовательских данных

Вы познакомились с двумя разными способами выполнения скриптов на сервере с помощью Terraform: один с использованием средства инициализации remote-exec, а другой — с применением скрипта пользовательских данных. Последний мне кажется более полезным по нескольким причинам.

Средство инициализации remote-exec требует открытия доступа к вашим серверам по SSH или WinRM, что усложняет управление (как вы сами могли убедиться, поработав с группами безопасности и SSH-ключами) и ухудшает безопасность по сравнению с пользовательскими данными, которым нужен лишь доступ к API AWS (он требуется в любом случае для использования Terraform).

Вы можете применять скрипты пользовательских данных в сочетании с группами ASG. Благодаря этому все серверы в заданной группе ASG (включая те, что запускаются в результате автомасштабирования или автовосстановления) выполнят скрипт во время своей загрузки. Средства инициализации ресурсов действуют только во время работы Terraform, но не с ASG.

Скрипт пользовательских данных можно видеть в консоли EC2 (выберите сервер и щелкните ActionsInstance SettingsView/Change User Data (ДействияНастройки сервераПросмотреть/изменить пользовательские данные)), а его журнал выполнения можно найти на самом сервере EC2 (обычно в файле /var/log/cloud-init*.log). Обе возможности полезны при отладке, и ни одна из них не доступна для средств инициализации ресурсов.

У средств инициализации ресурсов всего два преимущества.

Максимальный размер скриптов пользовательских данных равен 16 Кбайт, тогда как скрипты инициализации ресурсов могут быть произвольной длины.

Chef, Puppet и Salt автоматически устанавливают, конфигурируют и запускают на сервере соответствующие клиенты. Благодаря этому для настройки серверов проще использовать инструменты управления конфигурацией вместо специализированных скриптов.

Средство инициализации remote-exec не знает, когда именно сервер EC2 завершит загрузку и будет готов принимать соединения, поэтому оно периодически пытается подключиться по SSH, пока у него это не получится или пока не истечет время ожидания. Время ожидания по умолчанию равно пяти минутам, но вы можете его сконфигурировать. Рано или поздно соединение будет установлено и вы получите от сервера ответ Hello, World.

Следует отметить, что по умолчанию средство инициализации действует во время создания ресурсов. Это означает, что оно выполняется вместе с командой terraformapply и только во время начального создания ресурса. Оно не будет срабатывать при всех последующих запусках terraformapply, поэтому его основное применение — выполнение кода первоначальной инициализации. Если в средстве инициализации ресурсов указать when="destroy", оно будет действовать во время их удаления, то есть: а) после выполнения terraformdestroy и б) непосредственно перед удалением ресурса.

Вы можете указать несколько средств инициализации для одного и того же ресурса, и Terraform запустит их по очереди, сверху вниз. Чтобы объяснить Terraform, как обрабатывать ошибки, полученные в результате инициализации, можно использовать аргумент on_failure: если присвоить ему "continue", Terraform проигнорирует ошибку и продолжит создание/удаление ресурса; если присвоить ему "abort", Terraform прервет создание/удаление.


Средства инициализации ресурсов с использованием null_resource

Средства инициализации можно определять только внутри ресурса, но иногда при их выполнении хочется обойтись без привязки к определенному ресурсу. Это можно сделать с помощью сущности под названием null_resource, которая ведет себя как обычный ресурс Terraform, но при этом ничего не создает. Определив средство инициализации для null_resource, вы можете запустить свой скрипт в рамках жизненного цикла Terraform, не привязываясь ни к какому «настоящему» ресурсу:

resource "null_resource" "example" {

  provisioner "local-exec" {

    command = "echo \"Hello, World from $(uname -smp)\""

  }

}

У null_resource есть удобный аргумент под названием triggers, который принимает ассоциативный массив с ключами и значениями. При любом изменении значений ресурс null_resource будет создаваться заново, запуская тем самым все средства инициализации, которые в нем определены. Например, если вы хотите выполнять средство инициализации внутри null_resource при каждом запуске команды terraformapply, можете воспользоваться встроенной функцией uuid(). При каждом вызове внутри аргумента triggers она возвращает новый, свежесгенерированный идентификатор UUID:

resource "null_resource" "example" {

  # Используйте UUID, чтобы ресурс null_resource принудительно

  # создавался заново при каждом вызове 'terraform apply'

  triggers = {

    uuid = uuid()

  }

  provisioner "local-exec" {

    command = "echo \"Hello, World from $(uname -smp)\""

  }

}

Теперь при каждом выполнении terraformapply будет запускаться средство инициализации local-exec:

$ terraform apply

(...)

null_resource.example (local-exec): Hello, World from Darwin x86_64 i386

$ terraform apply

null_resource.example (local-exec): Hello, World from Darwin x86_64 i386


Внешний источник данных

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

Этот протокол работает определенным образом.

• Вы можете передавать данные из Terraform во внешнюю программу, используя аргумент query источника данных external. Внешняя программа может читать эти аргументы из стандартного ввода в виде JSON.

• Внешняя программа может передавать данные обратно в Terraform, записывая JSON в стандартный вывод. Остальной код Terraform может извлекать эти данные из JSON с помощью выходного атрибута result, принадлежащего внешнему источнику данных.

Вот пример:

data "external" "echo" {

  program = ["bash", "-c", "cat /dev/stdin"]

  query = {

    foo = "bar"

  }

}

output "echo" {

  value = data.external.echo.result

}

output "echo_foo" {

  value = data.external.echo.result.foo

}

В этом примере источник данных external используется для выполнения bash-скрипта, который возвращает обратно в стандартный вывод любые данные, полученные из стандартного ввода. Таким образом, любое значение, которое мы передадим через аргумент query, должно вернуться без изменений в виде выходного атрибута result. Вот что получится, когда мы выполним terraformapply для этого кода:

$ terraform apply

(...)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

echo = {

  "foo" = "bar"

}

echo_foo = bar

Как видите, data.external..result содержит ответ в формате JSON, возвращенный внешней программой, и вы можете перемещаться по нему с помощью синтаксиса вида data.external..result. (например, data.exter­nal.echo.result.foo).

Источник данных external — прекрасный «аварийный люк» на случай, когда вам нужно обращаться к данным в своем коде Terraform и у вас нет такого источника, который бы умел извлекать эти данные. Но не переусердствуйте, используя его и другие «аварийные люки», так как они делают ваш код менее переносимым и более хрупким. Например, источник данных external, который вы только что видели, использует bash. Значит, вы не сможете развернуть этот модуль Terraform из Windows.


Резюме

Итак, вы познакомились со всеми компонентами, необходимыми для создания кода Terraform промышленного уровня. Подытожим. В следующий раз, когда вы начнете работать над новым модулем, выполните такие действия.

1. Пройдитесь по списку задач для подготовки инфраструктуры промышленного уровня, представленному в табл. 6.2, и определите, какие пункты вы будете реа­лизовывать, а какие нет. Сопоставьте полученный результат с табл. 6.1, чтобы озвучить начальству примерные сроки выполнения.

2. Создайте папку examples и сначала напишите демонстрационный код. Выработайте на его основе максимально удобный и аккуратный API для своих модулей. Создайте по одному примеру для каждого важного сценария применения вашего модуля. Добавьте документацию и предусмотрите разумные значения по умолчанию, чтобы ваш пример было как можно легче развертывать.