База знаний

Настройка Terraform для управления сервисом CLO

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

Возможности Terraform

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

  • Серверы
  • Кластеры СУБД
  • Диски
  • Ресурсы объектного хранилища S3
  • Балансировщики нагрузки
  • Виртуальный роутер
  • Снапшоты сервера
  • Ключи SSH

Установка Terraform

Используйте официальную документацию для установки приложения Terraform.

Провайдер CLO доступен на Github и в официальном репозитории HashiCorp.

Подключение Terraform к сервису

Создайте каталог манифестов, и в нём файл манифеста main.tf для данных о конфигурации Terraform.

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

Примечание. Для ручной установки провайдера обратитесь к соответствующему разделу официальной документации.

Добавьте в файл следующий блок:

terraform {
  required_providers {
	clo = {
  	version = "2.8.0"
  	source = "clo-ru/clo"
	}
  }
}

provider "clo" {
 auth_url = "https://api.clo.ru"
 token = <mysecrettoken>
}

Примечание. Актуальные версии провайдера можно найти в официальной документации, нажав на кнопку USE PROVIDER.

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

Описание плана инфраструктуры 

Опишите в файле main.tf план инфраструктуры. Добавьте описание ресурсов, используя документацию к провайдеру.

Совет. Вы можете использовать примеры из GitHub-репозитория.

Перейдите в каталог, где находится файл манифеста main.tf, и выполните следующую команду:

terraform init

Проверьте, что план инфраструктуры не содержит ошибок:

terraform plan

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

Далее разверните инфраструктуру и создайте необходимые ресурсы командой:

terraform apply

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

Изменение инфраструктуры 

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

terraform apply

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

Удаление инфраструктуры

Чтобы удалить ресурсы, выполните в каталоге с файлом манифеста следующую команду:

terraform destroy

Примечание. Ресурсы инфраструктуры, удалённые с помощью панели управления, не удаляются автоматически из файла манифеста.

Пример плана инфраструктуры 

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

  • два облачных сервера с загрузочным диском из образа Ubuntu 26.04 LTS 64-bit, с произвольной конфигурацией с 2 vCPU и 4 ГБ RAM, диск 40 ГБ, внешний IP-адрес
  • кластер СУБД с базой данных
  • балансировщик нагрузки
  • четыре внешних IP-адреса
  • привязку новых IP-адресов к созданным сущностям на проекте

План описан в виде файла main.tf. В этом файле хранится описание создаваемых ресурсов и объявлены переменные, которые могут затем использоваться в других файлах манифестов.

Файл main.tf

variable "os_name" {
  default = "Ubuntu 26"
}

variable "clo_api_token" {
  description = "API-токен CLO.ru из личного кабинета"
  type        = string
  sensitive   = true
}

variable "ssh_public_key" {
  description = "Публичный SSH-ключ для доступа на серверы"
  type        = string
}

variable "db_admin_username" {
  description = "Имя администратора базы данных"
  type        = string
}

variable "db_admin_password" {
  description = "Пароль администратора базы данных"
  type        = string
  sensitive   = true
}

variable "db_name" {
  description = "Имя базы данных внутри кластера"
  type        = string
  default     = "app_database"
}

# Инициализация Terraform и конфигурации провайдеров
terraform {
  required_providers {
    clo = {
      version = "2.8.0"
      source  = "clo-ru/clo"
    }
  }
}

# Указываем данные для аутентификации
provider "clo" {
  auth_url = "https://api.clo.ru"
  token    = var.clo_api_token
}

# Получаем список проектов
data "clo_projects" "all_projects" {
}

# Получаем список доступных СУБД в проекте
data "clo_dbaas_datastores" "postgres" {
  project_id = local.pr_id
}

locals {
  pr_id                  = data.clo_projects.all_projects.result[0].id
  postgres_datastore_id  = data.clo_dbaas_datastores.postgres.result[0].id
}

# Получаем ID образа операционной системы. Имя образа берём из переменных.
data "clo_project_image" "ubuntu" {
  project_id = local.pr_id
  name       = var.os_name
}

# Создаём SSH-ключ для доступа на серверы
resource "clo_compute_keypair" "ssh_key" {
  project_id = local.pr_id
  name       = "main-ssh-key"
  public_key = var.ssh_public_key
}

# Создаем 4 внешних IP-адреса
resource "clo_network_ip" "external_ips" {

  count      = 4
  project_id = local.pr_id

  # Опционально можно включить DDoS-защиту для критичных IP, раскомментировав строку ниже:
  # ddos_protection = true
}

# Создаем два сервера с нужными параметрами
resource "clo_compute_instance" "web_servers" {
  count = 2

  project_id = local.pr_id
  name       = "web-server-${count.index + 1}"

  # Конфигурация 1 vCPU и 2 GB RAM задается напрямую
  flavor_vcpus = 1
  flavor_ram   = 2

  image_id = data.clo_project_image.ubuntu.image_id
  keypairs = [clo_compute_keypair.ssh_key.id]

  block_device {
    bootable     = true
    size         = 20 # Размер загрузочного диска в ГБ
    storage_type = "volume" # Тип хранилища: "volume" (сетевой) или "local"
  }

  # Привязываем первые два внешних IP к серверам
  addresses {
    address_id      = clo_network_ip.external_ips[count.index].id
    external        = true
    version         = 4
    ddos_protection = false
  }
}

# Создаём кластер СУБД
resource "clo_dbaas_cluster" "main_db" {
  project_id   = local.pr_id
  name         = "main-db-cluster"
  datastore_id = local.postgres_datastore_id # ID СУБД из data source
  storage_size = 10 # Размер хранилища в GiB

  flavor {
    vcpus = 1
    ram   = 2
  }

  # Привязываем третий внешний IP к кластеру БД
  address {
    id = clo_network_ip.external_ips[2].id
  }
}

# Создаём базу данных
resource "clo_dbaas_database" "app_db" {
  cluster_id     = clo_dbaas_cluster.main_db.id
  name           = var.db_name
  admin_username = var.db_admin_username
  admin_password = var.db_admin_password
}

# Создаём балансировщик нагрузки
resource "clo_network_loadbalancer" "web_lb" {
  project_id = local.pr_id
  name       = "web-load-balancer"
  algorithm  = "ROUND_ROBIN"

  # Привязываем четвёртый внешний IP к балансировщику
  address {
    id = clo_network_ip.external_ips[3].id
  }

  # Задаём параметры монитора
  healthmonitor {
    type           = "HTTP"
    delay          = 80  # Минимально допустимое значение интервала
    timeout        = 15  # Минимально допустимое значение таймаута
    max_retries    = 3
    url_path       = "/"
    http_method    = "GET"
    expected_codes = "200"
  }
}

# Правило балансировки должно указывать на внутренний (приватный, fixed) адрес
# конкретного backend-сервера, а не на публичный floating IP и не на адрес самого
# балансировщика — иначе к правилу не привяжется backend-сервер (останется server = null,
# и CLO API будет валить 500 при любом чтении списка правил). Ресурс clo_compute_instance
# не отдаёт этот скрытый внутренний адрес, поэтому берём его через data source.

data "clo_compute_instance" "web_servers" {
  count = 2
  id    = clo_compute_instance.web_servers[count.index].id
}

locals {
  web_server_internal_address_id = [
    for i in range(2) : [
      for addr_id in data.clo_compute_instance.web_servers[i].addresses : addr_id
      if addr_id != clo_network_ip.external_ips[i].id
    ][0]
  ]
}

# Задаём правила балансировки — одно на каждый backend-сервер, оба слушают один и тот же
# внешний порт: LB распределяет трафик между ними по алгоритму ROUND_ROBIN.
resource "clo_network_loadbalancer_rule" "http_rules" {
  count = 2

  loadbalancer_id        = clo_network_loadbalancer.web_lb.id
  address_id             = local.web_server_internal_address_id[count.index]
  external_protocol_port = 80
  internal_protocol_port = 80
}

# Выводим информацию о внешних IP-адресах созданных сущностей

output "load_balancer_ip" {
  description = "Внешний IP-адрес балансировщика нагрузки"
  value       = clo_network_ip.external_ips[3].address
}

output "web_server_ips" {
  description = "Внешние IP-адреса веб-серверов"
  value       = [clo_network_ip.external_ips[0].address, clo_network_ip.external_ips[1].address]
}

output "database_ip" {
  description = "Внешний IP-адрес кластера баз данных"
  value       = clo_network_ip.external_ips[2].address
}