Керування Apache CloudStack через Terraform: від ключів API до кластера Kubernetes
Блог WebDisk · категорія: Публічна хмара · час читання: ~26 хвилин
Коротко:- Apache CloudStack — це оркестратор хмари IaaS з відкритим кодом: одне API над серверами KVM, сховищем і мережею. Його поняття (зона, offering, мережа, VPC, проєкт) лягають майже 1:1 на ресурси Terraform.- Конфігурація провайдера — це три значення: адреса API, ключ і секрет. Решта — звичайний Terraform:plan,apply, стан, що зберігається віддалено в S3, без жодної додаткової служби для блокувань.- Показуємо повний приклад: кластер Kubernetes на CloudStack, описаний кодом — від VPC і правил ACL до готовогоkubeconfigі компонентів, розгорнутих через Helm. >Не працюєте з терміналом? Пропустіть блоки коду — перша половина тексту описує сам CloudStack і його архітектуру та читається без них.
Настав понеділок, надходить запит: «потрібне таке саме середовище, як продакшн, тільки менше». У хмарі, яку клікають руками, це прохання означає два дні роботи, список у нотатнику й тиху впевненість, що щось усе одно відрізнятиметься — бо хтось забуде про одне правило файрвола або обере інший дисковий offering. У хмарі, описаній кодом, воно означає скопіювати каталог, змінити чотири числа у файлі зі змінними й запустити pipeline.
Ця стаття — про те, як дійти до другого стану на Apache CloudStack — платформі, на якій працює наша публічна хмара. Почнемо з докладного опису самого CloudStack: що це таке, з чого складається і якими поняттями оперує — бо без цього Terraform є лише синтаксисом. Потім перейдемо до конфігурації провайдера, перших ресурсів і повного, реального прикладу: кластера Kubernetes, який ми піднімаємо в себе цілком з Terraform. Наприкінці — список пасток, які коштували нам часу, щоб вам не довелося відкривати їх самотужки. Текст призначений для двох груп: адміністраторів, які лише знайомляться з CloudStack і хочуть зрозуміти його поняття, та команд, які знають Terraform з гіперскейлерів і шукають відповідники у хмарі з відкритим кодом.
Що таке Apache CloudStack насправді?
Apache CloudStack — це платформа з відкритим кодом для побудови хмар IaaS — програмне забезпечення, яке перетворює шафи, повні серверів, масивів і комутаторів, на хмару із самообслуговуваною панеллю та API. Користувач натискає «створити машину», а CloudStack вирішує, на якому фізичному хості її запустити, створює її диск у сховищі, під’єднує до потрібної мережі, призначає IP-адресу й повертає консоль у браузері. Це той шар, який у гіперскейлерів називається «control plane» — тільки тут ви можете його завантажити, прочитати й запустити в себе.
Проєкт має довгу історію: він виник як комерційний продукт компанії Cloud.com, потрапив до Citrix, а 2012 року був переданий Apache Software Foundation, де 2013 року став проєктом найвищого рівня. Відтоді він розвивається як класичний проєкт ASF — з публічним списком розсилки, голосуваннями за релізи та ліцензією Apache 2.0, без «enterprise-версії» і без пастки подвійного ліцензування. Він живить публічні хмари, хмари телеком-операторів і приватні середовища в компаніях, які не хочуть віддавати контроль над шаром віртуалізації.
Варто одразу помістити CloudStack у контекст, бо найчастіше питання звучить так: «а чим це відрізняється від OpenStack?». Найкоротше: філософією складності. OpenStack — це набір кількох десятків проєктів, що взаємодіють між собою (Nova, Neutron, Cinder, Keystone, Glance…), кожен з яких має власну базу, власне API і власний життєвий цикл — це дає величезну гнучкість ціною чималої команди, яка утримує саму платформу. CloudStack — це один застосунок на Java (management server) і одна база MySQL, у якій міститься вся модель хмари. Встановлення вміщується в кілька пакетів, оновлення — це заміна пакета й міграція схеми бази. За цю простоту платять меншою модульністю — складніше замінити окрему підсистему на власну. Для оператора середнього розміру це зазвичай вигідний курс обміну.
CloudStack сам не віртуалізує — він керує гіпервізорами (гіпервізор — це програмне забезпечення, яке запускає віртуальні машини на фізичному сервері). Підтримує KVM (найпопулярніший вибір і наш), VMware vSphere, XCP-ng/XenServer та кілька менш важливих сьогодні. Зі сховищем він спілкується мовою NFS, iSCSI, локальних дисків і — що для нас ключове — Ceph RBD.
Ієрархія інфраструктури: зона, pod, кластер, хост
Щоб розмовляти з CloudStack, треба прийняти його спосіб поділу світу. Шари йдуть від найбільшого до найменшого:
- Зона (zone) — найбільша одиниця, на практиці один центр обробки даних або одна зона доступності. Зона має власну публічну мережу, власний secondary storage і власний набір системних машин. Це перше, що ви вказуєте, створюючи будь-що.
- Pod — зазвичай одна шафа або ряд шаф, на практиці один широкомовний домен для мережі керування.
- Кластер (cluster) — група хостів з однаковим типом гіпервізора, які поділяють той самий primary storage. Це межа, у якій CloudStack може вільно мігрувати машини наживо.
- Хост — окремий фізичний сервер із гіпервізором і агентом CloudStack.
До цього — два види дискового простору, які не варто плутати:
- Primary storage — тут живуть диски працюючих машин. Має бути швидким і видимим для хостів у кластері. У нас це пули Ceph RBD (окремий на NVMe, окремий на ємнісні диски).
- Secondary storage — сховище шаблонів систем, ISO-образів, снапшотів та експортованих томів. Не мусить бути швидким, мусить бути містким і доступним у межах усієї зони. Класично — NFS.
Системні машини, тобто хмара, яка обслуговує сама себе
Одна з характерніших рис CloudStack: частину власної функціональності він реалізує віртуальними машинами, які сам собі створює. Тут немає великого централізованого програмного маршрутизатора чи окремого кластера мережевих служб — є жменька невеликих VM, по одній на задачу:
- Virtual Router (
r-*-VM) — серце мережі тенанта, тобто окремого клієнта чи проєкту, ізольованого від решти. Для кожної ізольованої мережі або VPC CloudStack запускає маршрутизатор, який надає DHCP, DNS, SNAT (підміну адреси джерела, завдяки якій машини з приватної мережі виходять в інтернет спільною публічною адресою), перенаправлення портів, файрвол, load balancer і VPN site-to-site. Його можна замовити в резервованому варіанті: пара маршрутизаторів ділить одну адресу протоколом VRRP, тож коли активний падає, другий перебирає її за кілька секунд. - Secondary Storage VM (
s-*-VM) — обслуговує завантаження та реєстрацію шаблонів, вивантаження ISO, снапшоти й копіювання образів між зонами. - Console Proxy (
v-*-VM) — тунелює VNC з хостів у браузер, тобто саме завдяки їй працює кнопка «Консоль» у панелі.
Така архітектура має два важливі практичні наслідки. По-перше: мережеві служби масштабуються разом із кількістю мереж, бо кожна отримує власний маршрутизатор — немає одного вузького місця на всю хмару. По-друге, і це чесне попередження: load balancer у CloudStack живе всередині Virtual Router і є балансувальником четвертого рівня (TCP, з так званими липкими сесіями, тобто прив’язкою клієнта до одного сервера на час сесії, та — починаючи з CloudStack 4.22 — з термінацією SSL на самому маршрутизаторі; у попередніх випусках розвантаження SSL вимагало зовнішнього пристрою). Це не відповідник ALB з маршрутизацією за шляхами й заголовками — справжній ingress сьомого рівня треба поставити самому, наприклад у Kubernetes. Пропускну здатність такого LB обмежує один маршрутизатор, тож за великого трафіку це планують інакше.
Ієрархія обліку: домен, обліковий запис, користувач, проєкт
Паралельно до інфраструктури CloudStack веде другу ієрархію — ту, яка вирішує, хто що бачить і за що платить:
- Домен — деревоподібна організаційна структура. Домен
ROOTмістить усе, під ним можуть висіти домени партнерів, а під ними — домени клієнтів, кожен із власними лімітами. - Обліковий запис (account) — власник ресурсів і межа розрахунків. Обліковий запис може мати кількох користувачів.
- Користувач (user) — конкретна особа або інтеграція; саме на рівні користувача генеруються ключі API.
- Проєкт (project) — контейнер для ресурсів, спільних для багатьох облікових записів, зі спільним лімітом і спільним рахунком.
Проєкти для Terraform особливо важливі, бо майже кожен ресурс приймає параметр project — і його пропуск означає, що машина опиниться у приватному просторі облікового запису замість спільного. Це одна з найчастіших причин «чому я не бачу того, що щойно створив».
Offering-и: меню, яке складає не користувач
Тут CloudStack виразно відрізняється від AWS, і це та різниця, об яку спотикається кожен, хто приходить від гіперскейлерів. У CloudStack ви не описуєте в Terraform параметри машини — ви обираєте позицію з меню, яке склав оператор хмари. Це меню — offering-и:
- Service offering — обчислювальний профіль: кількість vCPU, частота, RAM, можливі ліміти IOPS (кількості дискових операцій за секунду) і теги хоста. Може бути жорстким (як
t3.medium) або customized — тоді ви самі задаєте параметри під час створення машини, у межах, встановлених оператором. - Disk offering — профіль тому: розмір (або «custom»), тип сховища, гарантії IOPS/пропускної здатності.
- Network offering — найцікавіший з них: визначає, які служби надає мережа. Чи має вона власний маршрутизатор із SNAT, чи має DHCP і DNS, чи надає load balancer, файрвол, VPN, чи працює в резервованому режимі. Зміна network offering змінює можливості мережі, а не лише її «розмір».
- VPC offering — те саме поверхом вище, для цілої приватної хмари.
Практичний висновок для Terraform: у коді ви посилаєтеся на offering-и за назвою або за UUID, а не описуєте їхні параметри. Якщо offering має тип «customized», конкретні значення потрапляють у поле details.
Мережа: ізольована, спільна і VPC
Мережева модель CloudStack виглядає так:
- Ізольована мережа (isolated) — приватна мережа одного облікового запису чи проєкту, з власним VLAN або VXLAN (це два способи виділити окрему мережу на спільній кабельній інфраструктурі; VXLAN новіший і знімає притаманний VLAN ліміт близько чотирьох тисяч мереж) і власним Virtual Router. Маршрутизатор робить SNAT в інтернет, а вхідний трафік ви пропускаєте через перенаправлення портів, static NAT або правила LB. Файрвол працює на публічній адресі, він зі збереженням стану і за замовчуванням закритий.
- Спільна мережа (shared) — спільна мережа для багатьох облікових записів, зазвичай із пулом адрес, які роздає DHCP. Без власного маршрутизатора на тенанта.
- VPC — приватна хмара з багатьма тірами (підмережами), спільним маршрутизатором і списками ACL між тірами. Відповідник VPC з AWS, з однією істотною відмінністю: правила ACL у CloudStack працюють зі збереженням стану, тож відповідь на дозволене з’єднання повертається без окремого правила; ingress і egress нумеруються та налаштовуються окремо. Це той варіант, який ми використовуємо для всього серйознішого.
- Групи безпеки (security groups) — фільтр трафіку, що прикріплюється до самих машин, а не до мережі. Працюють у зонах типу «basic», а також у зонах «advanced», якщо оператор увімкнув їх під час створення зони — тоді вони ізолюють машини в одній спільній мережі на всю зону (на KVM; на XenServer/XCP-ng лише за мережі в режимі мосту). Їх не поєднують ані з ізольованими мережами, ані з VPC — там роль фільтра виконують файрвол на публічній адресі та списки ACL.
API: одне, підписуване, асинхронне
Уся хмара виставляє одне HTTP API за адресою /client/api. Виклики підписуються алгоритмом HMAC-SHA1 парою ключ API + секрет, а відповідь ви отримуєте у JSON або XML. Вебпанель не має жодних «приватних» ендпоінтів — вона клікає точно те саме API, що й ви.
Друга риса, яку треба знати, перш ніж братися за автоматизацію: більшість операцій, що змінюють стан, є асинхронними. deployVirtualMachine не повертає готової машини — повертає ідентифікатор завдання, поступ якого ви опитуєте через queryAsyncJobResult. Провайдер Terraform робить це за вас, але це пояснює, чому деякі операції тривають, чому з’являється «eventual consistency» і чому іноді треба явно дочекатися готовності служби (повернемося до цього на прикладі з Kubernetes).
Для ручної роботи з цим API служить офіційний клієнт CloudMonkey (cmk) — чудовий для дослідження й для витягування UUID, які потім вставите в Terraform:
# перелік зон і обчислювальних offering-ів — корисне під час написання першого модуляcmk list zones filter=id,namecmk list serviceofferings filter=id,name,cpunumber,memorycmk list networkofferings filter=id,name,state
Чому описувати CloudStack кодом, а не клацати в панелі?
Панель CloudStack повнофункціональна — у ній можна зробити все. Проблема не в можливостях, а в тому, чого панель за своєю природою не забезпечує:
- Відтворюваність. Тестове середовище «таке саме, як продакшн», побудоване вручну, ніколи не буває таким самим. Побудоване з того самого модуля з іншим файлом змінних — буває.
- Перегляд змін перед виконанням.
terraform planпоказує різницю між описаним і фактичним станом, перш ніж будь-що станеться. У панелі відповідником є обачність оператора. - Історія та відповідальність. Хто додав правило ACL і навіщо? У репозиторії це один
git blame. У панелі — щонайбільше запис у журналі подій без обґрунтування. - Виявлення дрейфу. Хтось змінив щось вручну «на хвилинку»? Найближчий
planце покаже. - Відновлення після катастрофи. Код — найдостовірніша документація архітектури: та, яка напевно є актуальною, бо саме з неї постало середовище.
Ціна реальна, і варто її знати: код треба супроводжувати, стан треба захищати, а перше впровадження займе більше часу, ніж клацання в панелі. Віддача приходить із другим середовищем і з першою аварією.
Як налаштувати Terraform з CloudStack?
Налаштування — це чотири кроки: ключі API, провайдер, свідомий вибір його випуску та перенесення стану на віддалений backend. Три з них виконуєте раз на проєкт — і можна про них забути. Четвертий — вибір провайдера — це рішення, до якого повертаються найчастіше, тож приділимо йому найбільше місця.
Крок 1: ключі API
Terraform автентифікується ключами користувача CloudStack. Генеруєте їх у панелі: Accounts → виберіть обліковий запис → Users → виберіть користувача → Generate Keys, після чого копіюєте API Key і Secret Key. Секрет ви побачите повністю лише під час генерування — потім панель його маскує.
Два правила, яких варто дотримуватися від першого дня:
- Окремий користувач для автоматизації. Не використовуйте ключі людини. Технічний обліковий запис легше відібрати, ротувати та відстежити в журналі подій.
- Найменші потрібні права. Якщо Terraform має керувати одним проєктом, не давайте йому ролі адміністратора домену.
Крок 2: провайдер і змінні
Конфігурація провайдера — це буквально три значення. Ключове те, щоб два з них ніколи не потрапили до репозиторію:
# providers.tfterraform { required_version = "~> 1.10" required_providers { cloudstack = { source = "cloudstack/cloudstack" version = "0.6.0" } }}provider "cloudstack" { api_url = var.cloudstack_api_url api_key = var.cloudstack_api_key secret_key = var.cloudstack_secret_key}
# variables.tfvariable "cloudstack_api_url" { description = "Adres API CloudStacka, np. https://panel.example.com/client/api" type = string}variable "cloudstack_api_key" { description = "Klucz API użytkownika technicznego" type = string sensitive = true # не показуватиметься в логах plan/apply}variable "cloudstack_secret_key" { description = "Sekret użytkownika technicznego" type = string sensitive = true}
Значення передаєте змінними середовища — Terraform читає кожну змінну з префіксом TF_VAR_:
export TF_VAR_cloudstack_api_url="https://panel.example.com/client/api"export TF_VAR_cloudstack_api_key="$(pass show cloud/terraform/api-key)"export TF_VAR_cloudstack_secret_key="$(pass show cloud/terraform/secret-key)"terraform initterraform plan
У pipeline ті самі три значення вставляєте як замасковані змінні CI/CD. Файл *.tfvars із секретами — це найчастіший спосіб, у який ключі випадково потрапляють до git, тож краще просто його не створювати.
Крок 3: версія провайдера має значення
Це місце, де документація буває оптимістичною, тож скажемо прямо, як є. Офіційний провайдер cloudstack/cloudstack розвивається в репозиторії Apache і є правильною точкою старту — тільки в ритмі, до якого треба звикнути: стабільні випуски з’являються що півтора-два роки (0.4.0 — лютий 2022, 0.5.0 — квітень 2024, 0.6.0 — жовтень 2025). Останній стабільний випуск у реєстрі — це 0.6.0 від 27 жовтня 2025 року, а гілка 0.7.0 поки що є там винятково кандидатом на випуск (0.7.0-rc1 від 19 серпня 2026, опублікований незадовго до появи цього тексту; станом на серпень 2026). Через таку перерву в головній гілці репозиторію збирається запас ресурсів і атрибутів, яких стабільний випуск ще не має. Практичний висновок: перш ніж вирішити, що в провайдері чогось бракує, зазирніть до CHANGELOG — функція, якої не було рік тому, уже встигла ввійти до стабільного випуску.
Добра ілюстрація — атрибут cidrlist у ресурсі cloudstack_loadbalancer_rule: можливість обмежити списком адрес, хто взагалі може достукатися до правила load balancer. У випуску 0.5.0 його ще не було; до головної гілки він увійшов наприкінці серпня 2025 і є в стабільному 0.6.0. Мораль проста й повторювана: прогалина, яку ви обходили рік тому, сьогодні буває вже закрита в стабільному випуску — варто перевірити, перш ніж будувати обхідний шлях, який доведеться супроводжувати.
Практичні рекомендації:
- Стартуйте з офіційного
cloudstack/cloudstackіз простору Apache — саме він є еталоном для документації та прикладів, зокрема тих, що в цій статті. - Провайдер — це залежність із найвищими правами. Це програма, яку ви запускаєте на своєму ноутбуці та на CI runner: з ключами API до всієї хмари, зі значеннями атрибутів, позначених як
sensitive, і в тому самому середовищі, де лежать облікові дані до bucket зі станом. Ставтеся до нього як до будь-якої іншої залежності такого масштабу: беріть з офіційного простору в реєстрі, а якщо політика компанії цього вимагає — збирайте самі з перевіреного джерела й роздавайте з локального дзеркала (filesystem_mirror). Але усвідомлюйте межу:.terraform.lock.hclзахищає від тихої підміни бінарника постфактум, а не від коду, який був поганим від початку. - Фіксуйте точну версію (
version = "0.6.0", а не>= 0.6) і комітьте файл.terraform.lock.hcl. Хмара, яка «сама змінилася», бо pipeline завантажив новішого провайдера, — це дуже неприємний клас аварії. - OpenTofu працює так само. У 2023 році Terraform перейшов з ліцензії open source на BUSL (Business Source License): код і далі відкритий, а внутрішнє використання — зокрема й комерційне — безкоштовне, але формально це вже не open source, бо не можна пропонувати Terraform як послугу, конкурентну до платних продуктів HashiCorp. У відповідь постав форк під Linux Foundation; провайдери CloudStack — це звичайні плагіни, і їх підтримують обидва інструменти. Якщо ліцензія BUSL є для вас проблемою — найчастіше тому, що політика компанії допускає винятково ліцензії open source — заміна
terraformнаtofuв командах це зазвичай уся міграція.
Крок 4: стан (state) — і приємна несподіванка для операторів CloudStack
Terraform тримає у файлі стану відображення того, що створив. Тримати його локально працює рівно до моменту, коли друга людина або pipeline зроблять apply — потім починається драма. Тож стан іде до віддаленого backend, а тут оператор CloudStack у комфортній ситуації, бо майже завжди має під рукою сховище, сумісне з S3.
Донедавна backend s3 вимагав для блокування стану таблиці DynamoDB — тобто послуги, якої поза AWS просто немає. Від Terraform 1.10 існує нативне блокування, засноване на умовних записах у S3 (use_lockfile = true), а в 1.11 варіант із DynamoDB позначено як застарілий. Для кожного, хто працює на Ceph RGW, MinIO чи іншому S3, це означає, що повноцінний backend зі спільним блокуванням — це сьогодні кільканадцять рядків конфігурації:
# backend.tfterraform { backend "s3" { bucket = "terraform-states" key = "cloudstack/prod.tfstate" endpoints = { s3 = "https://s3.example.com" } region = "us-east-1" # формально обов'язковий; для S3 поза AWS не має значення # вимикаємо валідації, властиві виключно для AWS skip_credentials_validation = true skip_region_validation = true skip_requesting_account_id = true skip_s3_checksum = true use_lockfile = true # нативне блокування в S3, без DynamoDB (Terraform >= 1.10) }}
Ключі до bucket передаєте під час ініціалізації, щоб не записувати їх у репозиторії:
terraform init \ -backend-config="access_key=$S3_ACCESS_KEY" \ -backend-config="secret_key=$S3_SECRET_KEY"
Якщо ви користуєтеся Object Storage у WebDisk, то маєте готовий backend для Terraform — це той самий bucket і ті самі ключі, які використовуєте для всього іншого.
Увага — про це легко забути: файл стану містить значення атрибутів у відкритому вигляді, зокрема й ті, що позначені як sensitive. Bucket зі станами трактуйте як сховище секретів: приватний, із шифруванням на боці сервера, з версіонуванням і з доступом, обмеженим до тих, кому він справді потрібен.Перший ресурс: віртуальна машина
Найкоротша осмислена конфігурація виглядає так. Зверніть увагу, що параметри машини не вигадуються в коді — вони є вказівкою на offering:
data "cloudstack_zone" "main" { filter { name = "name" value = "^PL-DCO-1$" }}data "cloudstack_template" "ubuntu" { template_filter = "featured" filter { name = "name" value = "^Ubuntu 24\\.04 LTS$" }}resource "cloudstack_network" "app" { name = "app-net" cidr = "10.20.0.0/24" network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" zone = data.cloudstack_zone.main.id}resource "cloudstack_instance" "web" { name = "web-01" display_name = "web-01" service_offering = "Medium Instance" # позиція з меню оператора template = data.cloudstack_template.ubuntu.id network_id = cloudstack_network.app.id zone = data.cloudstack_zone.main.id root_disk_size = 40 # cloud-init: конфігурація системи під час першого старту user_data = base64encode(file("${path.module}/cloud-init/web.yaml")) expunge = true # destroy справді видаляє, не залишає в кошику}
Кілька речей, які варто прокоментувати:
- Значення в блоках
filter— це регулярні вирази, а не назви. Провайдер компілює їх як regex і зіставляє без прив’язки до початку та кінця, тожUbuntu 24.04 LTSзловить такожUbuntu 24.04 LTS minimal— а за кількох збігів data source шаблону вибере той із найновішою датою створення, не обов’язково той, який ви мали на увазі (data source зони — просто останній зі списку). Звідси\\.та^…$вище; за більш засміченої бібліотеки шаблонів варто додати другий фільтр, напр.{ name = "hypervisor", value = "KVM" }. service_offeringза назвою. Працює і є читабельним, але назва offering може змінитися. У середовищах, які мають жити довго, безпечніше вказати UUID.- Offering типу «customized» не має вшитих параметрів — їх передають у полі
details, напр.details = { cpuNumber = "4", cpuSpeed = "2000", memory = "8192" }. Без цього CloudStack відхилить запит. user_data— це стандартний канал cloud-init. Увага на ліміт розміру — за більших конфігурацій допомагаєbase64gzip()замістьbase64encode().expunge = trueозначає, щоterraform destroyсправді видаляє машину, замість залишати її у стані «Destroyed» до часу закінчення терміну. Для ефемерних середовищ це правильний вибір; для продакшену — обміркуйте.
VPC і тіри: правила ACL не є окремими ресурсами
Серйозніші середовища будують на VPC. Виглядає це природно: приватна хмара, у ній мережі, а перед мережами — список ACL:
resource "cloudstack_vpc" "main" { name = "prod-vpc" cidr = "10.0.0.0/16" vpc_offering = "Redundant VPC offering" zone = data.cloudstack_zone.main.id project = var.project}resource "cloudstack_network_acl" "app" { name = "prod-acl" description = "ACL dla tieru aplikacyjnego" vpc_id = cloudstack_vpc.main.id project = var.project}resource "cloudstack_network_acl_rule" "app" { acl_id = cloudstack_network_acl.app.id project = var.project # УВАГА: усі правила однієї ACL живуть в ОДНОМУ ресурсі rule { action = "allow" cidr_list = ["0.0.0.0/0"] protocol = "all" traffic_type = "egress" } # один порт або один діапазон на блок `rule` — 0.6.0 більше не приймає списку rule { action = "allow" cidr_list = var.admin_cidrs # адміністративні мережі / VPN protocol = "tcp" port = "22" traffic_type = "ingress" } rule { action = "allow" cidr_list = var.admin_cidrs protocol = "tcp" port = "6443" traffic_type = "ingress" } rule { action = "allow" cidr_list = ["0.0.0.0/0"] protocol = "tcp" port = "443" traffic_type = "ingress" }}resource "cloudstack_network" "app" { name = "prod-app-tier" cidr = "10.0.1.0/24" network_offering = "Isolated Network for VPC with SNAT" vpc_id = cloudstack_vpc.main.id acl_id = cloudstack_network_acl.app.id zone = data.cloudstack_zone.main.id project = var.project}
Ось місце для першої справжньої пастки. Ресурс cloudstack_network_acl_rule не представляє окремого правила — він представляє увесь набір правил цієї ACL. Блоки rule усередині нього — це повний список. Якщо хтось додасть правило вручну в панелі, найближчий apply його видалить, бо Terraform приведе набір до стану, описаного в коді. Це поведінка правильна й бажана — але вона дивує того, хто очікує моделі «один ресурс, одне правило».
І одразу друга річ, яка може здивувати: поле ports зникло. У 0.6.0 воно позначене як застаріле, а провайдер прямо відмовляється створити нове правило, яке його використовує — отримаєте помилку «The 'ports' field is no longer supported for creating new rules». Замість списку передаєте port з одним портом або одним діапазоном ("80" чи "80-90"), тож два порти — це просто два блоки rule. У зворотний бік це працює так само безжально: у гілці 0.5.x поля port іще немає і діє виключно ports. Це добрий приклад того, чому варто фіксувати точну версію та тримати .terraform.lock.hcl у репозиторії.
Публічна адреса: спершу резервація, потім NAT і load balancer
Порядок завжди той самий:
resource "cloudstack_ipaddress" "public" { vpc_id = cloudstack_vpc.main.id zone = data.cloudstack_zone.main.id project = var.project}# варіант 1: просте перенаправлення порту на одну машинуresource "cloudstack_port_forward" "ssh" { ip_address_id = cloudstack_ipaddress.public.id project = var.project forward { protocol = "tcp" private_port = 22 public_port = 2222 virtual_machine_id = cloudstack_instance.bastion.id }}# варіант 2: load balancer L4 на Virtual Router, зі списком дозволених джерелresource "cloudstack_loadbalancer_rule" "api" { name = "kubernetes-apiserver" description = "Dostęp do API Kubernetesa" ip_address_id = cloudstack_ipaddress.public.id network_id = cloudstack_network.app.id algorithm = "roundrobin" private_port = 6443 public_port = 6443 protocol = "tcp" member_ids = cloudstack_instance.controlplane[*].id project = var.project cidrlist = var.admin_cidrs # хто взагалі може достукатися до цього правила}
Атрибут cidrlist з’явився у випуску 0.6.0 — на ньому або новішому маєте його одразу. У старіших версіях правило для load balancer відкрите на світ, і закривати його доводиться окремим правилом файрвола.
Реальний приклад: кластер Kubernetes, описаний повністю кодом
Тепер найцікавіша частина — як це виглядає у проєкті, який справді працює. У нас увесь кластер Kubernetes на Talos Linux — дистрибутиві, призначеному виключно під Kubernetes, без оболонки та без SSH, який налаштовується суто декларативно через API — описаний за допомогою Terraform від порожньої зони CloudStack до працюючого ArgoCD.
Перш ніж покажемо код — чесна відповідь на питання, яке поставить кожен, хто знає CloudStack: так, CloudStack має вбудовану службу Kubernetes (CloudStack Kubernetes Service), а офіційний провайдер має для неї ресурс cloudstack_kubernetes_cluster, наявний уже у стабільному випуску 0.6.0. Один ресурс — і кластер стоїть. Для багатьох застосувань це правильний шлях, і немає причин його оминати.
Ми обрали довший шлях із трьох причин. По-перше, версії Kubernetes у вбудованій службі — це чергове «меню оператора»: доступне те, для чого адміністратор хмари завантажив образ; ми ж хотіли керувати версією системи вузлів і кластера одним URL у файлі .tfvars. По-друге, ми хотіли власного складу системного шару — Cilium як CNI, Ceph RBD під постійні томи, ArgoCD від першого запуску — і того самого шаблону також поза CloudStack. По-третє, Talos дає вузол без оболонки та без SSH, тобто конфігурацію, яку не вийде «підправити вручну на хвилинку».
Ціна цього вибору очевидна: кількасот рядків коду більше й утримання шару, який у вбудованій службі веде оператор хмари — разом з оновленнями вузлів. Якщо вам це не потрібно, починайте від вбудованої служби.
Структура репозиторію
├── modules/│ ├── cloudstack/ # VPC, мережа, ACL, вузли, LB, шаблон│ └── talos/ # конфігурація машин Talos + патчі└── environments/ ├── dev/ └── prod/ ├── backend.tf # стан у S3 (Ceph RGW) ├── providers.tf # cloudstack, talos, helm, kubectl, http ├── main.tf # зведення всього докупи ├── cloudstack.tfvars # значення середовища └── infra-deployments/ ├── charts/ # cilium, CSI, CCM, prometheus, argocd └── argocd-apps/ # застосунки GitOps
Поділ класичний і добре себе виправдовує: модулі описують «як», а каталоги середовищ описують «скільки і де». Різниця між dev і prod — це на практиці файл .tfvars; значення нижче наведено для прикладу — вони показують, що взагалі відрізняє середовища, а не розміри наших кластерів:
# environments/prod/cloudstack.tfvarszone = "PL-DCO-1"project = "..." # UUID проєкту CloudStackvpc_name = "Prod Redundant VPC"vpc_cidr = "10.0.0.0/16"vpc_offering = "Virtual Private Cloud with VR HA"talos_network_name = "prod-network"talos_network_cidr = "10.0.1.0/24"talos_network_offering = "Isolated Network for VPC with SNAT"talos_image_url = "https://factory.talos.dev/image/<hash>/<wersja>/cloudstack-amd64.raw.gz"cluster_name = "prod"controlplane_vip = "10.0.1.2"
У тому самому файлі є ще controlplane_count і worker_count — кількість вузлів на практиці є єдиним, що змінюється при масштабуванні середовища вгору або вниз; конкретні значення тримаємо у змінних середовища, а не в тілі модуля.
Шаблон системи просто з URL
Перше, що вражає людей, звиклих до ручного завантаження образів: CloudStack уміє зареєструвати шаблон, забираючи його з будь-якої HTTP-адреси, а Terraform уміє цим керувати. Talos надає готові образи для CloudStack через службу Image Factory, тож версія операційної системи вузлів стає… змінною у файлі конфігурації:
resource "cloudstack_template" "talos" { name = "Talos-${regex("v[0-9]+\\.[0-9]+\\.[0-9]+", var.talos_image_url)}" format = "RAW" hypervisor = "KVM" os_type = "Other PV Virtio-SCSI (64-bit)" url = var.talos_image_url zone = var.zone project = var.project}
Підняття Talos до новішої версії — це зміна одного URL. Назва шаблону витягує з адреси номер версії (тег вигляду vX.Y.Z) регулярним виразом, тож нова версія створює новий шаблон, замість тихцем перезаписати старий.
Вузли: машина + конфігурація в user_data
Talos не має SSH — усю конфігурацію вузла він отримує в user_data під час першого запуску. Це дуже добре пасує до Terraform: модуль talos генерує конфігурації машин, а модуль cloudstack вставляє їх в інстанси:
resource "cloudstack_instance" "controlplane" { count = var.controlplane_count name = "${var.cluster_name}-control-${count.index + 1}" service_offering = "cs.custom.nvme" template = cloudstack_template.talos.id network_id = cloudstack_network.talos.id zone = var.zone project = var.project uefi = true root_disk_size = 200 details = { cpuNumber = "2", cpuSpeed = "2000", memory = "16000" } user_data = base64gzip(var.talos_controlplane_config[count.index]) expunge = true}
base64gzip() замість base64encode() — це не прикраса: конфігурація Talos із патчами може перевищити ліміт розміру user_data.
expunge = true ми залишаємо тут свідомо, попри застереження з попереднього розділу: вузол кластера є замінним і відтворюється з того самого коду, а від втрати кластера захищає резервна копія etcd, а не кошик CloudStack. У машині, яка тримає дані, вибір був би протилежним.
Черговість подій, тобто де Terraform потребує допомоги
Найцікавіший фрагмент усього проєкту — це main.tf середовища, бо він показує те, чого не видно в туторіалах: підняття кластера — це послідовність, у якій частина кроків мусить зачекати на реальність, а не лише на граф залежностей Terraform.
Перш ніж простежимо перебіг, одну річ треба назвати прямо: від цього місця в одному каталозі працює кілька провайдерів одночасно. cloudstack створює інфраструктуру, talos розмовляє з вузлами через API Talos (звідси ресурси з префіксом talos_), helm і kubectl встановлюють компоненти у вже готовому кластері, а http слугує для однієї перевірки готовності. Тож ресурсів talos_* не шукайте в документації провайдера CloudStack — їх там немає і не буде. Terraform зводить це все в один прогін, бо вихід одного провайдера буває входом наступного.
Перебіг виглядає так:
- Створюється VPC і резервується публічна IP-адреса.
- Модуль
talosгенерує конфігурації машин — уже знаючи публічний endpoint кластера, бо адреса відома ще до створення вузлів. - Модуль
cloudstackстворює шаблон, мережу, ACL, вузли та правила LB. talos_machine_configuration_applyнадсилає конфігурацію до кожного вузла.talos_machine_bootstrapініціалізує etcd — розподілену базу, у якій Kubernetes тримає весь свій стан — на першому вузлі control plane; решта вузлів приєднуються до неї самі.- Terraform чекає, доки API Kubernetes справді відповість.
- Аж тоді завантажується kubeconfig і встановлюються компоненти через Helm.
Шостий крок — саме той, якого бракує в більшості прикладів. Bootstrap завершується ще до того, як kube-apiserver почне обслуговувати трафік, тож наступний ресурс потрапив би в порожнечу. Шаблон, вартий запам’ятовування, — опитування endpoint здоров’я з повторними спробами, як звичайний data source:
data "http" "check_kubeapi" { depends_on = [talos_machine_bootstrap.cluster] url = "https://${cloudstack_ipaddress.lb.ip_address}:6443/healthz" insecure = true # свідоме скорочення — сертифікат apiserver підписує CA кластера, а не публічний центр retry { attempts = 10 min_delay_ms = 15000 max_delay_ms = 15000 # сталий інтервал: до 2,5 хвилини терпіння }}resource "talos_cluster_kubeconfig" "cluster" { depends_on = [data.http.check_kubeapi] # ось у чому весь трюк client_configuration = module.talos.talos_client_configuration node = cloudstack_ipaddress.lb.ip_address}
Це рішення замість time_sleep зі значенням, узятим зі стелі: воно чекає рівно стільки, скільки треба, а якщо кластер не підніметься — перериває apply зрозумілою помилкою, замість тихцем іти далі. Явний max_delay_ms тут не є зайвим: без нього провайдер бере стандартну стелю 30 с і експоненційний backoff, тож реальне очікування зростає майже до п’яти хвилин.
Одне слово про insecure = true, щоб ніхто не переніс цю звичку далі: це скорочення, а не рекомендація. Перевіряємо виключно /healthz і нічого туди не надсилаємо, а сертифікат kube-apiserver підписаний CA кластера, тож стандартна перевірка все одно б його відкинула. Утім, закрити це можна — провайдер http приймає ca_cert_pem, а CA Kubernetes на той момент уже існує у стані Terraform, бо його генерує talos_machine_secrets; достатньо виставити його як вихід модуля.
Kubernetes-шар у тому самому прогоні
Після отримання kubeconfig той самий apply налаштовує провайдери helm і kubectl даними щойно створеного кластера і встановлює системний шар:
module "cilium" { source = "./infra-deployments/charts/cilium" }module "kube-prometheus-stack" { source = "./infra-deployments/charts/kube-prometheus-stack" }module "cloudstack-kubernetes-provider" { source = "./infra-deployments/charts/cloudstack-kubernetes-provider" /* ... */ }module "cloudstack-csi" { source = "./infra-deployments/charts/cloudstack-csi" /* ... */ }module "ceph_csi_rbd" { source = "./infra-deployments/charts/ceph-csi-rbd" /* ... */ }module "argocd" { source = "./infra-deployments/charts/argocd" }module "argocd-apps" { source = "./infra-deployments/argocd-apps" }
Три з цих компонентів варто розібрати окремо:
cilium— CNI кластера, тобто плагін, який відповідає за мережу між подами й політики трафіку.cloudstack-kubernetes-provider— cloud controller manager. Завдяки йомуServiceтипуLoadBalancerу Kubernetes справді замовляє в CloudStack публічну адресу й правило LB, замість вічно висіти у станіPending.cloudstack-csi— драйвер сховища. Дає змогуPersistentVolumeClaimстворювати справжній том CloudStack і під’єднувати його до потрібного вузла.
Межа відповідальності тут свідома: Terraform піднімає кластер і його системний шар, а від застосунків справу переймає GitOps — модель, у якій стан застосунків описаний у git-репозиторії, а агент, що працює в кластері (у нас ArgoCD), постійно порівнює його з реальністю й усуває розбіжності. Terraform чудовий у створенні інфраструктури і кепський як цикл постійної реконсиляції застосунків — змішування цих ролей закінчується pipeline’ами, що тривають чверть години.
Pipeline: plan автоматично, apply за ручним бар’єром
Усе це їде в GitLab CI у схемі, яку рекомендуємо для будь-якої продукційної інфраструктури:
stages: [validate, plan, apply]image: name: hashicorp/terraform:1.10.3 entrypoint: [""]before_script: - cd $BASE_PATH - terraform init -backend-config="access_key=$S3_ACCESS_KEY" -backend-config="secret_key=$S3_SECRET_KEY"validate: stage: validate script: [terraform validate]iac-scan: # статичний аналіз безпеки коду IaC stage: validate image: name: aquasec/trivy:latest entrypoint: [""] # без цього GitLab не запустить shell у цьому образі before_script: [] # тут немає чого ініціалізувати script: [trivy config --exit-code 1 $BASE_PATH]plan: stage: plan script: - terraform plan -out=tfplan -var-file cloudstack.tfvars - terraform show -json tfplan > tfplan.json artifacts: paths: [$BASE_PATH/tfplan, $BASE_PATH/tfplan.json] access: developer # план несе ті самі секрети, що й стан (GitLab >= 16.7) expire_in: 7 days # мусить пережити ручний бар’єр перед `apply`apply: stage: apply script: [terraform apply tfplan] when: manual dependencies: [plan] manual_confirmation: "Potwierdź, że chcesz wdrożyć te zmiany"
Артефакт плану так само чутливий, як файл стану. Збереженийtfplan— а тим пачеtfplan.json, боterraform show -jsonвиписує значення, позначеніsensitive, відкритим текстом — містить ті самі секрети, що й стан, разом із його копією з моменту перед зміною. Артефакти GitLab за замовчуванням може завантажити кожен, хто бачить job, і лежать вони тижнями, тож обмежте доступ (access: developer) і скоротіть ретенцію (expire_in) — пам’ятаючи, що артефакт мусить пережити ручний бар’єр передapply, бо без ньогоapplyне має чого розгортати.
Три речі, які тут роблять роботу:
applyвиконує збережений план, а не рахує його наново — саме подання файлу плану Terraform трактує як затвердження, тож-auto-approveтут зайвий. Розгортаєте рівно те, що хтось прочитав і прийняв.- Ручний бар’єр із підтверджувальним запитанням. Інфраструктура — не місце для
applyавтоматично після merge’у. - Скан безпеки IaC на етапі валідації.
trivy configпроходить файлами.tfі виловлює типові гріхи, перш ніж вони стануть реальністю — у нас найчастіше секрети, вклеєні вuser_data. Дві речі варто тут знати. По-перше: скануємо Trivy, боtfsecбув ним поглинутий і вже не отримує нових правил. По-друге, чесно: готових правил, написаних прямо під провайдер CloudStack, небагато — більший набір (відкриті на світ порти, вимкнене шифрування) стосується провайдерів гіперскейлерів. Це все одно дешевий бар’єр, у тому самому дусі, який ми описували в тексті про стек безпеки open source.
Пастки, які коштують часу
Зібрані з практики, у порядку від найчастіших:
- Версія провайдера. Функції з’являються разом із випусками, а ті виходять рідко. Перш ніж визнати, що «CloudStack цього не вміє», перевірте, чи не вміє цього ваша версія провайдера — хмарне API зазвичай має цю функцію вже давно.
- Зашиті UUID. Скопіювати ідентифікатор мережі з панелі і вставити його в код — це працює, до першої перебудови середовища. Усе, що можна знайти через
data source, через нього й знаходьте; решту тримайте у змінних, а не в тілі модуля. Це, зрештою, класичний борг: ми й самі маємо в коді одне таке місце і знаємо, що колись воно нас вкусить. - Асинхронність API.
depends_onгарантує черговість створення ресурсів, але не готовність служби. Там, де ви чекаєте на реальність (API Kubernetes, служба всередині машини, поширення DNS), скористайтеся перевіркою з повторними спробами — якdata "http"з блокомretryвище. - ACL як один ресурс. Усі правила списку живуть в одному ресурсі Terraform. Ручну зміну в панелі буде скасовано під час найближчого
apply. - Offering-и типу «customized» вимагають поля
detailsізcpuNumber,cpuSpeedіmemory. Без цього CloudStack відхилить запит, а повідомлення про помилку не завжди прямо вкаже причину. expunge = trueробитьdestroyнезворотним — машину не вдасться відновити зі стану «Destroyed». Це безпечно там, де машина є замінною (як вузол кластера), і ризиковано всюди, де вона тримає дані.- Load balancer працює на четвертому рівні й живе у Virtual Router. Не плануйте на ньому маршрутизації за шляхами ані великого HTTP-трафіку — для цього є ingress у кластері або окрема машина з reverse proxy.
- Секрети у стані — і в плані. Ключі API, паролі та ключі Ceph потрапляють до
tfstateу відкритому вигляді, а разом із ним — до збереженогоtfplanі доtfplan.json. Bucket зі станами захистіть як сховище паролів, а артефакт плану в CI трактуйте так само: обмежений доступ і коротка ретенція. - Паралельні pipeline’и. Без блокування стану два одночасні
applyздатні розсинхронізувати середовище.use_lockfile = trueкоштує один рядок.
CloudStack і Terraform у WebDisk Cloud
Наша публічна хмара стоїть на Apache CloudStack з гіпервізором KVM і сховищем на кластері Ceph — primary storage це пули RBD (окремо NVMe, окремо ємнісні), а Object Storage — це RGW з API, сумісним із S3. Усе, що ми описали вище, стосується тієї самої платформи, на якій ви можете запустити власне середовище.
Що це означає на практиці:
- Ключі API ви генеруєте самостійно в панелі — їх не треба в нас замовляти. Тим самим ключем обслужите Terraform, CloudMonkey і власні скрипти.
- Bucket для стану Terraform можете мати в нас поруч із машинами — з нативним блокуванням S3, без додавання будь-якої зовнішньої служби.
- Тут немає проміжного шару. Ви розмовляєте з тим самим API CloudStack, яким користується панель — а оскільки це проєкт Apache Software Foundation, ваш код не прив’язаний до одного постачальника. Це конкретний, вимірюваний спосіб обмеження залежності від постачальника, про яку ми писали ширше окремо.
- Обсяг самообслуговування ширший, ніж здається: VPC і тіри, списки ACL, публічні адреси, перенаправлення портів, правила LB, шаблони, снапшоти, томи — усе це ресурси Terraform.
А якщо те, чого ви шукаєте, — це готовий кластер із супроводом замість власного Terraform, то описаний вище проєкт є саме тим, що ми робимо в межах технічної підтримки Kubernetes; про те, чому підняття кластера — це найлегший етап, ми писали в окремій статті.
Часті питання
Чим Apache CloudStack відрізняється від OpenStack? Насамперед складністю та моделлю впровадження. OpenStack — це набір із кількох десятків взаємодіючих проєктів з окремими API та життєвими циклами: він дає максимальну гнучкість, але вимагає команди, яка утримує саму платформу. CloudStack — це один застосунок на Java з однією базою MySQL, що охоплює всю модель хмари: встановлення й оновлення значно простіші, ціною меншої модульності. Обидва є проєктами з відкритим кодом під ліцензією Apache 2.0, обидва мають провайдер Terraform.
Чи є для CloudStack офіційний провайдер Terraform? Так — cloudstack/cloudstack, який розвивають у репозиторії Apache. Однак треба знати його темп: останній стабільний випуск у реєстрі — це 0.6.0 з жовтня 2025 року, а гілка 0.7.0 присутня там поки що як кандидат на випуск (0.7.0-rc1, серпень 2026). Практичний висновок: фіксуйте точну версію, комітьте .terraform.lock.hcl і заглядайте до CHANGELOG — функція, якої бракувало рік тому, могла вже потрапити до стабільного випуску.
Звідки взяти ключі API до CloudStack? З панелі: Accounts → обліковий запис → Users → користувач → Generate Keys. Секрет показується повністю лише під час генерування. Для автоматизації заведіть окремого технічного користувача з найменшими потрібними правами — його легше зротувати та відстежити в журналі подій, ніж ключі, прив’язані до людини.
Де тримати файл стану Terraform, якщо я не маю AWS? У будь-якому сховищі, сумісному з S3 — Ceph RGW, MinIO чи Object Storage у постачальника. Backend s3 підтримує власний endpoint, а від Terraform 1.10 вміє блокувати стан нативно, умовними записами в S3 (use_lockfile = true), без таблиці DynamoDB. Пам’ятайте, що стан містить секрети відкритим текстом — bucket захистіть відповідно.
Чи можу я замість Terraform використати OpenTofu? Так. OpenTofu — це форк Terraform під Linux Foundation, створений після зміни ліцензії Terraform на BUSL (Business Source License): джерела залишаються відкритими, але формально це вже не ліцензія open source. Провайдери CloudStack — це звичайні плагіни, і вони працюють в обох інструментах; конфігурація залишається та сама, змінюється команда (tofu замість terraform). Якщо ліцензія BUSL є для вас проблемою, то зазвичай це вся міграція.
Якщо CloudStack має вбудовану службу Kubernetes (CKS), навіщо ставити кластер власним кодом? Немає навіщо, якщо вбудована служба покриває ваш випадок — офіційний провайдер має для неї ресурс cloudstack_kubernetes_cluster уже в стабільному випуску 0.6.0, і це найкоротша дорога до працюючого кластера. Власний кластер (у нас на Talos Linux) обирають тоді, коли потрібен контроль над версією та конфігурацією вузлів, власний набір CNI і драйверів сховища або той самий шаблон одразу на кількох платформах. Ціна — це більше коду і повна відповідальність за шар, який у вбудованій службі утримує оператор хмари.
Чому мій Service типу LoadBalancer у Kubernetes на CloudStack висить у стані Pending? Бо в кластері бракує компонента, який замовить адресу і правило в хмарі. За це відповідає cloudstack-kubernetes-provider, тобто cloud controller manager для CloudStack — без нього Kubernetes не має як попросити публічний IP. Аналогічно за томи відповідає cloudstack-csi.
Чи замінить load balancer CloudStack Application Load Balancer? Ні, і не варто його так трактувати. LB у CloudStack працює у Virtual Router, є балансувальником четвертого рівня з липкими сесіями і — від випуску 4.22 — з термінацією SSL на самому маршрутизаторі, а його пропускну здатність обмежує один маршрутизатор. Якщо ваша хмара стоїть на старішому випуску, розвантаження SSL на маршрутизаторі там просто немає. Для маршрутизації за шляхами, заголовками чи іменами хостів використайте ingress у кластері Kubernetes або власний reverse proxy — CloudStack тоді лише надає їм публічну адресу.
Чи впорається Terraform із хмарою, яка вже існує? Так, через імпорт наявних ресурсів до стану (terraform import або блоки import), але будьте реалістами: за більшого середовища це робота на дні, а не на години, і вона вимагає терплячого припасування коду до реальності, доки plan не перестане пропонувати зміни. Частіше вигідніше охопити кодом те, що нове, а старе мігрувати за нагоди перебудови.
Підсумок
Apache CloudStack має рису, яка добре пасує до інфраструктури як коду: він побудований навколо одного, узгодженого API і однієї зрозумілої об’єктної моделі. Зона, offering, мережа, VPC, ACL, шаблон, проєкт — це поняття, які однаково добре описують і панель, і файли .tf. Коли їх один раз зрозумієте, Terraform перестає бути перекладом між двома світами і стає просто другим способом сказати те саме.
Три речі варто забрати з цього тексту. По-перше: старт дешевий — три значення в конфігурації провайдера, backend на S3, який ви, найімовірніше, вже маєте, і перша машина в кільканадцяти рядках. По-друге: масштаб окупається — цілий кластер Kubernetes із мережею подів, сховищем і GitOps можна описати кодом і відтворити однією командою, а різниця між dev і prod зводиться до файлу зі змінними. По-третє: знайте обмеження — рідкісні випуски провайдера, offering-и, які визначає оператор, асинхронне API і load balancer четвертого рівня — це не дефекти, а властивості платформи, які треба врахувати під час проєктування середовища.
Якщо ви починаєте з нуля, добра черговість така: згенеруйте ключі API, опишіть одну машину й одну мережу, перенесіть стан до S3, додайте pipeline із ручним бар’єром перед apply, і аж потім беріться за модулі та середовища. Кожен із цих кроків має сенс самостійно — і жоден не вимагає переписування того, що вже працює.
Замислюєтеся, чи можна ваше середовище описати кодом і з чого почати? Напишіть нам — команда WebDisk охоче пройде з вами через перший модуль.