Оглавление

  1. День 1: Введение

  2. День 2: Поднимаем сервера

  3. День 3: Data source и outputs

Ссылки

Некоторые ссылки требует VPN

Справочник команд CLI aws

Справочник команд CLI yc

Terraform provider aws

Terraform provider Yandex

Сегодня мы познакомимся с понятиями data source и output; посмотрим, как применяются изменения в уже существующей инфраструктуре.

Data source

В прошлый раз для поднятия сервера нам приходилось часть параметром искать с помощью cli (или консоли), а конкретно subnet_id и id образа ОС. Что бы не делать это каждый раз и что бы иметь актуальную информацию (например последнею версию ОС) существует data source. Эта сущность дает возможность получить информацию от провайдера, которая никак не связана с текущей инфраструктурой (той что описана в нашем файле)

Синтаксис у всех провайдеров одинаковый, такой же как и для ресурса:

data "<НАЗВАНИЕ>" "<ИМЯ>" {
	<ПАРАМЕТР> = <ЗНАЧЕНИЕ>
	<ДРУГОЙ_ПАРАМЕТР> = {
		<ПАРАМЕТР> = <ЗНАЧЕНИЕ>
	}
}

Представьте что ресурс и дата это одно и тоже, но ресурс инициируем мы сами, а дата это то, что уже существует.

Yandex

Что бы получить id для Ubuntu 22.04, необходимо указать параметр family - ubuntu-2204-lts; название подсети для получения ее id - default-ru-central1-a

yandex/main.tf

...
data "yandex_compute_image" "last_ubuntu" {
  family = "ubuntu-2204-lts"  # ОС (Ubuntu, 22.04 LTS)
}

data "yandex_vpc_subnet" "default_a" {
  name = "default-ru-central1-a"  # одна из дефолтных подсетей
}
...

aws

С aws чуть посложнее: для получения id последней версии ubuntu необходимо указать id владельца образа и его название (это мы получали через cli в прошлой части):

  • owner_id - 099720109477

  • name - ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server*

Звездочка заменяет номер версии, который мы не указываем, потому что нам нужна всегда последняя версия: за это отвечает параметр most_recent = true

Для получения id подсети, укажем желаемую зону (пусть будет как в Яндексе - “а”)

Звездочка заменяет номер версии, который мы не указываем, потому что нам нужна всегда последняя версия: за это отвечает параметр most_recent = true

Для получения id подсети, укажем желаемую зону (пусть будет как в Яндексе - “а”)

aws/main.tf

...
data "aws_ami" "last_ubuntu" {
  most_recent      = true # только новейшая версия
  owners           = ["099720109477"] # id владельца образа

  filter {
    name   = "name" # название фильтра - по какому параметру искать
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server*"] # что искать
  }
}

data "aws_subnet" "default_a" {
  filter {
    name   = "availability-zone"
    values = ["eu-north-1a"]
  }
}
...

Outputs и подстановка значений

Теперь необходимо использовать эти полученные данные в место хардкода. Но, помимо подстановки их в параметры ресурсов, мы выведем их в терминал с помощью output.

Выводы (назовем из так) используются не только для вывода информации на экран, но пока остановимся на этом. Давайте добавим в вывод id образа ОС и id подсети, так же добавим ip адрес поднятого сервера.

Что бы получить нужные значения из объектов, нужно просто обратится к ним по имени через точку.

Важно: terraform читает все файлы с расширением .tf в директории как одно целое - можно все описывать в одном файле или в нескольких, как вам угодно (по этой же причине не важен порядок указания сущностей в самом файле).

touch outputs.tf

Yandex

yandex/outputs.tf

output "default_instance_public_ip" {
    value = yandex_compute_instance.default.network_interface[0].nat_ip_address
}

output "subnet_id" {
    value = data.yandex_vpc_subnet.default_a.subnet_id
}

output "last_ubuntu" {
    value = data.yandex_compute_image.last_ubuntu.id
}

yandex/main.tf

terraform {
  required_providers {
    yandex = {
      source = "yandex-cloud/yandex"
    }
  }
}

provider "yandex" {
  token     = "..." # OAuth-токен яндекса
  cloud_id  = "b1gos1rh49bip4rnmrmg"
  folder_id = "b1gjju43i1pr11i5c4ic"
  zone      = "ru-central1-a"
}


data "yandex_compute_image" "last_ubuntu" {
  family = "ubuntu-2204-lts"  # ОС (Ubuntu, 22.04 LTS)
}

data "yandex_vpc_subnet" "default_a" {
  name = "default-ru-central1-a"  # одна из дефолтных подсетей
}


# ресурс "yandex_compute_instance" т.е. сервер
# Terraform будет знаеть его по имени "yandex_compute_instance.default"
resource "yandex_compute_instance" "default" { 
  name = "test-instance"
	platform_id = "standard-v1" # тип процессора (Intel Broadwell)

  resources {
    core_fraction = 5 # Гарантированная доля vCPU
    cores  = 2 # vCPU
    memory = 1 # RAM
  }

  boot_disk {
    initialize_params {
      image_id = data.yandex_compute_image.last_ubuntu.id
    }
  }

  network_interface {
    subnet_id = data.yandex_vpc_subnet.default_a.subnet_id
    nat = true # автоматически установить динамический ip
  }
}

aws

aws/outputs.tf

output "default_instance_public_ip" {
    value = aws_instance.default.public_ip
}

output "subnet_id" {
    value = data.aws_subnet.default_a.id
}

output "last_ubuntu" {
  value = data.aws_ami.last_ubuntu.id
}

aws/main.tf

provider "aws" {
  access_key = "AK..."
  secret_key = "2X..."
  region = "eu-north-1"
}

data "aws_ami" "last_ubuntu" {
  most_recent      = true
  owners           = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server*"]
  }
}

data "aws_subnet" "default_a" {
  filter {
    name   = "availability-zone"
    values = ["eu-north-1a"]
  }
}

#ресурс "aws_instance" т.е. сервер
#terraform будет знаеть его по имени "aws_instance.default"
resource "aws_instance" "default" {
  ami           = data.aws_ami.last_ubuntu.id # ОС (Ubuntu, 22.04 LTS)
  instance_type = "t3.micro" # тип процессора и ресурс машины (CPU и RAM)
	subnet_id = data.aws_subnet.default_a.id # одна из дефолтных подсетей
  associate_public_ip_address = true # автоматически установить динамический ip
	tags = {
    Name = "test-instance"
	}
}

Применение и вывод данных в терминал

После terraform apply мы увидем примерно следующее:

Outputs:

# yandex
default_instance_public_ip = "51.250.95.35"
last_ubuntu = "fd8v0s6adqu3ui3rsuap"
subnet_id = "e9bdgo95ucmut6r7pioq"

# aws
default_instance_public_ip = "16.16.64.183"
last_ubuntu = "ami-0a2b8744b4fe77f92"
subnet_id = "subnet-82b67deb"

Если мы заглянем в вывод команды plan (тоже самое выводит apply перед применением), то увидим следующее:

Changes to Outputs:

# yandex
  + default_instance_public_ip = (known after apply)
  + last_ubuntu                = "fd8v0s6adqu3ui3rsuap"
  + subnet_id                  = "e9bdgo95ucmut6r7pioq"

# aws
	+ default_instance_public_ip = (known after apply)
  + last_ubuntu                = "ami-0a2b8744b4fe77f92"
  + subnet_id                  = "subnet-82b67deb"

Возвращаясь к началу данной статьи: data source это то, что уже существует, поэтому, еще до применения, нам известны эти данные: last_ubuntu и subnet_id; в отличии от default_instance_public_ip, который мы получаем от поднятого сервера, а значит узнаем мы эти данные только после apply.

Изменения в инфраструктуре

До этого мы создавали сущности с нуля. Теперь попробуем изменить уже существующие. Для примера, добавим к нашим серверам параметр с ssh ключом, и посмотрим, что предлагает terraform.

Что бы не вставлять публичный ключ в файл (слишком длинный и можем измениться), лучше используем file() для подстановки все содержимого любого файла в строку.

Yandex

В Яндекс для добавления ssh ключа нужна metadata. Также надо указать пользователя, к которому ключ даст доступ: пользователь ubuntu создается по умолчанию.

yandex/main.tf

...
resource "yandex_compute_instance" "default" { 
  name = "test-instance"
  ...
  metadata = {
    ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
  }
}

aws

В aws все немного сложнее - тут ключ это отдельная сущность, а значит надо добавить еще один ресурс: aws_key_pair и его указать в качестве значения параметра для aws_instance.

aws/main.tf

...
resource "aws_instance" "default" {
  ...
  key_name = aws_key_pair.test_key.key_name
}

resource "aws_key_pair" "test_key" {
  key_name   = "test-key"
  public_key = "${file("~/.ssh/id_rsa.pub")}"
}

Применение изменений

terraform plan покажет нам план действий terrafrom:

Yandex

Поведения яндекса немного не очевидное. Дело в том, что создав виртуальную машину без ssh ключа (то, что было у нас изначально), и добавив его потом, terraform не предлагает пересоздать машину. ~ update in-place означает, что ресурс будет изменен без пересоздания.

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # yandex_compute_instance.default will be updated in-place
  ~ resource "yandex_compute_instance" "default" {
        id                        = "fhm3kp62eahete5kofke"
      ~ metadata                  = {
          + "ssh-keys" = <<-EOT
...

Однако, после добавления ключа, подключение по нему будет недоступно. Необходимо пересоздать машину, что бы ssh не требовал пароль, а только ключ. Для этого добавим параметр replace в команду: apply -replace=yandex_compute_instance.default. Мы явно указываем, что необходимо пересоздать. Terraform это покажет - мы получим новый адрес, что видно в Changes to Outputs.

...
# yandex_compute_instance.default will be replaced, as requested
-/+ resource "yandex_compute_instance" "default" {
...
Changes to Outputs:
  ~ default_instance_public_ip = "51.250.95.35" -> (known after apply)
...

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

yc compute instance list
+----------------------+---------------+---------------+---------+--------------+-------------+
|          ID          |     NAME      |    ZONE ID    | STATUS  | EXTERNAL IP  | INTERNAL IP |
+----------------------+---------------+---------------+---------+--------------+-------------+
| fhmpng88a49dihen141a | test-instance | ru-central1-a | RUNNING | 62.84.117.26 | 10.128.0.17 |
+----------------------+---------------+---------------+---------+--------------+-------------+

aws

В aws все работает как и ожидается - terraform сам предлагает пересоздать сервер. Также он указывает: что именно заставляет его так поступать. В данном случае это key_name - # forces replacement значит, что именно этот параметр причина пересоздания.

Terraform will perform the following actions:

# aws_instance.default must be replaced
-/+ resource "aws_instance" "default" {
     ...
      + key_name                           = "test-key" # forces replacement
		 ...
# aws_key_pair.test_key will be created
+ resource "aws_key_pair" "test_key" {
...
Changes to Outputs:
  ~ default_instance_public_ip = "16.16.64.183" -> (known after apply)
...

К сожалению, мы не можем просто подключится к новому серверу на aws - нам необходимо настроить security group (сделаем это в одной из следующих статей). Но сервер работает и новый ключ создан.

aws ec2 describe-instances \
	--region eu-north-1 \
	--query "Reservations[*].Instances[*].{Instance:InstanceId,KeyName:KeyName,Address:PublicIpAddress,Name:Tags[?Key=='Name']|[0].Value}" \
	--output table
----------------------------------------------------------------------------
|                             DescribeInstances                            |
+--------------+-----------------------+-----------------+-----------------+
|    Address   |       Instance        |     KeyName     |      Name       |
+--------------+-----------------------+-----------------+-----------------+
|  13.49.23.154|  i-075afc37d4775ccb0  |  test-key       |  test-instance  |
+--------------+-----------------------+-----------------+-----------------+

aws ec2 describe-key-pairs \
	--region eu-north-1 \
	--query="KeyPairs[*].{KeyName:KeyName,CreateTime:CreateTime}" \
	--output table
-------------------------------------------------
|               DescribeKeyPairs                |
+----------------------------+------------------+
|         CreateTime         |     KeyName      |
+----------------------------+------------------+
|  2022-08-28T11:07:42+00:00 |  test-key        |
+----------------------------+------------------+

Уничтожим все c terraform destroy

Заключение

Data source незаменимый инструмент для построения сложной инфраструктуры. Outputs на данном этапе кажутся несколько бесполезными, но в дальнейшем мы познакомимся с другим применением данной возможности.

Любые вопросы пишите на почту v.valentinvolkov@gmail.com. Буду рад помочь!

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


  1. vadav13
    31.08.2022 18:43

    Good!