Первая статья, которая поможет понять основы автоматизации и специфику DevOps. Вся информация взята из разных источников, как открытых, так и закрытых.
Инфраструктура как код (Infrastructure as Code, IaC) — подход, который позволяет управлять IT-инфраструктурой через код, а не через графический интерфейс.
В нем описывается:
Также настройка производится с помощью определенных файлов конфигурации и последовательных скриптов
Terraform — инструмент, разработанный компанией HashiCorp. Он позволяет описывать и управлять инфраструктурой через код. Это один из самых популярных инструментов для IaC.
Конфигурация описывается в декларативных файлах на понятном языке HCL.
декларативных файл конфигурации описывает что хотим получить, а не как
Mutable Infrastructure (изменяемая инфраструктура) — Подход, при котором уже существующие ресурсы изменяются на месте без пересоздания.
У тебя есть сервер, ты его не удаляешь, а:
У тебя сервер живет долго и ты его постепенно просто меняешь.
В итоге каждый сервис будет индивидуальным — разное количество пакетов, разные данные, остаточные файлы, нагрузка в момент обновления и тд, и от этого и возникают проблемы.
Immutable infrastructure - подход, при котором уже созданные ресурсы не изменятся. Eсли нужно обновить, ты создаёшь новый ресурс, а старый удаляешь.
Вместо того чтобы зайти на сервер, обновить пакеты и изменить конфигурацию, ты делаешь так:
также допустим у тебя есть Mutable подход ты обновишь его прямо на сервере.
а Immutable подход ты соберешь новый образ, развернешь новый сервер с этим оброзом, а старый в утиль.
дорого и не неэффективно, затрачивает много ресурсов при развертывание новой ВМ чем должна, также затрачивает больше времени на создание новой инфраструктуры.
Terraform Использует плагины которые называются провайдерамис помощью них можно взаимодействовать с разными облачными и не только инфраструктурами.
Также можно обьединять несколько провайдеров в одном файле конфигурации
Также у провайдеров везде различные данные и переменные, поэтому для условного AWS использовать провайдер Proxmox нельзя.
Terraform State — это JSON-файл (terraform.tfstate), который хранит актуальную информацию о созданной инфраструктуре и сопоставляет ее с вашим кодом конфигурации. Он служит «источником истины», отслеживая текущие ресурсы, их свойства и метаданные. State необходим для сравнения желаемого состояния (код) с фактическим для планирования изменений.
Tfstate — Это файл состояния, который использует Terraform для отслеживания инфраструктуры.
Обычно он называется: Terraform.tftate
Этот файл используется для определения изменений, которое необходимо внести в инфраструктуру для нужной конфигурации. При одновременном использовании Terraform Apply, накладывается блокировка, дабы избедать возможных багов.
Команда -refresh-only нужна для обновления файла состояния без внесения изменений.
К примеру, я хочу поменять названия виртуальных машин в .tf файле, виртуалки уже созданы, названы старым именем, при выполнении команды terraform plan Terraform попытается найти виртуалки с новым названием, их не будет, увидит, что есть виртуалки с старым именем и попытается их удалить, чтобы в будущем не было такого, что Terraform пытается их удалить, можно обновить состояние путем выполнения команды terraform state mv "старое название" "новое название".
Также можно перемещать .tfstate в другие проекты, модули и тд.
пример команды:
terraform state mv -state-out=../terraform.tfstate aws_instance.example_new
aws_instance.example_new — перемещение из одной директории файла состояния в другую. Не стоит забывать про определение ресурса в main.tf
Для удаления данного ресурса стоит закоментировать все определение ресурса в main.tf и добавить removed block.
Пример ниже:

Аналогом более простым и понятным может стать команда terraform state rm 'название машины'
с точки логики это будет проще,но удаление произойдет только из state, а в AWS машина останется.
При использовании удаленных репозиториев хранения .tfstate нужно использовать следующие команды:
terraform state pull - для загрузки удаленного .tfstate.
terraform state push - для выгрузки локального .tfstate.
Данные команды к использованию не рекомендованы. Использовать только в очень крайних редких случаях
Terraform import - команда для импорта машин в .tfstate делаеться через удаление removed block при removed block пишется istance ID КОТОРЫЙ НУЖНО ЗАПИСАТЬ, и дальше нужно будет удалить блок removed путем документирования и снятие документирования с основной части .tf файла.
Пример:

Далее выполняется команда terraform import "название машины" <INSTANCE ID>.
Таким образом можно импортировать машину в .tfstate.
Важно! Можно импортировать уже существующую ВМ созданную вне Terraform (вручную).
Best practice считается использование import блока в файле конфигурации. Это делает настройку Terraform более правильной и гибкой. Пример оформления блока import представлен ниже.

Можно также и обновить состояние, если, к примеру, машина была удалена ВНЕ Terraform, т.е. минуя его, руками, то происходит несоотвествие реальной инфраструктуры и описанной в Terraform. Для обновления соответствия выполняется следующая команда:
terraform refresh - обновление state.
terraform state list - просмотр state.
В данном примере потребуется еще удалить данные из main.tf и из outputs.tf, чтобы иметь соответствие и при каждом Terraform plan не получать желание Terraform создать еще одну машину, которая была удалена ранее.
Terraform HCP позволяет бесплатно (до 5 человек) использовать данный .tfstate, чтобы вносить изменения вместе. Можно подключить VCS по типу GitHub, GitLab и другие, чтобы работать было проще и отслеживать историю commits.
HCP Terraform и Terraform Enterprise предоставляют хранилище состояния, удаленное выполнение и другие преимущества.
HashiCorp Sentinel используется как встроенный механизм Policy as Code(Политика как код) — он автоматически проверяет твои изменения инфраструктуры перед применением. Выполняет оценку стоимости для каждого запуска. Оценочная стоимость отображается в пользовательском интерфейсе запуска как дополнительный этап, между этапами планирования и применения.
Рекомендуется использовать определенные версии, а не latest, чтобы избежать не прозрачного обновления и недопониманий.
Для этого используется блок в .tf файле:
required_version = "~> 1.1.9"
где ~> означает то, что версия должна быть 1.1.9, меньше 1.2.0 (т.к. это другая версия), если, к примеру, есть версия 1.1.10, 1.1.11, 1.1.12, то они считаются подходящими (то есть до второго символа (минорного обновления)).
Версии определяются так:
МАЖОР.МИНОР.ПАТЧ, к примеру: 1.1.1.
Что немаловажно, версии Terraform и версии Provider работают отдельно друг от друга и обслуживаются. Иногда старая версия Provider не будет работать с новой версией Terraform.
Также имеются и другие фишки с версиями Terraform, вот синтаксис, приведенный в пример на официальном сайте Terraform:
1.7.5 - только версия 1.7.5.
>=1.7.5 - любая версия Terraform 1.7.5 и выше.
>=1.7.5, <1.9.5 - Terraform версии 1.7.5 и выше, но не старше 1.9.5
Представим ситуацию, нужно обновить Terraform до более новой версии, Provider основную версию Terraform (и новую и старую) поддерживает. Сначала делается инициализация с обновлением провайдеров:
terraform init -upgrade - эта команда скачает новую версию Terraform провайдеров (есл нужно), обновит локальный кэш в .terraform.
terraform plan - нужно проверить план, что новая версия совместима, будут отображены возможные изменения в инфраструктуре, No changes - идеально.
Теперь нужно сделать commit в удаленный репозиторий в другой branch (не main), создать Pull Request (GitHub) или Merge Request (GitLab).
terraform apply - применение изменений.
Для примера будет использоваться следующий кусок Terraform:

required_version - ограничение по версиям. Данная настройка применена к Terraform CLI, а не к провайдеру.
required_providers - указывает все плагины поставщика, необходимые для создания и управления ресурсами.
Далее указывается имя провайдера в блоке <PROVIDER> , а version указывает на версию провайдера (по умолчанию ставит самую новую), source - это адрес источника для поставщика.
provider_meta - указывает поля метаданных, которые может ожидать поставщик услуг. Эта функция является экспериментальной, не является обязательной для работы с Terraform.
backend - указывает механизмы сохранения .tfstate на удаленных репозиториях по типу GitLab или GitHub.
cloud - позволяет подключаться Terraform к Terraform HCP, либо устанавливать Terraform Enterprise. Можно указать только один блок cloud.
Нельзя настроить, если уже содержится backend!
Блок Resources описывает то, что будет определять виртуальную машину, сколько у нее будет ОЗУ, на какой ноде будет располагаться, все зависит от провайдера. В данном блоке можно указывать user-data (нужно для установки ПО в AWS) либо cicustom (тоже самое, но для cloud-init в Proxmox)
Источники данных получают данные от поставщика, но не создают и не изменяют ресурсы. Для доступа к данным используется блок data. Каждый блок данных связан с одним источником данных. Terraform может выполнять только операции чтения из источников данных.
Более подробно про Data Sources можно прочитать на официальном сайте Terraform и найти нужные команды с опциями: https://developer.hashicorp.com/terraform/language/v1.12.x/block/data
Также имеется возможность добавления блока postcondition с главной строкой lifecycle.
Postcondition (постусловие) — это утверждение, гарантирующее истинность определенных условий сразу после выполнения функции, метода или теста
Пример

Блоки precondition / postcondition нужны для того, чтобы проверить, что полученные данные соответствуют ожиданиям, и получить понятную ошибку, если это не так.
Еще один пример ниже. Допустим, у есть шаблон ВМ, нужно гарантировать, что ВМ, создаваемая из него, имеет минимум 8 ГБ ОЗУ. Вот как это будет выглядеть:
Более подробно про precondition можно сказать следующее, к примеру, на этапе создания ВМ нужно сначала указать минимальное количество ОЗУ (которое задается условием, перед применением terraform apply precondition проверяет, сходятся ли значения с минимумом), затем создать ВМ, а после, к примеру, проверить настройки сети, это уже будет postcondition. Если ВМ создалась, но при этом сеть не настроилась, то postcondition сообщает об ошибке и происходит taint, то есть машина помечается как испорченная, неготовая к работе. При следующем terraform apply сам Terraform предложит эту машину пересоздать, т.к. она негодная к работе.

Чтобы предотвратить удаление созданной инфраструктуры через
terraform destroy, можно добавить блок lifecycle сprevent_destroy = true. Пример оформления представлен ниже.

Также имеется возможность создания блока create_before_destroy, который позволяет минимизировать простои (полезно для k3s кластера, например). Сначала создастся что-то, описываемое в .tf файле, затем удалится то, что в файле конфигурации не сходится с реальной инфраструктурой, например, если названия поменялись или количество пакетов. Пример оформления кода ниже.

Имеется возможность создания псевдонимов для конфигурации поставщиков (если в пример берется AWS).
Пример

Таким образом можно ссылаться на что-либо благодаря псевдонимам.
Можно создать определенные зависимости одного блока от другого. К примеру, сначала надо настроить сеть, а лишь только после этого запустить процесс создания ВМ. Для этого и используется опция depends_on. Пример с данной командой можно увидеть ниже.

С мета-аргументами можно ознакомиться по ссылке: https://developer.hashicorp.com/terraform/language/v1.12.x/meta-arguments#depends_on
Variables - это переменные данные, которые нужны для того, чтобы производить настройку чего-либо без изменения конфигурации напрямую.
Пример Variables:

Также не стоит забывать, что variables можно использовать с condition и precondition, чтобы задавать условия, которые нужно соблюдать, например, максимум задаваемой ОЗУ на ВМ.
Немаловажная опция - sensitive, позволяет скрыть пароли и чувствительные данные при использовании terraform plan. Пример оформления кода представлен ниже.

Variables рекомендуется настраивать и хранить в отдельном файле - variables.tf
Более подробное описание типов данных string, number, bool и т.д. можно найти на официальном сайте Terraform:
https://developer.hashicorp.com/terraform/language/v1.12.x/expressions/type-constraints
Terraform предоставляет возможность создания check блока, который проверит что-либо на правильность настройки и работу, проверять можно даже скриптами, также check предоставит вывод о наличие ошибки, если правильно настроить блок.
Пример оформления check блока:

Dependency-Lock - Файл блокировки зависимостей — это файл, относящийся к конфигурации в целом, а не к каждому отдельному модулю в конфигурации.
Файл блокировки всегда называется .terraform.lock.hcl и это имя призвано обозначить, что это файл блокировки для различных элементов, которые Terraform кэширует в .terraform подкаталоге рабочей директории. Он обновлеяется и изменяется каждый раз, когда делается команда terraform init.
Его следует включать в удаленный репозиторий .tfstate!
Включать в удаленный репозиторий его нужно потому, чтобы люди могли обсуждать потенциальные изменения внешних зависимостей в ходе проверки кода, так же, как люди обсуждали бы потенциальные изменения самой конфигурации.
При terraform initё учитываются не только .tf файлы, но и .lock.hcl файл тоже (касается ограничений по версиям).
Работает он по принципу проверки контрольных сумм (checksums), но только если таковые имеются и были ранее записаны в файл .lock.hcl, поэтому Terraform может вернуть ошибку о том, что контрольные суммы не совпадают, в случае, если что-то пошло не так.
При обновлении старые hashes удаляются, а новые вносятся заново. Это принцип работы Dependency.
Работа в команде строится следующим образом:
Каждый разработчик, DevOps и Системный администратор имеет свою локальную версию Terraform, где можно поиграться с тестовыми стендами, не имея доступа до Prod, нужно для теста синтаксиса и правильности выполнения команд, возможной отладки.
Затем после написания всех нужных файлов конфигураций создается отдельный Branch, в который включены все изменения (может быть простым клоном репозитория и видоизменяться как угодно, то есть берется основа и далее меняется), делается Commit и Push.
Следующим этапом станет Merge Request, это нужно для того, чтобы из своего тестового Branch попасть в Main и внести изменения, но первый Merge Request будет запускать CI/CD Pipeline для того, чтобы вывести Terraform plan в CI, данный вывод смотрит Senior, затем принимает решение, либо Approve, либо Decline, во втором случае весь процесс начинается заново, вносятся изменения в первый этап.
После Approve запускается еще один CI/CD Pipeline для Terraform apply, либо изменения вносятся вручную. Этот этап направлен на Prod. Все Env берутся из Vault, все .tfstate хранятся на удаленных репозиториях. Гибкость.
Команда terraform init нужна для инициализации и подготовки рабочего каталога к работе с Terraform. Эту команду можно использовать несколько раз - это безопасно, но если были внесены изменения в файлы конфигураций и после введена команда terraform init, то могут быть ошибки, но эта команда не сотрет никакие файлы в директории, поскольку она просто инициализирует эту директорию для работы.
Важны опции данной команды, для обновления версии Terraform, допустим, будет использоваться команда terraform init -upgrade.
С подробными опциями команды terraform init можно ознакомиться тут: https://developer.hashicorp.com/terraform/cli/v1.12.x/commands/init
Команда terraform fmt автоматически приведет файл конфигурации в правильный стиль, это удобный инструмент форматирования.
С подробными опциями команды terraform fmt можно ознакомиться тут:https://developer.hashicorp.com/terraform/cli/v1.12.x/commands/fmt
Команда terraform validate проверяет и подтверждает синтаксическую корректность, а ткаже внутреннюю согласованность конфигурации. Команда запускается безопасно. Может выводить в двух видах: JSON и в обычном.
Команда terraform plan нужна для планирования каких-либо изменений перед их внесением. Описывает то, что должно получиться в итоге, сравнивает текущий файл конфигурации с текущей инфраструктурой. План можно сохранять путем ввода команды terraform plan -out "название плана", при terraform apply "название плана" можно применить сохраненный план.
Данный план не рекомендуется добавлять в системы контроля версий, т.к. они могут содержать конфиденциальную информацию. При просмотре Terraform plan можно увидеть пароли и т.д. даже используя переменные
.tfvars.
Работает по принципу prior_state, в нем хранится информация о текущей инфраструктуре, при втором terraform-plan он сравнивает себя с первым и видит изменения, похоже на Git (diff).
Делать только обновления без внесения изменений можно путем ввода опции -refresh-only. Нужно для обновления файла состояния.
В основном используется тогда, когда вручную, к примеру, была ПЕРЕИМЕНОВАНА виртуальная машина, нужно обновить .tfstate без внесения каких-либо изменений, можно сделать путем выполнения команды terraform plan -refresh-only и terraform apply -refresh-only
Для просмотра текущего плана можно написать команду terraform show, которая покажет всю нужную информацию.
Важная команда -
terraform plan -destroy. Она покажет то, что произойдет при выполнении командыterraform destroy.
Сама по себе команда terraform apply позволяет пользователю запустить процесс продвижения через Terraform. Имеет некоторое количество опций, которые описаны далее. На официальном сайте Terraform сказано, что по-хорошему использовать эту команду в таком виде:
terraform apply [options] [plan file] - то есть Plan должен быть сохранен, путем выполнения другой команды, которая представлена ниже.
terraform plan -out=plan - сохранение плана для дальнейшего применения через команду выше.
Существует такая опция как -replace, чтобы лучше понять ее логику, вот пример:
Развернутый сервер находится в AWS, он сломался, обнаружены какие-либо проблемы с ОС, можно сделать следующие шаги:
1.terraform destroy + terraform apply - уничтожит и создаст все.
2.Вручную удалить и создать ВМ, минуя Terraform.
3.terraform apply -replace="название ресурса" - пересоздание конкретного ресурса.
При таком процессе, конвейр выполнения команд выглядит следующим образом:
Старый ресурс -> Создается новый ресурс -> Обновление зависимостей -> Удаление старого ресурса -> State обновляется.
Если, к примеру, изменился main.tf, а применить изменения нужно только для определенной машины, к примеру, создано 8 виртуалок, одна из них не сильно и нужна и по-хорошему ее пересоздать с другим именем, но только ее, можно выполнить следующую команду, после изменения main.tf:
terraform plan/apply -target "название вм"
Также и имеется возможность применения для нескольких машин сразу:
terraform plan/apply -target "название вм" -target "название вм"
В обычном workflow такой подход не часто используется, использовать на свой страх и риск, но в целом для быстрого пересоздания можно использовать и этот вариант, но лучше replace.
Команда terraform destroy используется для удаления всех объектов, описанных в main.tf.
Можно вручную удалять некоторые ВМ и сразу же их сносить с state:
terraform destroy -target "название вм"
также в Terraform имеется возможность работы с консолью напрямую, нужно это для отладки, например, есть большое количество variables и нужно их вернуть (то есть показать), делается это следующим образом:
terraform console - открытие консоли Terraform.
var.private_subnet_cidr_blocks - просмотр переменной private_subnet_cidr_blocks. Возвращает она следующее значение:
> var.private_subnet_cidr_blocks
tolist([
"10.0.101.0/24",
"10.0.102.0/24",
"10.0.103.0/24",
"10.0.104.0/24",
"10.0.105.0/24",
"10.0.106.0/24",
"10.0.107.0/24",
"10.0.108.0/24",
])
Если нужно выбрать конкретную переменную, то можно сделать следующее:
> var.private_subnet_cidr_blocks[1]
"10.0.102.0/24"
Если нужно выборочно показать некоторые переменные, делается это вот так:
> slice(var.private_subnet_cidr_blocks, 0, 3)
tolist([
"10.0.101.0/24",
"10.0.102.0/24",
"10.0.103.0/24",
])
Имеется возможность выполнения скриптов с помощью Terraform. Пример следующий, нужно развернуть ВМ, внутри нее выполнить .bash скрипт, в который требуется передать переменные из variables.
Пример .bash скрипта:

Пример variables.tf:

Строку user_data полностью не видно, ее запись следующая:
user_data = templatefile("user_data.tftpl", { department = var.user_department, name = var.user_name })
Таким образом в переменные .bash скрипта передаются переменные var.user_departament и var.user_name, которые указаны в variables Terraform и имеют значение по умолчанию, которое так же можно менять.
Terraform поддерживает функцию file, которая нужна для того, чтобы передать какой-либо файл на созданную машину. В основном используется для передачи пары SSH ключей.
Пример оформления кода с функцией file, предварительно SSH-key.pub был перенесен в директорию проекта Terraform:

Далее подключиться к машине можно вот таким образом, например:
ssh ubuntu@$(terraform output -raw web_public_ip) -i ssh_key - это пример подключения под пользователем ubuntu, подключение производится на адрес, который можно получить посредством вывода команды terraform output -raw "название output", подключение производится путем использования ключа ssh_key.
Модуль — это набор ресурсов, которыми Terraform управляет совместно.
Модули как можно загрузить готовые, так и собрать самому. По сути, модуль - это главная заготовка, на которую ссылаются дочерние конфигурации. Если есть какие-либо объявленные переменные, то дочерние конфигурации также могут иметь переменные, в переменные дочерних модулей записываются данные, потом эти данные передаются в модуль, с помощью этого происходит развертывание инфраструктуры.
Модули нужно выносить отдельной строкой в блоках конфигурации. Пример выноса модуля с загрузкой готового шаблона Git представлен ниже.

Важно! Чтобы не тянуть все коммиты предыдущие, рекомендуется использовать
depth. Чаще всего хватаетdepth = 1для большинства выполненных задач.
Главное замечание - разработчики Terraform не рекомендуют включать
providerблоки в модули. Он используется глобально.
Стандартная структура модуля следующая:
1.Корневой модуль. Единственный обязательный элемент.
2.README. Корневой модуль и все вложенные модули должны иметь файлы README. Этот файл должен называться README или README.md. Последний будет рассматриваться как Markdown. В нем должно быть описание модуля и его назначение.
3.LICENSE. Лицензия, на основании которой доступен данный модуль. Если публикуется модуль в открытом доступе, многие организации не будут его использовать, если отсутствует четкая лицензия.
4.main.tf, variables.tf,outputs.tf. Это рекомендуемые имена файлов для минимального модуля, даже если они пустые. main.tf должен быть основной точкой входа. Для простого модуля здесь могут создаваться все ресурсы. Для сложного модуля создание ресурсов может быть разделено на несколько файлов, но любые вложенные вызовы модулей должны находиться в главном файле. variables.tf и outputs.tf должны содержать объявления переменных и выходных данных соответственно.
5.Переменные и выходные данные должны иметь описания. Все переменные и выходные данные должны иметь описание в одно-два предложения, объясняющее их назначение.
Пример структуры модуля:

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

С помощью данного блока можно переименовать модуль. Для этого обычно moved и используется.
Более подробно про команды moved можно прочитать тут:
https://developer.hashicorp.com/terraform/language/v1.12.x/block/moved