7.4. Деплой инфраструктуры в DevSpace через Terraform
Гайд для DevOps-инженеров, которые хотят управлять ресурсами в кластере DevSpace декларативно через Terraform. Целевая аудитория — инженер уровня middle с базовым опытом Kubernetes и Terraform.
|
-
DevSpace выставляет наружу стандартный Kubernetes API: https://api.devspace.cloupard.io:443. API-сервер использует внутренний Kubernetes CA — CA-сертификат встроен в выданный вам kubeconfig.
-
Аутентификация через OIDC (Keycloak), а не через статические kubeconfig’и или сервис-аккаунты — единый identity-слой, работа через SSO.
-
Внутри своего namespace тенанта вы создаёте не произвольные Kubernetes-ресурсы, а CozyStack-specific объекты из API-группы apps.cozystack.io/v1alpha1 (Kubernetes, Postgres, Kafka, Redis, VirtualMachine, …). RBAC в DevSpace настроен так, что прямой kubectl apply -f deployment.yaml в ваш namespace вернёт Forbidden.
-
В Terraform используется один провайдер — alekc/kubectl. Все ресурсы (CozyStack-specific и любые другие) деплоятся единообразно через kubectl_manifest. Аутентификация — через стандартный exec-плагин с kubelogin (он же kubectl oidc-login).
Что вы получаете от провайдера для доступа в DevSpace.
|
Что
|
Значение
|
|
Готовый kubeconfig
|
один файл — там уже CA, endpoint, имя вашего тенанта и exec-блок
|
|
API endpoint
|
https://api.devspace.cloupard.io:443
|
|
OIDC issuer URL
|
https://keycloak.devspace.cloupard.io/realms/cozy
|
|
OIDC client ID
|
kubernetes
|
|
CA сертификат API
|
встроен в kubeconfig в виде certificate-authority-data (base64)
|
|
Имя вашего тенанта
|
например tenant-kzj4to5r4t — виден в дашборде DevSpace
|
|
Логин/пароль в Keycloak
|
выдаётся вместе с аккаунтом
|
УСТАНОВКА ИНСТРУМЕНТОВ
На рабочей машине (или CI-раннере) понадобится три инструмента: Terraform, kubectl и kubelogin.
TERRAFORM
Требуется версия ≥ 1.5. Не ставьте через apt/yum/brew без проверки — там часто лежит устаревший Terraform или OpenTofu под тем же именем. Установка из официального источника HashiCorp.
macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
|
Linux (Debian/Ubuntu)
wget -O- https://apt.releases.hashicorp.com/gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
|
Linux (RHEL/Fedora)
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y terraform
|
Windows
choco install terraform
# или скачайте zip с https://developer.hashicorp.com/terraform/install и положите в PATH
|
Проверка
terraform version
# должно быть >= Terraform v1.5.0
|
Не путайте Terraform с OpenTofu. OpenTofu — открытый форк (бинарь называется tofu). Для целей этого гайда подходят оба, но синтаксис примеров — терраформовский. Если вы установили tofu — просто заменяйте terraform на tofu в командах.
|
kubectl
Любая актуальная версия. Инструкции: https://kubernetes.io/docs/tasks/tools/.
Проверка:
kubectl version --client
|
kubelogin (OIDC credential plugin)
Это именно int128/kubelogin (он же kubectl oidc-login), а не одноимённый Azure/kubkubectl oidc-login --version
elogin от Microsoft (тот заточен под AAD).
# macOS
brew install int128/kubelogin/kubelogin
# Linux (через krew — рекомендуется)
kubectl krew install oidc-login
# Linux вручную
curl -LO https://github.com/int128/kubelogin/releases/latest/download/kubelogin_linux_amd64.zip
unzip kubelogin_linux_amd64.zip
sudo mv kubelogin /usr/local/bin/
# Windows
choco install kubelogin
|
Проверка:
kubectl oidc-login --version
|
БАЗОВЫЙ KUBECONFIG ДЛЯ ИНТЕРАКТИВНОЙ РАБОТЫ
Kubeconfig выдаётся провайдером — собирать его руками не нужно. Выглядит он так:
apiVersion: v1
clusters:
- cluster:
server: https://api.devspace.cloupard.io:443
certificate-authority-data: LS0tLS1CRUdJTi... # внутренний Kubernetes CA, base64
name: cluster
contexts:
- context:
cluster: cluster
namespace: tenant-kzj4to5r4t
user: keycloak
name: tenant-kzj4to5r4t
current-context: tenant-kzj4to5r4t
users:
- name: keycloak
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://keycloak.devspace.cloupard.io/realms/cozy
- --oidc-client-id=kubernetes
- --skip-open-browser
|
Ключевые моменты:
-
certificate-authority-data обязателен. API-сервер представляется внутренним Kubernetes CA, который не входит в системные trust store’ы — без этого поля любой клиент получит x509: certificate signed by unknown authority.
-
--oidc-client-secret отсутствует, потому что клиент kubernetes public.
-
--skip-open-browser означает: kubelogin не пытается открыть браузер сам, а печатает URL в stdout. Удобно для серверов без графики и для случаев, когда terraform запускается из другого терминала.
Сохраните файл как ~/.kube/devspace.yaml и проверьте доступ:
export KUBECONFIG=~/.kube/devspace.yaml
# первый запрос — kubelogin напечатает URL, откройте его в браузере, залогиньтесь
kubectl auth whoami
# посмотрите доступные вам CozyStack-ресурсы
kubectl api-resources --api-group=apps.cozystack.io
|
При первом вызове kubelogin напечатает URL Keycloak. После успешного входа инструмент закеширует ID-token в ~/.kube/cache/oidc-login/. Дальше все запросы (включая terraform) будут переиспользовать кеш, пока токен валиден.
Если работаете через SSH без браузера — добавьте --grant-type=authcode-keyboard в args. kubelogin напечатает URL, который вы открываете на локальной машине, и попросит вставить код обратно в терминал.
TERRAFORM: ПОДКЛЮЧЕНИЕ ПРОВАЙДЕРА
Для всех ресурсов используется один провайдер — kubectl от alekc. Он умеет применять произвольные YAML-манифесты и корректно работает как с обычными Kubernetes-ресурсами, так и с aggregated API DevSpace (apps.cozystack.io/v1alpha1).
Есть два равноправных подхода к конфигурации — переиспользовать выданный kubeconfig напрямую или сконфигурировать провайдер явными полями.
Вариант 1 (рекомендуемый): через config_path
Самый простой и устойчивый к изменениям способ — указать путь к kubeconfig’у, который выдал провайдер DevSpace. Terraform сам прочитает оттуда endpoint, CA, namespace и exec-блок
terraform {
required_version = ">= 1.5"
required_providers {
kubectl = {
source = "alekc/kubectl"
version = "~> 2.0"
}
}
}
provider "kubectl" {
config_path = "~/.kube/devspace.yaml"
config_context = "tenant-kzj4to5r4t"
load_config_file = true
}
|
Плюсы: если провайдер обновит CA или endpoint — обновится kubeconfig, ваш Terraform-код менять не нужно.
Вариант 2: явная конфигурация провайдера
Если нужно держать всё в HCL (например, чтобы не таскать файлы в CI) — продублируйте содержимое kubeconfig в провайдере
provider "kubectl" {
host = "https://api.devspace.cloupard.io:443"
cluster_ca_certificate = base64decode(var.cluster_ca_data)
load_config_file = false
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "kubectl"
args = [
"oidc-login", "get-token",
"--oidc-issuer-url=https://keycloak.devspace.cloupard.io/realms/cozy",
"--oidc-client-id=kubernetes",
"--skip-open-browser",
]
}
}
variable "cluster_ca_data" {
description = "base64-encoded CA certificate (значение поля certificate-authority-data из kubeconfig)"
type = string
sensitive = true
}
|
В terraform.tfvars
cluster_ca_data = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..." # из вашего kubeconfig
|
Как работает exec-блок
При каждом запросе к API провайдер запускает указанную команду. Команда должна вернуть JSON формата ExecCredential. kubelogin get-token ровно это и делает: берёт кеш-токен из ~/.kube/cache/oidc-login/, если он валиден — отдаёт сразу; если нет — открывает браузер (или показывает URL для authcode-keyboard) и получает новый.
Поэтому terraform plan / terraform apply будут работать без диалогов после первой аутентификации.
Откуда Terraform берёт провайдер
source = "alekc/kubectl" — это сокращённая запись. Terraform разворачивает её в полный адрес registry.terraform.io/alekc/kubectl. registry.terraform.io — официальный публичный реестр HashiCorp; для провайдеров он работает примерно как Docker Hub для образов.
ДЕПЛОЙ РЕСУРСОВ
Что можно деплоить
Внутри namespace вашего тенанта вы создаёте объекты DevSpace. Команда для просмотра доступных типов
kubectl api-resources --api-group=apps.cozystack.io
|
Типичный список (зависит от версии DevSpace)
|
Kind
|
Plural
|
Что это
|
|
Kubernetes
|
kuberneteses
|
managed Kubernetes-кластер (control-plane через Kamaji + worker-ноды как VM)
|
|
Postgres
|
postgreses
|
managed PostgreSQL (CloudNativePG)
|
|
Kafka
|
kafkas
|
managed Kafka (Strimzi)
|
|
Redis
|
redises
|
managed Redis
|
|
MariaDB
|
mariadbs
|
managed MariaDB
|
|
ClickHouse
|
clickhouses
|
managed ClickHouse
|
|
VirtualMachine
|
virtualmachines
|
виртуальная машина (KubeVirt)
|
|
VMDisk
|
vmdisks
|
диск для VM
|
DevSpace-ресурсы через Terraform
Все ресурсы — через kubectl_manifest. Содержимое любого манифеста можно либо собрать в HCL через yamlencode({...}), либо положить отдельным YAML-файлом и подключить через heredoc / file().
Пример 1 — managed PostgreSQL
locals {
tenant_namespace = "tenant-kzj4to5r4t"
}
resource "kubectl_manifest" "pg" {
yaml_body = yamlencode({
apiVersion = "apps.cozystack.io/v1alpha1"
kind = "Postgres"
metadata = {
name = "app-db"
namespace = local.tenant_namespace
}
spec = {
external = false
size = "10Gi"
replicas = 2
storageClass = "replicated"
postgresql = {
parameters = {
max_connections = "100"
}
}
users = {
appuser = {
password = var.pg_password
}
}
databases = {
appdb = {
roles = {
admin = ["appuser"]
}
}
}
backup = { enabled = false }
resourcesPreset = "nano"
}
})
}
variable "pg_password" {
type = string
sensitive = true
}
|
Пример 2 — managed Kubernetes-кластер (если вам нужно запускать произвольные workload’ы — вы заказываете себе целый K8s-кластер внутри DevSpace, и уже там делаете что угодно)
resource "kubectl_manifest" "k8s" {
yaml_body = yamlencode({
apiVersion = "apps.cozystack.io/v1alpha1"
kind = "Kubernetes"
metadata = {
name = "prod-cluster"
namespace = local.tenant_namespace
}
spec = {
version = "1.31.0"
storageClass = "replicated"
controlPlane = {
replicas = 2
apiServer = { resourcesPreset = "small" }
controllerManager = { resourcesPreset = "micro" }
scheduler = { resourcesPreset = "micro" }
konnectivity = {
server = { resourcesPreset = "micro" }
}
}
nodeGroups = {
md0 = {
minReplicas = 2
maxReplicas = 5
instanceType = "u1.medium"
diskSize = "50Gi"
roles = ["ingress-nginx"]
}
}
addons = {
ingressNginx = { enabled = true, hosts = [] }
certManager = { enabled = false }
fluxcd = { enabled = false }
monitoringAgents = { enabled = false }
}
}
})
}
|
Это рекомендуемый паттерн: DevSpace-namespace для управления managed-сервисами, а свой K8s-кластер — для произвольной нагрузки.
ЗАПУСК И СОСТОЯНИЕ
terraform init
terraform plan
terraform apply
|
Хранение state’а
State содержит секреты (пароли БД, kubeconfig’и дочерних кластеров и т.д.) — локальный state допустим только для эксперимента. Рекомендуется хранить terraform.tfstate в любом внешнем S3-совместимом хранилище (Cloupard S3, AWS S3, MinIO и т.д.) с обязательным включением шифрования и version-locking — это стандартная практика.
ЧАСТЫЕ ПРОБЛЕМЫ
|
Симптом
|
Причина
|
Решение
|
|
x509: certificate signed by unknown authority
|
Не передан cluster_ca_certificate / certificate-authority-data
|
API использует внутренний Kubernetes CA — либо берите его из выданного kubeconfig’а, либо подкладывайте явно в provider
|
|
Unauthorized после успешного входа в Keycloak
|
Несовпадение client-id или issuer URL между провайдером и тем, что ждёт API-сервер
|
Сверьте флаги kubelogin с конфигом API-сервера (см. раздел 1)
|
|
forbidden: cannot create deployments.apps in namespace tenant-...
|
Попытка создать обычный Kubernetes-ресурс в namespace тенанта
|
По дизайну DevSpace вы создаёте объекты из apps.cozystack.io/v1alpha1. Если нужен Deployment — поднимите managed K8s-кластер (раздел 5.2) и деплойте туда
|
|
forbidden: User "alice" cannot list ... in namespace "tenant-..."
|
Пользователь аутентифицирован, но не в нужной Keycloak-группе
|
Попросите администратора DevSpace добавить вас в группу tenant-<name>-admin
|
|
failed to call OIDC provider: ... connection refused
|
Нет сетевого доступа к Keycloak
|
Откройте egress на keycloak.devspace.cloupard.io:443
|
|
kubectl oidc-login: open browser failed
|
Headless-окружение
|
Используйте --grant-type=authcode-keyboard
|
|
Error: namespaces "tenant-..." not found
|
Тенант ещё не создан администратором
|
Запросите создание тенанта у провайдера DevSpace
|
|
invalid_client от Keycloak
|
Передан --oidc-client-secret, но клиент kubernetes в DevSpace public
|
Уберите флаг --oidc-client-secret
|
|
failed to look up GVK [apps.cozystack.io/v1alpha1, Kind=...] among available CRDs: customresourcedefinitions ... is forbidden
|
Использован kubernetes_manifest из hashicorp/kubernetes — он на этапе plan делает LIST всех CRD на cluster scope, чего у тенанта нет (и не должно быть)
|
Используйте kubectl_manifest из провайдера alekc/kubectl — см. раздел 4
|
|
kubectl get crd | grep apps.cozystack.io ничего не показывает
|
apps.cozystack.io — это aggregated API, а не CRD
|
Используйте kubectl api-resources --api-group=apps.cozystack.io
|
ССЫЛКИ
-
Документация kubelogin: https://github.com/int128/kubelogin/blob/master/docs/usage.md
-
Terraform kubectl Provider (alekc): https://registry.terraform.io/providers/alekc/kubectl/latest/docs
-
CozyStack managed-сервисы: https://cozystack.io/docs/v1.4/applications/