Примерно с 2018 года я занимаюсь программированием инфраструктуры если не каждый день, то несколько раз в неделю. Я не утверждаю, что это позволяет мне претендовать на какой-то авторитет. Но за это время я определённо успел сформировать конкретные мнения по некоторым вопросам. В этой статье поделюсь некоторыми этими наблюдениями в качестве эмпирических правил, которым пытаюсь следовать при разработке для Terraform, но с тем же успехом вы можете применять их и в других языках для программирования инфраструктуры.

1. Не жертвуйте локальной разработкой

Это настоящий флагман, так что, если бы мне потребовалось оставить всего один пункт, то я оставил бы этот!

Как подсказывает мой опыт, такое жертвование выражается в двух формах. Во-первых, допускаются жёсткие зависимости от среды, в которой выполняются конвейеры. Например, вместо нормальных переменных Terraform используются инструменты замены токенов, либо общие модули копируются в каталог для развёртывания прямо во время выполнения. Это случается, если Terraform развёртывают с единственной целью — использовать его в конвейере.

На самом деле, конвейеры нужны для развёртывания изменений в реальных окружениях. Но для этого вы не должны возводить такую систему, в которой инженер не смог бы прямо у себя на компьютере тестировать и отлаживать создаваемую систему — полностью или частично. Если система запускается локально, то она может запускаться и в конвейере, но обратное верно не всегда. Не допускайте, чтобы git commit && git push && *вечно дожидаться, пока конвейер сообщит вам об опечатке* остался единственным способом протестировать изменение.

Другой вариант полного отказа от локальной разработки — злоупотребление предусмотренными в terraform обобщёнными словарями (maps) применяя их в качестве модульных переменных. Если модуль рассчитан на то, что всё его содержимое будет передаваться в map(string) —  а мне такое попадалось достаточно часто — то все, кто имеет дело с такими переменными, как разработчики, так и пользователи, работают вслепую, без всяких подсказок типов на уровне IDE или валидации. Такой подход грубо уродует старую добрую привычную разработку на локальной машине.

Иногда без динамических словарей никак не обойтись — например, при работе с метками ресурсов. Но, если вы знаете, какие параметры понадобятся вашему модулю — определите их как отдельные переменные или как объект с известными атрибутами.

### Не делайте так

variable "subnet_config" {
	type = map(string)
}

resource "subnet" "mySubnet" {
	name = var.subnet_config["name"]
	...
}

### А делайте так

variable "subnet_config" {
	type = object({
		name = string
		cidr = string

В первом примере мы летим вслепую. Во втором примере нам в качестве второго пилота помогает terraform-подобная система (нет, это не разновидность copilot). Если вам это кажется настолько очевидным, что даже в пояснении не нуждается, поверьте мне — нуждается.

2. Учитывайте, кто ваша аудитория

При разработке модуля terraform держите в уме, для кого он делается. Иногда целевая аудитория – это инженеры платформ или инфраструктурные инженеры, а эти люди зачастую ценят возможность видеть систему в мельчайших деталях и контролировать их конфигурацию. Если скрывать от пользователей эти детали, им это может не понравиться.

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

3. Старайтесь при помощи модулей снижать сложность распространённых паттернов

«Трамбовка сложности» (compressing complexity) — это термин, подсмотренный в сообществе Ruby on Rails. Сам приём — это вариант создания сбалансированных абстракций, которые не скрывают сложность, но всё равно не слишком забивают голову человеку, которому приходится ими управлять. Применительно к модулям terraform такая практика позволяет минимизировать обязательные параметры, но в то же время в достаточном количестве предоставить опциональные параметры с разумными и осознанно выбранными умолчаниями. Если в модуле хорошо утрамбована сложность, то разработчику легко приступить к работе с ним, поскольку не приходится досконально разбираться в модуле. Тем не менее, сохраняется возможность переопределить умолчания, если понадобится более полный контроль над модулем. .

Умение подходить к работе с модулями именно таким образом пригодится, когда вы приступите к компоновке ресурсов и других модулей в более высокоуровневые пакеты. В таких составных модулях могут содержаться полезные абстракции, описывающие паттерны платформ, имеющие смысл именно в той предметной области, в которой вы работаете. Грегор Хоп  отлично объяснил эту концепцию на следующем примере из финансового сектора. Пользователь определяет базу данных ledgered-database, в которой компонуется и конфигурируется ряд AWS-сервисов, которые, в свою очередь, хорошо поддаются повторному использованию. Пользователям такого модуля ничего не требуется знать о конкретной базе данных или технологиях маршрутизации событий — и, тем не менее, они могут быстро внедрить такой функционал в своё приложение. В качестве других примеров можно привести

  • feature-store для управления признаками при машинном обучении и для предоставления их. Это могут быть, в том числе, сервисы для хранения данных, обеспечения работы конвейеров, версионирования и хостинга API.

  • workflow-orchestrator для определения потоков бизнес-задач и управления ими. В том числе, это могут быть сервисы для управления очередями, срабатываниями, состоянием и наблюдаемостью.

  • policy-enforced-api для хостинга защищённых сервисов API, в том числе, шлюза API, аутентификации, соответствия политикам (например, политикам ограничения частоты передачи), мониторинга, шифрования и соответствия стандартам.

Модули такого рода очень ценны, поэтому не упускайте возможностей их создавать. Но учитывайте, что они полезны лишь в случае, если активно используются — так что вот вам ещё одна причина тщательно продумать, как их будет воспринимать пользователь. Вы ведь не хотите попасть в ловушку конкурирующих стандартов!

4. Структурируйте код terraform с применением модулей, стеков и единиц развёртывания

Здесь я перехожу к настоятельным предписаниям. По мере роста вашей базы кода на terraform приходится задумываться о том, как ее структурировать. Эта структура базируется на трёх каталогах верхнего уровня и, по моему опыту, по-настоящему хорошо работает. Опишу эти каталоги:

  • modules: содержит небольшие модули, описывающие отдельные ресурсы или комбинации отдельных ресурсов, обеспечивающих, например, передачу данных в частной сети или управление учётными данными.

  • stacks: содержит относительно крупные модули, в которых из мелких модулей составляются решения (именно здесь вам может встретиться один из тех высокоуровневых модулей, о которых шла речь в предыдущем разделе). В стеках заключена сложность отношений между различными ресурсами, а также часто содержится код, упрощающий взаимодействие между этими ресурсами. Например, это может быть код для создания секретов, присваивания ролей, т.п. Также в стеках контролируется, какие вариации доступны в разных окружениях — предоставляется ровно столько переменных, сколько нужно.

  • deployments: потребляет стеки и представляет отдельные инстансы или окружения конкретного стека. Именно здесь вы найдете конфигурации backend и provider для terraform, а также любые детали, которые могут отличаться от окружения к окружению (например, имена или размеры параметров).

В итоге у вас получится примерно такая структура:

terraform/
├── modules/
│   ├── app_service/
│   ├── sql_database/
│   ├── storage_account/
│   ├── virtual_network/
│   └── key_vault/
├── stacks/
│   ├── app1/
│   └── app2/
│   └── shared_service/
├── deployments/
│   ├── app1/
│   │   ├── dev/
│   │   ├── test/
│   │   └── prod/
│   └── app2/
│       ├── dev/
│       ├── test/
│       └── prod/
│   └── shared_service/
│       ├── dev/
│       ├── test/
│       └── prod/

А вот наглядное представление:

Обратите внимание: недавно компания Hashicorp а режиме бета-тестирования выкатила для своего облачного варианта Terraform новую фичу под названием “stacks”. Она не имеет никакого отношения к «стекам», о которых я пишу в этом разделе, хотя, концептуально первые и вторые «стеки» очень похожи.

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

На этом пока всё. Просто помните: инфраструктурный код может и должен быть удобен для разработчика!

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud  в нашем Telegram-канале 

Перейти ↩

? Читайте также:

Комментарии (0)