Terraform — эффективное управление инфраструктурой приложений


Наш DevOps отдел, как одно из ключевых звеньев в разработке и выпуске приложений, развивается очень бурно и стремительно. Причем, как качественно, так и количественно. Достаточно сложно представить, чтобы хоть какой-то из процессов DevOps не был автоматизирован. А особенно моделирование и развертывание инфраструктуры.

Infrastructure as a Code — одно из логичных направлений DevOps, позволяющее описывать, моделировать и изменять инфраструктуру в формате кода с применением многолетних практик разработки приложений. А еще оно помогает упростить масштабирование и контроль инфраструктуры в целом, а также сократить цикл развертывания и доставки приложения пользователю.
А теперь — ближе к делу. Одним из мощнейших инструментов моделирования и развертывания инфраструктуры является Terraform, разработанный компанией HashiCorp.

Что это?

Terraform — инструмент, позволяющий разрабатывать, изменять и версионировать инфраструктуру вашего сервиса в формате кода. Инфраструктура описывается в конфигурационных файлах и может применяться, как к работающей системе, так и к совершенно новой.
По своему формату, разработка terraform конфигурации напоминает описание инфраструктурных компонентов, необходимых для работы вашего приложения.
В целом формат описания инфраструктуры достаточно декларативен. Для него используется специально разработанный язык программирования HashiCorp Configuration Language (HCL). Но, можно использовать и просто JSON.

Применение на практике

Давайте взглянем на несколько примеров. В первом мы создадим AWS VPC  с подсетью. Для это нам придется воспользоваться документацией AWS. Она поможет понять все нюансы настройки.
Итак, создаем директорию “infrastructure” и описываем все необходимые для развертывания конфиги. Так как terraform выгружает все файлы c .tf расширением из директории, мы можем сразу разделять код по отдельным конфигам-модулям для простоты поиска и изменения отдельных частей инфраструктуры.
Далее описываем провайдера. В нашем случае это AWS.

variable "access_key_id" {
  type = "string"
}

variable "secret_access_key" {
  type = "string"
}

variable "region" {
  type = "string"
}

provider "aws" {
  access_key = "${var.access_key_id}"
  secret_key = "${var.secret_access_key}"
  region     = "${var.region}"
}

Обратите внимание, что `${}` используется для интерполяции. Этот способ позволяет указать, какие значения мы вводим самостоятельно.
Далее необходимо описать сам VPC resource используя следующую конструкцию.

resource "aws_vpc" "vpc-jetruby" {
  cidr_block = "10.0.0.0/16"

  tags {
    Name = "VPC"
  }
}


Единственным обязательным параметром для создания VPC является CIDR  блок.
Сейчас мы хотим предоставить доступ в интернет всем ресурсам, расположенным внутри нашей VPC. Для этого нам необходимо создать Internet gateway и подключить его к нашему VPC.

resource "aws_internet_gateway" "gateway-jetruby" {
  vpc_id = "${aws_vpc.vpc-jetruby.id}"

  tags {
    Name = "Intenet Gateway"
  }
}

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

resource "aws_subnet" "subnet-jetruby" {
  vpc_id     = "${aws_vpc.vpc-jetruby.id}"
  cidr_block = "10.0.0.0/16"

  map_public_ip_on_launch = "true"

  tags {
    Name = "Subnet"
  }
}

output "aws-id-subnet-jetruby" {
  value = "${aws_subnet.subnet-jetruby.id}"
}

Обратите внимание на директиву/переменную `output` (мы ее не описывали). Благодаря ней, у нас появляется возможность указать, какие данные должен запомнить Terraform и вернуть пользователю после выполнения.
Теперь, когда у нас есть подсеть, остается лишь описать несколько завершающих шагов. AWS автоматически ассоциирует подсеть с Main RouteTable, которая создается для каждой VPC. Но для того чтобы иметь полный контроль над исходящим трафиком, давайте создадим для подсети собственную RouteTable.

resource "aws_route_table" "route-table-jetruby" {
  vpc_id = "${aws_vpc.vpc-jetruby.id}"
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.gateway-jetruby.id}"
  }

  tags {
    Name = "Route Table"
  }
}

Бинго! А сейчас мы заставим подсеть работать. Для этого надо явно описать ассоциацию c RouteTable.

resource "aws_route_table_association" "route-table-association-jetruby" {
  subnet_id      = "${aws_subnet.subnet-jetruby.id}"
  route_table_id = "${aws_route_table.route-table-jetruby.id}"
}

Итак, мы описали всю инфраструктуру VPC и подсети. Давайте теперь посмотрим на все созданные в директории файлы и запустим планирование. Планирование — операция, позволяющая нам увидеть, что будет происходить в процессе “apply”. По сути, это симуляция развертывания реальной инфраструктуры.

➔ ls -lah
total 48
drwxr-xr-x    8 alxgol  staff   272B 23 окт 00:25 .
drwxr-xr-x  263 alxgol  staff   8,7K 23 окт 00:09 ..
-rw-r--r--    1 alxgol  staff   271B 18 окт 15:51 aws_provider.tf
-rw-r--r--    1 alxgol  staff   125B 23 окт 00:24 gateway.tf
-rw-r--r--    1 alxgol  staff   193B 23 окт 00:24 route-table-association.tf
-rw-r--r--    1 alxgol  staff   229B 23 окт 00:24 route-table.tf
-rw-r--r--    1 alxgol  staff   262B 23 окт 00:24 subnet.tf
-rw-r--r--    1 alxgol  staff    97B 23 окт 00:25 vpc.tf

➔ terraform plan
var.access_key_id
  Enter a value: access-key

var.region
  Enter a value: eu-west-2

var.secret_access_key
  Enter a value: secret-key

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.

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_internet_gateway.gateway-jetruby
    tags.%:    "1"
    tags.Name: "Intenet Gateway"
    vpc_id:    "${aws_vpc.vpc-jetruby.id}"

+ aws_route_table.route-table-jetruby
    route.#:                                    "1"
    route.~222644434.cidr_block:                "0.0.0.0/0"
    route.~222644434.egress_only_gateway_id:    ""
    route.~222644434.gateway_id:                "${aws_internet_gateway.gateway-jetruby.id}"
    route.~222644434.instance_id:               ""
    route.~222644434.ipv6_cidr_block:           ""
    route.~222644434.nat_gateway_id:            ""
    route.~222644434.network_interface_id:      ""
    route.~222644434.vpc_peering_connection_id: ""
    tags.%:                                     "1"
    tags.Name:                                  "Route Table"
    vpc_id:                                     "${aws_vpc.vpc-jetruby.id}"

+ aws_route_table_association.route-table-association-jetruby
    route_table_id: "${aws_route_table.route-table-jetruby.id}"
    subnet_id:      "${aws_subnet.subnet-jetruby.id}"

+ aws_subnet.subnet-jetruby
    assign_ipv6_address_on_creation: "false"
    availability_zone:               "<computed>"
    cidr_block:                      "10.0.0.0/16"
    ipv6_cidr_block:                 "<computed>"
    ipv6_cidr_block_association_id:  "<computed>"
    map_public_ip_on_launch:         "true"
    tags.%:                          "1"
    tags.Name:                       "Subnet"
    vpc_id:                          "${aws_vpc.vpc-jetruby.id}"

+ aws_vpc.vpc-jetruby
    assign_generated_ipv6_cidr_block: "false"
    cidr_block:                       "10.0.0.0/16"
    default_network_acl_id:           "<computed>"
    default_route_table_id:           "<computed>"
    default_security_group_id:        "<computed>"
    dhcp_options_id:                  "<computed>"
    enable_classiclink:               "<computed>"
    enable_dns_hostnames:             "<computed>"
    enable_dns_support:               "true"
    instance_tenancy:                 "<computed>"
    ipv6_association_id:              "<computed>"
    ipv6_cidr_block:                  "<computed>"
    main_route_table_id:              "<computed>"
    tags.%:                           "1"
    tags.Name:                        "VPC"


Plan: 5 to add, 0 to change, 0 to destroy.

Отлично, terraform составил план, согласно описанной конфигурации. По этому плану он создаст на нашем AWS аккаунте в eu-west-2 (London) регионе  5 ресурсов:
  1. VPC
  2. Gateway
  3. Subnet
  4. Route Table
  5. Route Table Association
Пока все идет хорошо. Давайте попробуем выполнить операцию “apply”, предназначенную для реального развертывания инфраструктуры на AWS.

➔ terraform apply
var.access_key_id
  Enter a value: access-key

var.region
  Enter a value: eu-west-2

var.secret_access_key
  Enter a value: secret-key

aws_vpc.vpc-jetruby: Creating...
  assign_generated_ipv6_cidr_block: "" => "false"
  cidr_block:                       "" => "10.0.0.0/16"
  default_network_acl_id:           "" => "<computed>"
  default_route_table_id:           "" => "<computed>"
  default_security_group_id:        "" => "<computed>"
  dhcp_options_id:                  "" => "<computed>"
  enable_classiclink:               "" => "<computed>"
  enable_dns_hostnames:             "" => "<computed>"
  enable_dns_support:               "" => "true"
  instance_tenancy:                 "" => "<computed>"
  ipv6_association_id:              "" => "<computed>"
  ipv6_cidr_block:                  "" => "<computed>"
  main_route_table_id:              "" => "<computed>"
  tags.%:                           "" => "1"
  tags.Name:                        "" => "VPC"
aws_vpc.vpc-jetruby: Creation complete (ID: vpc-02d7836b)
aws_internet_gateway.gateway-jetruby: Creating...
  tags.%:    "0" => "1"
  tags.Name: "" => "Intenet Gateway"
  vpc_id:    "" => "vpc-02d7836b"
aws_subnet.subnet-jetruby: Creating...
  assign_ipv6_address_on_creation: "" => "false"
  availability_zone:               "" => "<computed>"
  cidr_block:                      "" => "10.0.0.0/16"
  ipv6_cidr_block:                 "" => "<computed>"
  ipv6_cidr_block_association_id:  "" => "<computed>"
  map_public_ip_on_launch:         "" => "true"
  tags.%:                          "" => "1"
  tags.Name:                       "" => "Subnet"
  vpc_id:                          "" => "vpc-02d7836b"
aws_internet_gateway.gateway-jetruby: Creation complete (ID: igw-ebdd4e82)
aws_route_table.route-table-jetruby: Creating...
  route.#:                                   "" => "1"
  route.698417740.cidr_block:                "" => "0.0.0.0/0"
  route.698417740.egress_only_gateway_id:    "" => ""
  route.698417740.gateway_id:                "" => "igw-ebdd4e82"
  route.698417740.instance_id:               "" => ""
  route.698417740.ipv6_cidr_block:           "" => ""
  route.698417740.nat_gateway_id:            "" => ""
  route.698417740.network_interface_id:      "" => ""
  route.698417740.vpc_peering_connection_id: "" => ""
  tags.%:                                    "" => "1"
  tags.Name:                                 "" => "Route Table"
  vpc_id:                                    "" => "vpc-02d7836b"
aws_subnet.subnet-jetruby: Creation complete (ID: subnet-62e4a219)
aws_route_table.route-table-jetruby: Creation complete (ID: rtb-34f8d45d)
aws_route_table_association.route-table-association-jetruby: Creating...
  route_table_id: "" => "rtb-34f8d45d"
  subnet_id:      "" => "subnet-62e4a219"
aws_route_table_association.route-table-association-jetruby: Creation complete (ID: rtbassoc-103a2079)

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path:

Outputs:

aws-id-subnet-jetruby = subnet-62e4a219

Ура, работает! Вы также могли заметить, что в завершении операции terraform вывел в “output ID” подсети (запомним его для проверки), как мы это и описывали. Давайте теперь убедимся, что все описанное действительно было создано.
Идем на AWS и смотрим: появилась ли в у нас новая Virtual Private Cloud с именем VPC.
terraform-1
Смотрим на Internet Gateway, связанный с нашим VPC.
terraform-2
Да, он на месте. Теперь взглянем на подсети. Помните Subnet ID, который мы получили в “output” после “apply”. Вот он: `subnet-62e4a219`? Ищем его!
terraform-3
Отлично! Наконец, убедимся, что ассоциация подсети с личной Route Table прошла успешно. Найдем Route Table по его ID, указанному в созданной Subnet.
terraform-4

Вывод

Как видите, сам процесс описания ресурсов очень прост и понятен даже не знакомому с синтаксисом HCL человеку. Сам Terraform, как и любой инструмент, обладает рядом преимуществ и недостатков.
Явные преимущества состоят в следующих пунктах:
  1. Очень грамотная и качественная документация, отвечающая практически на любые вопросы инженера.
  2. Декларативность и понятность синтаксиса конфигурации.
  3. Возможность работы, как с популярными провайдерами вроде AWS так и с “Ad Hoc решениям”.
  4. Управление состоянием. Terraform автоматически определяет, какая часть вашей конфигурации уже развернута, какую следует удалить, а какую — добавить.
  5. Open Source и очень активное коммьюнити. Оно помогает инструменту стремительно развиваться и соответствовать всем требования рынка.
  6. Иммутабельность.
Выделить минусы не так просто. На наш взгляд, они крутятся вокруг statefil`a:
  1. Иммутабельность. Да, она уже была. Но это не только преимущество, но и недостаток. В чем сложность работы одной команды над одними и теми же конфигами? В том, что разные люди могут с легкостью перезаписать “statefile”, используемый Terraform’ом при “plan” и “apply” операциях. А это может привести к непредвиденным последствиям.
  2. Terraform хранит statefile в обыкновенном текстовом формате. Это может стать проблемой при использовании систем контроля версий. Если кто-то его повредит во время merg`a или любой другой операции, вам (вероятно) предстоит разворачивать всю инфраструктуру заново.

Похожее

Добавить комментарий

Оставить комментарий