Terraform#

Terraform представляет собой систему автоматизации развертывания и обновления информационной инфраструктуры с использованием доступного для организации оборудования и программного обеспечения. Для описания требуемой инфраструктуры Terraform предлагает декларативный язык на базе HCL.

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

Terraform предоставляет средства разработки собственных модулей в составе корпоративных систем автоматизации. Astra Automation включает в себя модули Terraform для управления информационными инфраструктурами, развертываемыми в различных облачных средах.

Принципы управления#

Принцип работы Terraform показан на схеме:

../../../_images/terraform-schema-light.svg ../../../_images/terraform-schema-dark.svg

Terraform для управления ресурсами использует провайдеры – особые расширения, содержащие сведениях об API нужных сервисов. Как правило, файлы провайдеров размещаются в реестре провайдеров Terraform, однако, могут быть загружены и со сторонних ресурсов.

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

Управление инфраструктурой с помощью Terraform состоит из следующих шагов:

  1. Описание ресурсов в конфигурационных файлах.

  2. Инициализация модулей и провайдеров.

    terraform init
    

    На этом этапе Terraform загружает все необходимые файлы и сохраняет в каталоге проекта в следующие подкаталоги:

    • .terraform/modules/ – модули;

    • .terraform/providers/ – провайдеры.

  3. Проверка плана инфраструктуры.

    Для проверки плана инфраструктуры используется следующая команда:

    terraform validate
    

    На этом этапе Terraform выполняет сверку описания инфраструктуры с формальными правилами, содержащимися в описаниях провайдера, модулей и переменных. Если в конфигурации инфраструктуры есть ошибки, Terraform на них укажет.

  4. Планирование изменений.

    Для получения списка изменений инфраструктуры, которые будут выполнены Terraform, чтобы привести инфраструктуру в соответствие с ее описанием в файлах проекта, используется следующая команда:

    terraform plan
    

    На этом этапе никаких фактических изменений инфраструктуры не происходит.

  5. Применение изменений.

    Для применения изменений используется следующая команда:

    terraform apply
    

Типовая структура проекта Terraform#

В самом простом случае описание инфраструктуры с помощью Terraform хранится в одном или нескольких файлах с расширением .tf, содержащих описания следующих сущностей:

  • общие настройки;

  • описание ресурсов.

Общие настройки#

К общим настройкам относятся:

  • сведения о провайдерах;

  • учетные данные для управления ресурсами выбранного сервиса.

Провайдер#

Провайдер определяет возможности Terraform по работе с сервисом:

  • параметры доступа к сервису;

  • перечень доступных для управления типов ресурсов;

  • перечень доступных для использования источников данных;

  • правила работы с ресурсами и источниками данных.

Перечень используемых провайдеров задается в блоке terraform.required_providers, например:

terraform {
  required_providers {
    sbercloud = {
      source = "sbercloud-terraform/sbercloud"
    }
  }
}

Здесь:

  • sbercloud – название провайдера;

  • sbercloud-terraform/sbercloud – ссылка на источник для загрузки файлов провайдера.

Учетные данные для работы с ресурсами задаются в блоке provider:

provider "sbercloud" {
  # Настройки доступа к сервису
}

Здесь sbercloud – название провайдера, указанное ранее.

Подробности об использовании провайдеров см. в документации Terraform.

Ресурс#

Ресурсы (виртуальные машины, сети, подсети, кластеры СУБД и т. п.) описываются в блоках resource:

resource "openstack_db_user_v1" "user-1" {
  # Описание ресурса
}

Здесь:

  • openstack_db_user_v1 – тип ресурса;

  • user-1 – название ресурса, используемое в Terraform.

По умолчанию Terraform использует следующий подход при применении изменений конфигурации:

  • создает ресурсы, которые есть в описании инфраструктуры но отсутствуют в файле состояния;

  • удаляет ресурсы, которые есть в файле состояния, но отсутствуют в описании инфраструктуры;

  • обновляет ресурсы, параметры которых изменились;

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

Подробности о ресурсах см. в документации Terraform.

Источник данных#

Источники данных (data sources) позволяют получать сведения о существующих ресурсах.

Источники данных описываются в блоках data, например:

data "yandex_mdb_mysql_cluster" "data-source" {
  name = "mysql-cluster-name"
}

где

  • yandex_mdb_mysql_cluster – тип источника данных (кластер Yandex Managed Service for MySQL);

  • data-source – название источника данных;

  • mysql-cluster-name – название кластера.

Подробности об источниках данных см. в документации Terraform.

Состояние#

Состояние (state) – это информация о фактических существующих в сервисе ресурсах и связи их идентификаторов с описаниями в конфигурационных файлах Terraform.

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

Подробности о состояниях см. в документации Terraform.

Переменные и значения#

Terraform позволяет модулям хранить данные и обмениваться ими друг с другом следующими способами:

  • с помощью локальных значений (Local Values);

  • с помощью переменных, также называемых входными переменными (Input Variables);

  • с помощью выходных значений (Output Values).

Локальные значения#

Локальные значения позволяют многократно ссылаться на одно и то же значение без необходимости вводить его заново. Аналогом локальных значений в обычных языках программирования являются константы.

Локальные значения описываются в блоке locals, например:

locals {
  image_id     = "fd87qct47l4isk56gucq",
  storage_size = 40
}

где image_id и storage_size – названия, а fd87qct47l4isk56gucq и 40 – значения.

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

Для обращения к локальному значению используется следующий синтаксис:

resource "vm" "vm-1" {
  image     = local.image_id
  disk_size = local.storage_size
  # ...
}

Подробности о локальных значениях см. в документации Terraform.

Входные переменные#

Входные переменные (input variables), часто для краткости называемые просто «переменными», позволяют использовать одни и те же значения в разных модулях и .tf-файлах.

Каждая входная переменная описывается в отдельном блоке variable, например:

variable "image_id" {
  type = string
}

Здесь image_id – название переменной, string – ее тип.

Для входной переменной может быть указано значение по умолчанию:

variable "image_id" {
  type    = string
  default = "fd87qct47l4isk56gucq"
}

Для обращения к входной переменной используется следующий синтаксис:

resource "vm" "vm-1" {
  image = var.image_id
  # ...
}

Значения входных переменных, объявленных в корневом модуле, могут быть заданы следующими способами:

  • Значение параметра -var в аргументах командной строки.

  • Объявления в файле с расширением .tfvars.

    Примечание

    Terraform автоматически загружает значения входных переменных из файлов terraform.tfvars и terraform.tfvars.json, а также из файлов, имена которых оканчиваются на .auto.tfvars и .auto.tfvars.json.

  • Переменные среды.

Подробности о входных переменных см. в документации Terraform.

Выходные значения#

Выходные значения (output values) используются для передачи данных за пределы модуля.

Каждое выходное значение описывается в отдельном блоке output, например:

output "bastion_ip" {
  value = vsphere_virtual_machine.virtual_machine.default_ip_address
}

Для обращения к выходной переменной модуля из другого модуля используется следующий синтаксис:

module "vm" {
  # ...
  public_ip = module.bastion.bastion_ip
}

где:

  • bastion – название модуля;

  • bastion_ip – название выходного значения.

Подробности о выходных значениях см. в документации Terraform.

Мета-аргументы#

Мета-аргументы (meta-arguments) используются для добавления в конфигурационные файлы дополнительной функциональности.

depends_on#

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

Пусть имеется следующее описание ресурсов, необходимых для запуска на ВМ приложения, работающего с базой данных PostgreSQL:

# Кластер Yandex Managed Service for PostgreSQL
resource "yandex_mdb_postgresql_cluster" "pg-cluster" {
  # ...
}

# Пользователь кластера "pg-cluster"
resource "yandex_mdb_postgresql_user" "db-owner" {
  cluster_id = yandex_mdb_postgresql_cluster.pg-cluster.id
  # ...
}

# База данных в кластере "pg-cluster"
resource "yandex_mdb_postgresql_database" "db1" {
  cluster_id = yandex_mdb_postgresql_cluster.pg-cluster.id
  name       = "db1"
  owner      = yandex_mdb_postgresql_user.db-owner.name
}

# Виртуальная машина
resource "yandex_compute_instance" "app-vm" {
  name = "app-vm"
  depends_on = [
    yandex_mdb_postgresql_cluster.pg-cluster,
    yandex_mdb_postgresql_user.db-owner,
    yandex_mdb_postgresql_database.db1
  ]

  # Прочие настройки ВМ
}

Между кластером СУБД, базой данных и пользователем есть прямая зависимость – поле cluster_id у базы данных и пользователя обязательно для заполнения. Порядок создания ресурсов в этом случае определяется логикой на стороне провайдера:

  1. Кластер СУБД.

  2. Пользователь СУБД.

  3. База данных.

В то же время, ВМ не зависит от кластера СУБД, базы данных или ее пользователя. Однако, приложение, запущенное на ВМ, не сможет работать до тех пор, пока кластер СУБД, база данных и используемый для доступа к базе данных пользователь не будут созданы.

Блок depends_on в описании ВМ указывает, что она зависит от перечисленных ресурсов и должна быть создана после них.

Подробности об использовании мета-аргумента depends_on см. в документации Terraform.

count#

Мета-аргумент count используется для создания однотипных ресурсов.

Пусть необходимо создать 4 одинаковых ВМ. Соответствующую конфигурацию можно описать без использования мета-аргумента count следующим образом:

resource "sbercloud_compute_instance" "vm-1" {
  name = "vm-1"
  # ...
}

resource "sbercloud_compute_instance" "vm-2" {
  name = "vm-2"
  # ...
}

resource "sbercloud_compute_instance" "vm-3" {
  name = "vm-3"
  # ...
}

resource "sbercloud_compute_instance" "vm-4" {
  name = "vm-4"
  # ...
}

При использовании мета-аргумента count эту запись можно сократить:

resource "sbercloud_compute_instance" "vms" {
  count = 4

  name = "vm-${count.index + 1}"
  # ...
}

Подробности об использовании мета-аргумента count см. в документации Terraform.

for_each#

Мета-аргумент for_each позволяет обрабатывать списки путем последовательного перебора их значений. Это позволяет сократить описание однотипных ресурсов, имеющих различия в конфигурации.

Пусть необходимо создать в одной сети несколько подсетей. Описание инфраструктуры в этом случае может иметь следующий вид:

resource "sbercloud_vpc" "vpc-1" {
  name = "vpc-1"
  cidr = "10.33.0.0/24"
}

resource "sbercloud_vpc_subnet" "subnet-1" {
  vpc_id        = sbercloud_vpc.vpc-1.id
  name          = "subnet-1"
  cidr          = "10.33.10.0/24"
  primary_dns   = "100.125.13.59"
  secondary_dns = "8.8.8.8"
}

resource "sbercloud_vpc_subnet" "subnet-2" {
  vpc_id        = sbercloud_vpc.vpc-1.id
  name          = "subnet-2"
  cidr          = "10.33.20.0/24"
  primary_dns   = "100.125.13.59"
  secondary_dns = "8.8.8.8"
}

resource "sbercloud_vpc_subnet" "subnet-3" {
  vpc_id        = sbercloud_vpc.vpc-1.id
  name          = "subnet-3"
  cidr          = "10.33.30.0/24"
  primary_dns   = "100.125.13.59"
  secondary_dns = "8.8.8.8"
}

Из этой записи видно, что значения полей vpc_id, primary_dns и secondary_dns повторяются.

С помощью for_each описание ресурсов можно значительно сократить:

resource "sbercloud_vpc" "vpc-1" {
  name = "vpc-1"
  cidr = "10.33.0.0/24"
}

locals {
  subnets = {
    "subnet-1" = { cidr = "10.33.10.0/24" },
    "subnet-2" = { cidr = "10.33.20.0/24" },
    "subnet-3" = { cidr = "10.33.30.0/24" }
  }
}

resource "sbercloud_vpc_subnet" "subnets" {
  for_each = local.subnets

  name          = each.key
  cidr          = each.value.cidr

  vpc_id        = sbercloud_vpc.vpc-1.id
  primary_dns   = "100.125.13.59"
  secondary_dns = "8.8.8.8"
}

Подробности об использовании мета-аргумента for_each см. в документации Terraform.

provider#

По умолчанию Terraform определяет нужный тип провайдера по типу ресурса. Мета-аргумент provider позволяет переопределить это поведение, явно указав провайдер, который должен использоваться для управления ресурсами.

Для использования мета-аргумента provider выполните следующие действия:

  1. Добавьте блок provider без поля alias. В этом блоке опишите настройки провайдера, используемого по умолчанию, например:

    provider "yandex" {
      # ...
      zone      = "ru-central1-a"
    }
    
  2. Добавьте блок provider, содержащий поле alias. Опишите специфичные настройки провайдера, а в поле alias укажите псевдоним, который будет использоваться для обращения к нему, например:

    provider "yandex" {
      alias     = "zone-b"
      # ...
      zone      = "ru-central1-b"
    }
    
  3. Добавьте поле provider к описанию ресурса. В значении укажите нужный провайдер в формате <name>.<alias>, где:

    • <name> – название провайдера;

    • <alias> – псевдоним.

    resource "yandex_compute_instance" "vm-1" {
      provider = yandex.zone-b
      # ...
    }
    

Подробности об использовании мета-аргумента provider см. в документации Terraform.

lifecycle#

В секции Ресурс описано стандартное поведение Terraform при применении конфигурации. Мета-аргумент lifecycle позволяет тонко настроить этот процесс.

В значении мета-аргумента lifecycle можно указать значения следующих полей:

  • create_before_destroy

    Тип: bool

    По умолчанию, если Terraform не может обновить ресурс «на лету», происходит следующее:

    1. Существующий ресурс нужного типа удаляется.

    2. Создается новый ресурс, который используется вместо удаленного.

    Мета-аргумент create_before_destroy позволяет изменить этот порядок. При значении true новый ресурс будет создан до того, как удален существующий.

  • prevent_destroy

    Тип: bool

    При значении true этот мета-аргумент блокирует изменения ресурса, для применения которых его необходимо удалить и создать заново. Он особенно полезен, например, для таких ресурсов, как кластеры СУБД, хранилища данных и т. д.

  • ignore_changes

    Тип: список атрибутов

    Когда Terraform обнаруживает различия между описанием ресурса и его фактическим состоянием в сервисе, он пытается изменить состояние ресурса таким образом, чтобы оно соответствовало описанию. Чтобы Terraform игнорировал изменение отдельных атрибутов ресурса, сделанное в сервисе, следует указать названия атрибутов в значении параметра ignore_changes.

    Специальное значение all позволяет Terraform игнорировать любые изменения ресурса.

  • replace_triggered_by

    Тип: список ресурсов или ссылок на атрибуты

    В значении этого мета-атрибута следует указать список ресурсов или атрибутов, при изменении которых ресурс должен быть пересоздан.

Подробнее об управлении жизненным циклом ресурсов см. в документации Terraform.

Подключение модулей#

Terraform позволяет группировать в модули (modules) ресурсы, используемые вместе. Это позволяет переиспользовать существующий код при описании инфраструктуры в новом проекте.

По умолчанию описание всех ресурсов размещается в одном модуле, называемом корневым (root).

Модуль может принимать входящие параметры и возвращать какие-либо значения. Таким образом, модуль может быть настроен для использования в проекте в соответствии с необходимыми требованиями.