resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = terraform.workspace == "default" ? "t2.medium" : "t2.micro"
}
Этот код использует тернарный синтаксис, чтобы присвоить параметру instance_type значение t2.medium либо t2.micro в зависимости от значения terraform.workspace. Все подробности о тернарном синтаксисе и условной логике в Terraform изложены в главе 5.
Рабочие области Terraform отлично подходят для быстрого развертывания и удаления разных версий вашего кода, но у них есть несколько недостатков.
• Файлы состояния всех ваших рабочих областей находятся в одном и том же хранилище (например, в одном бакете S3). Это означает, что все они используют аналогичные механизмы аутентификации и управления доступом, что является одной из основных причин, почему они не подходят для изоляции разных окружений, таких как среды для финального тестирования и промышленного применения.
• Рабочие области не видны в коде или терминале без использования команд terraformworkspace. При чтении кода невозможно сказать, в скольких рабочих областях развернут модуль: в одной или десяти, так как выглядят они идентично. Это усложняет обслуживание, поскольку у вас нет полноценного представления о вашей инфраструктуре.
• Из двух предыдущих пунктов вытекает тот факт, что рабочие области могут быть довольно сильно предрасположены к ошибкам. Из-за нехватки прозрачности можно легко забыть, в какой рабочей области вы находитесь, и внести изменения не в том месте (например, случайно выполнить команду terraformdestroy в рабочей области production вместо staging). Поскольку вам приходится использовать один и тот же механизм аутентификации для всех рабочих областей, у вас нет другого уровня защиты от подобных ошибок.
Чтобы как следует изолировать разные окружения, вместо рабочих областей лучше использовать структуру файлов и каталогов, о которой пойдет речь в следующем подразделе. Но прежде, чем двигаться дальше, не забудьте удалить три сервера EC2, которые вы только что развернули. Для этого выполните terraformworkspaceselect<имя> и terraformdestroy в каждой из трех рабочих областей.
Изоляция с помощью описания структуры файлов
Чтобы достичь полной изоляции между окружениями, нужно сделать следующее.
• Поместить конфигурационные файлы Terraform для каждой среды в отдельную папку. Например, вся конфигурация для среды финального тестирования может находиться в папке stage, а в папке prod может храниться конфигурация промышленной среды.
• Предусмотреть для каждой среды разные хранилища с разными механизмами аутентификации и управления доступом (предположим, у каждого окружения может быть отдельная учетная запись AWS со своим бакетом S3 в качестве хранилища).
Благодаря применению отдельных папок вам будет намного легче понять, в какой среде происходит развертывание, а использование отдельных файлов состояния с отдельными механизмами аутентификации значительно уменьшает вероятность того, что случайная оплошность в одной среде будет иметь какое-либо влияние на другие.
Концепцию изоляции лучше опустить на уровень ниже, вплоть до компонентов — наборов связанных между собой ресурсов, которые обычно развертываются совместно. Например, после настройки базовой сетевой топологии для своей инфраструктуры (в терминологии AWS это виртуальное частное облако (VPC) и все связанные с ним подсети, правила маршрутизации, VPN и сетевые списки доступа) вы будете менять ее не чаще раза в месяц. Но новые версии серверов могут развертываться по нескольку раз в день. Управляя инфраструктурой VPC и веб-сервера в одной и той же конфигурации Terraform, вы постоянно рискуете нарушить работу всей своей сетевой топологии (скажем, из-за простой опечатки в коде или случайного выполнения не той команды) без причины.
В связи с этим я рекомендую использовать отдельные папки Terraform (и, следовательно, отдельные файлы состояния) для каждого окружения (тестового, промышленного и т. д.) и каждого компонента (VPC, сервисов, баз данных). Чтобы посмотреть, как это выглядит на практике, разберем рекомендуемую структуру файлов и каталогов для проектов Terraform.
На рис. 3.7 показана типичная структура файлов в моих проектах.
На самом верхнем уровне находятся отдельные папки для каждого «окружения». Набор окружений зависит от проекта, но обычно он включает в себя следующее.
•stage — среда для предпромышленной нагрузки (тестирование).
• prod — среда для промышленной нагрузки (приложения, взаимодействующие с пользователями).
• mgmt — среда для инструментария DevOps (например, узел-бастион, Jenkins).
•global — папка с ресурсами, которые используются во всех средах (скажем, S3, IAM).
Рис. 3.7. Типичная структура файлов для проекта Terraform
Внутри каждого окружения есть отдельные папки для каждого компонента. Компоненты зависят от проекта, но обычно в их число входят следующие.
•vpc — сетевая топология для этой среды.
•services — приложения или микросервисы, которые запускаются в этой среде, например Ruby on Rails на стороне клиента или Scala на стороне сервера. Вы можете даже изолировать все приложения, поместив их в отдельные папки.
•data-storage — хранилища данных для этой среды, такие как MySQL или Redis. Хранилища можно изолировать друг от друга, разместив их в отдельных папках.
Внутри каждого компонента находятся конфигурационные файлы Terraform, которые названы по следующему принципу.
•variables.tf — входные переменные.
• outputs.tf — выходные переменные.
•main.tf — ресурсы.
При запуске Terraform просто ищет в текущей папке файлы с расширением .tf, поэтому вы можете называть свои файлы как угодно. Но, несмотря на это, вашим коллегам не все равно, какие названия файлов вы используете. Если задать последовательные и предсказуемые названия, это упростит просмотр кода. Вы всегда будете знать, где искать переменные, вывод или ресурсы. Если какой-то файл (особенно main.tf) становится слишком крупным, можете свободно вынести из него часть функциональности (и назвать ее, к примеру, iam.tf, s3.tf или database.tf), но это также может быть признаком того, что вам следует разбить свой код на более мелкие модули. Эту тему мы подробно рассмотрим в главе 4.
Как избежать дублирования кода
Структура файлов и каталогов, описанная в этом разделе, имеет много повторяющихся элементов. Например, одни и те же приложения frontend-app и backend-app находятся сразу в двух папках: stage и prod. Но не волнуйтесь, вам не придется копировать и вставлять весь этот код! В главе 4 вы увидите, как избегать дублирования и соблюдать принцип DRY с помощью модулей Terraform.
Возьмем код кластера с веб-сервером, который вы написали в главе 2, объединим его с кодом для Amazon S3 и DynamoDB из этой главы и организуем это все в виде структуры каталогов, представленной на рис. 3.8.
Рис. 3.8. Структура файлов для кода кластера с веб-сервером
Бакет S3, созданный вами в этой главе, необходимо переместить в папку global/s3. Поместите выходные переменные (s3_bucket_arn и dynamodb_table_name) в файл outputs.tf. При перемещении файлов в новое место убедитесь, что вы не забыли скопировать (скрытую) папку .terraform, иначе придется заново все инициализировать.
Кластер веб-серверов, который вы создали в главе 2, следует поместить в папку stage/services/webserver-cluster (своего рода тестовая версия этого кластера; промышленную мы добавим в следующей главе). Не забудьте скопировать папку .terraform и поместить входные и выходные переменные в файлы variables.tf и соответственно outputs.tf.
Вам также необходимо обновить кластер веб-серверов, чтобы он использовал S3 в качестве хранилища. Вы можете просто скопировать и вставить раздел backend из файла global/s3/main.tf без особых изменений, но не забудьте присвоить параметру key путь к папке, в которой находится код веб-сервера: stage/services/webserver-cluster/terraform.tfstate. Таким образом, код Terraform в системе управления версиями и структура ваших файлов состояния будут полностью совпадать и вам будет очевидно то, как они между собой связаны. Модуль s3 использует этот же принцип для установки параметра key.
Такая структура файлов и каталогов упрощает просмотр кода и помогает понять, какие именно компоненты развернуты в той или иной среде. Кроме того, она обеспечивает хорошую степень изоляции между окружениями и между компонентами внутри одного окружения. Благодаря этому, если что-то пойдет не так, это коснется лишь небольшой части вашей инфраструктуры.
Конечно, это свойство в каком-то смысле является и недостатком: разделение компонентов на отдельные папки не дает случайной оплошности нарушить работу всей инфраструктуры, но при этом лишает вас возможности развертывать всю инфраструктуру одной командой. Если бы все компоненты для одного окружения были описаны в одном конфигурационном файле Terraform, вы бы могли запустить все это окружение с помощью единого вызова terraformapply. Но если все компоненты находятся в отдельных папках, нужно выполнить terraformapply в каждой из них. Стоит отметить, что в случае применения Terragrunt вы можете автоматизировать этот процесс с помощью команды apply-all39.
У этой структуры файлов есть еще одна проблема: она усложняет использование зависимостей ресурсов. Приложение имеет прямой доступ к атрибутам базы данных (то есть может сослаться на адрес базы данных через aws_db_instance.foo.address), если код этих двух компонентов находится в одних и тех же конфигурационных файлах Terraform. Но если код приложения и базы данных размещен в разных папках, как я советовал, эта возможность теряется. К счастью, Terraform предлагает решение этой проблемы: источник данных terraform_remote_state.
Источник данных terraform_remote_state
В главе 2 вы использовали источники данных для извлечения из AWS информации, доступной только для чтения. Например, источник aws_subnet_ids возвращал список подсетей вашего облака VPC. Но существует другой источник, terraform_remote_state, который особенно полезен при работе с состоянием. С его помощью можно извлечь файл состояния, который является частью другой конфигурации Terraform, и сделать это сугубо для чтения.