Skip to content

3.2 Terraform

Знакомство с Terraform

  • Инструмент для декларативного описания инфраструктуры
  • Описание инфраструктуры хранится в конфигурационных .tf-файлах
  • State
    • Ваши ресурсы не знают о том что они созданы через терраформ
    • Терраформ без state'a не знает ничего о вашей инфраструктуре
1
2
3
4
5
6
7
terraform -h

terraform init
terraform plan
terraform apply
terraform destroy
terraform show

Note

Провайдер это уже кем то написанная программа которая позволяет вам на языке Терраформа общаться с какой то системой где вы будете делать свою инфраструктуру

provider "name" {
    key = value
}

resource "type" "name" {
    key = value
}

variable "name" {
    type = ""
    default = ""
}

Troubleshooting

  • terraform validate
  • terraform console
  • TF_LOG="DEBUG"
  • TF_LOG_FILE

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

У каждого провайдера свои ресурсы и у каждого ресурса свой набор аргументов

Но есть общие мета аргументы

  • provisioner (запуск чего то сразу после выполнения например ansible (лучше использовать cloud init))
  • depends_on (неявные зависимости порядок создания ресурсов)
  • count (счетчик создание нескольких одинаковых ресурсов)
  • for_each (циклы с разными переменными)
  • provider
  • lifecycle (Внутри блока lifecycle имеются следующие аргументы create_before_destroy, prevent_destroy, ignore_changes, и replace_triggered_by.)

Пример создания ВМ на Яндекс Облаке

export YC_TOKEN="$(yc iam create-token)"
terraform {
  required_providers {
    yandex = {
      source = "yandex-cloud/yandex"
    }
  }
}

provider "yandex" {
  cloud_id = var.cloud_id
  folder_id = var.folder_id
  zone = var.zone
  # token = var.yc_token
}

resource "yandex_compute_instance" "vm-1" {
  name = "terraform1"

  resources {
    cores = 2
    memory = 2
  }

  boot_disk {
    initialize_params {
      image_id = var.image_id
    }
  }

  network_interface {
    subnet_id = yandex_vpc_subnet.subnet-1.0.id
    nat = true # чтобы машине был выдан внешний IP-адрес
  }

  metadata = {
    ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
  }

  lifecycle {
    prevent_destroy = true
    ignore_changes = [boot_disk]
  }

}

resource "yandex_vpc_network" "network-1" {
  name = "network1"
}

resource "yandex_vpc_subnet" "subnet-1" {
  count = 3
  name = "subnet-${count.index}"
  zone = "ru-central1-a"
  network_id = yandex_vpc_network.network-1.id
  v4_cidr_blocks = ["192.168.${count.index+10}.0/24"]
}

output "internal_ip_address_vm_1" {
  value = yandex_compute_instance.vm-1.network_interface.0.ip_address
}

output "external_ip_address_vm_1" {
  value = yandex_compute_instance.vm-1.network_interface.0.nat_ip_address
}
1
2
3
4
zone = "ru-central1-a"
folder_id = "xxxxxx"
cloud_id = "xxxxxx"
image_id = "fd8tkfhqgbht3sigr37c"
variable "zone"{
  type = string
}

variable "cloud_id"{
  type = string
}

variable "folder_id"{
  type = string
}

variable "image_id"{
  type = string
}

# variable "yc_token"{
  # type = string
# }

Дополнительные ресурсы

  • local
  • random
  • null
main.tf
1
2
3
4
5
6
7
8
resource "random_password" "rnd" {
  length = 16
}

output "password" {
  value = random_password.rnd.result
  sensitive = true
}

Строковые шаблоны

Например можно сгенерировать инвентори для ансибла

[lbs]
%{ for i in range(length(names)) ~}
%{ if names[i] ==  "lb" ~}
${names[i]} ansible_host=${addrs[i]} ansible_user=${user}
%{ endif ~}
%{ endfor ~}

[apps]
%{ for i in range(length(names)) ~}
%{ if split("-", names[i])[0] ==  "app" ~}
${names[i]} ansible_host=${addrs[i]} ansible_user=${user}
%{ endif ~}
%{ endfor ~}
variable "instances" {
  type = list(string)
  default = [
    "app-1",
    "app-2",
    "lb"
  ]
}

variable "user" {
  type    = string
  default = "ubuntu"
}
resource "yandex_compute_instance" "vm-app" {
  for_each = toset(var.instances)
  name     = each.key

  allow_stopping_for_update = true
  scheduling_policy {
    preemptible = true
  }

  resources {
    core_fraction = 5
    cores         = 2
    memory        = 4
  }

  boot_disk {
    initialize_params {
      image_id = var.image_id
    }
  }

  network_interface {
    subnet_id = var.subnet_id
    nat       = true
  }

  metadata = {
    ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
  }

  connection {
    type        = "ssh"
    host        = self.network_interface.0.nat_ip_address
    user        = "ubuntu"
    agent       = false
    private_key = file("~/.ssh/id_rsa")
  }
}

locals {
  names = values(yandex_compute_instance.vm-app)[*].name
  ips   = values(yandex_compute_instance.vm-app)[*].network_interface.0.nat_ip_address
}

resource "local_file" "ansible_inventory" {
  content = templatefile("hosts.tpl", {
      names = local.names,
      addrs = local.ips,
      user = var.user
  })
  filename = "inventory"
}

Управление стейтом

  • Хранение state в s3
  • Использовать блокировки для предотвращения перезатирания
  • Можно указывать remote state как data source

Data-sources

  • Получает информацию о других ресурсах в облаке созданных не нами
1
2
3
data "yandex_vpc_network" "default" {
    name = var.network_name
}

Модули

resource "google_compute_instance" "app" {
    name = "example-instance-${count.index + 1}"
    machine_type = "f1-micro"
    count = var.instances_count
    boot_disk {
        initialize_params {
            image = var.app_image
        }
    }
    network_interface {
        network = "default"
        access_config {}
    }
}
1
2
3
4
5
module "app" {
    source = "../modules/app/main.tf"
    instances_count = 2
    app_image = "myapp-centos-8"
}

depends_on

  • Список ресурсов и модулей от которых зависит текущий ресурс или модуль
  • Указывается если нельзя указать агрумент зависящий от другого ресурса

lifecycle

  • create_before_destroy
  • prevent_destroy (запретить удаление)
  • ignore_changes

provisioner

  • Выполнение действий выходящих за рамки апи ресурсов
  • Также для многих облаков существует специальный аргумент userdata, metadata
resource "aws_instance" "web" {
# ...

  provisioner "file" {
    source      = "script.sh"
    destination = "/tmp/script.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "/tmp/script.sh args",
    ]
  }
}