Как работать с Terraform

Используемые термины: Terraform , Ubuntu , Rocky Linux .

В данной инструкции мы попробуем разобраться с тем, как работать с Terraform. Мы рассмотрим его установку и базовую настройку, немного синтаксиса и примеры работы с облачным провайдером Яндекс.Облако. Инструкция рассчитана на работу с Linux (Deb и RPM).

Регистрация на хостинге

Для работы с хостинг-провайдером нам заранее понадобятся авторизационные данные и идентификаторы ресурсов. Их мы будем прописывать в сценариях terraform.

Нам нужно зарегистрироваться на сервисе Yandex.Cloud (в нашем примере, мы будем работать, именно, с ним). После этого, нам нужно получить:

1. OAuth-токен. Используется в процедуре аутентификации для получения IAM-токена и нужен нам для прохождения авторизации при подключении terraform. Выдается на 1 год. Получить можно, отправив запрос со страницы документации Яндекс.

2. Идентификатор облака. После регистрации на хостинге мы заходим в контроль-панель . Мы должны увидеть наши ресурсы, в частности, облако:

Получаем идентификатор облака

А справа от него будет идентификатор.

3. Идентификатор каталога. На той же странице контроль панели, ниже облака мы увидим каталог:

Получаем идентификатор каталога

Также, справа мы можем скопировать его идентификатор.

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

Установка Terraform

Terraform является клиентом и необходимо выполнить установку на компьютер, с которого планируется управление инфраструктурой. Актуальная инструкция по развертыванию представлена на официальном сайте . На текущий момент клиент может быть установлен из репозитория, с использованием бинарника, собран из исходников, а также с помощью choco на Windows или brew на Mac OS. В нашем примере будут рассмотрены установка на Ubuntu и Rocky Linux из репозитория, а также загрузка бинарника.

Ubuntu (Debian)

Обновляем список пакетов:

apt update

Установим дополнительные пакеты:

apt install gnupg software-properties-common curl

* где:

  • gnupg — программа для шифровки и дешифровки цифровых подписей. Нужна для работы с репозиториями.
  • software-properties-common — утилита для работы с репозиториями.
  • curl — отправка GET, POST и других запросов.

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

curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add —

Добавляем репозиторий:

apt-add-repository «deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main»

Обновляем список пакетов, чтобы загрузить списки с нового репозитория:

apt update

Можно устанавливать terraform:

apt install terraform

Rocky Linux

Устанавливаем утилиту для работы с репозиториями:

yum install yum-utils

Добавляем репозиторий:

yum-config-manager —add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo

Устанавливаем terraform:

yum install terraform

Скачиваем бинарник

Суть данного метода — скачать бинарный файл и перенести его в каталог /usr/local/bin .

Таким же образом устанавливаем terraform на Windows.

Для начала нам понадобятся утилиты unzip и wget. Устанавливаются они по-разному, в зависимости от дистрибутива Linux.

а) Ubuntu / Debian:

apt install unzip wget

б) Rocky Linux:

yum install unzip wget

Переходим к загрузке бинарного файла. Посмотреть ссылку на него можно на странице официального сайта :

Копируем ссылку на terraform

Воспользуемся ссылкой, чтобы скачать архив:

wget https://releases.hashicorp.com/terraform/1.1.7/terraform_1.1.7_linux_amd64.zip

* в нашем примере будет загружена версия 1.1.7.

Распакуем архив командой:

unzip terraform_*_linux_amd64.zip

Перенесем распакованный бинарник в каталог:

mv terraform /usr/local/bin/

После установки

Убедимся, что terraform работает. Для этого вводим команду:

terraform -version

Мы должны получить что-то на подобие:

Terraform v1.1.7
on linux_amd64

Также рекомендуется установить автоподстановки:

terraform -install-autocomplete

Это позволит нам завершать команды terraform с помощью клавиши Tab.

Теперь создадим каталог, в котором будем работать со сценариями для тераформа:

mkdir -p /opt/terraform/yandex

* в моем примере я решил работать в каталоге /opt/terraform/yandex .

Перейдем в созданный каталог:

cd /opt/terraform/yandex

Мы готовы приступить непосредственно к работе с terraform.

Установка провайдера

Чтобы terraform мог корректно взаимодействовать с инфраструктурой провайдера, необходимо использовать набор инструкций, которые будет использовать наша утилита. В терминологии terraform это называется провайдер.

Как говорилось выше, мы будем работать с Yandex.Cloud — описание его провайдера можно найти на сайте Яндекс Облака . Рассмотрим подробнее установку провайдера для работы с ним.

В нашем рабочем каталоге создаем первый файл:

vi main.tf

terraform {
required_version = «= 1.1.7»

required_providers {
yandex = {
source = «yandex-cloud/yandex»
version = «= 0.73»
}
}
}

provider «yandex» {
token = «<OAuth>»
cloud_id = «<идентификатор облака>»
folder_id = «<идентификатор каталога>»
zone = «<зона доступности по умолчанию>»
}

* где:

  • terraform required_version — версия клиента terraform, для которого должен запускаться сценарий. Параметр не обязательный, но является правилом хорошего тона и может помочь избежать некоторых ошибок, которые возникнут из-за несовместимости при работе в команде.
  • required_providers version — версия провайдера. Мы можем указать, как в данном примере, необходимость использовать конкретную версию, а можно с помощью знака >= или <= указать не выше или не ниже определенной.
  • token — OAuth-токен для прохождения авторизации. Был нами получен после регистрации на хостинге ( описание выше ).
  • cloud_id — идентификатор для облака, в котором будут создаваться ресурсы. Также получен был нами заранее.
  • folder_id — идентификатор каталога, который также был получен в начале инструкции.
  • zone — ресурсы, которые хранятся на мощностях Яндекс разделены по зонам. Каждая зона — это определенная географическая локация центра обработки данных. Список доступных зон можно посмотреть на странице Зоны доступности .

Теперь выполним команду:

terraform init

Система загрузит нужные файлы и установит провайдер:

Initializing the backend…

Initializing provider plugins…
— Finding latest version of yandex-cloud/yandex…
— Installing yandex-cloud/yandex v0.73.0…
— Installed yandex-cloud/yandex v0.73.0 (self-signed, key ID E40F590B50BB8E40)

Terraform has been successfully initialized!

Мы готовы двигаться дальше.

Работа с ресурсами

Мы рассмотрим небольшие примеры по созданию, редактированию и удалению ресурсов. В большей степени мы будем работать с виртуальными машинами. Большую часть информации по написанию сценариев можно найти на официальном сайте хостинга или самого terraform.

Создание ресурсов

В нашем рабочем каталоге создадим новый файл:

vi infrastructure1.tf

Напишем минимально необходимый сценарий для создания виртуальной машины:

data «yandex_compute_image» «ubuntu_image» {
family = «ubuntu-2004-lts»
}

resource «yandex_compute_instance» «vm-test1» {
name = «test1»

resources {
cores = 2
memory = 2
}

boot_disk {
initialize_params {
image_id = data.yandex_compute_image.ubuntu_image.id
}
}

network_interface {
subnet_id = yandex_vpc_subnet.subnet_terraform.id
nat = true
}

metadata = {
user-data = «${file(«./meta.yml»)}»
}

}

resource «yandex_vpc_network» «network_terraform» {
name = «net_terraform»
}

resource «yandex_vpc_subnet» «subnet_terraform» {
name = «sub_terraform»
zone = «ru-central1-a»
network_id = yandex_vpc_network.network_terraform.id
v4_cidr_blocks = [«192.168.15.0/24»]
}

* где:

  • data — позволяет запрашивать данные. В данном примере мы обращаемся к ресурсу yandex_compute_image с целью поиска идентификатора образа, с которого должна загрузиться наша машина.
    • yandex_compute_image — ресурс образа. Его название определено провайдером.
    • ubuntu_image — произвольное название ресурса. Его мы определили сами и будем использовать в нашем сценарии.
    • ubuntu-2004-lts — в нашем примере мы запрашиваем данные ресурса, который ищем по family с названием ubuntu-2004-lts. Данное название можно посмотреть в контроль панели хостинга — нужно выбрать образ и кликнуть по нему. Откроется страница с дополнительной информацией. В данном случае ubuntu-2004-lts соответствуем Ubuntu 20.04 LTS.
  • resource — позволяет создавать различные ресурсы.
    • yandex_compute_instance — ресурс виртуальной машины. Его название определено провайдером.
    • vm-test1 — наше название ресурса для виртуальной машины.
    • yandex_vpc_network — ресурс сети, определенный провайдером.
    • network_terraform — наше название для ресурса сети.
    • yandex_vpc_subnet — ресурс подсети, определенный провайдером.
    • subnet_terraform — наше название для ресурса подсети.
    • metadata — обратите особое внимание на передачу метеданных. В данном примере мы передаем содержимое файла, а сам файл рассмотрим ниже.

** в нашем примере будет создана виртуальная машина с названием test1, 2 CPU, 2 Gb RAM в сети 192.168.15.0/24 на базе Ubuntu 20.04 LTS.
*** обратите внимание, что мы указали идентификатор той подсети для виртуальной машины, которую создали также с помощью нашего сценария terraform.

Создаем файл с метеданными:

meta.yml

#cloud-config
users:
— name: remontka
groups: sudo
shell: /bin/bash
sudo: [‘ALL=(ALL) NOPASSWD:ALL’]
ssh-authorized-keys:
— ssh-rsa AAAAB3Nza………………UXFDCb/ujrK4KbpCyvk=

* в данном файле мы опишем пользователя, под которым мы сможем подключиться к нашему серверу по SSH:

  • #cloud-config — как выяснилось, этот комментарий обязательный.
  • name — имя пользователя, который будет создан на виртуальной машине.
  • groups — в какую группу добавить пользователя.
  • shell — оболочка shell по умолчанию.
  • sudo — правило повышения привилений.
  • ssh-authorized-keys — список ключей, которые будут добавлены в authorized_keys.

Попробуем испытать наш сценарий. Сначала вводим:

terraform plan

Если мы не ошиблись, утилита покажет, что terraform сделает в нашей облачной инфраструктуре. Очень важно внимательно просматривать изменения.

После того, как мы убедимся в корректности действий, применяем настройку:

terraform apply

Мы еще раз увидим, что будет выполнено, а также получим запрос на подтверждение действий — вводим yes:

Enter a value: yes

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

Редактирование данных

Если необходимо внести изменения в нашу инфраструктуру, то достаточно просто открыть наш файл со сценарием:

vi infrastructure1.tf

Внести нужные изменения, например:


resources {
cores = 4
memory = 4
}

* в нашем конкретном случае, мы увеличили количество процессоров и объем памяти для созданной виртуальной машины.

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

Error: Changing the `secondary_disk`, `resources`, `platform_id`, `network_acceleration_type` or `network_interfaces` on an instance requires stopping it. To acknowledge this action, please set allow_stopping_for_update = true in your config file.

Мы должны явно разрешить для конкретных ресурсов выполнение остановки их работы для внесения изменений. В нашем файле для конкретного ресурса добавим:


resource «yandex_compute_instance» «vm-test1» {
name = «test1»
allow_stopping_for_update = true

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

Строим план:

terraform plan

Мы должны увидеть, что будет изменено в нашей инфраструктуре. В моем случае:

~ resources {
~ cores = 2 -> 4
~ memory = 2 -> 4
# (2 unchanged attributes hidden)
}

Применяем настройки:

terraform apply

Проверяем через контроль-панель, что изменения вступили в силу.

Удаление ресурсов

Для удаления ресурса можно использовать разные приемы.

1. Например, можно указать count = 0:

resource «yandex_compute_instance» «vm-test1» {
count = 0

2. Можно закомментировать или удалить строки ресурса из файла tf.

3. Если мы хотим оставить содержимое файлов нетронутым, но при этом удалить все ресурсы, вводим:

terraform destroy

Утилита пройдет по всем файлам tf в директории, найдет ресурсы и выполнит их удаление.

Файл state

Очень важно уметь работать с файлом состояния terraform, а также понять, что это. Данный файл появляется после первого применения сценария в том же рабочем каталоге:

ls

terraform.tfstate

В нем содержатся все изменения, которые происходили с применением terraform. Без него последний не будет знать, что уже было сделано — на практике это приведет к дублированию инфраструктуры, то есть, если мы создали сервер, потеряли файл состояния и снова запустили terraform, он создаст еще один сервер. Для больших инфраструктур с большой историей все намного критичнее.

И так, файл состояния важен с точки зрения понимания инфраструктуры самим terraform. Также у него есть еще одна функция — блокировка параллельных выполнений. Это важно для работы в командах, где одновременные действия могут привести к неожиданным последствиям. Чтобы этого не произошло, terraform создает блокировку, которая запрещает выполнения, если уже идет процесс применения плана.

Из вышесказанного делаем выводы:

  • Файлы состояния нужно бэкапить.
  • Они должны находиться в надежном месте.
  • У всех инженеров, которые работают с инфраструктурой должен быть доступ к файлу состояния. Они не должны его копировать на свои компьютеры и использовать индивидуально.

Рассмотрим возможность хранения данного файла состояния на облачном хранилище Яндекс.

Сначала мы должны создать хранилище. Это можно сделать через веб-интерфейс, но мы это сделаем в terraform.

Но сначала мы сделаем файл:

vi variables.tf

variable «yandex_folder_id» {
type = string
default = «<идентификатор каталога>»
}

* где yandex_folder_id — название для нашей переменной; <идентификатор каталога> — тот идентификатор, который мы указали в файле main под аргументом folder_id .

Теперь откроем файл main:

vi main.tf

И отредактируем значение folder_id на:


folder_id = var.yandex_folder_id

* в данном примере мы теперь задаем folder_id не явно, а через созданную переменную.

Теперь создадим файл:

vi yandex_storage_bucket.tf

resource «yandex_iam_service_account» «sa» {
folder_id = var.yandex_folder_id
name = «sa-remontka»
}

resource «yandex_resourcemanager_folder_iam_member» «sa-editor» {
folder_id = var.yandex_folder_id
role = «storage.editor»
member = «serviceAccount:${yandex_iam_service_account.sa.id}»
}

resource «yandex_iam_service_account_static_access_key» «sa-static-key» {
service_account_id = yandex_iam_service_account.sa.id
description = «Static access key for object storage»
}

resource «yandex_storage_bucket» «state» {
bucket = «tf-state-bucket-remontka»
access_key = yandex_iam_service_account_static_access_key.sa-static-key.access_key
secret_key = yandex_iam_service_account_static_access_key.sa-static-key.secret_key
}

* рассмотрим файл немного подробнее:

  • yandex_iam_service_account — создание ресурса для учетной записи. В нашем примере она будет иметь название sa-remontka и входить в системный каталог yandex_folder_id (переменная, которую мы создали ранее).
  • yandex_resourcemanager_folder_iam_member — создание роли. Она будет иметь доступ для редактирования хранилищ. В состав роли войдет созданная запись sa (с именем sa-remontka ).
  • yandex_iam_service_account_static_access_key — создаем ключ доступа для нашей сервисной учетной записи.
  • yandex_storage_bucket — создаем хранилище с названием tf-state-bucket-remontka .

Создаем еще один файл:

vi outputs.tf

output «access_key» {
value = yandex_iam_service_account_static_access_key.sa-static-key.access_key
sensitive = true
}
output «secret_key» {
value = yandex_iam_service_account_static_access_key.sa-static-key.secret_key
sensitive = true
}

* это делается для получения значений аргументов access_key и secret_key и сохранения данных значений в файле состояния. Если access_key можно посмотреть в панели Яндекса, то secret_key мы увидеть не можем.

Строим план и применяем настройки:

terraform plan

terraform apply

Заходим в контроль-панель Яндекса и видим, что у нас создалось хранилище. Его мы будем использовать для хранения файлов состояний terraform.

Открываем наш файл main:

vi main.tf

Добавляем:

terraform {

backend «s3» {
endpoint = «storage.yandexcloud.net»
bucket = «tf-state-bucket-remontka»
region = «ru-central1-a»
key = «terraform/infrastructure1/terraform.tfstate»
access_key = «<access_key>»
secret_key = «<secret_key >»

skip_region_validation = true
skip_credentials_validation = true
}
}

* в раздел terraform мы добавили инструкцию для хранения файла state. В нашем примере он будет размещен на бакете tf-state-bucket-remontka по пути terraform/infrastructure1/terraform.tfstate .
** особое внимание обратите на access_key и secret_key . В разделе terraform мы не можем указать переменные, как это делали при создании хранилища. Сами значения можно найти в нашем локальном файле state.

Используем команду:

terraform init

Система снова инициализирует состояние текущего каталога и создаст файл state на удаленном хранилище. Чтобы убедиться, можно зайти в контроль панель, найти наше хранилище, а в нем — файл terraform/infrastructure1/terraform.tfstate .

Реальный пример

Мы развернем 2 веб-сервера и поместим их за Network load balancer.

Для этого мы создадим новый рабочий каталог:

mkdir -p /opt/terraform/yandex-network-load balancer

И перейдем в него:

cd /opt/terraform/yandex-network-load balancer

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

Мы создадим 5 файлов:

  1. main.tf — в нем мы опишем инструкции для init, а именно требования к версии клиента terraform, провайдера.
  2. web-servers.tf — сценарий для создания 2-х веб-серверов.
  3. network-load-balancer.tf — создание Network Load Balancer.
  4. variables.tf — описание переменных.
  5. outputs.tf — вывод значений после отработки terraform на экран и в файл состояния.

1. Создаем файл main.tf:

vi main.tf

terraform {
required_version = «= 1.1.7»

required_providers {
yandex = {
source = «yandex-cloud/yandex»
version = «= 0.73»
}
}
}
provider «yandex» {
token = «<OAuth>»
cloud_id = «<идентификатор облака>»
folder_id = «<идентификатор каталога>»
zone = «<зона доступности по умолчанию>»
}

* это стандартный вариант файла main.tf, который мы разбирали в начале.

2. web-servers.tf:

vi web-servers.tf

data «yandex_compute_image» «lamp» {
family = «lamp»
}

data «yandex_compute_image» «lemp» {
family = «lemp»
}

resource «yandex_compute_instance» «vm-test1» {
name = «vm-test1»
allow_stopping_for_update = true

resources {
cores = 2
memory = 2
}

boot_disk {
initialize_params {
image_id = data.yandex_compute_image.lamp.id
}
}

network_interface {
subnet_id = yandex_vpc_subnet.subnet_terraform.id
nat = true
}

}

resource «yandex_compute_instance» «vm-test2» {
name = «vm-test2»
allow_stopping_for_update = true

resources {
cores = 2
memory = 2
}

boot_disk {
initialize_params {
image_id = data.yandex_compute_image.lemp.id
}
}

network_interface {
subnet_id = yandex_vpc_subnet.subnet_terraform.id
nat = true
}

}

resource «yandex_vpc_network» «network_terraform» {
name = «network_terraform»
}

resource «yandex_vpc_subnet» «subnet_terraform» {
name = «subnet_terraform»
zone = «ru-central1-a»
network_id = yandex_vpc_network.network_terraform.id
v4_cidr_blocks = [«192.168.15.0/24»]
}

* в данном примере мы создадим 2 виртуальные машины — одну с образом lamp (Apache), вторую на lemp (NGINX).

3. network-load-balancer.tf:

vi network-load-balancer.tf

resource «yandex_lb_network_load_balancer» «lb-remontka» {
name = «lb-remontka»

listener {
name = «listener-web-servers»
port = 80
external_address_spec {
ip_version = «ipv4»
}
}

attached_target_group {
target_group_id = yandex_lb_target_group.web-servers.id

healthcheck {
name = «http»
http_options {
port = 80
path = «/»
}
}
}
}

resource «yandex_lb_target_group» «web-servers» {
name = «web-servers-target-group»

target {
subnet_id = yandex_vpc_subnet.subnet_terraform.id
address = yandex_compute_instance.vm-test1.network_interface.0.ip_address
}

target {
subnet_id = yandex_vpc_subnet.subnet_terraform.id
address = yandex_compute_instance.vm-test2.network_interface.0.ip_address
}
}

* в данном сценарии мы:

  • создаем target_group из 2-х наших веб-серверов.
  • создаем балансировщик lb-remontka.
  • привязываем к балансировщику target_group.

4. variables.tf:

vi variables.tf

variable «yandex_folder_id» {
type = string
default = «<folder_id>»
}

* создадим переменную с folder_id, в котором мы должны работать. Выше мы уже делали такую переменную.

5. outputs.tf:

vi outputs.tf

output «lb_ip_address» {
value = yandex_lb_network_load_balancer.lb-remontka.*
}

* на самом деле, это не обязательно — информацию о созданном балансировщике можно посмотреть в контроль панели, но для примера мы решили это рассмотреть.

Готово. После применения данного сценария мы получим то, что хотели — две виртуальные машины + балансировщик сети, который будет распределять запросы между данными серверами.

EnglishRussianUkrainian