1 added
1 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Terraform позволяет хранить описание облачной инфраструктуры в виде кода. Мы можем получить готовую к работе инфраструктуру со всеми зависимостями, выполнив всего одну команду - terraform apply.</p>
1
<p>Terraform позволяет хранить описание облачной инфраструктуры в виде кода. Мы можем получить готовую к работе инфраструктуру со всеми зависимостями, выполнив всего одну команду - terraform apply.</p>
2
-
<p>В этом уроке мы попрактикуемся с Terraform и попробуем с его помощью развернуть с нуля готовую к работе инфраструктуру: создадим базу и сервер, свяжем их друг с другом, п��ставим на сервер приложение и подключим его к базе.</p>
2
+
<p>В этом уроке мы попрактикуемся с Terraform и попробуем с его помощью развернуть с нуля готовую к работе инфраструктуру: создадим базу и сервер, свяжем их друг с другом, поставим на сервер приложение и подключим его к базе.</p>
3
<h2>Подключение Terraform к облаку</h2>
3
<h2>Подключение Terraform к облаку</h2>
4
<p>Для начала организуем в проекте доступ к нашему облаку. Создадим папку с проектом и добавим в него файл .gitignore, в котором исключим рабочие файлы Terraform и секреты:</p>
4
<p>Для начала организуем в проекте доступ к нашему облаку. Создадим папку с проектом и добавим в него файл .gitignore, в котором исключим рабочие файлы Terraform и секреты:</p>
5
<p>secret.* .terraform *.backup</p>
5
<p>secret.* .terraform *.backup</p>
6
<p>Добавим файл<em>providers.tf</em>, в котором опишем нужный нам провайдер и его параметры подключения:</p>
6
<p>Добавим файл<em>providers.tf</em>, в котором опишем нужный нам провайдер и его параметры подключения:</p>
7
<p>Объявим используемые там переменные в<em>variables.tf</em>.</p>
7
<p>Объявим используемые там переменные в<em>variables.tf</em>.</p>
8
<p>Рекомендуем настроить<a>консольную утилиту</a>для облака. В этом случае через нее можно получить токен и сразу сконвертировать его в нужную нам переменную:</p>
8
<p>Рекомендуем настроить<a>консольную утилиту</a>для облака. В этом случае через нее можно получить токен и сразу сконвертировать его в нужную нам переменную:</p>
9
<p>Команда выполнит запрос к облаку, извлечет из ответа строку с токеном и сохранит ее в файл<em>secret.auto.tfvars</em>.</p>
9
<p>Команда выполнит запрос к облаку, извлечет из ответа строку с токеном и сохранит ее в файл<em>secret.auto.tfvars</em>.</p>
10
<p>Можно получить токен и<a>другим способом</a>. Главное сохранить его в секретный *.auto.tfvars.</p>
10
<p>Можно получить токен и<a>другим способом</a>. Главное сохранить его в секретный *.auto.tfvars.</p>
11
<p>Допишем к токену значения переменных yc_cloud_id и yc_folder_id - это id облака и каталога, в которых будем работать.</p>
11
<p>Допишем к токену значения переменных yc_cloud_id и yc_folder_id - это id облака и каталога, в которых будем работать.</p>
12
<p>Теперь перейдем к описанию ресурсов. Поскольку в примере мы используем облако Yandex, нам нужно будет развернуть базовую сетевую инфраструктуру, в которой будут размещаться виртуальные машины и кластеры баз данных.</p>
12
<p>Теперь перейдем к описанию ресурсов. Поскольку в примере мы используем облако Yandex, нам нужно будет развернуть базовую сетевую инфраструктуру, в которой будут размещаться виртуальные машины и кластеры баз данных.</p>
13
<h2>Сетевая связность</h2>
13
<h2>Сетевая связность</h2>
14
<p>Для нашей инфраструктуры будет достаточно одной виртуальной сети yandex_vpc_network и подсети yandex_vpc_subnet:</p>
14
<p>Для нашей инфраструктуры будет достаточно одной виртуальной сети yandex_vpc_network и подсети yandex_vpc_subnet:</p>
15
<p>Мы описали сетевые ресурсы и дали им одинаковые имена. Поскольку это разные ресурсы, конфликтов из-за одинакового имени не будет, а в интерфейсе облака мы явно увидим, что управляется Terraform.</p>
15
<p>Мы описали сетевые ресурсы и дали им одинаковые имена. Поскольку это разные ресурсы, конфликтов из-за одинакового имени не будет, а в интерфейсе облака мы явно увидим, что управляется Terraform.</p>
16
<p>Такая сетевая архитектура является спецификой облака Yandex. В нее можно не углубляться, главное для понимания:</p>
16
<p>Такая сетевая архитектура является спецификой облака Yandex. В нее можно не углубляться, главное для понимания:</p>
17
<ol><li>Подсеть предоставляет диапазон IP-адресов, которые мы можем использовать для наших серверов и баз данных</li>
17
<ol><li>Подсеть предоставляет диапазон IP-адресов, которые мы можем использовать для наших серверов и баз данных</li>
18
<li>Сеть включает в себя подсети, ресурсы в которых могут общаться друг с другом</li>
18
<li>Сеть включает в себя подсети, ресурсы в которых могут общаться друг с другом</li>
19
</ol><p>Разместим в этой сетевой инфраструктуре облачную базу данных.</p>
19
</ol><p>Разместим в этой сетевой инфраструктуре облачную базу данных.</p>
20
<h2>Кластер БД</h2>
20
<h2>Кластер БД</h2>
21
<p>Опишем ресурс облачного кластера БД PostgreSQL. Добавим в него пользователя и создадим базу:</p>
21
<p>Опишем ресурс облачного кластера БД PostgreSQL. Добавим в него пользователя и создадим базу:</p>
22
<p>В ресурсах пользователя и базы данных сразу пропишем зависимость от кластера.</p>
22
<p>В ресурсах пользователя и базы данных сразу пропишем зависимость от кластера.</p>
23
<p>Для создания пользователя и самой базы мы будем использовать sensitive-переменные db_user, db_password и db_name. Их необходимо объявить, а затем добавить значения в наш<em>secret.auto.tfvars</em>:</p>
23
<p>Для создания пользователя и самой базы мы будем использовать sensitive-переменные db_user, db_password и db_name. Их необходимо объявить, а затем добавить значения в наш<em>secret.auto.tfvars</em>:</p>
24
<p>db_name = "hexlet" db_user = "me" db_password = "bvcdV6sdBS7AXZs"</p>
24
<p>db_name = "hexlet" db_user = "me" db_password = "bvcdV6sdBS7AXZs"</p>
25
<p>Теперь опишем сервер и настроим так, чтобы при развертывании инфраструктуры на нем запускалось приложение. Приложение будет подключаться к кластеру БД.</p>
25
<p>Теперь опишем сервер и настроим так, чтобы при развертывании инфраструктуры на нем запускалось приложение. Приложение будет подключаться к кластеру БД.</p>
26
<h2>Сервер и приложение</h2>
26
<h2>Сервер и приложение</h2>
27
<p>Запустим на сервере приложение<a>wiki.js</a>, которое можно развернуть<a>с помощью Docker</a>. Это избавит нас от установки дополнительных пакетов на сервер. Единственное, что нам потребуется для запуска - наличие на сервере Docker.</p>
27
<p>Запустим на сервере приложение<a>wiki.js</a>, которое можно развернуть<a>с помощью Docker</a>. Это избавит нас от установки дополнительных пакетов на сервер. Единственное, что нам потребуется для запуска - наличие на сервере Docker.</p>
28
<p>Облако предоставляет готовый образ на базе Ubuntu с предустановленным Docker.</p>
28
<p>Облако предоставляет готовый образ на базе Ubuntu с предустановленным Docker.</p>
29
<p>Мы можем скопировать id этого образа или использовать data source, чтобы получить актуальный образ по family ID:<em>container-optimized-image</em>.</p>
29
<p>Мы можем скопировать id этого образа или использовать data source, чтобы получить актуальный образ по family ID:<em>container-optimized-image</em>.</p>
30
<p>Опишем сервер с помощью ресурса yandex_compute_instance:</p>
30
<p>Опишем сервер с помощью ресурса yandex_compute_instance:</p>
31
<p>Мы задаем для сервера:</p>
31
<p>Мы задаем для сервера:</p>
32
<ul><li>Выделенные ресурсы</li>
32
<ul><li>Выделенные ресурсы</li>
33
<li>Образ виртуальной машины</li>
33
<li>Образ виртуальной машины</li>
34
<li>Подсеть, в которую он будет помещен</li>
34
<li>Подсеть, в которую он будет помещен</li>
35
<li>Метаданные для инициации</li>
35
<li>Метаданные для инициации</li>
36
</ul><p>Также для сетевого интерфейса машины задаем nat = true, чтобы машине был выдан внешний IP-адрес.</p>
36
</ul><p>Также для сетевого интерфейса машины задаем nat = true, чтобы машине был выдан внешний IP-адрес.</p>
37
<p>Не хватает описания запуска приложения и передачи ему параметров базы данных. Сделаем это с помощью<strong>Terraform provisioner</strong>. Провижнеры позволяют выполнять различные операции в процессе развертывания инфраструктуры - создавать файлы, выполнять скрипты локально или на новых созданных машинах.</p>
37
<p>Не хватает описания запуска приложения и передачи ему параметров базы данных. Сделаем это с помощью<strong>Terraform provisioner</strong>. Провижнеры позволяют выполнять различные операции в процессе развертывания инфраструктуры - создавать файлы, выполнять скрипты локально или на новых созданных машинах.</p>
38
<p>Мы будем использовать провижнер remote-exec. Он подключается к серверу с помощью параметров, описанных в блоке<em>connection</em>. Там он выполняет операции, описанные в блоке<em>inline</em>.</p>
38
<p>Мы будем использовать провижнер remote-exec. Он подключается к серверу с помощью параметров, описанных в блоке<em>connection</em>. Там он выполняет операции, описанные в блоке<em>inline</em>.</p>
39
<p>И<em>connection</em>, и<em>provisioner</em>нужно добавить внутрь ресурса yandex_compute_instance. Допишем их ниже блока<em>metadata</em>:</p>
39
<p>И<em>connection</em>, и<em>provisioner</em>нужно добавить внутрь ресурса yandex_compute_instance. Допишем их ниже блока<em>metadata</em>:</p>
40
<p>В<em>connection</em>мы настроили подключение по ssh под пользователем ubuntu. Для этого использовали наш локальный приватный ключ. Провижнер будет подключаться по адресу self.network_interface[0].nat_ip_address - в это поле после создания сервера сохранится внешний IP-адрес сервера. Когда провижнер подключится, он выполнит команды, описанные в<em>inline</em>.</p>
40
<p>В<em>connection</em>мы настроили подключение по ssh под пользователем ubuntu. Для этого использовали наш локальный приватный ключ. Провижнер будет подключаться по адресу self.network_interface[0].nat_ip_address - в это поле после создания сервера сохранится внешний IP-адрес сервера. Когда провижнер подключится, он выполнит команды, описанные в<em>inline</em>.</p>
41
<p>В блок<em>inline</em>команды передаются списком. У нас всего одна длинная команда, для удобства чтения мы записываем ее с переносами строки и передаем в heredoc-формате. Форматирование в примере выше является корректным: маркеры блока EOT для корректной работы не должны иметь отступов.</p>
41
<p>В блок<em>inline</em>команды передаются списком. У нас всего одна длинная команда, для удобства чтения мы записываем ее с переносами строки и передаем в heredoc-формате. Форматирование в примере выше является корректным: маркеры блока EOT для корректной работы не должны иметь отступов.</p>
42
<p>В итоге после создания виртуальной машины провижнер подключится к машине по ssh и запустит docker-контейнер ghcr.io/requarks/wiki:2.5 с приложением. В параметры подключения к кластеру БД указываем те же переменные, которые использовали при описании кластера. Хост кластера БД станет известен после его создания. Чтобы получить его в скрипте, ссылаемся на поле кластера yandex_mdb_postgresql_cluster.dbcluster.host.0.fqdn.</p>
42
<p>В итоге после создания виртуальной машины провижнер подключится к машине по ssh и запустит docker-контейнер ghcr.io/requarks/wiki:2.5 с приложением. В параметры подключения к кластеру БД указываем те же переменные, которые использовали при описании кластера. Хост кластера БД станет известен после его создания. Чтобы получить его в скрипте, ссылаемся на поле кластера yandex_mdb_postgresql_cluster.dbcluster.host.0.fqdn.</p>
43
<p>Единственное, что осталось учесть, - это порядок создания ресурсов. В этом нам помогут зависимости.</p>
43
<p>Единственное, что осталось учесть, - это порядок создания ресурсов. В этом нам помогут зависимости.</p>
44
<h2>Зависимости</h2>
44
<h2>Зависимости</h2>
45
<p>Технически возможна ситуация, когда виртуальная машина развернется раньше кластера БД, и приложение не сможет подключиться к базе. В этом случае нам хотелось бы иметь явную зависимость, ограничивающую создание виртуальных машин, пока кластер не готов к работе.</p>
45
<p>Технически возможна ситуация, когда виртуальная машина развернется раньше кластера БД, и приложение не сможет подключиться к базе. В этом случае нам хотелось бы иметь явную зависимость, ограничивающую создание виртуальных машин, пока кластер не готов к работе.</p>
46
<p>Добавим свойства depends_on в наших ресурсах. Кластер баз данных будет зависеть от ресурсов сети и подсети, а виртуальная машина - от кластера БД:</p>
46
<p>Добавим свойства depends_on в наших ресурсах. Кластер баз данных будет зависеть от ресурсов сети и подсети, а виртуальная машина - от кластера БД:</p>
47
<p>Для сервера тоже нужна подсеть, но поскольку мы уже неявно обозначили зависимость от нее через кластер БД, в блок depends_on ее можно не добавлять.</p>
47
<p>Для сервера тоже нужна подсеть, но поскольку мы уже неявно обозначили зависимость от нее через кластер БД, в блок depends_on ее можно не добавлять.</p>
48
<p>Terraform при развертывании инфраструктуры строит свой внутренний граф зависимостей на основе ссылок на поля других ресурсов. Если инфраструктура несложная (как в этом уроке), он вполне способен правильно выстроить порядок сам. Но лучше сразу привыкнуть держать зависимости под контролем.</p>
48
<p>Terraform при развертывании инфраструктуры строит свой внутренний граф зависимостей на основе ссылок на поля других ресурсов. Если инфраструктура несложная (как в этом уроке), он вполне способен правильно выстроить порядок сам. Но лучше сразу привыкнуть держать зависимости под контролем.</p>
49
<p>Также этот подход добавляет прозрачности тому, что и в каком порядке происходит при конфигурации инфраструктуры.</p>
49
<p>Также этот подход добавляет прозрачности тому, что и в каком порядке происходит при конфигурации инфраструктуры.</p>
50
<p>На этом этапе у нас всё готово. Осталось выполнить terraform apply и убедиться, что все создалось и запустилось.</p>
50
<p>На этом этапе у нас всё готово. Осталось выполнить terraform apply и убедиться, что все создалось и запустилось.</p>
51
<h2>Создание инфраструктуры</h2>
51
<h2>Создание инфраструктуры</h2>
52
<p>Выполним terraform apply:</p>
52
<p>Выполним terraform apply:</p>
53
<p>Terraform will perform the following actions: # yandex_compute_instance.vm will be created + resource "yandex_compute_instance" "vm" { ... # yandex_mdb_postgresql_cluster.dbcluster will be created + resource "yandex_mdb_postgresql_cluster" "dbcluster" { ...</p>
53
<p>Terraform will perform the following actions: # yandex_compute_instance.vm will be created + resource "yandex_compute_instance" "vm" { ... # yandex_mdb_postgresql_cluster.dbcluster will be created + resource "yandex_mdb_postgresql_cluster" "dbcluster" { ...</p>
54
<p>Запустим создание инфраструктуры и проследим за порядком:</p>
54
<p>Запустим создание инфраструктуры и проследим за порядком:</p>
55
<p>yandex_vpc_network.net: Creating... yandex_vpc_network.net: Creation complete after 2s [id=enp49digsf8iut549fve] yandex_vpc_subnet.subnet: Creating... yandex_vpc_subnet.subnet: Creation complete after 1s [id=e9b0r2vjb50q1s37is8v] yandex_mdb_postgresql_cluster.dbcluster: Creating... yandex_mdb_postgresql_cluster.dbcluster: Still creating... [10s elapsed] ... yandex_mdb_postgresql_cluster.dbcluster: Still creating... [6m20s elapsed] yandex_mdb_postgresql_cluster.dbcluster: Creation complete after 6m23s [id=c9qnurf8pfd32bpmitmm] yandex_mdb_postgresql_user.dbuser: Creating... yandex_compute_instance.vm: Creating... yandex_mdb_postgresql_user.dbuser: Still creating... [10s elapsed] yandex_compute_instance.vm: Still creating... [10s elapsed] ... andex_mdb_postgresql_user.dbuser: Creation complete after 24s [id=c9qnurf8pfd32bpmitmm:me] yandex_mdb_postgresql_database.db: Creating... yandex_compute_instance.vm: Still creating... [30s elapsed] yandex_mdb_postgresql_database.db: Still creating... [10s elapsed] yandex_compute_instance.vm: Still creating... [40s elapsed] yandex_compute_instance.vm: Provisioning with 'remote-exec'... yandex_compute_instance.vm (remote-exec): Connecting to remote host via SSH... yandex_compute_instance.vm (remote-exec): Host: 51.250.1.251 yandex_compute_instance.vm (remote-exec): User: ubuntu yandex_compute_instance.vm (remote-exec): Password: false yandex_compute_instance.vm (remote-exec): Private key: true yandex_compute_instance.vm (remote-exec): Certificate: false yandex_compute_instance.vm (remote-exec): SSH Agent: true yandex_compute_instance.vm (remote-exec): Checking Host Key: false yandex_compute_instance.vm (remote-exec): Target Platform: unix yandex_compute_instance.vm (remote-exec): Connected! yandex_compute_instance.vm: Still creating... [1m0s elapsed] yandex_compute_instance.vm (remote-exec): Unable to find image 'ghcr.io/requarks/wiki:2.5' locally yandex_compute_instance.vm (remote-exec): 2.5: Pulling from requarks/wiki yandex_compute_instance.vm (remote-exec): 31e352740f53: Pulling fs layer yandex_compute_instance.vm (remote-exec): 2629b68d4311: Pulling fs layer ... yandex_compute_instance.vm (remote-exec): Status: Downloaded newer image for ghcr.io/requarks/wiki:2.5 yandex_compute_instance.vm (remote-exec): c33a8f713d927bc0fa3f042a0d20a44b5abcc7798312ff58964d2f46664b4a17 yandex_compute_instance.vm: Creation complete after 1m23s [id=fhmdenv9loufk2m2gcj2] Apply complete! Resources: 6 added, 0 changed, 0 destroyed.</p>
55
<p>yandex_vpc_network.net: Creating... yandex_vpc_network.net: Creation complete after 2s [id=enp49digsf8iut549fve] yandex_vpc_subnet.subnet: Creating... yandex_vpc_subnet.subnet: Creation complete after 1s [id=e9b0r2vjb50q1s37is8v] yandex_mdb_postgresql_cluster.dbcluster: Creating... yandex_mdb_postgresql_cluster.dbcluster: Still creating... [10s elapsed] ... yandex_mdb_postgresql_cluster.dbcluster: Still creating... [6m20s elapsed] yandex_mdb_postgresql_cluster.dbcluster: Creation complete after 6m23s [id=c9qnurf8pfd32bpmitmm] yandex_mdb_postgresql_user.dbuser: Creating... yandex_compute_instance.vm: Creating... yandex_mdb_postgresql_user.dbuser: Still creating... [10s elapsed] yandex_compute_instance.vm: Still creating... [10s elapsed] ... andex_mdb_postgresql_user.dbuser: Creation complete after 24s [id=c9qnurf8pfd32bpmitmm:me] yandex_mdb_postgresql_database.db: Creating... yandex_compute_instance.vm: Still creating... [30s elapsed] yandex_mdb_postgresql_database.db: Still creating... [10s elapsed] yandex_compute_instance.vm: Still creating... [40s elapsed] yandex_compute_instance.vm: Provisioning with 'remote-exec'... yandex_compute_instance.vm (remote-exec): Connecting to remote host via SSH... yandex_compute_instance.vm (remote-exec): Host: 51.250.1.251 yandex_compute_instance.vm (remote-exec): User: ubuntu yandex_compute_instance.vm (remote-exec): Password: false yandex_compute_instance.vm (remote-exec): Private key: true yandex_compute_instance.vm (remote-exec): Certificate: false yandex_compute_instance.vm (remote-exec): SSH Agent: true yandex_compute_instance.vm (remote-exec): Checking Host Key: false yandex_compute_instance.vm (remote-exec): Target Platform: unix yandex_compute_instance.vm (remote-exec): Connected! yandex_compute_instance.vm: Still creating... [1m0s elapsed] yandex_compute_instance.vm (remote-exec): Unable to find image 'ghcr.io/requarks/wiki:2.5' locally yandex_compute_instance.vm (remote-exec): 2.5: Pulling from requarks/wiki yandex_compute_instance.vm (remote-exec): 31e352740f53: Pulling fs layer yandex_compute_instance.vm (remote-exec): 2629b68d4311: Pulling fs layer ... yandex_compute_instance.vm (remote-exec): Status: Downloaded newer image for ghcr.io/requarks/wiki:2.5 yandex_compute_instance.vm (remote-exec): c33a8f713d927bc0fa3f042a0d20a44b5abcc7798312ff58964d2f46664b4a17 yandex_compute_instance.vm: Creation complete after 1m23s [id=fhmdenv9loufk2m2gcj2] Apply complete! Resources: 6 added, 0 changed, 0 destroyed.</p>
56
<p>По логу можно заметить:</p>
56
<p>По логу можно заметить:</p>
57
<ul><li>Заданный нами порядок соблюдается</li>
57
<ul><li>Заданный нами порядок соблюдается</li>
58
<li>Провижнер логирует в Terraform операции, которые выполняет</li>
58
<li>Провижнер логирует в Terraform операции, которые выполняет</li>
59
<li>Создание сервера не считается завершенным до тех пор, пока провижнер не отработал полностью</li>
59
<li>Создание сервера не считается завершенным до тех пор, пока провижнер не отработал полностью</li>
60
</ul><p>Приложению понадобится пара минут, чтобы установить все зависимости и накатить миграции в БД. После этого мы можем зайти на внешний IP сервера и увидеть админку wiki.js, готового к работе.</p>
60
</ul><p>Приложению понадобится пара минут, чтобы установить все зависимости и накатить миграции в БД. После этого мы можем зайти на внешний IP сервера и увидеть админку wiki.js, готового к работе.</p>
61
<p>Можем создать администратора и добавить пару статей в нашу новую wiki. Приложение сохранит данные в базу.</p>
61
<p>Можем создать администратора и добавить пару статей в нашу новую wiki. Приложение сохранит данные в базу.</p>
62
<p>После этого мы можем полностью удалить наш сервер через интерфейс облака и повторно вызвать terraform apply. Terraform не найдет yandex_compute_instance.vm и предложит создать его заново. Развернем сервер снова и зайдем на него по новому внешнему IP, который ему выдало облако. Там мы должны увидеть ту же самую wiki и уже созданные нами статьи.</p>
62
<p>После этого мы можем полностью удалить наш сервер через интерфейс облака и повторно вызвать terraform apply. Terraform не найдет yandex_compute_instance.vm и предложит создать его заново. Развернем сервер снова и зайдем на него по новому внешнему IP, который ему выдало облако. Там мы должны увидеть ту же самую wiki и уже созданные нами статьи.</p>
63
<p>Когда закончите практику, не забудьте выполнить terraform destroy, чтобы убрать из облака всю созданную в проекте инфраструктуру.</p>
63
<p>Когда закончите практику, не забудьте выполнить terraform destroy, чтобы убрать из облака всю созданную в проекте инфраструктуру.</p>
64
<h2>Выводы</h2>
64
<h2>Выводы</h2>
65
<p>В этом уроке мы разобрались, как с помощью Terraform описать и поднять в облаке готовое к работе stateful-приложение. Такое решение с сервером и базой является вариантом применения подхода "неизменяемая инфраструктура" - когда мы полностью конфигурируем сервер на этапе создания. А если нужно что-то поменять в настройках - просто удаляем его и инициируем создание нового.</p>
65
<p>В этом уроке мы разобрались, как с помощью Terraform описать и поднять в облаке готовое к работе stateful-приложение. Такое решение с сервером и базой является вариантом применения подхода "неизменяемая инфраструктура" - когда мы полностью конфигурируем сервер на этапе создания. А если нужно что-то поменять в настройках - просто удаляем его и инициируем создание нового.</p>