
Привет, друзья!
В этой серии статей я делюсь с вами своим опытом решения различных задач из области веб-разработки и не только.
Другие статьи серии:
Предыдущая статья была посвящена деплою Angular+Java веб-приложения на виртуальном сервере Ubuntu Linux с помощью Ansible. В этой статье мы научимся настраивать для этого деплоя сеть и создавать виртуальный сервер в облаке с помощью Terraform.
Интересно? Тогда прошу под кат.
Итак, наша задача - подготовить облачную инфраструктуру к деплою приложения, а именно:
создать виртуальный сервер Ubuntu Linux с пользователем
ansible
получить внешний IP-адрес сервера для подключения CLI
ansible
В качестве облака мы будем использовать Yandex Cloud. Обратите внимание, что конфигурация Terraform сильно зависит от конкретного облака, поэтому в другом облаке она будет выглядеть иначе.
Предварительные условия:
установка terraform (я буду использовать версию 1.5.7)
OAuth-токен Yandex Cloud (
yc config set token <OAuth-токен>
)
Пара слов о Terraform
Terraform — это инфраструктура как код (IaC) — инструмент с открытым исходным кодом, разработанный компанией HashiCorp, который позволяет управлять и автоматизировать инфраструктуру в облаках и дата-центрах с помощью конфигурационных файлов.
Основные особенности Terraform:
Язык конфигурации
Terraform использует HCL (HashiCorp Configuration Language) — простой и читаемый язык, похожий на JSON. Примеры конфигурации описывают ресурсы, такие как виртуальные машины, базы данных, сети и т.д.
Поддержка множества провайдеров
Terraform может работать с разными облачными провайдерами и сервисами:
AWS
Google Cloud Platform
Microsoft Azure
Yandex Cloud
Kubernetes
и др.
Управление жизненным циклом инфраструктуры
Terraform позволяет:
создавать инфраструктуру (
terraform apply
)изменять существующую конфигурацию
удалять ненужные ресурсы (
terraform destroy
)Планирование изменений
Команда terraform plan
показывает, какие изменения будут внесены в инфраструктуру, прежде чем они будут применены — это снижает риск ошибок.
Идемпотентность
При повторном применении конфигурации Terraform гарантирует, что результат останется тем же — никакого дублирования ресурсов.
Дополнительные материалы:
Terraform: от незнания к best practices (близко к тому, чем мы будем заниматься в этой статье)
Конфигурация Terraform
В домашней директории (/home/<user>
) создаем файл .terraformrc
следующего содержания:
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
Создаем директорию terraform
со следующей структурой:
- main.tf
- provider.tf
- user-data.txt
- variables.tf
- versions.tf
Определяем провайдера в файлах versions.tf
и provider.tf
.
versions.tf
:
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
version = ">= 0.87.0"
}
}
# Хранение состояния Terraform (terraform.tfstate) в бакете (bucket) Yandex Cloud
# backend "s3" {
# endpoint = "https://storage.yandexcloud.net"
# bucket = "terraform-state"
# region = "ru-central1"
# key = "terraform.tfstate"
# skip_region_validation = true
# skip_credentials_validation = true
# }
}
provider.tf
:
provider "yandex" {
cloud_id = var.cloud_id
folder_id = var.folder_id
zone = var.zone
}
Определяем переменные в файле variables.tf
:
variable "cloud_id" {
type = string
# Заменить
default = "b1g..."
}
variable "folder_id" {
type = string
# Заменить
default = "b1g..."
}
variable "zone" {
type = string
default = "ru-central1-a"
}
Определяем данные пользователей в файле user-data.txt
(он потребуется нам позднее):
#cloud-config
users:
- name: ansible
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
ssh_authorized_keys:
# Заменить
- ssh-rsa AAA...
Обратите внимание:
комментарий
#cloud-config
является обязательнымсодержание этого файла зависит от версии Terraform
Определяем основную конфигурацию Terraform в файле main.tf
:
module "instance" {
source = "./modules/tf-yc-instance"
subnet_id = module.network.subnet_ids[var.zone]
}
module "network" {
source = "./modules/tf-yc-network"
}
output "external_ip" {
value = module.instance.external_ip
}
output "internal_ip" {
value = module.instance.internal_ip
}
output "network_id" {
value = module.network.network_id
}
output "subnet_ids" {
value = module.network.subnet_ids
}
Здесь мы видим подключение 2 модулей: instance
для виртуальной машины и network
для сети. Обратите внимание, что мы передаем ID подсети, соответствующий зоне ru-central1-a
(subnet_id
), из модуля network
в модуль instance
. Также мы указываем Terraform после завершения работы вывести в терминал внешний и внутренний IP-адреса сервера, ID сети и подсетей. Из этих значений нам нужен только внешний IP, остальное выводится ради забавы.
Не забудьте добавить файлы состояния Terraform в файл .gitignore
:
*.tfstate
*.tfstate.*
Конфигурация модуля сети
Создаем директорию modules/tf-yc-network
со следующей структурой:
- main.tf
- outputs.tf
- README.md
- variables.tf
- versions.tf
Файл versions.tf
идентичен одноименному файлу в директории terraform
(без блока s3
).
Определяем ресурсы сети и подсети в файле main.tf
:
data "yandex_vpc_network" "default" {
name = "default"
}
data "yandex_vpc_subnet" "default" {
for_each = var.network_zones
name = "default-${each.value}"
}
Определяем переменную в файле variables.tf
:
variable "network_zones" {
description = "Список зон для создания подсетей"
type = set(string)
default = ["ru-central1-a", "ru-central1-b", "ru-central1-d"]
}
Определяем возвращаемые значения в файле outputs.tf
:
output "network_id" {
description = "ID созданной сети"
value = data.yandex_vpc_network.default.id
}
output "subnet_ids" {
description = "ID созданных подсетей"
value = { for zone, subnet in data.yandex_vpc_subnet.default : zone => subnet.id }
}
Разберем, что такое yandex_vpc_subnet
и как будет выглядеть subnet_ids
на выходе.
data "yandex_vpc_subnet" "default"
Этот блок получает существующие подсети по имени default-<zone>
в разных зонах.
Мы используем for_each
по списку зон, значит, получаем несколько подсетей, по одной на каждую зону из переменной network_zones
.
Terraform создаст следующие ресурсы:
data.yandex_vpc_subnet.default["ru-central1-a"]
data.yandex_vpc_subnet.default["ru-central1-b"]
data.yandex_vpc_subnet.default["ru-central1-d"]
Каждый из них будет искать подсеть с именем:
default-ru-central1-a
default-ru-central1-b
default-ru-central1-d
output "subnet_ids"
Этот блок создает словарь (map), где:
ключ — это имя зоны (
ru-central1-a
и т.д.)значение — это
subnet.id
(идентификатор найденной подсети)
Примерный вывод:
subnet_ids = {
"ru-central1-a" = "e2lu123abcde12345678"
"ru-central1-b" = "e2lu456fghij45678901"
"ru-central1-d" = "e2lu789klmno78901234"
}
Этот вывод используется для передачи идентификатора подсети ru-central1-a
в модуль instance
.
Обратите внимание на следующее:
все указанные подсети должны уже существовать в Yandex Cloud с именами
default-ru-central1-a
и т.д. Если хотя бы одной не существует — Terraform выдаст ошибкумы работаем с
data
, а не сresource
, то есть ничего не создается, только читается
Конфигурация модуля сервера
Создаем директорию modules/tf-yc-instance
со следующей структурой:
- main.tf
- outputs.tf
- README.md
- variables.tf
- versions.tf
Файл versions.tf
идентичен одноименному файлу в директории terraform
(без блока s3
).
Определяем создание виртуального сервера vm-1
в файле main.tf
:
resource "yandex_compute_instance" "vm-1" {
name = var.resource_name
platform_id = var.platform_id
resources {
cores = var.cpu_cores
memory = var.memory_size
}
boot_disk {
initialize_params {
image_id = var.image_id
size = var.disk_size
}
}
network_interface {
# Получаем из модуля `network`
subnet_id = var.subnet_id
nat = var.enable_nat
}
scheduling_policy {
preemptible = var.preemptible
}
metadata = {
# Данные пользователей
user-data = "${file(var.user_data_file)}"
# Альтернатива - ssh-keys = "ansible:${file("~/.ssh/id_rsa.pub")}"
}
}
Определяем переменные в файле variables.tf
:
variables.tf
variable "resource_name" {
description = "Имя виртуальной машины"
type = string
default = "my-vm"
}
variable "platform_id" {
description = "Платформа виртуальной машины"
type = string
default = "standard-v3"
}
variable "cpu_cores" {
description = "Количество ядер процессора"
type = number
default = 2
}
variable "memory_size" {
description = "Объем оперативной памяти (в ГБ)"
type = number
default = 2
}
variable "image_id" {
description = "ID образа диска"
type = string
default = "fd80qm01ah03dkqb14lc"
}
variable "disk_size" {
description = "Размер диска (в ГБ)"
type = number
default = 50
}
variable "enable_nat" {
description = "Включить NAT для ВМ"
type = bool
default = true
}
variable "preemptible" {
description = "Использовать прерываемую ВМ"
type = bool
default = true
}
variable "user_data_file" {
description = "Путь к файлу с данными пользователей"
type = string
default = "./user-data.txt"
}
variable "subnet_id" {
type = string
}
Обратите внимание на следующее:
image_id
- образ диска Ubuntu 20.04user_data_file
- путь к файлу с данными пользователей (относительно директорииterraform
)subnet_id
- идентификатор подсети, который передается из модуляnetwork
Определяем возвращаемые значения в outputs.tf
:
output "external_ip" {
description = "Внешний IP-адрес виртуальной машины"
value = yandex_compute_instance.vm-1.network_interface.0.nat_ip_address
}
output "internal_ip" {
description = "Внутренний IP-адрес виртуальной машины"
value = yandex_compute_instance.vm-1.network_interface.0.ip_address
}
Обратите внимание, что названия outputs
в модулях и terraform/main.tf
должны совпадать.
Итого
Финальная структура проекта:
terraform
| main.tf
| provider.tf
| user-data.txt
| variables.tf
| versions.tf
| modules
| tf-yc-instance
| main.tf
| outputs.tf
| README.md
| variables.tf
| versions.tf
| tf-yc-network
| main.tf
| outputs.tf
| README.md
| variables.tf
| versions.tf
Команды для запуска Terraform:
# Выполняется в корневой директории (`terraform`).
# Установка провайдера
terraform init
# Опционально, валидация конфигурации
terraform validate
# Генерация плана создания и настройки инфрастуктуры
terraform plan
# Применение конфигурации
terraform apply
Мы рассмотрели далеко не все возможности, предоставляемые Terraform, но думаю вы получили неплохое представление о том, что и как позволяет делать этот замечательный инструмент. Наряду с другими популярными решениями для автоматизации ИТ-процессов (Ansible, Docker, Kubernetes и т.д.), Terraform на сегодняшний день является важной частью арсенала DevOps-инженера.
Happy devopsing!