Terraform: новый подход к Infrastructure as code

в 9:00, , рубрики: cloud computing, Go, iac, terraform, Администрирование баз данных, Блог компании Издательский дом «Питер», высокая производительность, книги, непрерывная интеграция, Программирование

Привет, коллеги! Пока блистательный Илон Маск вынашивает амбициозные планы терраформирования Марса, мы интересуемся новыми возможностями, связанными с парадигмой "Infrastructure as Code" и хотим предложить вам перевод статьи об одном из представителей «великолепной семерки» — Terraform. Книга Евгения Брикмана по теме неплохая, но ей скоро год, так что просим высказаться — хотите ли увидеть ее на русском языке

Слово Камалу Мархуби (Kamal Marhubi) из компании Heap.

Наша инфраструктура работает на базе AWS, и мы управляем ею при помощи Terraform. В этой публикации мы подобрали для вас практические советы и уловки, которые пригодились нам по ходу работы.

Terraform и инфраструктура на уровне кода

Terraform – это инструмент от компании Hashicorp, помогающий декларативно управлять инфраструктрой. В данном случае не приходится вручную создавать инстансы, сети и т.д. в консоли вашего облачного провайдера; достаточно написать конфигурацию, в которой будет изложено, как вы видите вашу будущую инфраструктуру. Такая конфигурация создается в человеко-читаемом текстовом формате. Если вы хотите изменить вашу инфраструктуру, то редактируете конфигурацию и запускаете terraform apply. Terraform направит вызовы API к вашему облачному провайдеру, чтобы привести инфраструктуру в соответствие с конфигурацией, указанной в этом файле.

Если перенести управление инфраструктурой в текстовые файлы, то открывается возможность вооружиться всеми излюбленными инструментами для управления исходным кодом и процессами, после чего переориентируем их для работы с инфраструктурой. Теперь инфраструктура подчиняется системам контроля версий, точно как исходный код, ее можно точно так же рецензировать или откатывать к более раннему состоянию, если что-нибудь пойдет неправильно.

Вот как, к примеру, в Terraform определяется инстанс EC2 с томом EBS:

resource "aws_instance" "example" {
  ami           = "ami-2757f631"
  instance_type = "t2.micro"

  ebs_block_device {
    device_name = "/dev/xvdb"
    volume_type = "gp2"
    volume_size = 100
  }
}

Если вы еще не пробовали Terraform, то вот это руководство для начинающих подойдет и поможет быстро освоиться с потоком задач в этом инструменте.

Модель данных Terraform

В общей перспективе модель данных Terraform проста: Terraform управляет ресурсами, а у ресурсов есть атрибуты. Несколько примеров из мира AWS:

  • Инстанс EC2 – это ресурс с такими атрибутами, как тип машины, загрузочный образ, зона доступности и группы безопасности
  • Том EBS – это ресурс с такими атрибутами как размер тома, тип тома, IOPS
  • Эластичный балансировщик нагрузки – это ресурс с атрибутами для резервных инстансов, характеристик их работоспособности и некоторых других феноменов

Terraform обеспечивает сопоставление ресурсов, описанных в конфигурационном файле, с соответствующими ресурсами облачного провайдера. Такое сопоставление именуется состоянием, это гигантский файл JSON. При запуске terraform apply Terraform обновляет состояние, направляя соответствующий запрос облачному поставщику. Затем сравнивает возвращенные ресурсы с той информацией, что записана в вашей конфигурации Terraform. Если обнаружится какая-либо разница, то создается план, в сущности – перечень изменений, которые нужно внести в ресурсы облачного провайдера, чтобы фактическая конфигурация соответствовала той, что указана у вас в конфигурации. Наконец, Terraform применяет эти изменения, направляя соответствующие вызовы облачному провайдеру.

Не всякий ресурс Terraform – это ресурс AWS

Понять такую модель данных с ресурсами и атрибутами не столь сложно, однако, она может не вполне совпадать с API облачного провайдера. Фактически, единственный ресурс Terraform может соответствовать как одному, так и нескольким базовым объектам облачного провайдера – либо даже не соответствовать ни одному. Вот несколько примеров из AWS:

  • aws_ebs_volume в Terraform соответствует одному тому AWS EBS
  • aws_instance в Terraform со встроенным блоком ebs_block_device как в предыдущем примере соответствует двум ресурсам EC2: инстансу и тому
  • aws_volume_attachment в Terraform не соответствует ни одному объекту в EC2!

Последнее может показаться удивительным. При создании aws_volume_attachment Terraform сделает запрос AttachVolume; при уничтожении этого тома – сделает запрос DetachVolume. Ни один объект EC2 в этом не участвует: aws_volume_attachment в Terraform полностью синтетический! Как и у всех ресурсов в Terraform, у него есть ID. Но, тогда как в большинстве случаев ID приобретается у облачного провайдера, ID aws_volume_attachment – просто хеш из ID тома, ID инстанса и имени устройства. Бывают и другие случаи, где в Terraform фигурируют синтетические ресурсы – например, aws_route53_zone_association, aws_elb_attachment и aws_security_group_rule. Чтобы найти их, можно поискать в имени ресурса association или attachment, что, правда, не всегда помогает.

Все задачи решаются несколькими способами, так что при выборе будьте внимательны!

При работе с Terraform совершенно одинаковую инфраструктуру можно бывает представить несколькими разными способами. Вот другой вариант описания нашего инстанса-примера с томом EBS в Terraform, дающий на выходе точно такие же ресурсы EC2:

resource "aws_instance" "example" {
  ami           = "ami-2757f631"
  instance_type = "t2.micro"
}

resource "aws_ebs_volume" "example-volume" {
  availability_zone = "${aws_instance.example.availability_zone}"
  type              = "gp2"
  size              = 100
}

resource "aws_volume_attachment" "example-volume-attachment" {
  device_name = "/dev/xvdb"
  instance_id = "${aws_instance.example.id}"
  volume_id   = "${aws_ebs_volume.example-volume.id}"
}

Теперь том EBS стал полноправным ресурсом Terraform, мы отграничили его от инстанса EC2. Есть и третий ресурс, синтетический, связывающий два первых. Если представить наш инстанс и том, мы сможем добавлять и удалять тома, попросту добавляя и удаляя ресурсы aws_ebs_volume и aws_volume_attachment.

Зачастую не важно, какое именно представление EBS вы выберете. Но иногда, если выбор был ошибочным, изменить после этого вашу инфраструктуру будет довольно сложно!

Мы ошиблись с выбором

Именно на этом мы и обожглись. Мы работаем с большим PostgreSQL-кластером на AWS, и к каждому инстансу в качестве хранилища прикреплено 18 томов EBS. Все эти инстансы представлены в Terraform как единственный ресурс aws_instance с томами EBS, определяемыми в блоках ebs_block_device.

В инстансах нашей базы данных информация хранится в файловой системе ZFS. ZFS позволяет динамически добавлять блочные устройства, чтобы наращивать файловую систему без всяких задержек. Таким образом, мы постепенно увеличиваем наше хранилище по мере того, как клиенты присылают нам все больше и больше данных. Поскольку мы – аналитическая компания и собираем всевозможную информацию, такая возможность – огромное подспорье для нас. Мы постоянно оптимизируем в нашем кластере запросы и операции вставки. Таким образом, мы не увязнем с жестким соотношением CPU-хранилище, выбранным нами при заготовке кластера, а сможем корректировать баланс на лету, чтобы эффективно задействовать самые последние нововведения.

Этот процесс мог бы получиться еще более гладким, если бы не блоки ebs_block_device. Да, можно надеяться, что Terraform позволит добавить 19-й блок ebs_block_device к инстансу aws_instance – и все просто станет работать. Однако, Terraform усматривает здесь неподъемное изменение: он «не знает», как изменить инстанс из 18 томов, чтобы их стало 19. Нет, Terraform собирается снести весь инстанс и сделать на его месте новый! Нам меньше всего хотелось чего-то подобного в нашей базе данных, где хранятся терабайты информации!

До недавнего времени мы использовали обходной путь и заставляли Terraform синхронизироваться в несколько этапов:

  1. запускали скрипт, использовавший AWS CLI для создания и добавления томов
  2. запускали terraform refresh, чтобы Terraform обновил состояние и
  3. наконец, изменяли конфигурацию так, чтобы она соответствовала новым реалиям

Между этапами 2 и 3 команда terraform plan покажет, что Terraform собирался уничтожить и создать заново все инстансы нашей базы данных. Таким образом, было невозможно работать с этими инстансами в Terraform, пока кто-нибудь не обновит конфигурацию. Следует ли говорить, как страшно перманентно пребывать в таком состоянии!

Состояние Terraform: переходим к хирургии

Обнаружив подход с aws_volume_attachment, мы решили перестроить наше представление. Каждый том превратился в два новых ресурса Terraform: aws_ebs_volume и aws_volume_attachment. У нас в кластере было по 18 томов на инстанс, а перед нами выстроились более тысячи новых ресурсов. Перестройка представления – это не просто изменение конфигурации Terraform. Нам предстояло докопаться до состояния Terraform и изменить его видение ресурсов.

Учитывая, что у нас добавлялось более тысячи ресурсов, мы определенно не собирались делать это вручную. Состояние Terraform хранится в формате JSON. Хотя, этот формат стабилен, в документации указано, что «непосредственно редактировать файлы с состоянием не рекомендуется». Нам бы все равно пришлось это делать, но мы хотели быть уверены, что делаем это верно. Мы решили не заниматься обратной разработкой формата JSON, а написали программу, которая использует внутренние элементы Terraform как библиотеку для считывания, изменения и записи. Это было не так просто, поскольку для всех из нас это была первая программа на Go, с которой довелось работать! Но мы считали, что необходимо убедиться: да мы не перепутаем в одну кучу все Terraform-состояния всех инстансов нашей базы данных.

Мы выложили инструмент на GitHub, на случай, если вам захочется поиграть с ним и ощутить себя в нашей шкуре.

Терраформируем аккуратно

Запуск terraform apply – один из тех немногочисленных актов, которым можно всерьез повредить всю корпоративную инфраструктуру. Есть несколько советов, следуя которым, риски можно снизить – и вообще будет не так страшно.

Всегда готовьте план –out и следуйте этому плану

Если запустить terraform plan -out planfile, Terraform запишет план в planfile. Затем можно в точности получить этот план, запустив terraform apply planfile. Таким образом, изменения, которые будут применены в этот момент, в точности соответствуют тем, что выведет вам Terraform в момент планирования. Исключена ситуация, в которой инфраструктура могла бы неожиданно измениться из-за того, что кто-то из коллег откорректировал ее между вашими операциями «спланировать» и «применить».

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

Для перебора изменений сделайте специальную роль IAM «только для чтения»

После запуска terraform plan Terraform обновляет заложенное в него представление вашей архитектуры. Для этого ему необходим всего лишь доступ к вашему облачному провайдеру с правами «только для чтения». Заложив для него такую роль, можно перебрать изменения, внесенные в конфигурацию, и проверить их при помощи terraform plan, даже не рискуя, что неосторожная команда apply перечеркнет вам всю работу, сделанную за день – или за неделю!

Работая с AWS, можно управлять в Terraform ролями IAM и соответствующими правами доступа. Роль в Terraform выглядит так:

resource "aws_iam_role" "terraform-readonly" {
  name = "terraform-readonly"
  path = "/",
  assume_role_policy = "${data.aws_iam_policy_document.assume-terraform-readonly-role-policy.json}"
}

В assume_role_policy просто выводится список пользователей, которые вправе принять эту роль.

Наконец, нужна политика, которая предоставляет доступ «только для чтения» ко всем ресурсам AWS. Amazon любезно предоставляет документ с описанием политик, который можно просто скопировать и вставить – именно таким документом мы и пользовались. Определяем политику aws_iam_policy, ссылающуюся на этот документ:

resource "aws_iam_policy" "terraform-readonly" {
  name = "terraform-readonly"
  path = "/"
  description = "Readonly policy for terraform planning"
  policy = "${file("policies/terraform-readonly.json")}"
}

Затем применяем политику к роли terraform-readonly, добавляя при этом aws_iam_policy_attachment:

resource "aws_iam_policy_attachment" "terraform-readonly-attachment" {
  name = "Terraform read-only attachment"
  roles = ["${aws_iam_role.terraform-readonly.name}"]
  policy_arn = "${aws_iam_policy.terraform-readonly.arn}"
}

Теперь можно воспользоваться методом AssumeRole, относящимся к API Secure Token Service, чтобы получить временные учетные данные, позволяющие лишь запрашивать AWS, но не вносить изменения. Запустив terraform plan, мы обновим состояние Terraform так, чтобы оно отражало актуальное состояние инфраструктуры. Если вы работаете с локальным состоянием, то эта информация будет записываться в файл terraform.tfstate. Если пользуетесь удаленным состоянием, например, в S3, то вашей роли «только для чтения» также потребуется и право на запись – иначе до S3 вам не добраться.

Организовать такую роль оказалось гораздо легче, чем переписывать все состояние Terraform для использования aws_volume_attachment с томами нашей базы данных. Мы знали, что никаких изменений в инфраструктуре AWS не планируется – поменяться должно было лишь ее представление в Terraform. В конце концов, мы совершенно не собирались менять инфраструктуру – зачем же нам такая возможность?

Идеи на будущее

Наша команда растет, и новые сотрудники учатся вносить изменения в инфраструктуру при помощи Terraform. Хочется, чтобы этот процесс был прост и безопасен. Большинство отказов связано с человеческими ошибками и с изменениями в конфигурации, а изменения при помощи Terraform могут быть чреваты и тем, и другим – это жуть, согласитесь.

Например, в небольшой команде легко гарантировать, что в любой момент времени работать с Terraform будет только один человек. В более крупной команде гарантировать этого не получится – остается на это только уповать. Если команда terraform apply будет одновременно запущена с двух узлов, то в результате может получиться ужасная недетерминированная мешанина. В Terraform 0.9 появилась возможность блокировки состояния – она гарантирует, что в любой момент времени может быть применена только одна команда terraform apply.

Еще один участок работы, где так хочется добиться легкости и безопасности – это рецензирование изменений, вносимых в инфраструктуру. На данном этапе при рецензировании мы просто копипастим вывод terraform plan как комментарий к обзору – и, когда вариант будет одобрен, вносим все изменения вручную.

Мы уже приспособились использовать наш инструмент непрерывной интеграции, когда валидируем конфигурацию Terraform. Пока просто запускаем команду terraform validate, и инструмент проверяет, нет ли в коде синтаксических ошибок. Наша следующая задача – приспособить инструмент непрерывной интеграции запускать terraform plan и выводить внесенные в инфраструктуру изменения как комментарий к обзору кода. Система непрерывной интеграции должна автоматически запускать terraform apply, как только изменение будет одобрено. Так мы исключаем один из этапов, который требовалось выполнять вручную, а также обеспечиваем более согласованный контрольный след (историю изменений), прослеживаемый в комментариях. В версии Terraform Enterprise есть такая возможность – так что рекомендуем к ней присмотреться.

Автор: ph_piter

Источник

Поделиться

* - обязательные к заполнению поля