[Terraform + SaltStack] Готуємо PrestoDB кластер в скороварці (Частина #1)

Що тут цікавого?
image
Рецепт приготування смачного і корисного PrestoDB кластера використовуючи скороварку на базі Terraform і SaltStack в публічному хмарі AWS. Розглянемо докладно нюанси підготовки до роботи самої скороварки, необхідні кроки для правильного приготування страви і, звичайно, трошки розповімо про споживанні готового страви. Цю частину можна використовувати як навчальний матеріал з Terraform.

отже, приступимо:
Інгредієнти для рецепта
  • Terraform — 1 шт.
  • SaltStack — 1 майстер і 1+ миньен
  • PrestoDB — 1 координатор і 1+ воркер
  • AWS акаунт — 1 шт.
  • Кмітливість та напилок — за смаком


Розглянемо інгредієнти докладніше: (без правил їх приготування)
1. Terraform — Чудовий інструмент від хлопців з Hashicorp (вони ж зробили такі, вельми корисні, штуки як Vagrant, Consul, Packer, Vault та ін) використовується для створення і модифікації інфраструктур в різних хмарних і не тільки оточеннях.
2. SaltStack — Інструмент для автоматизованого конфігурації і налаштування серверів. Ваш покірний слуга вже писав про це тут і тут.
3. PrestoDB — Надбудова над Big Data провайдерами для можливості робити запити до них рідною і зрозумілою SQL. Розроблена хлопцями з Facebook, які перевели її в статус OSS за що їм величезне спасибі.
4. AWS або будь-яке інше публічне/приватне хмара, наприклад: GCE або OpenStack) зі списку підтримуваних Terraform в якому буде надалі працювати наш PrestoDB кластер. Ми будемо використовувати AWS т. к. він найбільш поширений (серед public cloud платформ) і зрозумілий багатьом маси без додаткових пояснень.
5. У статті будуть описані лише базові принципи роботи зв'язки зазначених продуктів, і деякі хитрощі для полегшення процесу, але я не буду детально зупинятися на нюансах роботи того чи іншого компонента — по кожному з них в принципі можна написати книгу. Тому адаптувати зазначені прийоми використовуючи голову дуже вітається. І ще — не пишіть у коментарях, що щось не налаштоване оптимально (зокрема PrestoDB) — це не та мета, яку я переслідую.

Готуємо скороварку!
У будь-якому кулінарному рецепті є замовчування про те, що сковорідки і каструлі вже готові до приготування, але в нашому випадку правильна підготовка скороварки (Terraform+SaltStack) є майже на 80% запорукою успішного приготування страви.
Отже, почнемо з Terraform. Ну є ж CloudFormation для AWS або SaltCloud від творців SaltStack так чому ж обраний саме Terraform? Основна фішка Terraform в його простоті і зрозумілою DSL — для створення инстанса (або 10ти) необхідно і достатньо такого опису (маємо на увазі Terraform викачаний і знаходиться в межах $PATH):
provider "aws" {
access_key = "XXXXXXXXXXXXXXXXXXXXX" # AWS IAM key 
secret_key = "******************************************" # AWS IAM secret
region = "us-east-1" # region used to create resources
}

resource "aws_instance" "example_inst" {
ami = "ami-6d1c2007" # CentOS 7 AMI located in US-East-1
instance_type = "t2.medium" 
count = "1" # or "10" can be used for parallel creation
vpc_security_group_ids = [ "default-sg" ] # some security group with at least 22 port opened 
key_name = "secure_key" # pre created AWS E2 key pair
subnet_id = "sub-abcdef123" # AWS VPC subnet 
}

і простий послідовності команд:

terraform plan
 
terraform apply
 

описова частина цілком зрозуміла і, мені здається, не вимагає пояснень для тих, хто знайомий з AWS. Детальніше про доступні AWS ресурси тут. Звичайно ж маємо на увазі, що AWS аккаунт, ключі якого зазначені в Terraform конфігурації, має привілеї на створення необхідних ресурсів.
Власне найцікавіше криється в виклики самого Terraform — terraform plan — робить «прикидку» того, що необхідно зробити з останнього стану (в нашому прикладі — треба створити новий інстанси) і показує які ресурси будуть створені, видалені або модифіковані, apply — власне запустить процес створення запланованих ресурсів. У разі якщо Terraform вже запускався і Ви змінили конфігурацію (скажімо, додали инстансов) на етапі планування буде показано які відсутні ресурси будуть створені і apply може створити відсутні.

terraform destroy
 

допоможе повністю прибрати всі ресурси створені за допомогою Terraform (враховуються перебувають в поточному каталозі .tfstate файли, що зберігають опис стан створеної інфраструктури).
Важливий момент, про який не варто забувати — terraform в більшості випадків не буде модифікувати вже наявні ресурси — він просто видалить старі і пересоздаст заново. Це означає, наприклад, що якщо ви створили інстанси типу t2.medium і потім змінили конфігурацію вказавши новий тип для инстанса, скажімо m4.xlarge, то при запуску apply Terraform спочатку знищить раніше створений, а потім створить новий. Це може здатися дивним для користувачів AWS (можна було зупинити інстанси, змінити його тип і запустити заново не втративши при цьому даних на диску) але це зроблено для надання однаково прогнозованого поведінки на всіх платформах. І ще одне: Terraform не вміє (і не має вміти за своєю природою) контролювати ресурси під час їх життєвого циклу — це означає, що Terraform не надає команди типу stop або reboot для створених з його допомогою инстансов — Ви повинні використовувати інші засоби для управління створеною інфраструктурою.
Terraform надає прекрасний набір функціоналу доступний у своєму DSL — це змінні (https://www.terraform.io/docs/configuration/variables.html), интерполяторы (необхідні для итерирования, модифікації змінних), модулі і т. д. Ось один з прикладів використання всього цього:
# Cluster shortname
variable cluster_name { default = "example-presto" }

# Count of nodes in cluster
variable cluster_size { default = 3 }

# Default owner for all nodes
variable cluster_owner { default = "user@example.com" }

# Default AWS AMI to use for cluster provisioning
variable cluster_node_ami { default = "ami-6d1c2007" }

# Default AWS type to use for cluster provisioning
variable cluster_node_type { default = "t2.large" }

# Defualt VPC subnet
variable cluster_vpc_subnet { default = "subnet-da628fad" }

# Default Security group to apply to instances
variable cluster_sg { default = "sg-xxxxxxx" }

# Default KeyPair to use for provisioning
variable cluster_keyname { default = "secure_key" }

# Clurter worker nodes
resource "aws_instance" "worker_nodes" {

ami = "${var.cluster_node_ami}"
instance_type = "${var.cluster_node_type}"
count = "${var.cluster_size - 1}" # one node will be used for coordinator
vpc_security_group_ids = [ "${var.cluster_sg}" ]
key_name = "${var.cluster_keyname}"
subnet_id = "${var.cluster_vpc_subnet}"

disable_api_termination = true

tags {
Name = "${var.cluster_name}-cluster-worker-${format("%02d", count.index+1)}"
Owner = "${var.cluster_owner}"
Purpose = "PrestoDB cluster '${var.cluster_name}' node ${format("%02d", count.index+1)}"
}
}


тут приклад використання змінних, арифметичних операцій над ними, інтерполяція за допомогою format, використання індексу поточного елемента (якщо створюється кілька однотипних инстансов), а також тегування ресурсів.
Але лише створення/знищення инстансов не достатньо — потрібно їх ще якось ініціалізувати (скопіювати файли, встановити і налаштувати специфічний софт, оновити систему, провести конфігурацію кластера і т. д.) для цього Terraform вводить поняття Provisioners. До основних з них відносяться: file, remote-exec, chef і null-resource. Типовими операціями є копіювання файлів і запуск скриптів на віддаленому инстансе.
Ось попередній приклад з включеними операціями провіженінга:
# Localy stored SSH private key filename
variable cluster_keyfile { default = "~/.ssh/secure_key.pem" }

# Clurter worker nodes
resource "aws_instance" "worker_nodes" {

ami = "${var.cluster_node_ami}"
instance_type = "${var.cluster_node_type}"
count = "${var.cluster_size - 1}" # one node will be used for coordinator
vpc_security_group_ids = [ "${var.cluster_sg}" ]
key_name = "${var.cluster_keyname}"
subnet_id = "${var.cluster_vpc_subnet}"

disable_api_termination = true

tags {
Name = "${var.cluster_name}-cluster-worker-${format("%02d", count.index+1)}"
Owner = "${var.cluster_owner}"
Purpose = "PrestoDB cluster '${var.cluster_name}' node ${format("%02d", count.index+1)}"
}

# Copy bootstrap script
provisioner "file" {
source = "bootstrap-script.sh"
destination = "/tmp/bootstrap-script.sh"
connection {
type = "ssh"
user = "centos"
private_key = "${file("${var.cluster_keyfile}")}"
}
}

# Running provisioning commands
provisioner "remote-exec" {
inline = [
"yum -y update",
"sudo sh /tmp/bootstrap-script.sh"
]
connection {
type = "ssh"
user = "centos"
private_key = "${file("${var.cluster_keyfile}")}"
}
}
}


Основне зауваження — зазначення інформації про підключення до віддаленого хосту — для AWS це найчастіше доступ по ключу — тому Ви повинні вказати де саме цей ключ лежить (для зручності була введена змінна). Зверніть увагу, що атрибут private_key в секції connection не може приймати шлях до файлу (тільки ключ текстом) — замість цього використовується інтерполятор $file{} який відкриває файл на диску і повертає його вміст.
Ми дісталися до створення простого кластера складається з декількох инстансов (не будемо вдаватися в подробиці вмісту файлу bootstrap-script.sh — покладемо, що там прописана установка необхідного софта). Давайте розглянемо, як у нашій скороварці робити кластера з виділеним майстром. Загалом будемо вважати, що worker ноди кластера повинні знати, де знаходиться master нода для того, щоб в ній зареєструватися і надалі отримувати завдання (давайте залишимо всякі смакоту типу Raft і Gossip протоколи для встановлення майстра і поширення інформації в кластері для інших статей) — для простоти — покладемо worker повинен знати ip адреса master-а. Як це реалізувати в Terraform? Для початку треба створити окремий інстанси для master-а:
resource "aws_instance" "master_node" {
ami = "${var.cluster_node_ami}"
instance_type = "${var.cluster_node_type}"
count = "1"

<...skipped...>

provisioners {
<...skipped...>
}
}


потім, додамо залежність в worker ноди:
# Clurter worker nodes
resource "aws_instance" "worker_nodes" {
depends_on = ["aws_instance.master_node"] # dependency from master node introduced
ami = "${var.cluster_node_ami}"
instance_type = "${var.cluster_node_type}"
count = "${var.cluster_size - 1}" # one node will be used for coordinator

<...skipped...>
}

модифікатор ресурсу depends_on можна використовувати для визначення порядку виконання завдань по створенню инфрастуктур — Terraform не буде створювати worker ноди до тих пір, поки не буде повністю створена master нода. Як видно з прикладу як залежності(тей) можна вказувати список конструйований типу ресурсу із зазначенням через точку його імені. У AWS Ви можете створювати не тільки инстансы, але і VPC, мережі і т. д. — їх потрібно буде вказувати як залежності використовують для VPC ресурсів — це буде гарантувати правильний порядок створення.
Але, продовжимо з передачею адреси master ноди всім worker нодам. Для цього Terraform надає механізм посилань на раннє створені ресурси — тобто ви можете просто отримати інформацію про ip адресу master ноди в описі worker-а:
# Clurter worker nodes
resource "aws_instance" "worker_nodes" {
depends_on = ["aws_instance.master_node"] # dependency from master node introduced
ami = "${var.cluster_node_ami}"
instance_type = "${var.cluster_node_type}"
count = "${var.cluster_size - 1}" # one node will be used for coordinator

<...skipped...>

# Running provisioning commands
provisioner "remote-exec" {
inline = [
"yum -y update",
"sudo sh /tmp/bootstrap-script.sh ${aws_instance.master_node.private_ip}" # master-ip passed to script
]
connection {
type = "ssh"
user = "centos"
private_key = "${file("${var.cluster_keyfile}")}"
}
}
}


тобто за допомогою змінних виду ${aws_instance.master_node.private_ip} можна отримати доступ до майже будь-якої інформації про ресурс. В даному прикладі маємо на увазі, що bootstrap-script.sh може приймати як параметр адресу master ноди і використовувати його надалі для внутрішнього конфігурування.
Іноді не досить і таких зв'язків, — приміром, необхідно викликати якісь скрипти на стороні master ноди після підключення worker нсд (взяти ключі, запустити init завдання на worker ноди тощо) для цього є механізм який Terraform називається null-resource — це fake ресурс який з допомогою механізму залежностей (див. вище) може бути створений після того, як будуть створені всі master і worker ноди. Ось приклад такого ресурсу:
resource "null_resource" "cluster_provision" {
depends_on = [
"aws_instance.master_node",
"aws_instance.worker_nodes"
]

# Changes to any instance of the workers cluster nodes master or node requires re-provisioning
triggers {
cluster_instance_ids = "${aws_instance.master_node.id},${join(",", aws_instance.worker_nodes.*.id)}"
}

# Bootstrap script can only run on master node
connection {
host = "${aws_instance.coordinator_node.private_ip}"
type = "ssh"
user = "centos"
private_key = "${file("${var.cluster_keyfile}")}"
}

provisioner "remote-exec" { 
inline = [
<... some after-provision scripts calls on master node...>
]
}
}


невелике пояснення:
1. depends_on — ми вказуємо список тих ресурсів, які повинні бути заздалегідь готові.
2. triggers — формуємо стоку (id всіх инстансов через кому, в нашому випадку) зміна якої викличе виконання всіх зазначених у цьому ресурсі провизионеров.
3. вказуємо на якому инстансе потрібно виконати скрипти провизионинга зазначені в цьому ресурсі в секції connection.

Якщо Вам потрібно зробити кілька кроків на різних серверах — створюйте кілька null-resource із зазначенням необхідних залежностей.

В цілому, описаного буде достатньо для створення досить складних инфрастурктур з допомогою Terraform.
Ось ще кілька важливих порад для тих, хто любить вчитися на чужих помилках:
1. Не забувайте дбайливо зберігати .tfstate файли у яких Terraform зберігає останній стан створеної инфрастуркутуры (до всього — це json файл, який можна використовувати як вичерпне джерело інформації про створені ресурси)
2. Не міняйте створені за допомогою Terraform ресурси вручну (використовуючи консолі управління самим сервісами та інші зовнішні фреймворки) — при наступному запуску plan & apply ви отримаєте нарощування не відповідає поточному опису ресурсу, що буде досить несподівано і часто плачевно.
3. Намагайтеся спочатку відтестувати свої конфігурації на невеликих за розміром інстансах / невеликій їх кількості, — багато помилок дуже важко відловити в ході створення конфігурацій, а вбудований в Terraform валідатор покаже тільки синтаксичні помилки (і то не всі).

У другій частині розглянемо продовження приготування до роботи скороварки — опишемо як покласти на верх створеної інфраструктури SaltStack master + minions щоб поставити PrestoDB.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.