… И почему вам, вероятно, не следует их делать

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

Поскольку не за горами terraform v0.12, стоит взглянуть на то, как мы использовали terrafor v0.11. Если вы (ab) использовали любой из перечисленных ниже элементов, вероятно, лучше было бы еще раз просмотреть этот код при подготовке к новой версии.

Молниеносное введение в Terraform и IaC

Если вы не знакомы, terraform - это инструмент для инфраструктуры, который позволяет нам создавать нашу инфраструктуру в виде кода (IaC). Код в традиционном смысле, вероятно, вызовет мысли о функциях и циклах в умах людей, имеющих опыт работы с языками программирования общего назначения. Однако не так быстро, поскольку terraform (и многие другие инструменты IaC) не совсем обладают функциями языка программирования общего назначения. Вместо этого мы полагаемся и зависим от них, чтобы позволить нам кодировать то, что мы хотим, чтобы наша инфраструктура выглядела декларативно.

В качестве чрезмерного упрощения декларативный способ попросить кого-нибудь дать вам сэндвич с арахисовым маслом и желе будет выглядеть так: «Дайте мне бутерброд с арахисом и желе». Совершенно наоборот, необходимо перечислить этому человеку каждый шаг, который требуется для создания сэндвича с арахисовым маслом и желе.

resource "sandwich" "mysandwich" {
  name = "My Sandwich"
  type = "PB&J"
}

Здесь мы просим terraform создать наш сэндвич-ресурс. Код в terraform и его sandwich провайдер найдут нож для масла, найдут ложку, вычерпывают желе и т. Д. И т. Д. И т. Д. Все, что нам нужно знать и объявить, - это наши ожидания после операции. В конце мы ожидаем, что запуск кода будет идемпотентным - он не должен вызывать изменение, если текущее состояние уже соответствует объявленному состоянию. Если сэндвич уже существует, он не должен создавать новый (если, конечно, мы не укажем другой ресурс для другого сэндвича).

Благодаря этому terraform отлично подходит не только для управления инфраструктурой, но и в качестве живого документа, описания вашей инфраструктуры. Это наша цель - живой, дышащий способ управления инфраструктурой с легкостью справки. Нетрудно догадаться, подключился ли кто-то по ssh к машине для последнего изменения или обновил оперативную память в консоли Cloud Provider Console, поскольку terraform поддерживает все это для нас, одновременно выступая в качестве живой документации.

Глупые вещи, которых нельзя делать # 1: FizzBuzz

Иногда люди видят часть кода Infrastructure-as-Code и ожидают использования традиционного языка программирования общего назначения (например, JavaScript, Python, Ruby и т. Д. И т. Д.). Незнание таких инструментов, как terraform, может привести к сценариям, в которых человек сопоставляет часть кода с тем, что он знает.

Хотя у меня (еще) не было кого-нибудь, кто попросил бы меня написать Towers of Hanoi, популярную задачу по программированию для собеседований, у меня действительно был кто-то, кто просил меня создать Fizz Buzz в терраформе. Мне не нужно говорить вам, что это не очень разумная просьба. :)

На всякий случай вот одно решение:

provider "template" {
  version = "~> 2.1"
}
variable "numbers" {
  default = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
}
data "template_file" "fizzbuzz" {
  template = <<EOF
%{ for number in split(",", numbers) ~}
$${number % 3 == 0 ? "Fizz" : "" ~}
$${number % 5 == 0 ? "Buzz" : "" ~}
$${number % 5 == 0 || number % 3 == 0 ? "" : number}
%{ endfor ~}
EOF
vars = {
    numbers = "${join(",", compact(var.numbers))}"
  }
}
output "result" {
  value = "${data.template_file.fizzbuzz.rendered}"
}

Выше вы можете видеть, что нам нужно сначала сериализовать наш массив чисел, введенный пользователем. Значения, используемые в шаблонах с terraform, должны быть примитивами.

Обратите внимание, что все переменные должны быть примитивами. Прямые ссылки на списки или карты вызовут ошибку проверки . - Справочник по ресурсам файла шаблона Terraform

Затем мы должны split() переданные данные, чтобы превратить их обратно в массив для использования с нашим циклом for. Мы будем печатать «fizz» для чисел, кратных 3, «buzz» для чисел, кратных 5, и только число, если число не делится ни на 3, ни на 5.

Это не то, что вам следует делать с terraform. Никогда.

Terraform и Infrastructure-as-Code в целом должны быть удобочитаемыми и надежными. «Умное» использование, такое как сериализация и десериализация для обхода языковых ограничений, не является разумным и особенно непонятным для следующего человека, обслуживающего инфраструктуру. Наш вариант использования fizzbuzz (вместо, скажем, управления инфраструктурой) более чем вероятно выходит за рамки целей команды terraform. Мы не можем быть уверены, что эта «сообразительность» продолжит работать.

Краткое объяснение слова `count`

Terraform имеет параметр count, который можно использовать с ресурсами для создания экземпляров 0 или более ресурсов. Это отлично подходит для создания количества одинаковых ресурсов.

Распространенная путаница, с которой вы можете столкнуться (или даже развлечь себя), заключается в том, что счетчик можно использовать с другими элементами, такими как модули.

Не может. count предназначен для ресурсов.

Указание ресурса с count, равным 0, может использоваться, и часто используется, как способ условно включаемых частей инфраструктуры. Ресурс с count = 0 находится в очень своеобразном состоянии - он существует как определение ресурса, и вы не можете создать ресурс с тем же именем ресурса terraform, но вы не можете ссылаться на него или использовать его.

variable "vegetarian" {
  default = true
}
resource "sandwich" "ham" {
  count = "${var.vegetarian == true ? 0 : 1}"
}
resource "sandwich" "tofurkey" {
  count = "${var.vegetarian == true ? 1 : 0}"
}
resource "meal" "lunch" {
  sandwich = "${sandwich.tofurkey.id}"
}

Приведенный выше код предназначен для создания бутерброда для нашего пользователя. Оба сэндвича, ветчина или тофуркей, существуют как ресурсы, когда terraform их проверяет. Во время выполнения будет создан только один, управляемый значением var.vegetarian.

Как показано выше, наш пример псевдокода создаст вегетарианский бутерброд для нашего пользователя-вегетарианца. Что произойдет, если вы перевернете флаг vegetarian?

Сэндвич tofurkey, упомянутый для обеда, больше не существует, и терраформирование выдаст ошибку. Вы не можете использовать inline, если, например, sandwich.tofurkey.id || sandwich.ham.id, и terraform выдаст ошибку. В большинстве случаев любая очевидная вещь, которую вы можете попробовать, приведет к ошибке терраформирования.

Объекты, которые terraform использует для управления ресурсами, просто не предназначены для этого. Они проверяются и отправляются провайдеру с его собственной проверкой. Вы получите ссылку на несуществующий ресурс.

Глупые вещи, которых не следует делать # 2: злоупотребление `count`

Если вы решили, что вам абсолютно необходимо злоупотреблять подсчетом динамических ресурсов, существует ужасное решение: element(concat(a, b, 0)).

concat работает со списками и получит неопределенный или пустой список без ошибок по какой-либо причине в рамках реализации.

Это , вероятно, не должно работать, но работает:

variable "vegetarian" {
  default = true
}
resource "sandwich" "ham" {
  count = "${var.vegetarian == true ? 0 : 1}"
}
resource "sandwich" "tofurkey" {
  count = "${var.vegetarian == true ? 1 : 0}"
}
resource "meal" "lunch" {
  sandwich = "${element(concat(sandwich.tofurkey.*.id, sandwich.ham.*.id), 0)}"
}

Неопределенный ресурс не произойдет, и ссылка «провалится» без добавления каких-либо элементов в объединенный список. Функция element() выберет первый элемент.

Это ужасно непонятная вещь, которая откровенно злоупотребляет языковыми функциями и реализациями встроенных модулей. Маловероятно, что разработчики terraform сочтут это допустимым использованием terraform, и вы рискуете, что это больше не будет работать в будущих версиях.

Глупые вещи, которых не следует делать # 3: злоупотребляйте функциями для создания динамических вложенных блоков

Ресурсы в terraform могут включать вложенные блоки. Чтобы продолжить наш пример сэндвича:

resource "sandwich" "tofurkey" {}
resource "sandwich" "ham" {
  extras {
    cheese = "cheddar"
  }
}
resource "sandwich" "roastbeef" {
  extras {
    cheese = "swiss"
  }
}
resource "sandwich" "special" {
  extras {
    cheese = "${var.cheese}"
  }
}

Помните о нашем стремлении быть декларативным. Эти вложенные блоки создают дополнительные функции в наших ресурсах, они проверяются и передаются поставщику ресурсов, а в некоторых случаях являются необязательными.

Нижестоящий провайдер может принять наш сэндвич с тофурки без объявления extras. Он также может поддерживать только типы сыра swiss или cheddar и отклонять gouda просто потому, что поставщик (пока) не поддерживает его.

В нашем специальном сэндвиче используется переменная для cheese. Эта переменная из-за проверки должна быть swiss или cheddar и не иметь другого значения. В terraform нельзя отменить определенную переменную. Если нам нужен специальный бутерброд без сыра, terraform не может описать этот сценарий - если none не реализован поставщиком и cheese не будет проверяться никакими значениями, кроме swiss или cheddar, вы останетесь только с злоупотреблением языком.

Это то, что сбивает с толку многих пользователей terraform, у которых разный опыт работы с разными провайдерами. Инженер по внедрению профессиональных услуг GitLab запутался, утверждал, что это возможно в terraform v0.11, и из-за этого стоил кому-то еще должности. Если вы ветеран DevOps 1 год и проработали менее 6 месяцев, то это понятно, но не забывайте о своих действиях по отношению к другим людям.

Не будь этим человеком. Следующая версия terraform будет поддерживать динамические вложенные блоки. :)

Кто-то неизбежно будет сотрудничать с вами (или без него) в написанном вами коде терраформирования. Любой из вышеперечисленных «уловок» следует применять предельно консервативно - в идеале - ни в коем случае. :)