В этом уроке мы рассмотрим типовой рабочий процесс с использованием Terraform без погружения в детали. Опишем небольшую инфраструктуру и поработаем с ней, развернем, обновим и удалим. А в следующих уроках, рассмотрим все это подробно.
Подготовка
На протяжении всего курса, мы будем описывать нашу инфраструктуру в репозитории, который станет частью вашего портфолио. Для этого создайте на Github репозиторий hexlet-terraform и клонируйте его себе на компьютер. Все дальнейшие команды будут выполняться внутри этого репозитория.
Теперь установите Terraform по этой ссылке. Проверьте что он работает:
Зарегистрируйтесь в Yandex Cloud. На этой странице описано как получить OAuth-токен. Сделайте запрос и получите ключ, который понадобится Terraform для выполнения удаленных команд.
Инициализация
Перед началом работы с Terraform, нужно определиться с облаком, которое будет использоваться. Все примеры курса даются в Yandex Cloud, но вы можете выбрать любое другое облако, которое вам по душе. Правда для этого придется самостоятельно копаться в документации, чтобы повторять за тем, что дается в уроке.
Когда облако выбрано, нужно найти его в списке провайдеров и перейти в документацию. Либо можно посмотреть документацию в самом облачном провайдере. Здесь описано, как инициализировать работу с ним.
Зачем нужен провайдер? Провайдер это модуль Terraform, который умеет работать с конкретным облаком. Как только он подключается к проекту, внутри появляются команды для взаимодействия с его сервисами.
Для подключения провайдера Yandex Cloud, создайте файл .terraformrc и добавьте в него содержимое:
Эти данные содержат информацию об источнике, и которого будет устанавливаться провайдер.
Затем создайте файл main.tf и добавьте в него такое содержимое:
Помимо указания списка провайдеров, мы описываем переменную yc_token, которая записывается в аттрибут token провайдера yandex. Это ключ необходим Terraform для выполнения команд по API. Значение ключа указывать в конфигурации нельзя, иначе, кто-нибудь сможет с его помощью выполнить любой код на YandexCloud, включая полное уничтожение всей инфраструктуры.
Для работы с секретами, Terraform предлагает создавать файлы с расширением *.auto.tfvars, которые добавляются в .gitignore. Сам ключ, при этом, можно хранить в зашифрованном виде в Ansible Vault.
Создадим файл secrets.auto.tfvars, в котором описываются переменные, содержащие секретные данные. Добавим туда наш ключ как значение переменной yc_token:
После того как провайдер добавлен, нужно выполнить инициализацию:
Во время инициализации скачивается код провайдера в директорию .terraform. Эта служебная директория, содержимое которой нам не важно, поэтому ее добавляют в .gitignore.
Инициализация выполняется каждый раз, когда репозиторий клонируется заново или обновляются версии зависимостей. Несмотря на название, команда terraform init больше похожа на установку зависимостей в JavaScript с помощью npm install.
Кроме .terraform, в директории с проектом оказывается файл .terraform.lock.hcl.
Описание инфраструктуры
На странице документации провайдера, слева меню, в котором есть список возможностей провайдера. Мы начнем с самой базовой - создании сервера.
В терминах Terraform yandex_compute_instance называется ресурсом. Это то, чем мы управляем в нашем облаке. Создадим сервер:
Для создания сервера, нам пришлось создать дополнительные ресурсы yandex_vpc_network, yandex_vpc_subnet, yandex_compute_disk.
Описание аргументов resources, boot_disk и других идет в документации сразу после примера в секции Argument Reference. Там же указано какие из них обязательные, а какие нет. С другой стороны, в этом разделе не хватает информации о возможных значениях. Откуда брать значения для platform_id или cores? Иногда в документации есть ссылка на страницу с возможными значениями, но это бывает не всегда. К сожалению здесь не остается ничего другого, как пытаться найти эту информацию в документации облачного провайдера. Здесь обычно помогает Google.
Ресурсы требуют указания folder_id — это ваш каталог внутри которого будет создан ресурс. Его идентификатор можно найти в консоли Yandex Cloud. Если каталог не создан, создайте его.
Язык описания инфраструктуры отдаленно напоминает JSON и интуитивно понятен в большинстве ситуаций. Главное что нужно понимать, он описывает не команды, а состояние того, что мы хотим получить в конце. Это значит что порядок описания инфраструктуры не имеет значения, Terraform все равно сделает изменения в том порядке, в котором нужно.
Инфраструктуру можно описывать в любых файлах с расширением *.tf. Terraform самостоятельно их загружает и вычисляет порядок, в котором надо выполнять изменения. Для простоты мы все делаем в файле main.tf. Когда кода много, его удобно раскладывать по разным файлам.
Создание
Теперь, когда ресурсы описаны, можно их создать. Делается это в два шага. Сначала нам показывают план изменений и если он соответствует нашим ожиданиям, то мы подтверждаем его выполнение и Terraform делает это
Очень важно убедиться, что здесь нет ничего опасного, например удаление или пересоздание каких-то ресурсов, которые нельзя трогать. В этом смысле, Terraform максимально опасный инструмент. Неверно примененный план может привести к полной потере всего проекта включая бекапы.
Если нас все устраивает, то нужно набрать yes. Создание серверов займет какое-то время. Terraform выполняет все операции синхронно, поэтому он закончит работу только после внесения всех изменений.
Если все прошло успешно, то зайдите в личный кабинет Yandex Cloud и убедитесь, что сервер создан. Правда магия?
После того как Terraform выполнит изменения, он создает и затем обновляет файл terraform.tfstate. Этот файл хранит состояние инфраструктуры на текущий момент. Зачем это нужно? С его помощью Terraform вычисляет разницу между тем, что мы хотим получить в итоге и тем что есть сейчас. Без него Terraform будет считать, что инфраструктура каждый раз создается заново. Этот файл обычно не хранят в репозитории, так как вместе с состоянием в нем могут быть чувствительные данные вроде паролей и токенов. Более того, с этим файлом в один момент времени может работать только один человек, иначе Terraform не сможет правильно оценить текущую инфраструктуру. Для этого используют специальные сервисы, которые хранят файл состояния на удаленном сервере
Кроме terraform.tfstate Terraform создает файлы бекапы с расширением *.backup. Их нужно добавить в .gitignore.
Ну и наконец, давайте попробуем все удалить:
Собирая все вместе
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 22:46:22 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="tnYOLI49VQ2JLzEoHkpeNyv0NWJCfPM6qDIqaZyuO2BZp8UbfEP4bT9sFbASRa5A6_0YyEpLDZgV0rA9zqncDg";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Обзор Terraform | Terraform: Основы</title>
<meta name="description" content="Обзор Terraform / Terraform: Основы: Проходим все этапы настройки и создания своей инфраструктуры">
<link rel="canonical" href="https://ru.hexlet.io/courses/terraform-basics/lessons/overview/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Обзор Terraform">
<meta property="og:title" content="Terraform: Основы">
<meta property="og:description" content="Обзор Terraform / Terraform: Основы: Проходим все этапы настройки и создания своей инфраструктуры">
<meta property="og:url" content="https://ru.hexlet.io/courses/terraform-basics/lessons/overview/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="0VKIB7iLaNrfUJIRIDhQ4xrrFD7JXZPmsaa3Wvvy1q8-g0MwSvXFumkTtoksN6CU2uI5lMFqbUQMRi0OqfUxwQ" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk3NywicHVyIjoiYmxvYl9pZCJ9fQ==--bc5ef27286509b0ecf2f8ae6cbdce2376db3d394/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/500%20Internal%20Server%20Error-cuate.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY0MywicHVyIjoiYmxvYl9pZCJ9fQ==--74611367ca7524225d6b8670846088b4aa9fa1d2/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Server-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T22:46:22.721Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"C9lMZ65abaYrZpEbvrZoC30nmIBKuN4gAwf4HrFZSznkCIdQXCTAxp0ltYOyuZh8vS61KkKPIIK-52JK416sVw","topics":[{"id":93040,"title":"По этой ссылке https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli\nне удается скачать\nкоманда `brew install hashicorp/tap/terraform` выдает curl: (22) The requested URL returned error: 404","plain_title":"По этой ссылке https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli не удается скачать команда brew install hashicorp/tap/terraform выдает curl: (22) The requested URL returned error: 404 ","creator":{"public_name":"Ruslan","id":509650,"is_tutor":false},"comments":[{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":182970,"body":"А не пробовали VPN использовать?","topic_id":93040},{"creator":{"public_name":"Ruslan","id":509650,"is_tutor":false},"id":182994,"body":"под впном и пробовал...Да и странно 404 видеть, тогда как будто другой код был бы, не?","topic_id":93040},{"creator":{"public_name":"Maksim Litvinov","id":198906,"is_tutor":true},"id":183015,"body":"Да, он 404 отдает без впн. Попробуйте тогда еще другой способ установки, ручной. У себя проверил, у меня с впн и при помощи brew устанавливается корректно и вручную \n тоже архив скачивается успешно. Может, другой сервер попробуйте","topic_id":93040}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Обзор Terraform","entity_url":null,"active":true}},{"id":83248,"title":"> После того как Terraform выполнит изменения, он создает и затем обновляет файл terraform.tfstate. Этот файл хранит состояние инфраструктуры на текущий момент. Зачем это нужно? С его помощью Terraform вычисляет разницу между тем, что мы хотим получить в итоге и тем что есть сейчас. Без него Terraform будет считать, что инфраструктура каждый раз создается заново. Его обязательно нужно добавлять в git-репозиторий.\n\n1) Файлы с расширением `.tfstate` включаются в стандартный `.gitignore` для Terraform из репозитория https://github.com/github/gitignore\n2) В нём может быть sensitive информация\n3) Статья о том, что так делать не нужно - https://openupthecloud.com/commit-terraform-tfstate-git/","plain_title":"После того как Terraform выполнит изменения, он создает и затем обновляет файл terraform.tfstate. Этот файл хранит состояние инфраструктуры на текущий момент. Зачем это нужно? С его помощью Terraform вычисляет разницу между тем, что мы хотим получить в итоге и тем что есть сейчас. Без него Terraform будет считать, что инфраструктура каждый раз создается заново. Его обязательно нужно добавлять в git-репозиторий. 1) Файлы с расширением .tfstate включаются в стандартный .gitignore для Terraform из репозитория https://github.com/github/gitignore 2) В нём может быть sensitive информация 3) Статья о том, что так делать не нужно - https://openupthecloud.com/commit-terraform-tfstate-git/ ","creator":{"public_name":"Viachaslau Hryshchuk","id":210045,"is_tutor":false},"comments":[{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":170439,"body":"Добрый день.\n\nВ урок внесли изменения. Tfstate теперь не рекомендуем хранить в репозитории. В планах урок по remote state, но не могу сказать, когда он появится","topic_id":83248},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":169118,"body":"Добрый день.\n\nВы правы, в этом файле могут быть чувствительные данные. Но если вы будете игнорировать файл `.tfstate`, то тогда не сможете синхронизировать изменения между собой.\nА еще может быть такое, что один разработчик что-то менял, в это время другой, они вносят своя изменения и их нужно как-то мержить.\n\nИ для этого используется удаленный сервер для хранения состояния в одном месте, чтобы все синхронизировать. У нас в планах урок по этой теме. Мы в нем подсветим этот момент ","topic_id":83248},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":169123,"body":"Также еще добавлю, что у нас в одном из репозиториев используется хранение стейта в репозитории.\nЭто сделано для простоты, поскольку инфраструктуру мы разворачиваем в облачном провайдере и там по сути 1 сервер + конфиг для Datadog. \nИзменения в инфраструктуру вносятся редко и только через пулл реквесты, поэтому мы сделали такой вариант. Но думаю, что в будущем это поменяется.","topic_id":83248}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Обзор Terraform","entity_url":null,"active":true}},{"id":84210,"title":"Добрый день. В разделе \"Собирая все вместе\" почему отсутствует ссылка на наличие файла состояния terraform.tfstate ?","plain_title":"Добрый день. В разделе \"Собирая все вместе\" почему отсутствует ссылка на наличие файла состояния terraform.tfstate ? ","creator":{"public_name":"Антон","id":440830,"is_tutor":false},"comments":[{"creator":{"public_name":"Антон","id":440830,"is_tutor":false},"id":171097,"body":"Спасибо","topic_id":84210},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":170611,"body":"Антон, добрый день.\n\nФайл состояния генерируется и у всех будет разным. Плюс там могут быть секретные данные. \nСам файл не должен быть в репозитории, поэтому здесь мы добавим в пример теории игнорирование этого файла.","topic_id":84210}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Обзор Terraform","entity_url":null,"active":true}},{"id":100793,"title":"В теории написано, что файлы *.tfstate нельзя хранить в репозитории, а далее в .gitignore вы его не записываете почему-то","plain_title":"В теории написано, что файлы *.tfstate нельзя хранить в репозитории, а далее в .gitignore вы его не записываете почему-то ","creator":{"public_name":"Павел Бурыка","id":617236,"is_tutor":false},"comments":[{"creator":{"public_name":"Elena Gromova","id":548102,"is_tutor":true},"id":192612,"body":"**Павел Бурыка**, добрый день! Подскажите, где именно забыли добавить файлы `*.tfstate` в `.gitignore`?","topic_id":100793},{"creator":{"public_name":"Павел Бурыка","id":617236,"is_tutor":false},"id":192615,"body":"**Elena Gromova**, в этом уроке \n\nСобирая все вместе\n\n```\n# gitignore\n.terraform\n*.auto.tfvars\n*.backup\n```","topic_id":100793},{"creator":{"public_name":"Elena Gromova","id":548102,"is_tutor":true},"id":192617,"body":"**Павел Бурыка**, а, поняла. Дело в том, что файлы *.tfstate рекомендуется не просто добавить в .gitignore, а вообще хранить на удаленном сервере, поэтому его нет в примере который вы привели.","topic_id":100793}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Обзор Terraform","entity_url":null,"active":true}},{"id":104128,"title":"При выполнении terraform apply\n╷\n│ Error: Invalid function argument\n│\n│ on main.tf line 37, in resource \"yandex_compute_instance\" \"default\":\n│ 37: ssh-keys = \"ubuntu:${file(\"~/.ssh/id_rsa.pub\")}\"\n│ ├────────────────\n│ │ while calling file(path)\n│\n│ Invalid value for \"path\" parameter: no file exists at \"~/.ssh/id_rsa.pub\"; this function works only with files that\n│ are distributed as part of the configuration source code, so if this file will be created by a resource in this\n│ configuration you must instead obtain this result from an attribute of that resource.","plain_title":"При выполнении terraform apply ╷ │ Error: Invalid function argument │ │ on main.tf line 37, in resource \"yandexcomputeinstance\" \"default\": │ 37: ssh-keys = \"ubuntu:${file(\"~/.ssh/idrsa.pub\")}\" │ ├──────────────── │ │ while calling file(path) │ │ Invalid value for \"path\" parameter: no file exists at \"~/.ssh/idrsa.pub\"; this function works only with files that │ are distributed as part of the configuration source code, so if this file will be created by a resource in this │ configuration you must instead obtain this result from an attribute of that resource. ","creator":{"public_name":"Nicestas","id":899326,"is_tutor":false},"comments":[{"creator":{"public_name":"Nicestas","id":899326,"is_tutor":false},"id":196659,"body":"В тексте этого не сказано, но сделал следующее:\n(1) mkdir ~/.ssh\n(2) cd ~/.ssh\n(3) ssh-keygen -t ed25519 -C\n(4) дал название id_rsa.pub","topic_id":104128}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Обзор Terraform","entity_url":null,"active":true}}],"lesson":{"exercise":null,"units":[{"id":5516,"name":"theory","url":"/courses/terraform-basics/lessons/overview/theory_unit"},{"id":5860,"name":"quiz","url":"/courses/terraform-basics/lessons/overview/quiz_unit"}],"links":[{"id":425570,"name":"Terraform Example (Github)","url":"https://github.com/hexlet-components/terraform-example\n"},{"id":425571,"name":"Автоматическое форматирование конфигурации","url":"https://www.terraform.io/cli/commands/fmt\n"}],"ordered_units":[{"id":5516,"name":"theory","url":"/courses/terraform-basics/lessons/overview/theory_unit"},{"id":5860,"name":"quiz","url":"/courses/terraform-basics/lessons/overview/quiz_unit"}],"id":2437,"slug":"overview","state":"approved","name":"Обзор Terraform","course_order":200,"goal":"Проходим все этапы настройки и создания своей инфраструктуры","self_study":"Выполните все действия из теории к уроку\n","theory_video_provider":null,"theory_video_uid":null,"theory":"В этом уроке мы рассмотрим типовой рабочий процесс с использованием Terraform без погружения в детали. Опишем небольшую инфраструктуру и поработаем с ней, развернем, обновим и удалим. А в следующих уроках, рассмотрим все это подробно.\n\n## Подготовка\n\nНа протяжении всего курса, мы будем описывать нашу инфраструктуру в репозитории, который станет частью вашего портфолио. Для этого создайте на Github репозиторий *hexlet-terraform* и клонируйте его себе на компьютер. Все дальнейшие команды будут выполняться внутри этого репозитория.\n\nТеперь установите Terraform по [этой ссылке](https://learn.hashicorp.com/tutorials/terraform/install-cli). Проверьте что он работает:\n\n```bash\nterraform -v\nTerraform v1.7.1\n```\n\nЗарегистрируйтесь в [Yandex Cloud](https://cloud.yandex.ru/ru/). [На этой странице](https://cloud.yandex.ru/ru/docs/iam/concepts/authorization/oauth-token) описано как получить OAuth-токен. Сделайте запрос и получите ключ, который понадобится Terraform для выполнения удаленных команд.\n\n## Инициализация\n\nПеред началом работы с Terraform, нужно определиться с облаком, которое будет использоваться. Все примеры курса даются в Yandex Cloud, но вы можете выбрать любое другое облако, которое вам по душе. Правда для этого придется самостоятельно копаться в документации, чтобы повторять за тем, что дается в уроке.\n\nКогда облако выбрано, нужно найти его в [списке провайдеров](https://registry.terraform.io/browse/providers) и перейти в [документацию](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs). Либо можно посмотреть [документацию в самом облачном провайдере](https://cloud.yandex.ru/ru/docs/tutorials/infrastructure-management/terraform-quickstart). Здесь описано, как инициализировать работу с ним.\n\nЗачем нужен провайдер? Провайдер это модуль Terraform, который умеет работать с конкретным облаком. Как только он подключается к проекту, внутри появляются команды для взаимодействия с его сервисами.\n\nДля подключения провайдера Yandex Cloud, создайте файл *.terraformrc* и добавьте в него содержимое:\n\n```terraform\nprovider_installation {\n network_mirror {\n url = \"https://terraform-mirror.yandexcloud.net/\"\n include = [\"registry.terraform.io/*/*\"]\n }\n direct {\n exclude = [\"registry.terraform.io/*/*\"]\n }\n}\n```\n\nЭти данные содержат информацию об источнике, и которого будет устанавливаться провайдер.\n\nЗатем создайте файл *main.tf* и добавьте в него такое содержимое:\n\n```terraform\n// main.tf - имя файла выбрано произвольно, важно только расширение\nterraform {\n required_providers {\n yandex = {\n source = \"yandex-cloud/yandex\"\n }\n }\n required_version = \">= 0.13\"\n}\n\n// Terraform должен знать ключ, для выполнения команд по API\n\n// Определение переменной, которую нужно будет задать\nvariable \"yc_token\" {}\n\nprovider \"yandex\" {\n zone = \"ru-central1-a\"\n token = var.yc_token\n}\n```\n\nПомимо указания списка провайдеров, мы описываем переменную `yc_token`, которая записывается в аттрибут `token` провайдера *yandex*. Это ключ необходим Terraform для выполнения команд по API. Значение ключа указывать в конфигурации нельзя, иначе, кто-нибудь сможет с его помощью выполнить любой код на YandexCloud, включая полное уничтожение всей инфраструктуры.\n\nДля работы с секретами, Terraform предлагает создавать файлы с расширением *\\*.auto.tfvars*, которые добавляются в *.gitignore*. Сам ключ, при этом, можно хранить в зашифрованном виде в [Ansible Vault](https://docs.ansible.com/ansible/latest/user_guide/vault.html).\n\nСоздадим файл *secrets.auto.tfvars*, в котором описываются переменные, содержащие секретные данные. Добавим туда наш ключ как значение переменной `yc_token`:\n\n```terraform\n// Terraform автоматически подгрузит этот файл\nyc_token = \"<тут секретный ключ>\"\n```\n\nПосле того как провайдер добавлен, нужно выполнить инициализацию:\n\n```bash\nterraform init\n\nInitializing the backend...\n\nInitializing provider plugins...\n- Finding latest version of yandex-cloud/yandex...\n- Installing yandex-cloud/yandex v0.106.0...\n- Installed yandex-cloud/yandex v0.106.0 (self-signed, key ID E40F590B50BB8E40)\n\nPartner and community providers are signed by their developers.\nIf you'd like to know more about provider signing, you can read about it here:\nhttps://www.terraform.io/docs/cli/plugins/signing.html\n\nTerraform has created a lock file .terraform.lock.hcl to record the provider\nselections it made above. Include this file in your version control repository\nso that Terraform can guarantee to make the same selections by default when\nyou run \"terraform init\" in the future.\n\nTerraform has been successfully initialized!\n```\n\nВо время инициализации скачивается код провайдера в директорию *.terraform*. Эта служебная директория, содержимое которой нам не важно, поэтому ее добавляют в *.gitignore*.\n\nИнициализация выполняется каждый раз, когда репозиторий клонируется заново или обновляются версии зависимостей. Несмотря на название, команда `terraform init` больше похожа на установку зависимостей в JavaScript с помощью `npm install`.\n\nКроме *.terraform*, в директории с проектом оказывается файл *.terraform.lock.hcl*.\n\n## Описание инфраструктуры\n\nНа странице документации провайдера, слева меню, в котором есть список возможностей провайдера. Мы начнем с самой базовой - [создании сервера](https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/compute_instance).\n\nВ терминах Terraform *yandex_compute_instance* называется ресурсом. Это то, чем мы управляем в нашем облаке. Создадим сервер:\n\n```terraform\nterraform {\n required_providers {\n yandex = {\n source = \"yandex-cloud/yandex\"\n }\n }\n required_version = \">= 0.13\"\n}\n\nvariable \"yc_token\" {}\n\nprovider \"yandex\" {\n zone = \"ru-central1-a\"\n token = var.yc_token\n}\n\nresource \"yandex_compute_instance\" \"default\" {\n name = \"test\"\n platform_id = \"standard-v1\"\n zone = \"ru-central1-a\"\n folder_id = \"<идентификатор каталога>\"\n\n resources {\n cores = 2\n memory = 4\n }\n\n boot_disk {\n disk_id = yandex_compute_disk.default.id\n }\n\n network_interface {\n subnet_id = \"${yandex_vpc_subnet.default.id}\"\n }\n\n metadata = {\n ssh-keys = \"ubuntu:${file(\"~/.ssh/id_rsa.pub\")}\"\n }\n}\n\nresource \"yandex_vpc_network\" \"default\" {\n folder_id = \"<идентификатор каталога>\"\n}\n\nresource \"yandex_vpc_subnet\" \"default\" {\n zone = \"ru-central1-a\"\n network_id = \"${yandex_vpc_network.default.id}\"\n v4_cidr_blocks = [\"10.5.0.0/24\"]\n folder_id = \"<идентификатор каталога>\"\n}\n\nresource \"yandex_compute_disk\" \"default\" {\n name = \"disk-name\"\n type = \"network-ssd\"\n zone = \"ru-central1-a\"\n image_id = \"fd83s8u085j3mq231ago\" // идентификатор образа Ubuntu\n folder_id = \"<идентификатор каталога>\"\n\n labels = {\n environment = \"test\"\n }\n}\n```\n\nДля создания сервера, нам пришлось создать дополнительные ресурсы *yandex_vpc_network*, *yandex_vpc_subnet*, *yandex_compute_disk*.\n\nОписание аргументов *resources*, *boot_disk* и других идет в документации сразу после примера в секции *Argument Reference*. Там же указано какие из них обязательные, а какие нет. С другой стороны, в этом разделе не хватает информации о возможных значениях. Откуда брать значения для *platform_id* или *cores*? Иногда в документации есть ссылка на страницу с возможными значениями, но это бывает не всегда. К сожалению здесь не остается ничего другого, как пытаться найти эту информацию в документации облачного провайдера. Здесь обычно помогает Google.\n\nРесурсы требуют указания *folder_id* — это ваш каталог внутри которого будет создан ресурс. Его идентификатор можно найти [в консоли Yandex Cloud](https://console.cloud.yandex.ru/cloud/). Если каталог не создан, создайте его.\n\nЯзык описания инфраструктуры отдаленно напоминает JSON и интуитивно понятен в большинстве ситуаций. Главное что нужно понимать, он описывает не команды, а состояние того, что мы хотим получить в конце. Это значит что порядок описания инфраструктуры не имеет значения, Terraform все равно сделает изменения в том порядке, в котором нужно.\n\nИнфраструктуру можно описывать в любых файлах с расширением *\\*.tf*. Terraform самостоятельно их загружает и вычисляет порядок, в котором надо выполнять изменения. Для простоты мы все делаем в файле *main.tf*. Когда кода много, его удобно раскладывать по разным файлам.\n\n## Создание\n\nТеперь, когда ресурсы описаны, можно их создать. Делается это в два шага. Сначала нам показывают план изменений и если он соответствует нашим ожиданиям, то мы подтверждаем его выполнение и Terraform делает это\n\n```bash\nterraform apply\n\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n + create\n\nTerraform will perform the following actions:\n\n # yandex_compute_disk.default will be created\n + resource \"yandex_compute_disk\" \"default\" {\n + block_size = 4096\n + created_at = (known after apply)\n + folder_id = \"<идентификатор каталога>\"\n + id = (known after apply)\n + image_id = \"fd83s8u085j3mq231ago\"\n + labels = {\n + \"environment\" = \"test\"\n }\n + name = \"disk-name\"\n + product_ids = (known after apply)\n + size = 150\n + status = (known after apply)\n + type = \"network-ssd\"\n + zone = \"ru-central1-a\"\n }\n\n # yandex_compute_instance.default will be created\n + resource \"yandex_compute_instance\" \"default\" {\n + created_at = (known after apply)\n + folder_id = \"<идентификатор каталога>\"\n + fqdn = (known after apply)\n + gpu_cluster_id = (known after apply)\n + hostname = (known after apply)\n + id = (known after apply)\n + maintenance_grace_period = (known after apply)\n + maintenance_policy = (known after apply)\n + metadata = {\n + \"ssh-keys\" = \"<тут публичный ssh-ключ>\"\n }\n + name = \"test\"\n + network_acceleration_type = \"standard\"\n + platform_id = \"standard-v1\"\n + service_account_id = (known after apply)\n + status = (known after apply)\n + zone = \"ru-central1-a\"\n\n + boot_disk {\n + auto_delete = true\n + device_name = (known after apply)\n + disk_id = (known after apply)\n + mode = (known after apply)\n }\n\n + network_interface {\n + index = (known after apply)\n + ip_address = (known after apply)\n + ipv4 = true\n + ipv6 = (known after apply)\n + ipv6_address = (known after apply)\n + mac_address = (known after apply)\n + nat = false\n + nat_ip_address = (known after apply)\n + nat_ip_version = (known after apply)\n + security_group_ids = (known after apply)\n + subnet_id = (known after apply)\n }\n\n + resources {\n + core_fraction = 100\n + cores = 2\n + memory = 4\n }\n }\n\n # yandex_vpc_network.default will be created\n + resource \"yandex_vpc_network\" \"default\" {\n + created_at = (known after apply)\n + default_security_group_id = (known after apply)\n + folder_id = \"<идентификатор каталога>\"\n + id = (known after apply)\n + labels = (known after apply)\n + name = (known after apply)\n + subnet_ids = (known after apply)\n }\n\n # yandex_vpc_subnet.default will be created\n + resource \"yandex_vpc_subnet\" \"default\" {\n + created_at = (known after apply)\n + folder_id = \"<идентификатор каталога>\"\n + id = (known after apply)\n + labels = (known after apply)\n + name = (known after apply)\n + network_id = (known after apply)\n + v4_cidr_blocks = [\n + \"10.5.0.0/24\",\n ]\n + v6_cidr_blocks = (known after apply)\n + zone = \"ru-central1-a\"\n }\n\nPlan: 4 to add, 0 to change, 0 to destroy.\n\nDo you want to perform these actions?\n Terraform will perform the actions described above.\n Only 'yes' will be accepted to approve.\n\n Enter a value: \n\n```\n\nОчень важно убедиться, что здесь нет ничего опасного, например удаление или пересоздание каких-то ресурсов, которые нельзя трогать. В этом смысле, Terraform максимально опасный инструмент. Неверно примененный план может привести к полной потере всего проекта включая бекапы.\n\nЕсли нас все устраивает, то нужно набрать *yes*. Создание серверов займет какое-то время. Terraform выполняет все операции синхронно, поэтому он закончит работу только после внесения всех изменений.\n\n```bash\n Enter a value: yes\n\n# Для удобства здесь видны имена, которые задаются в конфигурации\nyandex_vpc_network.default: Creating...\nyandex_compute_disk.default: Creating...\nyandex_vpc_network.default: Creation complete after 3s [id=enp3kbem2tc27rl1p3cr]\nyandex_vpc_subnet.default: Creating...\nyandex_vpc_subnet.default: Creation complete after 1s [id=e9b313hmo8022as59e1n]\nyandex_compute_disk.default: Still creating... [10s elapsed]\nyandex_compute_disk.default: Creation complete after 12s [id=fhmu797gh7d23f2ekna2]\nyandex_compute_instance.default: Creating...\nyandex_compute_instance.default: Still creating... [10s elapsed]\nyandex_compute_instance.default: Still creating... [20s elapsed]\nyandex_compute_instance.default: Creation complete after 26s [id=fhmdbscqlcgdgn0p8id6]\n\nApply complete! Resources: 4 added, 0 changed, 0 destroyed.\n```\n\nЕсли все прошло успешно, то зайдите в личный кабинет Yandex Cloud и убедитесь, что сервер создан. Правда магия?\n\nПосле того как Terraform выполнит изменения, он создает и затем обновляет файл *terraform.tfstate*. Этот файл хранит состояние инфраструктуры на текущий момент. Зачем это нужно? С его помощью Terraform вычисляет разницу между тем, что мы хотим получить в итоге и тем что есть сейчас. Без него Terraform будет считать, что инфраструктура каждый раз создается заново. Этот файл обычно не хранят в репозитории, так как вместе с состоянием в нем могут быть чувствительные данные вроде паролей и токенов. Более того, с этим файлом в один момент времени может работать только один человек, иначе Terraform не сможет правильно оценить текущую инфраструктуру. Для этого используют специальные сервисы, которые хранят файл состояния на удаленном сервере\n\nКроме *terraform.tfstate* Terraform создает файлы бекапы с расширением *\\*.backup*. Их нужно добавить в *.gitignore*.\n\nНу и наконец, давайте попробуем все удалить:\n\n```bash\nterraform destroy\n\nterraform destroy\ndigitalocean_droplet.web1: Refreshing state... [id=292822008]\ndigitalocean_droplet.web2: Refreshing state... [id=292822010]\n\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the\nfollowing symbols:\n - destroy\n\nTerraform will perform the following actions:\n\n # yandex_compute_disk.default will be destroyed\n - resource \"yandex_compute_disk\" \"default\" {\n - block_size = 4096 -> null\n - created_at = \"2024-01-31T12:34:31Z\" -> null\n - folder_id = \"<идентификатор каталога>\" -> null\n - id = \"fhm3acu0uitbelemhm0k\" -> null\n - image_id = \"fd83s8u085j3mq231ago\" -> null\n - labels = {\n - \"environment\" = \"test\"\n } -> null\n - name = \"disk-name\" -> null\n - product_ids = [\n - \"f2eth1eavhqoh47mqj08\",\n ] -> null\n - size = 150 -> null\n - status = \"ready\" -> null\n - type = \"network-ssd\" -> null\n - zone = \"ru-central1-a\" -> null\n\n - disk_placement_policy {}\n }\n\n # yandex_compute_instance.default will be destroyed\n - resource \"yandex_compute_instance\" \"default\" {\n - created_at = \"2024-01-31T12:34:47Z\" -> null\n - folder_id = \"<идентификатор каталога>\" -> null\n - fqdn = \"fhmpldmndpo4ufoji6gf.auto.internal\" -> null\n - id = \"fhmpldmndpo4ufoji6gf\" -> null\n - labels = {} -> null\n - metadata = {\n - \"ssh-keys\" = \"<тут публичный ssh-ключ>\"\n } -> null\n - name = \"test\" -> null\n - network_acceleration_type = \"standard\" -> null\n - platform_id = \"standard-v1\" -> null\n - status = \"running\" -> null\n - zone = \"ru-central1-a\" -> null\n\n - boot_disk {\n - auto_delete = true -> null\n - device_name = \"fhm3acu0uitbelemhm0k\" -> null\n - disk_id = \"fhm3acu0uitbelemhm0k\" -> null\n - mode = \"READ_WRITE\" -> null\n\n - initialize_params {\n - block_size = 4096 -> null\n - image_id = \"fd83s8u085j3mq231ago\" -> null\n - name = \"disk-name\" -> null\n - size = 150 -> null\n - type = \"network-ssd\" -> null\n }\n }\n\n - metadata_options {\n - aws_v1_http_endpoint = 1 -> null\n - aws_v1_http_token = 2 -> null\n - gce_http_endpoint = 1 -> null\n - gce_http_token = 1 -> null\n }\n\n - network_interface {\n - index = 0 -> null\n - ip_address = \"10.5.0.8\" -> null\n - ipv4 = true -> null\n - ipv6 = false -> null\n - mac_address = \"d0:0d:19:ab:6d:76\" -> null\n - nat = false -> null\n - security_group_ids = [] -> null\n - subnet_id = \"e9bc80kd45lfmfq2fq0d\" -> null\n }\n\n - placement_policy {\n - host_affinity_rules = [] -> null\n - placement_group_partition = 0 -> null\n }\n\n - resources {\n - core_fraction = 100 -> null\n - cores = 2 -> null\n - gpus = 0 -> null\n - memory = 4 -> null\n }\n\n - scheduling_policy {\n - preemptible = false -> null\n }\n }\n\n # yandex_vpc_network.default will be destroyed\n - resource \"yandex_vpc_network\" \"default\" {\n - created_at = \"2024-01-31T12:34:31Z\" -> null\n - default_security_group_id = \"enp2dag2iedkjuak3pkn\" -> null\n - folder_id = \"<идентификатор каталога>\" -> null\n - id = \"enpvpoj117va949tjmae\" -> null\n - labels = {} -> null\n - subnet_ids = [\n - \"e9bc80kd45lfmfq2fq0d\",\n ] -> null\n }\n\n # yandex_vpc_subnet.default will be destroyed\n - resource \"yandex_vpc_subnet\" \"default\" {\n - created_at = \"2024-01-31T12:34:33Z\" -> null\n - folder_id = \"<идентификатор каталога>\" -> null\n - id = \"e9bc80kd45lfmfq2fq0d\" -> null\n - labels = {} -> null\n - network_id = \"enpvpoj117va949tjmae\" -> null\n - v4_cidr_blocks = [\n - \"10.5.0.0/24\",\n ] -> null\n - v6_cidr_blocks = [] -> null\n - zone = \"ru-central1-a\" -> null\n }\n\nPlan: 0 to add, 0 to change, 4 to destroy.\n\nDo you really want to destroy all resources?\n Terraform will destroy all your managed infrastructure, as shown above.\n There is no undo. Only 'yes' will be accepted to confirm.\n\n Enter a value: yes\n\nyandex_compute_instance.default: Destroying... [id=fhmpldmndpo4ufoji6gf]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 10s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 20s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 30s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 40s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 50s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 1m0s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 1m10s elapsed]\nyandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 1m20s elapsed]\nyandex_compute_instance.default: Destruction complete after 1m30s\nyandex_vpc_subnet.default: Destroying... [id=e9bc80kd45lfmfq2fq0d]\nyandex_compute_disk.default: Destroying... [id=fhm3acu0uitbelemhm0k]\nyandex_compute_disk.default: Destruction complete after 0s\nyandex_vpc_subnet.default: Destruction complete after 5s\nyandex_vpc_network.default: Destroying... [id=enpvpoj117va949tjmae]\nyandex_vpc_network.default: Destruction complete after 0s\n\nDestroy complete! Resources: 4 destroyed.\n```\n\n## Собирая все вместе\n\n```bash\n# gitignore\n.terraform\n*.auto.tfvars\n*.backup\n```\n\n```terraform\n// main.tf\nterraform {\n required_providers {\n yandex = {\n source = \"yandex-cloud/yandex\"\n }\n }\n required_version = \">= 0.13\"\n}\n\nvariable \"yc_token\" {}\n\nprovider \"yandex\" {\n zone = \"ru-central1-a\"\n token = var.yc_token\n}\n\nresource \"yandex_compute_instance\" \"default\" {\n name = \"test\"\n platform_id = \"standard-v1\"\n zone = \"ru-central1-a\"\n folder_id = \"<идентификатор каталога>\"\n\n resources {\n cores = 2\n memory = 4\n }\n\n boot_disk {\n disk_id = yandex_compute_disk.default.id\n }\n\n network_interface {\n subnet_id = \"${yandex_vpc_subnet.default.id}\"\n }\n\n metadata = {\n ssh-keys = \"ubuntu:${file(\"~/.ssh/id_rsa.pub\")}\"\n }\n}\n\nresource \"yandex_vpc_network\" \"default\" {\n folder_id = \"<идентификатор каталога>\"\n}\n\nresource \"yandex_vpc_subnet\" \"default\" {\n zone = \"ru-central1-a\"\n network_id = \"${yandex_vpc_network.default.id}\"\n v4_cidr_blocks = [\"10.5.0.0/24\"]\n folder_id = \"<идентификатор каталога>\"\n}\n\nresource \"yandex_compute_disk\" \"default\" {\n name = \"disk-name\"\n type = \"network-ssd\"\n zone = \"ru-central1-a\"\n image_id = \"fd83s8u085j3mq231ago\"\n folder_id = \"<идентификатор каталога>\"\n\n labels = {\n environment = \"test\"\n }\n}\n```\n\n```terraform\n// secrets.auto.tfvars\nyc_token = \"<api key>\"\n```\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":5515,"name":"theory","url":"/courses/terraform-basics/lessons/init/theory_unit"},{"id":13152,"name":"quiz","url":"/courses/terraform-basics/lessons/init/quiz_unit"}],"links":[{"id":425569,"name":"Облачные провайдеры для практик DevOps","url":"https://help.hexlet.io/practice-guides/oblachnye-provaidery-dlya-praktik-po-devops"}],"ordered_units":[{"id":5515,"name":"theory","url":"/courses/terraform-basics/lessons/init/theory_unit"},{"id":13152,"name":"quiz","url":"/courses/terraform-basics/lessons/init/quiz_unit"}],"id":2436,"slug":"init","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом и его целями","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Облачные провайдеры позволяют работать со своими сервисами через веб-интерфейсы. Потратив час-два времени в Digital Ocean, Amazon, Yandex.Cloud можно создать типовую инфраструктуру: пару серверов, управляемую базу данных, балансировщик, файловое хранилище, CDN. Все это соединить, правильно настроить сеть, подумать о безопасности и, в конце концов, начать с ней работать.\n\n\n\nКажется несложно, но со временем, инфраструктура начнет расти, ее придется обновлять иногда пересоздавать. Делать это не имея перед собой слепка системы крайне сложно, вся инфраструктура и настройки разбросаны по разным разделам и регулируются множеством галочек. В случае ошибок практически невозможно откатиться и узнать какое изменение привело к проблемам. Особенно если с инфраструктурой работает несколько человек. Становится сложно отследить кто какое изменение внес.\n\nДля решения этих проблем используется подход **инфраструктура как код**. Вместо того, чтобы \"тыкать\" кнопки в интерфейсе, мы описываем нужную нам инфраструктуру в коде на каком-то удобном языке, который затем, в автоматическом режиме, делает нужные нам изменения. Одним из таких инструментов является Terraform.\n\nTerraform — инструмент, с помощью которого автоматизируется настройка серверной инфраструктуры. Он интегрирован со всеми популярными облаками и может развернуть одной кнопкой любые доступные там сервисы от баз данных и серверов, до CDN и балансировщиков.\n\nВ этом курсе мы научимся им пользоваться и развернем пару кластеров.\n\nДля экспериментов в этом курсе мы рекомендуем [Yandex Cloud](https://help.hexlet.io/practice-guides/oblachnye-provaidery-dlya-praktik-po-devops/). Зарегистрировавшись, вам будет доступен пробный период на 60 дней, которого должно хватить для экспериментов.\n"},"id":260,"slug":"terraform-basics","challenges_count":0,"name":"Terraform: Основы","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите Terraform. Вы узнаете больше о том, что такое инфраструктура как код. В итоге научитесь создавать ресурсы и поддерживать их идемпотентность. Terraform пригодится, если вы решите автоматизировать настройку серверной инфраструктуры. Знания из этого курса помогают программистам работать с облачной инфраструктурой.","kind":"basic","updated_at":"2026-01-20T11:54:10.765Z","language":"shell","duration_cache":10800,"skills":["Автоматически разворачивать инфраструктуру","Внедрять Terraform в уже существующий проект","Использовать переменные для настройки конфигурации","Настраивать зависимости между ресурсами"],"keywords":[],"lessons_count":10,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTA3OSwicHVyIjoiYmxvYl9pZCJ9fQ==--c5012d420e7691e22195551e248cc78c53b10bb9/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":45,"slug":"infrastructure-automation","title":"Автоматизация IT-инфраструктуры","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":1850,"duration_in_months":1},"id":78,"slug":"infrastructure-automation","title":"Автоматизация инфраструктуры","subtitle":"Навык, позволяющий автоматизировать развертывание и управление серверной инфраструктурой с Terraform","subtitle_for_lists":"Навык управления инфраструктурой с Terraform","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"infrastructure-automation","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk3NywicHVyIjoiYmxvYl9pZCJ9fQ==--bc5ef27286509b0ecf2f8ae6cbdce2376db3d394/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/500%20Internal%20Server%20Error-cuate.png"},{"stack":{"id":225,"slug":"devops-engineer-from-scratch","title":"DevOps-инженер с нуля","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"not_finished","order":50,"duration_in_months":14},"id":355,"slug":"devops-engineer-from-scratch","title":"DevOps-инженер с нуля","subtitle":"Полное погружение в DevOps: весь стек от Linux до Kubernetes","subtitle_for_lists":"Полное погружение в DevOps: весь стек от Linux до Kubernetes","locale":"ru","current":true,"duration_in_months_text":"14 месяцев","stack_slug":"devops-engineer-from-scratch","price_text":"от 6 792 ₽","duration_text":"14 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png"},{"stack":{"id":303,"slug":"devops-for-developers","title":"DevOps для разработчиков","audience":"for_programmers","start_type":"weekly","pricing_model":"purchase","priority":"medium","kind":"profession","state":"published","stack_state":"not_finished","order":150,"duration_in_months":3},"id":444,"slug":"devops-for-developers","title":"DevOps для разработчиков","subtitle":"Изучите деплой, автоматизацию, GitHub Actions, Docker, Ansible, Terraform, IaC","subtitle_for_lists":"Изучите деплой, автоматизацию, GitHub Actions, Docker, Ansible, Terraform, IaC","locale":"ru","current":true,"duration_in_months_text":"3 месяца","stack_slug":"devops-for-developers","price_text":"от 2 797 ₽","duration_text":"3 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY0MywicHVyIjoiYmxvYl9pZCJ9fQ==--74611367ca7524225d6b8670846088b4aa9fa1d2/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Server-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/terraform-basics/lessons/overview/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Terraform: Основы</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Обзор Terraform</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Обзор Terraform","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Terraform: Основы"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>В этом уроке мы рассмотрим типовой рабочий процесс с использованием Terraform без погружения в детали. Опишем небольшую инфраструктуру и поработаем с ней, развернем, обновим и удалим. А в следующих уроках, рассмотрим все это подробно.</p>
<h2 id="heading-2-1">Подготовка</h2>
<p>На протяжении всего курса, мы будем описывать нашу инфраструктуру в репозитории, который станет частью вашего портфолио. Для этого создайте на Github репозиторий <em>hexlet-terraform</em> и клонируйте его себе на компьютер. Все дальнейшие команды будут выполняться внутри этого репозитория.</p>
<p>Теперь установите Terraform по <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://learn.hashicorp.com/tutorials/terraform/install-cli" rel="noopener noreferrer" target="_blank">этой ссылке</a>. Проверьте что он работает:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">terraform -v
Terraform v1.7.1</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Зарегистрируйтесь в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://cloud.yandex.ru/ru/" rel="noopener noreferrer" target="_blank">Yandex Cloud</a>. <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://cloud.yandex.ru/ru/docs/iam/concepts/authorization/oauth-token" rel="noopener noreferrer" target="_blank">На этой странице</a> описано как получить OAuth-токен. Сделайте запрос и получите ключ, который понадобится Terraform для выполнения удаленных команд.</p>
<h2 id="heading-2-2">Инициализация</h2>
<p>Перед началом работы с Terraform, нужно определиться с облаком, которое будет использоваться. Все примеры курса даются в Yandex Cloud, но вы можете выбрать любое другое облако, которое вам по душе. Правда для этого придется самостоятельно копаться в документации, чтобы повторять за тем, что дается в уроке.</p>
<p>Когда облако выбрано, нужно найти его в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://registry.terraform.io/browse/providers" rel="noopener noreferrer" target="_blank">списке провайдеров</a> и перейти в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs" rel="noopener noreferrer" target="_blank">документацию</a>. Либо можно посмотреть <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://cloud.yandex.ru/ru/docs/tutorials/infrastructure-management/terraform-quickstart" rel="noopener noreferrer" target="_blank">документацию в самом облачном провайдере</a>. Здесь описано, как инициализировать работу с ним.</p>
<p>Зачем нужен провайдер? Провайдер это модуль Terraform, который умеет работать с конкретным облаком. Как только он подключается к проекту, внутри появляются команды для взаимодействия с его сервисами.</p>
<p>Для подключения провайдера Yandex Cloud, создайте файл <em>.terraformrc</em> и добавьте в него содержимое:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Эти данные содержат информацию об источнике, и которого будет устанавливаться провайдер.</p>
<p>Затем создайте файл <em>main.tf</em> и добавьте в него такое содержимое:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// main.tf - имя файла выбрано произвольно, важно только расширение
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
}
}
required_version = ">= 0.13"
}
// Terraform должен знать ключ, для выполнения команд по API
// Определение переменной, которую нужно будет задать
variable "yc_token" {}
provider "yandex" {
zone = "ru-central1-a"
token = var.yc_token
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Помимо указания списка провайдеров, мы описываем переменную <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">yc_token</code>, которая записывается в аттрибут <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">token</code> провайдера <em>yandex</em>. Это ключ необходим Terraform для выполнения команд по API. Значение ключа указывать в конфигурации нельзя, иначе, кто-нибудь сможет с его помощью выполнить любой код на YandexCloud, включая полное уничтожение всей инфраструктуры.</p>
<p>Для работы с секретами, Terraform предлагает создавать файлы с расширением <em>*.auto.tfvars</em>, которые добавляются в <em>.gitignore</em>. Сам ключ, при этом, можно хранить в зашифрованном виде в <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://docs.ansible.com/ansible/latest/user_guide/vault.html" rel="noopener noreferrer" target="_blank">Ansible Vault</a>.</p>
<p>Создадим файл <em>secrets.auto.tfvars</em>, в котором описываются переменные, содержащие секретные данные. Добавим туда наш ключ как значение переменной <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">yc_token</code>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">// Terraform автоматически подгрузит этот файл
yc_token = "<тут секретный ключ>"</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>После того как провайдер добавлен, нужно выполнить инициализацию:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of yandex-cloud/yandex...
- Installing yandex-cloud/yandex v0.106.0...
- Installed yandex-cloud/yandex v0.106.0 (self-signed, key ID E40F590B50BB8E40)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Во время инициализации скачивается код провайдера в директорию <em>.terraform</em>. Эта служебная директория, содержимое которой нам не важно, поэтому ее добавляют в <em>.gitignore</em>.</p>
<p>Инициализация выполняется каждый раз, когда репозиторий клонируется заново или обновляются версии зависимостей. Несмотря на название, команда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">terraform init</code> больше похожа на установку зависимостей в JavaScript с помощью <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">npm install</code>.</p>
<p>Кроме <em>.terraform</em>, в директории с проектом оказывается файл <em>.terraform.lock.hcl</em>.</p>
<h2 id="heading-2-3">Описание инфраструктуры</h2>
<p>На странице документации провайдера, слева меню, в котором есть список возможностей провайдера. Мы начнем с самой базовой - <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://registry.terraform.io/providers/yandex-cloud/yandex/latest/docs/resources/compute_instance" rel="noopener noreferrer" target="_blank">создании сервера</a>.</p>
<p>В терминах Terraform <em>yandex_compute_instance</em> называется ресурсом. Это то, чем мы управляем в нашем облаке. Создадим сервер:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
}
}
required_version = ">= 0.13"
}
variable "yc_token" {}
provider "yandex" {
zone = "ru-central1-a"
token = var.yc_token
}
resource "yandex_compute_instance" "default" {
name = "test"
platform_id = "standard-v1"
zone = "ru-central1-a"
folder_id = "<идентификатор каталога>"
resources {
cores = 2
memory = 4
}
boot_disk {
disk_id = yandex_compute_disk.default.id
}
network_interface {
subnet_id = "${yandex_vpc_subnet.default.id}"
}
metadata = {
ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
}
}
resource "yandex_vpc_network" "default" {
folder_id = "<идентификатор каталога>"
}
resource "yandex_vpc_subnet" "default" {
zone = "ru-central1-a"
network_id = "${yandex_vpc_network.default.id}"
v4_cidr_blocks = ["10.5.0.0/24"]
folder_id = "<идентификатор каталога>"
}
resource "yandex_compute_disk" "default" {
name = "disk-name"
type = "network-ssd"
zone = "ru-central1-a"
image_id = "fd83s8u085j3mq231ago" // идентификатор образа Ubuntu
folder_id = "<идентификатор каталога>"
labels = {
environment = "test"
}
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Для создания сервера, нам пришлось создать дополнительные ресурсы <em>yandex_vpc_network</em>, <em>yandex_vpc_subnet</em>, <em>yandex_compute_disk</em>.</p>
<p>Описание аргументов <em>resources</em>, <em>boot_disk</em> и других идет в документации сразу после примера в секции <em>Argument Reference</em>. Там же указано какие из них обязательные, а какие нет. С другой стороны, в этом разделе не хватает информации о возможных значениях. Откуда брать значения для <em>platform_id</em> или <em>cores</em>? Иногда в документации есть ссылка на страницу с возможными значениями, но это бывает не всегда. К сожалению здесь не остается ничего другого, как пытаться найти эту информацию в документации облачного провайдера. Здесь обычно помогает Google.</p>
<p>Ресурсы требуют указания <em>folder_id</em> — это ваш каталог внутри которого будет создан ресурс. Его идентификатор можно найти <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://console.cloud.yandex.ru/cloud/" rel="noopener noreferrer" target="_blank">в консоли Yandex Cloud</a>. Если каталог не создан, создайте его.</p>
<p>Язык описания инфраструктуры отдаленно напоминает JSON и интуитивно понятен в большинстве ситуаций. Главное что нужно понимать, он описывает не команды, а состояние того, что мы хотим получить в конце. Это значит что порядок описания инфраструктуры не имеет значения, Terraform все равно сделает изменения в том порядке, в котором нужно.</p>
<p>Инфраструктуру можно описывать в любых файлах с расширением <em>*.tf</em>. Terraform самостоятельно их загружает и вычисляет порядок, в котором надо выполнять изменения. Для простоты мы все делаем в файле <em>main.tf</em>. Когда кода много, его удобно раскладывать по разным файлам.</p>
<h2 id="heading-2-4">Создание</h2>
<p>Теперь, когда ресурсы описаны, можно их создать. Делается это в два шага. Сначала нам показывают план изменений и если он соответствует нашим ожиданиям, то мы подтверждаем его выполнение и Terraform делает это</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# yandex_compute_disk.default will be created
+ resource "yandex_compute_disk" "default" {
+ block_size = 4096
+ created_at = (known after apply)
+ folder_id = "<идентификатор каталога>"
+ id = (known after apply)
+ image_id = "fd83s8u085j3mq231ago"
+ labels = {
+ "environment" = "test"
}
+ name = "disk-name"
+ product_ids = (known after apply)
+ size = 150
+ status = (known after apply)
+ type = "network-ssd"
+ zone = "ru-central1-a"
}
# yandex_compute_instance.default will be created
+ resource "yandex_compute_instance" "default" {
+ created_at = (known after apply)
+ folder_id = "<идентификатор каталога>"
+ fqdn = (known after apply)
+ gpu_cluster_id = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ maintenance_grace_period = (known after apply)
+ maintenance_policy = (known after apply)
+ metadata = {
+ "ssh-keys" = "<тут публичный ssh-ключ>"
}
+ name = "test"
+ network_acceleration_type = "standard"
+ platform_id = "standard-v1"
+ service_account_id = (known after apply)
+ status = (known after apply)
+ zone = "ru-central1-a"
+ boot_disk {
+ auto_delete = true
+ device_name = (known after apply)
+ disk_id = (known after apply)
+ mode = (known after apply)
}
+ network_interface {
+ index = (known after apply)
+ ip_address = (known after apply)
+ ipv4 = true
+ ipv6 = (known after apply)
+ ipv6_address = (known after apply)
+ mac_address = (known after apply)
+ nat = false
+ nat_ip_address = (known after apply)
+ nat_ip_version = (known after apply)
+ security_group_ids = (known after apply)
+ subnet_id = (known after apply)
}
+ resources {
+ core_fraction = 100
+ cores = 2
+ memory = 4
}
}
# yandex_vpc_network.default will be created
+ resource "yandex_vpc_network" "default" {
+ created_at = (known after apply)
+ default_security_group_id = (known after apply)
+ folder_id = "<идентификатор каталога>"
+ id = (known after apply)
+ labels = (known after apply)
+ name = (known after apply)
+ subnet_ids = (known after apply)
}
# yandex_vpc_subnet.default will be created
+ resource "yandex_vpc_subnet" "default" {
+ created_at = (known after apply)
+ folder_id = "<идентификатор каталога>"
+ id = (known after apply)
+ labels = (known after apply)
+ name = (known after apply)
+ network_id = (known after apply)
+ v4_cidr_blocks = [
+ "10.5.0.0/24",
]
+ v6_cidr_blocks = (known after apply)
+ zone = "ru-central1-a"
}
Plan: 4 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Очень важно убедиться, что здесь нет ничего опасного, например удаление или пересоздание каких-то ресурсов, которые нельзя трогать. В этом смысле, Terraform максимально опасный инструмент. Неверно примененный план может привести к полной потере всего проекта включая бекапы.</p>
<p>Если нас все устраивает, то нужно набрать <em>yes</em>. Создание серверов займет какое-то время. Terraform выполняет все операции синхронно, поэтому он закончит работу только после внесения всех изменений.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">Enter a value: yes
# Для удобства здесь видны имена, которые задаются в конфигурации
yandex_vpc_network.default: Creating...
yandex_compute_disk.default: Creating...
yandex_vpc_network.default: Creation complete after 3s [id=enp3kbem2tc27rl1p3cr]
yandex_vpc_subnet.default: Creating...
yandex_vpc_subnet.default: Creation complete after 1s [id=e9b313hmo8022as59e1n]
yandex_compute_disk.default: Still creating... [10s elapsed]
yandex_compute_disk.default: Creation complete after 12s [id=fhmu797gh7d23f2ekna2]
yandex_compute_instance.default: Creating...
yandex_compute_instance.default: Still creating... [10s elapsed]
yandex_compute_instance.default: Still creating... [20s elapsed]
yandex_compute_instance.default: Creation complete after 26s [id=fhmdbscqlcgdgn0p8id6]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если все прошло успешно, то зайдите в личный кабинет Yandex Cloud и убедитесь, что сервер создан. Правда магия?</p>
<p>После того как Terraform выполнит изменения, он создает и затем обновляет файл <em>terraform.tfstate</em>. Этот файл хранит состояние инфраструктуры на текущий момент. Зачем это нужно? С его помощью Terraform вычисляет разницу между тем, что мы хотим получить в итоге и тем что есть сейчас. Без него Terraform будет считать, что инфраструктура каждый раз создается заново. Этот файл обычно не хранят в репозитории, так как вместе с состоянием в нем могут быть чувствительные данные вроде паролей и токенов. Более того, с этим файлом в один момент времени может работать только один человек, иначе Terraform не сможет правильно оценить текущую инфраструктуру. Для этого используют специальные сервисы, которые хранят файл состояния на удаленном сервере</p>
<p>Кроме <em>terraform.tfstate</em> Terraform создает файлы бекапы с расширением <em>*.backup</em>. Их нужно добавить в <em>.gitignore</em>.</p>
<p>Ну и наконец, давайте попробуем все удалить:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">terraform destroy
terraform destroy
digitalocean_droplet.web1: Refreshing state... [id=292822008]
digitalocean_droplet.web2: Refreshing state... [id=292822010]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
- destroy
Terraform will perform the following actions:
# yandex_compute_disk.default will be destroyed
- resource "yandex_compute_disk" "default" {
- block_size = 4096 -> null
- created_at = "2024-01-31T12:34:31Z" -> null
- folder_id = "<идентификатор каталога>" -> null
- id = "fhm3acu0uitbelemhm0k" -> null
- image_id = "fd83s8u085j3mq231ago" -> null
- labels = {
- "environment" = "test"
} -> null
- name = "disk-name" -> null
- product_ids = [
- "f2eth1eavhqoh47mqj08",
] -> null
- size = 150 -> null
- status = "ready" -> null
- type = "network-ssd" -> null
- zone = "ru-central1-a" -> null
- disk_placement_policy {}
}
# yandex_compute_instance.default will be destroyed
- resource "yandex_compute_instance" "default" {
- created_at = "2024-01-31T12:34:47Z" -> null
- folder_id = "<идентификатор каталога>" -> null
- fqdn = "fhmpldmndpo4ufoji6gf.auto.internal" -> null
- id = "fhmpldmndpo4ufoji6gf" -> null
- labels = {} -> null
- metadata = {
- "ssh-keys" = "<тут публичный ssh-ключ>"
} -> null
- name = "test" -> null
- network_acceleration_type = "standard" -> null
- platform_id = "standard-v1" -> null
- status = "running" -> null
- zone = "ru-central1-a" -> null
- boot_disk {
- auto_delete = true -> null
- device_name = "fhm3acu0uitbelemhm0k" -> null
- disk_id = "fhm3acu0uitbelemhm0k" -> null
- mode = "READ_WRITE" -> null
- initialize_params {
- block_size = 4096 -> null
- image_id = "fd83s8u085j3mq231ago" -> null
- name = "disk-name" -> null
- size = 150 -> null
- type = "network-ssd" -> null
}
}
- metadata_options {
- aws_v1_http_endpoint = 1 -> null
- aws_v1_http_token = 2 -> null
- gce_http_endpoint = 1 -> null
- gce_http_token = 1 -> null
}
- network_interface {
- index = 0 -> null
- ip_address = "10.5.0.8" -> null
- ipv4 = true -> null
- ipv6 = false -> null
- mac_address = "d0:0d:19:ab:6d:76" -> null
- nat = false -> null
- security_group_ids = [] -> null
- subnet_id = "e9bc80kd45lfmfq2fq0d" -> null
}
- placement_policy {
- host_affinity_rules = [] -> null
- placement_group_partition = 0 -> null
}
- resources {
- core_fraction = 100 -> null
- cores = 2 -> null
- gpus = 0 -> null
- memory = 4 -> null
}
- scheduling_policy {
- preemptible = false -> null
}
}
# yandex_vpc_network.default will be destroyed
- resource "yandex_vpc_network" "default" {
- created_at = "2024-01-31T12:34:31Z" -> null
- default_security_group_id = "enp2dag2iedkjuak3pkn" -> null
- folder_id = "<идентификатор каталога>" -> null
- id = "enpvpoj117va949tjmae" -> null
- labels = {} -> null
- subnet_ids = [
- "e9bc80kd45lfmfq2fq0d",
] -> null
}
# yandex_vpc_subnet.default will be destroyed
- resource "yandex_vpc_subnet" "default" {
- created_at = "2024-01-31T12:34:33Z" -> null
- folder_id = "<идентификатор каталога>" -> null
- id = "e9bc80kd45lfmfq2fq0d" -> null
- labels = {} -> null
- network_id = "enpvpoj117va949tjmae" -> null
- v4_cidr_blocks = [
- "10.5.0.0/24",
] -> null
- v6_cidr_blocks = [] -> null
- zone = "ru-central1-a" -> null
}
Plan: 0 to add, 0 to change, 4 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
yandex_compute_instance.default: Destroying... [id=fhmpldmndpo4ufoji6gf]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 10s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 20s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 30s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 40s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 50s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 1m0s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 1m10s elapsed]
yandex_compute_instance.default: Still destroying... [id=fhmpldmndpo4ufoji6gf, 1m20s elapsed]
yandex_compute_instance.default: Destruction complete after 1m30s
yandex_vpc_subnet.default: Destroying... [id=e9bc80kd45lfmfq2fq0d]
yandex_compute_disk.default: Destroying... [id=fhm3acu0uitbelemhm0k]
yandex_compute_disk.default: Destruction complete after 0s
yandex_vpc_subnet.default: Destruction complete after 5s
yandex_vpc_network.default: Destroying... [id=enpvpoj117va949tjmae]
yandex_vpc_network.default: Destruction complete after 0s
Destroy complete! Resources: 4 destroyed.</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-5">Собирая все вместе</h2>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>bash</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>terraform</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>terraform</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code"># gitignore
.terraform
*.auto.tfvars
*.backup</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/infrastructure-automation?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Автоматизация инфраструктуры</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Навык управления инфраструктурой с Terraform</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk3NywicHVyIjoiYmxvYl9pZCJ9fQ==--bc5ef27286509b0ecf2f8ae6cbdce2376db3d394/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/500%20Internal%20Server%20Error-cuate.png" alt="Автоматизация инфраструктуры" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/devops-engineer-from-scratch?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">14 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">DevOps-инженер с нуля</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полное погружение в DevOps: весь стек от Linux до Kubernetes</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk2NSwicHVyIjoiYmxvYl9pZCJ9fQ==--84278a1852c9c6fb13b80a69f395bac6e47a422e/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Cloud%20sync-bro.png" alt="DevOps-инженер с нуля" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/devops-for-developers?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">3 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">DevOps для разработчиков</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите деплой, автоматизацию, GitHub Actions, Docker, Ansible, Terraform, IaC</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzY0MywicHVyIjoiYmxvYl9pZCJ9fQ==--74611367ca7524225d6b8670846088b4aa9fa1d2/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Server-bro.png" alt="DevOps для разработчиков" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 2 797 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/terraform-basics/lessons/overview/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 10</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/terraform-basics/lessons/overview/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>