Введение
Мне наконец-то довелось поиграться с гибридными облаками! Нашей команде достался кусок какого-то французского облачного провайдера со стайкой виртуальных машин, и Всевышние, естественно, первым делом попросили нас подключить машины хоть к какому мониторинигу. Ну, чтобы знать, сколько процентов процессора простаивает, сколько памяти осталось, и вообще, не устанавливает ли нам кто втихаря майнинговую ферму. И хотя я раньше упоённо писал и про мониторинг, и про сбор логов, в этой ситуации проще всего показалось не выпентриваться, а подключить этот зоопарк машин напрямую к Azure. Мы всё равно там днями просиживаем. А так, если машины появятся в Azure, то на них можно и нативные мониторинговые средства натравить, и даже с политиками безопасности поиграться.
Сделать это оказалось на удивление просто. Azure Arc — майкрософтскиая платформа для создания гибридных облаков — просто выдаёт bash-скрипт, а тот уже творит магию практически автономно. Единственное, что нужно сделать руками, это создать несколько «принимающих» Azure ресурсов. Но это даже проще, чем bash-скрипт.
В общем, в этом посте я хочу слегка примазаться к апологетам гибридных облаков и показать, как при помощи удобрений и палок Azure Arc, Digital Ocean и Terraform можно создать маленькое гибридное облако. Почему Digital Ocean? Потому что он уже восемь лет хостит мои блоги за 5-6 баксов в месяц, чем я очень доволен. Почему Terraform? Господа, ну мы же не в каменном веке живём! Пора бы уже всё по Infrastructure as a Code делать.
Так что будем начинать.
Шаг uno: Создаём дроплет в Digital Ocean
Digital Ocean (здесь и далее — DO) называет свои виртуальные машины «дроплет», что при желании можно перевести как «помёт». Но, как показал опыт, это никак не влияет на качество машины. Создав всего пару файлов (main.tf
and digitalocean.tf
), добавив в них Terraform провайдеров (digitalocean
для дроплетов, и tls
для создания SSH ключей к оном)у, и, наконец, досыпав сверху немного ресурсов, мы можем создать реально работающую виртуальную машину секунд за сорок. Для людей из Microsoft такие цифры кажутся чёрной магией и поводом сменить работу.
main.tf
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
terraform { required_providers { digitalocean = { source = "digitalocean/digitalocean" version = "~> 2.0" } tls = { source = "hashicorp/tls" version = "4.0.4" } } } provider "digitalocean" { # Pick credentials from env var } provider "tls" { } locals { digital_ocean_region = "tor1" # Toronto } |
digitalocean.tf
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
resource "tls_private_key" "this" { algorithm = "RSA" rsa_bits = 4096 } resource "digitalocean_ssh_key" "this" { name = "Arc provisioner's key" public_key = tls_private_key.this.public_key_openssh } resource "digitalocean_droplet" "this" { image = "ubuntu-22-04-x64" name = "digital-ocean-vm" region = local.digital_ocean_region size = "s-1vcpu-1gb" ssh_keys = [ digitalocean_ssh_key.this.fingerprint, ] } resource "terraform_data" "arc-connection" { triggers_replace = [ digitalocean_droplet.this.id, digitalocean_ssh_key.this.fingerprint, ] connection { type = "ssh" user = "root" private_key = tls_private_key.this.private_key_openssh host = digitalocean_droplet.this.ipv4_address agent = false timeout = "30m" } provisioner "remote-exec" { inline = [ "whoami", ] } } |
Я добавил пароль для доступа к DO в переменную окружения DIGITALOCEAN_ACCESS_TOKEN
, поэтому никаких телодвижений аутентификации в самом Terraform делать не пришлось.
Для тех, кто Terraform видит только по праздникам, я даже могу пояснить, что именно делают те два файла в ответ на команду terraform apply
:
- Создают случайный SSH ключ (ресурс
tls_private_key.this
), - регистрируют сей ключ в DO (
digitalocean_ssh_key.this
), - создают виртуальную машину (
digitalocean_droplet.this
), добавив ссылку на зарегистрированный SSH ключ в её параметры (digitalocean_ssh_key.this.fingerprint
). Наконец, - вызывают
whoami
команду (terraform_data.arc-connection
) на ещё тёплой машине, чтобы подтвердить, что SSH ключ (tls_private_key.this.private_key_openssh
) таки действительно работает. А позже мы заменимwhoami
на что-то более полезное.
В итоге, я действительно запустил terraform apply
, и Наука и Партия сделали именно то, что обещали: DO нарисовал новую виртуальную машину, а вывод Terraform показал, что whoami
вернул root
. Красотень.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
terraform apply -auto-approve # ... # terraform_data.arc-connection: Still creating... [30s elapsed] # terraform_data.arc-connection (remote-exec): Connecting to remote host via SSH... # terraform_data.arc-connection (remote-exec): Host: ********* # terraform_data.arc-connection (remote-exec): User: root # terraform_data.arc-connection (remote-exec): Password: false # terraform_data.arc-connection (remote-exec): Private key: true # terraform_data.arc-connection (remote-exec): Certificate: false # terraform_data.arc-connection (remote-exec): SSH Agent: false # terraform_data.arc-connection (remote-exec): Checking Host Key: false # terraform_data.arc-connection (remote-exec): Target Platform: unix # terraform_data.arc-connection (remote-exec): Connected! # terraform_data.arc-connection (remote-exec): root # terraform_data.arc-connection: Creation complete after 40s [id=ff294411-b6da-ed3c-d803-fa4405d0a6e5] |
Шаг zwei: Подготавливаем скрипт для подключения к Azure Arc
Теперь наступает чуть-чуть мелкой магии. Чтобы подключить машину к Azure Arc, нужно запустить на ней bash скрипт. Azure же настолько любезен, что шаблон этого скрипта выдаст абсолютно бесплатно.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# Add the service principal application ID and secret here servicePrincipalClientId="*********"; servicePrincipalSecret="*********"; export subscriptionId="*********"; export resourceGroup="*********"; export tenantId="*********"; export location="*********"; export authType="principal"; export correlationId="*********"; export cloud="AzureCloud"; # Download the installation package output=$(wget https://aka.ms/azcmagent -O ~/install_linux_azcmagent.sh 2>&1); if [ $? != 0 ]; then wget -qO- --method=PUT --body-data="{\"subscriptionId\":\"$subscriptionId\",\"resourceGroup\":\"$resourceGroup\",\"tenantId\":\"$tenantId\",\"location\":\"$location\",\"correlationId\":\"$correlationId\",\"authType\":\"$authType\",\"operation\":\"onboarding\",\"messageType\":\"DownloadScriptFailed\",\"message\":\"$output\"}" "https://gbl.his.arc.azure.com/log" &> /dev/null || true; fi; echo "$output"; # Install the hybrid agent bash ~/install_linux_azcmagent.sh; # Run connect command sudo azcmagent connect --service-principal-id "$servicePrincipalClientId" --service-principal-secret "$servicePrincipalSecret" --resource-group "$resourceGroup" --tenant-id "$tenantId" --location "$location" --subscription-id "$subscriptionId" --cloud "$cloud" --correlation-id "$correlationId"; |
Вот только шаблон этот нужно ещё дополнить и слегка подредактировать. Во-первых, удалить sudo
с 24-й строки, так как мы и так запускаем скрипт под root. А во-вторых, шаблон очень любит общаться, что в условиях автономного полёта эквивалентно лёгкой форме шизофрении. К счастью, строка export DEBIAN_FRONTEND=noninteractive
в состоянии предотвратить этот душевный недуг.
Наконец, в скрипте-шаблоне есть пару пустых мест, которые надо бы заполнить значениями. А именно:
- Service Principal, он же сервисный аккаунт. С паролем и правами на подключение машин к
Azure
Arc (рольAzure Connected Machine Onboarding
, строки 2 и 3) - Айдишки подписки и тенанта (строки 6 и 8)
- Ресурсная группа и её регион (строки 7 и 9)
- Корреляционная айдишка — случайный GUID (строка 11). Она, может, и в состоянии помочь в дебаггинге, но мне в жизни ни разу не пригодилась.
Есть ещё маленький и скользкий подводный камень: перед любыми танцами с гибридным облаком в нашей Azure подписке (subscription) надо включить соответствующего провайдера — Microsoft.HybridCompute
. По умолчанию он выключен, что провоцирует Azure Arc на весьма странные жалобы на права доступа. Не спрашивайте, откуда я знаю, но эти жалобы гуглятся так себе.
И всё. Теперь можно создать третий файл — arc.tf
, который будет отвечать за Azure сторону вещей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
resource "azurerm_resource_provider_registration" "this" { name = "Microsoft.HybridCompute" } resource "azuread_application" "this" { display_name = "Arc-Onboarding-App" } resource "azuread_service_principal" "this" { application_id = azuread_application.this.application_id } resource "azuread_service_principal_password" "this" { service_principal_id = azuread_service_principal.this.object_id } resource "azurerm_resource_group" "this" { name = "Arc-Machines" location = local.azure_arc_region } data "azurerm_subscription" "this" { } resource "azurerm_role_assignment" "this" { scope = data.azurerm_subscription.this.id role_definition_name = "Azure Connected Machine Onboarding" principal_id = azuread_service_principal.this.object_id } resource "random_uuid" "this" { } locals { arc_onboarding_script = <<EOT export DEBIAN_FRONTEND=noninteractive # Add the service principal application ID and secret here servicePrincipalClientId="${azuread_service_principal.this.application_id}"; servicePrincipalSecret="${azuread_service_principal_password.this.value}"; export subscriptionId="${data.azurerm_subscription.this.subscription_id}"; export resourceGroup="${azurerm_resource_group.this.name}"; export tenantId="${data.azurerm_subscription.this.tenant_id}"; export location="${local.azure_arc_region}"; export authType="principal"; export correlationId="${random_uuid.this.result}"; export cloud="AzureCloud"; # Download the installation package output=$(wget https://aka.ms/azcmagent -O ~/install_linux_azcmagent.sh 2>&1); if [ $? != 0 ]; then wget -qO- --method=PUT --body-data="{\"subscriptionId\":\"$subscriptionId\",\"resourceGroup\":\"$resourceGroup\",\"tenantId\":\"$tenantId\",\"location\":\"$location\",\"correlationId\":\"$correlationId\",\"authType\":\"$authType\",\"operation\":\"onboarding\",\"messageType\":\"DownloadScriptFailed\",\"message\":\"$output\"}" "https://gbl.his.arc.azure.com/log" &> /dev/null || true; fi; echo "$output"; # Install the hybrid agent bash ~/install_linux_azcmagent.sh; # Run connect command azcmagent connect --service-principal-id "$servicePrincipalClientId" --service-principal-secret "$servicePrincipalSecret" --resource-group "$resourceGroup" --tenant-id "$tenantId" --location "$location" --subscription-id "$subscriptionId" --cloud "$cloud" --correlation-id "$correlationId"; EOT } |
Всё, мы почти уже там. Сейчас добавим ещё три новых Terraform провайдера (azuread
, azurerm
, random
) и azure_arc_region
переменную в main.tf
, и terraform apply
снова сотворит чёрную магию. Теперь уже в Azure.
main.tf
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
terraform { required_providers { # ... azurerm = { source = "hashicorp/azurerm" version = "3.69.0" } azuread = { source = "hashicorp/azuread" version = "~> 2.15.0" } random = { source = "hashicorp/random" version = "3.5.1" } } } # ....... provider "azurerm" { # Pick credentials from azure-cli features { resource_group { prevent_deletion_if_contains_resources = false } } } provider "azuread" { # Pick credentials from azure-cli } provider "random" { } locals { digital_ocean_region = "tor1" # Toronto azure_arc_region = "canadacentral" # Toronto } |
Реально сильное колдунство.
Кстати, логины-пароли, как и в случае с Digital Ocean, Azure вытянул из переменных окружения. В продакшeн среде, конечно, я бы был более явным с конфигурацией.
Шаг III: Подключаем Digital Ocean дроплет к Azure Arc
Итак, кульминация сегодняшнего концерта. У нас уже есть виртуальная машина в Digital Ocean. Есть и скрипт подключения оной к Azure Arc. Что нам осталось? Да, скрестить ужа с гадюкой. То есть заменить whoami
в нашей SSH команде (terraform_data.arc-connection
) на ссылку на скрипт. Ну и добавить туда явную зависимость от Microsoft.HybridCompute
провайдера и Azure Connected Machine Onboarding
роли. Ведь подключать виртуальную машину к Azure без прав доступа и с выключенным гибридным облаком, согласитесь, глупо.
digitalocean.tf
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# .... resource "terraform_data" "arc-connection" { # .... depends_on = [ azurerm_role_assignment.this, azurerm_resource_provider_registration.this, ] provisioner "remote-exec" { inline = [ local.arc_onboarding_script, ] } } |
Всё, момент истины. Я удалил уже созданные ресурсы (через terraform destroy
, естественно), чтобы убедиться, что создание всего от начала до конца работает стабильно, и terraform apply -auto-approve
сотворил самое настоящее гибридное облако за несчастные 2 минуты и 8 секунд:
Человеки!
Усилия бесчисленных поколений привели нас к этому моменту. Египтяне построили пирамиды. Греки изобрели логику и ещё некоторые популярные ныне формы досуга. Жители славного города Заславль тоже сделали много хорошего. Просто мы не выпендриваемся. И, как вишенка на торте, как высшая форма разумной мысли, как новая звезда в непрерывно тускнеющей вселенной, наш Терраформ проект создал виртуальную машину в одном облаке и подключил его к другому. Будучи погребённым в цепких объятиях Azure Arc, Digital Ocean дроплет теперь может получить автоматическое управление обновлениями, отслеживание изменений конфигурации, мониторинг, сбор логов, и прочие проклятия зрелого облака.
В следующем посте мы посмотрим на некоторые из этих проклятий. В частности, на мониторинг. Да, частота выдачи мною технических постов скатилась к уровню статистической погрешности, но, думаю, новый пост выйдет быстрее, чем как всегда. Может, в течение пары недель. Или трёх. Максимум месяц-два. Ну не через полгода же! В общем, ждите.