Прошло уже почти 5 лет с тех пор, как я избрал Terraform в качестве одного из основных наборов инструментов DevOps и облачной инженерии для управления сервисами моих проектов на AWS, Azure, OpenStack, Github и Datadog. Его очень удобно использовать благодаря простому и понятному человеку синтаксису, а также огромному количеству модулей в реестре HashiCorp. Кроме того, множество поддерживаемых сервисов стали очень популярными среди DevOps и Cloud инженеров, ведь у вас в итоге может быть всего один инструмент для выполнения всех ваших ежедневных IaC рутин.

В этой статье я поделюсь своим опытом в отношении соглашений об именовании при написании стандартных блоков кода в Terraform. Некоторые из них стремятся улучшить стандартный файл Terraform, а некоторые необходимо учитывать из-за скрытых ограничений в соглашениях об именовании в API поставщиков, таких как AWS и Azure.

Общие соглашения

  • Всегда используйте _ (нижнее подчеркивание) вместо - (тире)

resource "aws_db_instance" "dev_db" {
  ...
  name                 = "backend_db_instance"
  ...
}
  • Используйте только буквы в нижнем регистре и цифры:

resource "aws_key_pair" "ops" {
  key_name   = "roozbeh_key_1"
  public_key = "ssh-rsa ..."
}

Соглашения о ресурсах и источниках данных

  • Не дублируйте тип ресурса в имени ресурса (ни частично, ни полностью):

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
  ...
}
  • Имя ресурса должно быть проименовано this, если не удается подобрать более описательного и общего имени:

resource "aws_nat_gateway" "this" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.example.id
  ...
}
  • В качестве имен всегда используйте существительные в единственном числе:

resource "aws_eip" "loadbalancer" {
  instance = aws_instance.web.id
  vpc      = true
}
  • Использование — внутри значений аргументов и в местах, где значение будет продемонстрировано человеку (например, внутри DNS-имени инстанса RDS или записи Route 53):

resource "aws_route53_record" "www" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "www.example-domain.com"
  type    = "A"
  ttl     = "300"
  records = [aws_eip.lb.public_ip]
}
  • Добавляйте аргумент count в самый верх блока ресурсов и отделяйте его от остального новой строкой для наглядности:

resource "aws_instance" "web" {
  count = "5"
  
  ...
}
  • Добавляйте аргумент tags, если он поддерживается ресурсом, в качестве последнего реального аргумента, после которого следуют depends_on и lifecycle при необходимости. Все они должны быть разделены одной пустой строкой:

resource "aws_nat_gateway" "this" {
  count         = "1"

  ...

  tags = {
    Name = "..."
  }

  depends_on = ["aws_internet_gateway.this"]

  lifecycle {
    create_before_destroy = true
  }
}
  • При использовании условия в аргументе count по возможности используйте какое-либо логическое значение, в противном случае используйте length или другую интерполяцию:

count = "${length(var.public_subnets) > 0 ? 1 : 0}"
  • Чтобы сделать инвертированные условия, не создавайте новую переменную, если в этом нет реальной необходимости, используйте вместо этого 1 - boolean value:

count = "${1 - var.create_public_subnets}"

Соглашения о переменных

  • Не изобретайте колесо в ресурсных модулях — используйте те же имена переменных, описание и значение по умолчанию как определено в разделе «Argument Reference» для ресурса, с которым вы работаете.

  • Используйте объявление type = "list", если default = []:

variable "availability_zone_names" {
  type    = list(string)
  default = [
    "eu-central-1a"
    "eu-central-1b"
    "eu-central-1c"
  ]
}
  • Используйте объявление type = "map", если default = {}:

variable "images" {
  type = "map"

  default = {
    eu-central-1 = "image-1234"
    eu-west-1    = "image-4567"
  }
}
  • Используйте множественное числа в именах переменных типа list и map:

variable "users" {
  type    = "list"
  ...
}
variable "images" {
  type = "map"
  ...
}
  • При определении переменных порядок следующий: description , type, default. Всегда добавляйте description для всех переменных, даже если вы думаете, что это очевидно.

variable "key" {
  description = "description"
  type        = "string"
  default     = "value"
}

Выводы (Outputs)

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

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

  • Хорошая структура для имен вывода выглядит так - {имя}_{тип}_{атрибут}, где:

1. {имя} - имя ресурса или источника данных без префикса поставщика. {имя} для aws_subnet - это subnet, а для aws_vpc - vpc.

2. {тип} - это тип источника ресурса.

3. {атрибут} является атрибутом возвращаемого вывода.

  • Если выход возвращает значение с интерполяционными функциями и множеством ресурсов, {имя} и {тип} должны быть максимально стандартизованы (this зачастую является наиболее общим и поэтому предпочтительным).

  • Если возвращаемое значение является списком, оно должно иметь имя во множественном числе.

  • Всегда включайте description для всех результатов, даже если вы думаете, что это очевидно.


Материал подготовлен в рамках курса «Infrastructure as a code». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.

Комментарии (9)


  1. ghostinushanka
    07.08.2021 02:40
    +5

    То что вы перевели, наглая копипаста с гитбука Антона Бабенко — www.terraform-best-practices.com/naming, причём на хабре об уже была публикация, гораздо более подробная habr.com/en/post/517804.

    Пиар он такой…


    1. aelaa
      07.08.2021 10:46

      Не читал ни Антона, ни оригинал, но правила как-то сами организовались примерно такие же. Может они достаточно самоочевидные?


      1. ghostinushanka
        07.08.2021 11:41

        Тут дело не в самоочевидности, а в том что «оригинал» имеет в себе целые секции, которые «выделили, скопировали и вставили» 1 в 1.
        Ну и например называть ресурсы «this» это вам самоочевидно настолько, что вы до этого сами додумались в разрез в документацией Hashicorp где в примерах эта конструкция не используется вообще ни разу?


        1. aelaa
          07.08.2021 11:44

          про this я как раз упомянул ниже) Я только про то, что правила вполне выводятся сами, не потому что кто-то сказал

          А с прямым переводом нисколько не спорю


  1. KGeist
    07.08.2021 05:01

    >вместо - (тире)

    Это дефис.


    1. KGeist
      07.08.2021 18:08

      Если посмотреть код символа, это Hyphen-Minus.

      В оригинальной статье автор использует dash (—), не hyphen (-)


  1. aelaa
    07.08.2021 10:52
    +1

    Имя ресурса должно быть проименовано this

    Ну давайте еще переменные в коде this называть. Oh, wait...

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

    Для nat_gateway веселье начнется, когда появится второй. Назовем его that?


    1. ghostinushanka
      07.08.2021 11:43

      Нет, вы переименуете первый и обоим дадите соответствующие названия по смыслу. Это как-раз таки нормальная практика, даже без «best practice» советов.

      Мы до этого сами дошли ещё до того, как модули Антона или книгу Евгения прочитали в своё время. Только вместо «this» мы их «default» называем.


      1. aelaa
        07.08.2021 11:51

        На меня упадет кирпич, и никто это не будет переименовывать.

        Мы называем main, чтобы в случае появления второго можно было уточнить, и оба имени были понятными