Terraform: пример работы – основные команды, state-файлы, бекенды, модули

By | 06/25/2018
 

Пример использования Terraform, его основные команды, работа с бекенда и модулями.

Достаточно кратко, но со ссылками на документацию.

Устанавливаем на Arch Linux:

sudo pacman -S terraform

Для авторизации используем созданный AWS профиль setevoy-root.

main.tf

Создаём файл main.tf, в котором указываем использование AWS provider, регион, и имя AWS профиля:

provider "aws" {
  region = "${var.aws-region}"
  profile = "setevoy-root"
}

Тут регион мы выносим в переменные, которые будут задаваться через файл variables.tf:

variable "aws-region" {
  default = "eu-west-1"
  description = "Default Amazon region"
}

Далее – в main.tf добавим создание Terraform ресурсаAWS EC2:

provider "aws" {
  region = "${var.aws-region}"
  profile = "setevoy-root"
}

resource "aws_instance" "tf-example-ec2" {

  ami = "ami-34414d4d"
  instance_type = "t2.nano"
  key_name = "${var.aws-key-name}"
  associate_public_ip_address = "true"

  tags {
    "Name"      = "tf-example-ec2"
    "Env"       = "${var.aws-cluster-name}"
  }
}

И в variables.tf – добавляем переменную с именем ключа:

variable "aws-region" {
  default = "eu-west-1"
  description = "Default Amazon region"
}

variable "aws-key-name" {
  default = "tf-example-ec2-key"
  description = "EC2 acces key pair"
}

Документация по переменным в Terraform – тут>>>.

Первый запуск

Т.к. мы используем aws provider – Terraform должен сначала загрузить его плагин.

Для этого – используем init:

terraform init
Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.24.0)...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 1.24"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Файл aws плагина по умолчанию размещается в текущем каталоге, где создаётся папка .terraform (путь можно переопределить с помощью -plugin-dir=):

ls -la .terraform/plugins/linux_amd64/
total 74232
drwxr-xr-x 2 setevoy setevoy     4096 Jun 25 17:19 .
drwxr-xr-x 3 setevoy setevoy     4096 Jun 25 17:19 ..
-rwxr-xr-x 1 setevoy setevoy       79 Jun 25 17:19 lock.json
-rwxr-xr-x 1 setevoy setevoy 75997088 Jun 25 17:19 terraform-provider-aws_v1.24.0_x4

init выполняет так же настройку бекендов для хранения state-файлов, но о них позже.

tarraform plan

Теперь можно запустить plan, что бы увидеть – какие действия будут выполнены Terraform-ом:

terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
+ aws_instance.tf-example-ec2
id:                           <computed>
ami:                          "ami-34414d4d"
associate_public_ip_address:  "true"
availability_zone:            <computed>
ebs_block_device.#:           <computed>
ephemeral_block_device.#:     <computed>
get_password_data:            "false"
instance_state:               <computed>
instance_type:                "t2.nano"
ipv6_address_count:           <computed>
ipv6_addresses.#:             <computed>
key_name:                     "tf-example-ec2-key"
network_interface.#:          <computed>
network_interface_id:         <computed>
password_data:                <computed>
placement_group:              <computed>
primary_network_interface_id: <computed>
private_dns:                  <computed>
private_ip:                   <computed>
public_dns:                   <computed>
public_ip:                    <computed>
root_block_device.#:          <computed>
security_groups.#:            <computed>
source_dest_check:            "true"
subnet_id:                    <computed>
tags.%:                       "1"
tags.Name:                    "tf-example-ec2"
tenancy:                      <computed>
volume_tags.%:                <computed>
vpc_security_group_ids.#:     <computed>
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

В его выводе видим, что будет создан один ресурс (символ + перед aws_instance.tf-example-ec2).

-” значит удаление, а “~” – модификацию существующего ресурса.

terraform apply

Что бы выполнить непосредственно создание ресурсов, описанных в main.tf – вызываем apply:

terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
+ aws_instance.tf-example-ec2
id:                           <computed>
ami:                          "ami-34414d4d"
associate_public_ip_address:  "true"
availability_zone:            <computed>
ebs_block_device.#:           <computed>
ephemeral_block_device.#:     <computed>
get_password_data:            "false"
instance_state:               <computed>
instance_type:                "t2.nano"
ipv6_address_count:           <computed>
ipv6_addresses.#:             <computed>
key_name:                     "tf-example-ec2-key"
network_interface.#:          <computed>
network_interface_id:         <computed>
password_data:                <computed>
placement_group:              <computed>
primary_network_interface_id: <computed>
private_dns:                  <computed>
private_ip:                   <computed>
public_dns:                   <computed>
public_ip:                    <computed>
root_block_device.#:          <computed>
security_groups.#:            <computed>
source_dest_check:            "true"
subnet_id:                    <computed>
tags.%:                       "1"
tags.Name:                    "tf-example-ec2"
tenancy:                      <computed>
volume_tags.%:                <computed>
vpc_security_group_ids.#:     <computed>
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_instance.tf-example-ec2: Creating...
ami:                          "" => "ami-34414d4d"
associate_public_ip_address:  "" => "true"
availability_zone:            "" => "<computed>"
ebs_block_device.#:           "" => "<computed>"
ephemeral_block_device.#:     "" => "<computed>"
get_password_data:            "" => "false"
instance_state:               "" => "<computed>"
instance_type:                "" => "t2.nano"
ipv6_address_count:           "" => "<computed>"
ipv6_addresses.#:             "" => "<computed>"
key_name:                     "" => "tf-example-ec2-key"
network_interface.#:          "" => "<computed>"
network_interface_id:         "" => "<computed>"
password_data:                "" => "<computed>"
placement_group:              "" => "<computed>"
primary_network_interface_id: "" => "<computed>"
private_dns:                  "" => "<computed>"
private_ip:                   "" => "<computed>"
public_dns:                   "" => "<computed>"
public_ip:                    "" => "<computed>"
root_block_device.#:          "" => "<computed>"
security_groups.#:            "" => "<computed>"
source_dest_check:            "" => "true"
subnet_id:                    "" => "<computed>"
tags.%:                       "" => "1"
tags.Name:                    "" => "tf-example-ec2"
tenancy:                      "" => "<computed>"
volume_tags.%:                "" => "<computed>"
vpc_security_group_ids.#:     "" => "<computed>"
aws_instance.tf-example-ec2: Still creating... (10s elapsed)
aws_instance.tf-example-ec2: Still creating... (20s elapsed)
aws_instance.tf-example-ec2: Still creating... (30s elapsed)
aws_instance.tf-example-ec2: Still creating... (40s elapsed)
aws_instance.tf-example-ec2: Creation complete after 47s (ID: i-062174155cee10e51)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Проверяем, используя ID, выданный Terraform-ом:

aws ec2 describe-instances --instance-ids i-062174155cee10e51
{
"Reservations": [
{
"Groups": [],
"Instances": [
{
"AmiLaunchIndex": 0,
"ImageId": "ami-34414d4d",
"InstanceId": "i-062174155cee10e51",
"InstanceType": "t2.nano",
"KeyName": "tf-example-ec2-key",
"LaunchTime": "2018-06-25T14:36:09.000Z",
"Monitoring": {
"State": "disabled"
},
...

terraform destroy

Для удаления созданных ресурсов – используем destroy, который удалит все ресурсы, описанные в main.tf:

terraform destroy
aws_instance.tf-example-ec2: Refreshing state... (ID: i-062174155cee10e51)
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
- aws_instance.tf-example-ec2
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_instance.tf-example-ec2: Destroying... (ID: i-062174155cee10e51)
aws_instance.tf-example-ec2: Still destroying... (ID: i-062174155cee10e51, 10s elapsed)
aws_instance.tf-example-ec2: Still destroying... (ID: i-062174155cee10e51, 20s elapsed)
aws_instance.tf-example-ec2: Still destroying... (ID: i-062174155cee10e51, 30s elapsed)
aws_instance.tf-example-ec2: Destruction complete after 32s
Destroy complete! Resources: 1 destroyed.

State-files

Terraform хранит информацию об инфрастуктуре в т.н. “state-файлах”.

По умолчанию это файл terraform.tfstate в каталоге проекта.

Например, если проверить файл сейчас – в нём будет ноль ресурсов, т.к. мы только что выполнили destroy:

cat terraform.tfstate
{
"version": 3,
"terraform_version": "0.11.7",
"serial": 3,
"lineage": "a83f41ba-0a1e-ec21-cc4c-312940dfb53f",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {},
"depends_on": []
}
]
}

Перед выполнением apply – TF создаст бекап текущего state-файла (если он есть) в файл terraform.tfstate.backup – сейчас тут описано состояние проекта до выполнения destroy:

cat terraform.tfstate.backup
{
"version": 3,
"terraform_version": "0.11.7",
"serial": 3,
"lineage": "a83f41ba-0a1e-ec21-cc4c-312940dfb53f",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"aws_instance.tf-example-ec2": {
"type": "aws_instance",
"depends_on": [],
"primary": {
"id": "i-062174155cee10e51",
...

Имя файла, в котором будет храниться состояние инфрастуктуры, можно задать с помощью -state, а имя файла бекапа – с помощью -backup.

Проверяем – создаём ЕС2 заново, заодно используем опцию -auto-approve, что бы не вводить yes каждый раз:

terraform apply -state /tmp/tf-example-ec2.tfstate -backup /tmp/tf-example-ec2.tfstate.backup -auto-approve

Проверяем файл состояния:

cat /tmp/tf-example-ec2.tfstate
{
"version": 3,
"terraform_version": "0.11.7",
"serial": 4,
"lineage": "a83f41ba-0a1e-ec21-cc4c-312940dfb53f",
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"aws_instance.tf-example-ec2": {
"type": "aws_instance",
"depends_on": [],
"primary": {
"id": "i-01b63239677ef31e8",
...

Аналогично при destroy необходимо указать этот же файл состояния – /tmp/tf-example-ec2.tfstate:

terraform destroy -state /tmp/tf-example-ec2.tfstate -backup /tmp/tf-example-ec2.tfstate.backup -auto-approve
aws_instance.tf-example-ec2: Refreshing state... (ID: i-0c6779781dabd81d8)
aws_instance.tf-example-ec2: Destroying... (ID: i-0c6779781dabd81d8)
...
aws_instance.tf-example-ec2: Destruction complete after 31s
Destroy complete! Resources: 1 destroyed.

terraform state

Для работы с файлами состояния – у Terrafrom имеется команда state.

Например – просмотреть список ресурсов в state-файле можно с помощью list:

terraform state list -state=/tmp/tf-example-ec2.tfstate
aws_instance.tf-example-ec2

Backends

По умолчанию – Terraform создаёт файлы локально, но так же их можно хранить в удалённых хранилищах.

Наиболее используемый вариант (по крайней мере из тех, что мне встречались) – это AWS S3 корзина.

Обновляем наш main.tf, и описываем корзину, в которой будем хранить стейт-файлы – backend "s3":

provider "aws" {
  region = "${var.aws-region}"
  profile = "setevoy-root"
}

resource "aws_instance" "tf-example-ec2" {

  ami = "ami-34414d4d"
  instance_type = "t2.nano"
  key_name = "${var.aws-key-name}"
  associate_public_ip_address = "true"

  tags {
    "Name"      = "tf-example-ec2"
  }
}

terraform {
  backend "s3" {
    profile = "setevoy-root"
    bucket  = "tf-example-states"
    key     = "tf-example/terraform.tfstate"
    region  = "eu-west-1"
  }
}

Создаём саму корзину:

aws s3api create-bucket --bucket tf-example-states --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1
{
"Location": "http://tf-example-states.s3.amazonaws.com/"
}

Т.к. мы добавили бекенд s3 – выполняем инициализацию Terrafrom-проекта ещё раз:

terraform init
Initializing the backend...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "s3" backend. No existing state was found in the newly
configured "s3" backend. Do you want to copy this state to the new "s3"
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value: yes
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 1.24"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Terraform скопировал текущий state-файл в указанную корзину:

aws s3 ls s3://tf-example-states/tf-example/
2018-06-25 18:40:11        317 terraform.tfstate

Теперь состояние проекта будет записываться в этот файл.

Модули Terraform

О чём ещё стоит упомянуть – это модули в Terraform, которые позволяют разделять ресурсы и облегчают управление ими.

Например, файл main.tf являет собой root-модуль проекта, в котором мы описываем ресурсы.

Аналогично – можно создать каталог, например ec2, и уже в нём описать ресурс ЕС2 и добавить ему свои переменные.

Создаём каталог:

mkdir ec2

Создаём файл, например ec2.tf, и к нему заодно файл переменных – variables.tf:

touch ec2/{ec2.tf,variables.tf}

В файле ec2/ec2.tf описываем создание ресурса ЕС2:

resource "aws_instance" "tf-example-ec2" {

  ami = "${var.aws-ec2-ami-id}"
  instance_type = "${var.aws-ec2-type}"
  key_name = "${var.aws-key-name}"
  associate_public_ip_address = "true"

  tags {
    "Name"      = "tf-example-ec2"
  }
}

В переменных – задаём сами переменные:

variable "aws-ec2-ami-id" {
      default = "ami-34414d4d"
}

variable "aws-ec2-type" {
  default = "t2.nano"
}

variable "aws-key-name" {
  default = "tf-example-ec2-key"
}

В файле main.tf описываем используем созданного модуля ec2:

provider "aws" {
  region = "${var.aws-region}"
  profile = "setevoy-root"
}

module "ec2" {

  source            = "ec2"
}

/*
resource "aws_instance" "tf-example-ec2" {

  ami = "ami-34414d4d"
  instance_type = "t2.nano"
  key_name = "${var.aws-key-name}"
  associate_public_ip_address = "true"

  tags {
    "Name"      = "tf-example-ec2"
  }
}
*/

terraform {
  backend "s3" {
    profile = "setevoy-root"
    bucket  = "tf-example-states"
    key     = "tf-example/terraform.tfstate"
    region  = "eu-west-1"
  }
}

Т.к. добавили новый модуль – то сначала выполняем init:

terraform init
Initializing modules...
- module.ec2
Getting source "ec2"
Initializing the backend...
Initializing provider plugins...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 1.24"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Теперь можно сделать plan:

terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
+ module.ec2.aws_instance.tf-example-ec2
id:                           <computed>
ami:                          "ami-34414d4d"
...

И выполнить apply:

terraform apply -auto-approve
module.ec2.aws_instance.tf-example-ec2: Creating...
ami:                          "" => "ami-34414d4d"
associate_public_ip_address:  "" => "true"
availability_zone:            "" => "<computed>"
ebs_block_device.#:           "" => "<computed>"
ephemeral_block_device.#:     "" => "<computed>"
get_password_data:            "" => "false"
instance_state:               "" => "<computed>"
instance_type:                "" => "t2.nano"
ipv6_address_count:           "" => "<computed>"
ipv6_addresses.#:             "" => "<computed>"
key_name:                     "" => "tf-example-ec2-key"
network_interface.#:          "" => "<computed>"
network_interface_id:         "" => "<computed>"
password_data:                "" => "<computed>"
placement_group:              "" => "<computed>"
primary_network_interface_id: "" => "<computed>"
private_dns:                  "" => "<computed>"
private_ip:                   "" => "<computed>"
public_dns:                   "" => "<computed>"
public_ip:                    "" => "<computed>"
root_block_device.#:          "" => "<computed>"
security_groups.#:            "" => "<computed>"
source_dest_check:            "" => "true"
subnet_id:                    "" => "<computed>"
tags.%:                       "" => "1"
tags.Name:                    "" => "tf-example-ec2"
tenancy:                      "" => "<computed>"
volume_tags.%:                "" => "<computed>"
vpc_security_group_ids.#:     "" => "<computed>"
module.ec2.aws_instance.tf-example-ec2: Still creating... (10s elapsed)
module.ec2.aws_instance.tf-example-ec2: Creation complete after 15s (ID: i-08d703ddd4f252382)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

В целом – на этом пока всё.

Писать о Terraform можно (и, наверное, буду) много, но у него отличная документация.