Оглавление
Ссылки
Некоторые ссылки требует VPN
Сегодня мы познакомимся с понятиями 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. Буду рад помочь!
vadav13
Good!