В этой статье блога мы подробно рассмотрим, как Terraform работает с выходными данными и как мы можем эффективно использовать выходные значения в наших проектах Terraform. Выходные значения позволяют нам обмениваться данными между модулями и рабочими пространствами, а также обеспечивают гибкость при передаче значений внешним системам для целей автоматизации.
Вы пришли в нужное место, если вы новичок в Terraform! Компания Spacelift собрала в своем блоге тонну ценных материалов, руководств и сообщений в блоге, посвященных Terraform и тому, как его используют эксперты отрасли.
Выходные и входные значения
Входные переменные позволяют нам настраивать конфигурации Terraform без жесткого кодирования каких-либо значений. Таким образом, мы можем повторно использовать модули Terraform, назначая пользовательские значения в соответствии с нашими потребностями. Обычно в контексте Terraform мы называем их просто переменными.
Чтобы определить входные переменные, мы должны объявить их с помощью блока переменных:
variable "aws_region" {
description = "AWS region"
type = string
}
variable "ec2_instance_type" {
description = "Instance type for EC2 instances"
type = string
default = "t2.small"
}
Имя переменной — это метка, которую мы задаем после ключевого слова variable. Для каждой переменной у нас есть возможность задать некоторые аргументы, такие как default, type, description, validation, sensitive и nullable. Подробно об этих аргументах и о том, как их установить, читайте в официальной документации здесь.
После объявления наших входных переменных мы можем использовать их в модулях, ссылаясь на них следующим образом var. где совпадает метка, следующая за ключевым словом variable. Например, для ссылки на переменную ec2_instance_type, которую мы определили выше:
resource "aws_instance" "web_server" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.ec2_instance_type
}
С другой стороны, выходные значения позволяют нам экспортировать полезную информацию из наших проектов Terraform, которые мы определили и обеспечили с помощью Terraform. В контексте Terraform для простоты мы называем выходные значения просто выходами.
Комбинируя входные и выходные переменные, мы получаем гибкость, позволяющую легко настраивать, автоматизировать, повторно использовать и делиться нашим кодом Terraform. Входные переменные похожи на аргументы функций в традиционном программировании, а выходные переменные работают аналогично возвращаемым значениям функции. Обе переменные одинаково важны для обеспечения функциональности наших проектов Terraform и облегчения входящего и исходящего потока данных.
Случаи использования выходных переменных в Terraform
Более конкретно, выходные значения весьма полезны в некоторых случаях:
- Мы можем передавать информацию из дочерних модулей в родительский модуль с помощью выходных значений.
- Более того, из корневого модуля мы можем выводить выходные данные в командную строку или передавать эти выходные данные внешним системам для автоматизации. Когда мы используем удаленное состояние, мы можем получить доступ к выводам корневого модуля с помощью других конфигураций, используя источник данных terraform_remote_state. Выходные значения дочерних модулей недоступны.
Объявление и использование выходных значений
Для того чтобы определить выходное значение, мы должны использовать блок output:
output "instance_public_ip" {
description = "Public IP of EC2 instance"
value = aws_instance.web_server.public_ip
}
В приведенном выше примере мы определяем выходное значение с именем instance_public_ip. Таким образом, мы можем передать значение в родительский модуль или отобразить его конечному пользователю, если это вывод корневого модуля.
Аргумент value, который является возвращаемым выходным значением, принимает выражение, ссылающееся на другие ресурсы или атрибуты модуля. Terraform отображает и показывает выходы только при выполнении terraform apply, но не при выполнении terraform plan.
Чтобы использовать выходы вложенных модулей из родительских модулей, мы должны ссылаться на них как:
module.<module_name>.<output_value_name>
Например, чтобы сослаться на выходное значение instance_public_ip, которое мы объявили выше в модуле aws_web_server_instance, из его родительского модуля, мы должны использовать:
module.aws_web_server_instance.instance_public_ip
Давайте рассмотрим, как мы можем использовать все это в реальном примере. В этом репозитории GitHub мы определяем конфигурацию Terraform для инфраструктуры этого примера. Чтобы следовать этому, вам потребуется установить Terraform, подготовить учетную запись AWS и пройти аутентификацию с помощью ключей AWS через командную строку. Обратите внимание, что, если вы будете следовать этому примеру, с вашего счета в AWS может быть списано несколько долларов.
В этом примере мы создадим необходимую инфраструктуру для веб-сервера. Для нужд этой демонстрации мы разделили нашу конфигурацию Terraform на три модуля: корневой и два дочерних, отвечающих за работу с ресурсами, связанными с VPC, и ресурсами, связанными с экземплярами EC2.
Структура проекта выглядит следующим образом:
Для каждого модуля мы определяем файл main.tf, который обрабатывает основную функциональность модуля.
Декларации переменных и значения по умолчанию заполняются в файлах variables.tf, а для корневого модуля мы также используем файл terraform.tfvars для установки значений некоторых переменных.
Хорошей практикой является определение наших выходов в отдельных файлах outputs.tf, как вы можете видеть в приведенном выше примере структуры проекта. Объявляя выходные значения в файле outputs.tf для каждого модуля, мы улучшаем понятность наших модулей, так как пользователям легче быстро понять, каких выходных данных от них ожидать.
Корневой модуль использует и настраивает провайдера aws, а затем просто вызывает два дочерних модуля aws_web_server_vpc и aws_web_server_instance в main.tf верхнего каталога.
корневой модуль main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.16.0"
}
}
}
provider "aws" {
region = var.aws_region
}
module "aws_web_server_vpc" {
source = "./modules/aws-web-server-vpc"
}
module "aws_web_server_instance" {
source = "./modules/aws-web-server-instance"
ec2_instance_type = var.ec2_instance_type
vpc_id = module.aws_web_server_vpc.vpc_id
subnet_id = module.aws_web_server_vpc.subnet_id
}
Заметим, что при вызове модуля aws_web_server_instance мы передаем два выражения, использующие выходные значения из модуля aws_web_server_vpc с модулем нотации…, который мы видели ранее.
корневой модуль outputs.tf
output "vpc_id" {
description = "ID of the vpc"
value = module.aws_web_server_vpc.vpc_id
}
output "instance_id" {
description = "ID of EC2 instance"
value = module.aws_web_server_instance.instance_id
}
output "instance_public_ip" {
description = "Public IP of EC2 instance"
value = module.aws_web_server_instance.instance_public_ip
}
Мы определяем три выходных значения для нашего корневого модуля, и ожидаем увидеть их в командной строке после обеспечения нашей инфраструктуры. Проверяя параметр value каждого блока, мы замечаем, что все они происходят из выходных значений двух дочерних модулей, и, объявив их выходными значениями корневого модуля, мы можем передать их в командную строку.
переменные корневого модуля.tf
variable "aws_region" {
description = "AWS region"
type = string
}
variable "ec2_instance_type" {
description = "Instance type for EC2 instances"
type = string
default = "t2.small"
}
корневой модуль terraform.tfvars
aws_region = "us-east-1"
ec2_instance_type = "t2.nano"
Далее рассмотрим наши два дочерних модуля и то, как мы используем выходные значения для передачи параметров между ними.
aws-web-server-vpc модуль main.tf
resource "aws_vpc" "web_server" {
cidr_block = var.vpc_cidr_block
instance_tenancy = "default"
tags = {
Name = var.vpc_name
}
}
resource "aws_subnet" "web_server" {
vpc_id = aws_vpc.web_server.id
cidr_block = var.subnet_cidr_block
map_public_ip_on_launch = true
availability_zone = var.aws_az
tags = {
Name = var.subnet_name
}
}
resource "aws_internet_gateway" "web_server" {
vpc_id = aws_vpc.web_server.id
tags = {
Name = var.igw_name
}
}
resource "aws_route_table" "web_server" {
vpc_id = aws_vpc.web_server.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.web_server.id
}
tags = {
Name = var.rt_name
}
}
resource "aws_route_table_association" "web_server" {
subnet_id = aws_subnet.web_server.id
route_table_id = aws_route_table.web_server.id
}
В приведенном выше модуле мы определяем некоторые ресурсы, необходимые для сетевого уровня нашей инфраструктуры.
aws-web-server-vpc module variables.tf
variable "vpc_cidr_block" {
description = "CIDR block for webserver VPC"
type = string
default = "10.0.0.0/16"
}
variable "vpc_name" {
description = "Name of the vpc"
type = string
default = "web_server"
}
variable "subnet_cidr_block" {
description = "CIDR block for the webserver subnet"
type = string
default = "10.0.0.0/24"
}
variable "subnet_name" {
description = "Name for the webserver subnet"
type = string
default = "web_server"
}
variable "aws_az" {
description = "Availability Zone for the webserver subnet"
type = string
default = "us-east-1a"
}
variable "igw_name" {
description = "Name for the Internet Gateway of the webserver vpc"
type = string
default = "web_server"
}
variable "rt_name" {
description = "Name for the route table of the webserver vpc"
type = string
default = "web_server"
}
aws-web-server-vpc module outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.web_server.id
}
output "subnet_id" {
description = "ID of the VPC subnet"
value = aws_subnet.web_server.id
}
Два вывода, которые мы экспортируем из этого модуля, передаются в модуль aws-web-server-instance в качестве параметров для создания экземпляра EC2 внутри vpc и подсети, которые мы только что создали. Мы видели, как это обрабатывается в файле main.tf корневого модуля. Выходное значение vpc_id передается как выход корневого модуля и должно быть выведено в командной строке после того, как мы применим план.
Наконец, конфигурация Terraform для модуля aws-web-server-instance использует переданную информацию из модуля aws-web-server-vpc. Он создает и настраивает экземпляр веб-сервера соответствующим образом.
aws-web-server-instance module main.tf
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_security_group" "web_server" {
name = var.ec2_security_group_name
description = var.ec2_security_group_description
vpc_id = var.vpc_id
ingress {
description = "Allow traffic on port 80 from everywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = var.ec2_security_group_name
}
}
resource "aws_instance" "web_server" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.ec2_instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.web_server.id]
tags = {
Name = var.ec2_instance_name
}
user_data = <<-EOF
#!/bin/bash
sudo yum update -y
sudo yum install httpd -y
sudo systemctl enable httpd
sudo systemctl start httpd
echo "<html><body><div>This is a test webserver!</div></body></html>" > /var/www/html/index.html
EOF
}
aws-web-server-instance module variables.tf
variable "ec2_instance_name" {
description = "Name for web server EC2 instance"
type = string
default = "web_server"
}
variable "ec2_instance_type" {
description = "Instance type for web server EC2 instance"
type = string
default = "t2.micro"
}
variable "ec2_security_group_name" {
description = "Security group name for web server EC2 instance"
type = string
default = "web_server"
}
variable "ec2_security_group_description" {
description = "Security group description for web server EC2 instance"
type = string
default = "Allow traffic for webserver"
}
variable "vpc_id" {
description = "VPC id for web server EC2 instance"
type = string
}
variable "subnet_id" {
description = "Subnet id for web server EC2 instance"
type = string
}
Два выходных значения, которые мы передаем через корневой модуль, также определены в файле outputs.tf этого модуля.
output "instance_id" {
description = "ID of EC2 instance"
value = aws_instance.web_server.id
}
output "instance_public_ip" {
description = "Public IP of EC2 instance"
value = aws_instance.web_server.public_ip
}
Пора завершить все дела и выполнить план по созданию нашей демонстрационной инфраструктуры.
Наш план терраформирования показывает 7 новых ресурсов, которые должны быть добавлены, и отображает изменения наших трех выходных значений, объявленных в корневом модуле. Давайте продолжим и применим план.
Как и ожидалось, три выходных значения, объявленные в корневом модуле, отображаются в командной строке, отлично!
Мы могли бы использовать эти значения для автоматизации других частей наших систем и процессов, но пока мы можем получить значение из instance_public_ip и перейти по адресу http://, и мы должны увидеть наш демонстрационный веб-сервер запущенным.
Отлично! Все работает, как и ожидалось.
Команда вывода Terraform
Выходные значения хранятся в файле state Terraform. Поскольку мы успешно применили наш план, теперь мы можем получить доступ к этим выходным значениям по своему усмотрению. Для этого мы можем использовать команду вывода terraform.
Кроме того, мы можем запросить отдельные выходные значения по имени следующим образом
terraform output
Чтобы получить необработанное значение без кавычек, используйте флаг -raw.
_terraform output -raw _
Чтобы получить вывод в формате JSON, можно использовать флаг -json.
Это очень полезно, когда мы хотим передать выходные данные другим инструментам для автоматизации, поскольку JSON гораздо проще обрабатывать программно. Обратите внимание, что Terraform не защищает чувствительные значения вывода при использовании флага -json.
Когда мы закончим, давайте удалим все эти ресурсы, чтобы не платить за них.
В верхней части нашего репозитория выполните:
terraform destroy
Опции и аргументы выходных значений
При определении выходных значений у нас есть несколько опций, которые могут помочь нам лучше определить и организовать их.
Описание аргумента необязательно, но всегда считается хорошей практикой включать его в объявления выводимых значений, чтобы документировать их назначение. Этот аргумент должен кратко объяснять назначение каждого вывода и использоваться в качестве вспомогательного описания для пользователей модуля. Мы уже видели подобные примеры, поскольку определили аргумент description во всех объявлениях блоков вывода в нашем предыдущем демонстрационном примере.
В случаях, когда мы хотим обрабатывать чувствительные значения и подавлять их в выводе командной строки, мы можем объявить значение вывода как чувствительное. Terraform будет редактировать значения чувствительных выходов при планировании, применении, уничтожении или запросе выходов, чтобы избежать их вывода на консоль. На практике это хороший вариант использования, когда мы хотим передавать значения другим модулям Terraform или инструментам автоматизации, не раскрывая их промежуточным пользователям.
output "example_password" {
description = "An example DB password"
value = aws_db_instance.database.password
sensitive = true
}
Обратите внимание, что Terraform не будет редактировать конфиденциальные выходные значения, если вы запрашиваете конкретный выход по имени. После применения плана с выходом, объявленным как чувствительный, в консоли появляется сообщение с отредактированным значением.
Эти значения по-прежнему записываются в файлы состояния, поэтому любой, кто может получить к ним доступ, может также получить доступ к любым конфиденциальным значениям нашей конфигурации Terraform.
Аргумент depends_on в выходных декларациях используется для явного определения зависимостей, когда это необходимо. В большинстве случаев Terraform делает это автоматически, но есть некоторые редкие случаи использования, когда эта опция может оказаться полезной, когда это не так. При использовании этой опции включите комментарий, чтобы объяснить, почему это необходимо.
Удаленный источник данных состояния Terraform
Иногда нам может потребоваться обмен данными между различными конфигурациями Terraform с отдельными состояниями. Именно здесь в игру вступают источники данных terraform_remote_state. С помощью этого источника данных мы можем получить выходные данные корневого модуля из другой конфигурации Terraform. Этот встроенный источник данных доступен без какой-либо дополнительной настройки.
Продолжая наш предыдущий пример, предположим, что мы хотим создать новую подсеть в vpc нашего модуля aws-web-server-vpc. На этот раз новая подсеть должна быть определена в совершенно отдельной конфигурации Terraform, которая имеет свое собственное состояние. Мы можем использовать terraform_remote_state для получения значения vpc_id, определенного как выход корневого модуля нашего предыдущего примера. В этом случае мы используем локальный бэкенд, чтобы получить состояние другой конфигурации на локальной машине. Бэкэнд может быть любым удаленным бэкэндом, который указывает на состояние Terraform в реальном сценарии.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.16.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
data "terraform_remote_state" "terraform_output" {
backend = "local"
config = {
path = "../terraform-output/terraform.tfstate"
}
}
resource "aws_subnet" "test_terraform_remote_state_subnet" {
vpc_id = data.terraform_remote_state.terraform_output.outputs.vpc_id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1b"
tags = {
Name = "test_terraform_remote_state_subnet"
}
}
Обратите внимание, что из удаленного состояния доступны только выходные значения корневого модуля. Если мы хотим передавать значения из вложенных модулей, мы должны настроить объявление выходных значений passthrough, как мы определили ранее в корневом модуле нашего предыдущего примера.
Хотя этот вариант удобен для некоторых случаев использования, он также имеет некоторые оговорки. Чтобы использовать этот источник данных, пользователь должен иметь доступ ко всему снимку состояния, что потенциально может привести к раскрытию конфиденциальных данных. Ознакомьтесь с официальной документацией, чтобы найти альтернативные способы обмена данными между конфигурациями.
Ключевые моменты
Мы рассмотрели, как Terraform обрабатывает и экспортирует выходные значения между модулями и различные варианты конфигурации выходов. Более того, мы сравнили входные и выходные переменные и рассмотрели несколько примеров использования выходных данных. Наконец, мы рассмотрели полный пример использования выходных значений в конфигурации Terraform между различными модулями и печати их в консоль.
Спасибо за чтение, и я надеюсь, что вам понравилась эта статья блога «Выходные данные Terraform» так же, как и мне.