В продолжении рассказа ((https://clubs.at.yandex-team.ru/infra-cloud/1954 про контекст и мотивацию)) хочется начать делиться более детальными и обзорными вещами. Поехали.

== Infrastructure models
Это первая часть обзора подходов к управлению и построению инфраструктуры. Начнём с ((https://www.terraform.io/ terraform))'а, как основного представителя важного направления //infrastructure as code//. Он точно не единственный представитель, но мы рассмотрим только его без ограничения общности.

Note: //инфраструктурой// мы будем называть те компоненты (сети, виртуальные машины, инстансы баз данных и т.д), которые необходимы для работы приложений (взаимодействуют с пользователями).

== Что такое terraform?
Описание на сайте прямо отвечает на этот вопрос - это client side инструмент для __развёртывания__ инфраструктуры. Это важный момент, который определяет область применения и ответственности.
Wokflow работы с terraform выглядит так:
  * **Write** - описываем инфраструктуру на языке ((https://github.com/hashicorp/hcl2/blob/master/hcl/hclsyntax/spec.md HCL))
  * **Plan** - отсматриваем планируемые изменений
  * **Apply** - применяем изменений в сотнях поддерживаемых провайдеров

Небольшой пример. Мы пишем текст вида:
%%
resource "azurerm_resource_group" "main_resource_group" {
  name = "${var.resource_prefix}-main-rg"
  location = var.location
}

resource "azurerm_resource_group" "aks_resource_group" {
  name = "${var.resource_prefix}-aks-rg"
  location = var.location
}

resource "azurerm_public_ip" "aks_public_outbound_ip" {
  name = "${var.resource_prefix}-outbound-ip"
  location = var.location
  resource_group_name = azurerm_resource_group.aks_resource_group.name
  allocation_method = "Static"
  sku = "Standard"
}
%%
Далее (доставив на рабочую станцию нужные реквизиты для всех используемых провайдеров) мы можем запустить ##terraform plan##, который:
  * проинтерпретирует переданные файлы, развернув циклы и подставив шаблоны
  * построит граф объектов, которые нужны развернуть/изменить у провайдера
  * поднимет //состояние// (см. ниже)
  * сформирует план изменений (получив статус объектов у провайдера)

Если нас всё устраивает, то мы запускаем ##terraform apply##, который применит план и сохранит состояние.

На этом всё. Это штатные примитивы, которые даёт CLI Terraform. Но уже этот инструментарий обещает изолировать пользователя от деталей работы с отдельными провайдерами.

=== Зачем state?
Небольшое отступление про состояние. Чтобы разобраться, посмотрим в ((https://www.terraform.io/docs/language/state/purpose.html официальную документацию)). Там рассматривается следующий пример: когда у нас есть описание ресурса ##resource "aws_instance" "foo"##, Terraform должен понимать, что существующий AWS инстанс ##i-abcd1234## представляет этот ресурс из исходного файла. Т.е. необходима "память" для такого сопоставления. Но т.к. terraform - это CLI, который работает по принципу oneshot, то хранить данные между запусками проблематично.

Решение стандартно - локальный CLI сохраняет данные в локальный файл. Но при работе //в команде// возникает проблема - эти данные нужно передавать между участниками. Задача быстро из относительно простой (сохранить и прочитать файл) стала гораздо сложнее - поддерживать консистентно состояние в распределённой системе:
  * состояние нужно хранить распределённо и высоко доступно
  * нужна консистентность
  * доступ на чтение и запись должен поддерживать конкурентную работу (локи?)

Рекомендации здесь - ((https://www.terraform.io/docs/language/state/backends.html ручное управление состоянием)) и ((https://www.terraform.io/docs/language/state/locking.html локами)). Эта задача отдана пользователям - нужно выбрать и настроить backend, обучить команду правилам работы с ним. Либо использовать ((https://www.terraform.io/docs/language/state/remote.html#locking-and-teamwork платную версию)).

Задачу нужно решать любым реализациям infrastructure as code. Забегая вперёд, тот же Kubernetes решает эту задачу предоставляя всем акторам (пользователям и контроллерам) консистентное API с примитивами выбора лидера (взятия локов) и надёжную распределённую систему хранения.

=== Building on terraform
Из простых, но композируемых компонентов зачастую можно построить систему - когда результат больше суммы частей. Terraform, предоставляя текстовые описания и CLI инструмент хорошо подходит для интеграции в CI/CD pipeline'ы. Предполагается, что работа с инфраструктурой не будет отличаться от работы с другим кодом (gitops) - т.е. описание инфраструктуры хранится в репозитории. А далее изменения:
  * проходят штатный цикл - собственно написание, написание тестов, ревью
  * принимаются в основную ветку
  * применяются централизованным pipeline'ом

В таком построении нет конкуренции за состоянием и ресурсами (если CI/CD может гарантировать это), а все инструменты пользователем освоены и понятны ему.

=== Declare imperatively
Важным аспектом terraform'а, который позволяет ему успешно решать задачи, то, что после интерпретации получается граф объектов. Пользователь не указывает, //какие// действия нужно выполнить для приведения инфраструктуры в нужное состояние. Это очень полезное свойство, оно сильно снижает порог входа (не нужно разбираться с механиками работы каждого провайдера) и когнитивные нагрузки во время эксплуатации. Пользователь совершает меньше ошибок - инфраструктура "просто" работает.

=== As code
Terraform позиционирует себя как реализацию подхода ##infrastructure as code##. А исходный код подразумевает:
  * абстракции - язык HCL2 (вдохновлённый nginx.conf) очень слабовыразителен
  * написание тестов, отладку - существующий инструментарий и практики не поддерживают этот аспект
  * переиспользование - есть наблюдения, что это нетривиально ((https://www.linkedin.com/pulse/terraform-etc-infrastructure-code-kills-devops-sam-savage/ [1])) ((https://levelup.gitconnected.com/infrastructure-as-codes-broken-promises-4c9dc86f909c [2])))

Как минимум частично устранить эти недостатки можно c помощью ((https://github.com/hashicorp/terraform-cdk/ CDK for Terraform)) - набор средств совместимости ((https://docs.aws.amazon.com/cdk/latest/guide/home.html AWS CDK)) и terraform. В таком случае можно использовать все выразительные средства и инструментарий императивных языков (Python, Go, Typescript, JS, Java, C#) для построения графа объектов:
%%(python)
class MyEcsConstructStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)
        vpc = ec2.Vpc(self, "MyVpc", max_azs=3)  # default is all AZs in region
        cluster = ecs.Cluster(self, "MyCluster", vpc=vpc)
        ecs_patterns.ApplicationLoadBalancedFargateService(self, "MyFargateService",
            cluster=cluster,            # Required
            cpu=512,                    # Default is 256
            desired_count=6,            # Default is 1
            task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
                image=ecs.ContainerImage.from_registry("amazon/amazon-ecs-sample")),
            memory_limit_mib=2048,      # Default is 512
            public_load_balancer=True)  # Default is False
%%
А затем передавать этот граф в Terraform. 

Стоит отметить, что создать и поддерживать такую инфраструктуру для нескольких языков - нетривиально.


=== Provision vs manage
Развёртывание и управление - это разные задачи. Развертывание инфраструктуры с нуля в новом облаке - //редкая// задача. До развёртывания - эти объекты //не существуют//, значит у них нет пользователей. Т.е. нет конкуренции (не нужно синхронизировать работы, см. "распределённые локи"), в случае неуспеха - никто не пострадает.

И ##terraform## - это консольный инструмент именно для //развёртывания// инфраструктуры, работающий локально в режиме oneshot. На этом его задачи закачиваются. Им потенциально удобно разворачивать инфраструктуру, но управлять ей в течение длинного и тернистого жизненного цикла может быть нетривиально. Нам нужны средства координации, переименования объектов, аккуратные миграции с изменением атрибутов и только явным удалением объектов, чего по-умолчанию terraform не даёт:
<[By default, when Terraform must change a resource argument that cannot be updated in-place due to remote API limitations, Terraform **will instead destroy** the existing object and then create a new replacement object with the new configured arguments.]>

Давайте посмотрим ещё на некоторые аспекты.

=== I want to read
Со стороны пользователя картина выглядит вполне законченной. У меня есть возможность единообразно управлять различной инфраструктурой. Я могу создавать, изменять и удалять различные ресурсы. Если вспомнить аббревиатуру ##CRUD## - create, read, update, delete, то можно заметить, что `read` у нас нет. Если же смотреть на полный жизненный цикл - то возможность читать, получать списком и как-то обрабатывать ресурсы (фильтровать) объекты - важная функция, которую должна предоставлять инфраструктура и лучше, чтобы она была доступна в том же инструменте. Чтением (для понимания статуса и диагностики) могут заниматься как инженеры, так и автоматизированные задачи (//другой код//) - например, находить некорректные/опасные настройки, уязвимые версии пакетов/образов. И делать это хотелось в provider agnostic стиле, т.е. не обращаясь к API провайдеров - т.к. их много и они потенциально очень разные.

У Terraform есть команды ##state list## и ##state show##, но они работают только с файлом состояния. И текущее состояние вашего инстанса в AWS не покажет. Почему этого нет в terraform? Как мне кажется, причин может быть несколько. Одну из них мы назвали, ##terraform## - это инструмент развёртывания и статус объектов не самая нужная вещь.

Вторая вызвана архитектурой и самим подходом ##infrastructure as code##. Если посмотреть на пример из отступления про состояние, то без state'а у нас нет отношений между объектом у провайдера и результатами интерпретации кода. Для чтения скорее всего нужен *тот же самый код*, которым созданы объекты. Это представляет ((https://www.terraform.io/docs/cli/commands/state/show.html#example-show-a-resource-configured-with-for_each некоторые трудности)) даже для формулирования задачи (как мне указать фильтр объектов, которые порождены в цикле), реализация тоже не выглядит простой.

=== Apply continuously
Из-за того, что ##terraform## **штатно** запускается на рабочей станции, возникает ещё один потенциально важный ньюнас, который можно упустить. Что в таком сценарии является триггером события "запусти apply"? Действия выполняет очень сложный механизм - инженер. Соответственно, ##terraform## штатно не имеет исполняющегося процесса, стэка и не может реагировать на внешние события, когда объект провайдера поменял своё состояние. Например, VM пропала, сломалась, была ((https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-retirement.html списана)) и т.д - что нельзя назвать редкостью. Чтобы на такое реагировать - нужна //отдельная// инфраструктура для запуска самих control loop'ов управления инфраструктурой.

== Outro
В этой части мы базово познакомились с terraform'ом с точки зрения пользователя и обратили внимание на сильные стороны и ограниченную область ответственности. Так же теперь мы можем оценить объём задач, которые нужно реализовать и количество граблей, которые нужно --реализовать-- обойти, выбирая infrastructure as code. Это задача не невозможная, но она масштабная.

А в следующей части мы попробуем посмотреть на картинку чуть шире - на провайдера - там много интересного.