- examples/: удалены старые директории (TNAR, demo-event-log, demo-managed-functions, hello-go, hello-node, notes-python, pg-list-python, simple-node, simple-python) - examples/README.md: обновлён (только POSTGRES остался) - doc/decisions/build-deploy-pipeline.md: новый документ по пайплайну сборки/деплоя - services/funcs/main.go: правки из текущей сессии
15 KiB
Build & Deploy Pipeline — Terraform Provider + Operator
Дата: 2026-03-20
Контекст
Этот документ описывает полный цикл внесения изменений в систему:
от правки Go-кода до работающего terraform apply в продакшен-примере.
Охватывает:
- как изменять/добавлять ресурсы в terraform-провайдере
- как собирать и публиковать провайдер
- как собирать Docker-образ оператора
- как деплоить оператор в кластер
- как всё связано
1. Архитектура компонентов
┌──────────────────────────────────────────────────────┐
│ Пользователь пишет Terraform код (functions.tf) │
│ resource "sless_job" {...} / "sless_service" {...} │
└───────────────────┬──────────────────────────────────┘
│ terraform apply
▼
┌──────────────────────────────────────────────────────┐
│ Terraform Provider (terraform-provider-sless) │
│ Go бинарник в /tmp/sless-provider-dev/ │
│ Исходники: sless/terraform/provider/ │
│ Публикуется на: terra.k8c.ru/naeel/sless │
└───────────────────┬──────────────────────────────────┘
│ HTTP REST API
▼
┌──────────────────────────────────────────────────────┐
│ sless-operator (Kubernetes Deployment) │
│ Namespace: sless │
│ Image: pearlharbor.registryk8s.services.ngcloud.ru/ │
│ naeel/sless-operator:v0.1.xx │
│ Исходники: sless/ (корень репо) │
│ Управляет CRD: sless_function, sless_job, │
│ sless_service, sless_trigger │
└──────────────────────────────────────────────────────┘
2. Где что хранится
| Компонент | Путь на удалённой машине | Путь на редактирование (sshfs) |
|---|---|---|
| Оператор (Go) | /home/naeel/terra/sless/ |
/home/naeel/remote_dev/sless/ |
| Terraform provider | /home/naeel/terra/sless/terraform/provider/ |
/home/naeel/remote_dev/sless/terraform/provider/ |
| Пример POSTGRES | /home/naeel/terra/sless/examples/POSTGRES/ |
/home/naeel/remote_dev/sless/examples/POSTGRES/ |
| Dev override provider | /tmp/sless-provider-dev/ |
только на удалённой машине |
Правило: файлы редактируются через sshfs (/home/naeel/remote_dev/),
команды выполняются только на удалённой машине 5.172.178.213 через SSH.
3. Как изменить ресурс в terraform-провайдере
3.1 Структура провайдера
terraform/provider/
├── main.go # точка входа, регистрация провайдера
├── go.mod
├── internal/
│ ├── client/
│ │ └── client.go # REST-клиент к sless API (типы запросов/ответов)
│ ├── resources/
│ │ ├── job_resource.go # ресурс sless_job
│ │ ├── service_resource.go # ресурс sless_service
│ │ ├── function_resource.go # ресурс sless_function
│ │ └── trigger_resource.go # ресурс sless_trigger
│ └── provider/
│ └── provider.go # конфигурация провайдера (endpoint, token)
└── hack/
└── build-and-publish.sh # скрипт сборки + публикации в S3
3.2 Добавить новый атрибут к существующему ресурсу
Пример: добавить timeout_sec к sless_job.
Шаг 1 — client.go: добавить поле в JobRequest и JobResponse:
type JobRequest struct {
// ... существующие поля
TimeoutSec int32 `json:"timeout_sec,omitempty"`
}
Шаг 2 — job_resource.go: добавить поле в JobModel:
type JobModel struct {
// ... существующие поля
TimeoutSec types.Int64 `tfsdk:"timeout_sec"`
}
Шаг 3 — job_resource.go: добавить в Schema():
"timeout_sec": schema.Int64Attribute{
Optional: true,
Computed: true,
Default: int64default.StaticInt64(30),
MarkdownDescription: "Таймаут выполнения в секундах.",
},
Шаг 4 — job_resource.go: использовать в Create() при формировании запроса:
req := client.JobRequest{
// ...
TimeoutSec: int32(plan.TimeoutSec.ValueInt64()),
}
3.3 Создать новый ресурс
- Создать файл
terraform/provider/internal/resources/myresource_resource.go - Реализовать интерфейс
resource.Resource(минимум:Metadata,Schema,Create,Read,Update,Delete) - Зарегистрировать в
provider/provider.go:
func (p *SlessProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
resources.NewJobResource,
resources.NewMyResource, // добавить сюда
}
}
3.4 Что НЕ делать
- Не добавлять поле в
Schema()без добавления его вModelstruct — паника приterraform plan - Не забыть
tfsdk:"..."тег — без него поле невидимо - При
RequiresReplace: если ресурс immutable по этому полю, добавитьplanmodifier.RequiresReplace()
4. Как изменить CRD (API типы) в операторе
4.1 Файлы
api/v1alpha1/
├── job_types.go # FunctionJobSpec / FunctionJobStatus / FunctionJobPhase
├── function_types.go # FunctionSpec / FunctionStatus
├── service_types.go # ServiceSpec / ServiceStatus
└── zz_generated.deepcopy.go # ГЕНЕРИРУЕТСЯ АВТОМАТИЧЕСКИ — не трогать вручную
4.2 Добавить поле в Spec
- Добавить в
job_types.go:
type FunctionJobSpec struct {
// +kubebuilder:validation:Required
NewField string `json:"newField"`
}
- Регенерировать deepcopy:
ssh -i /home/naeel/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 \
'cd /home/naeel/terra/sless && bin/controller-gen object paths="./api/..."'
- Регенерировать CRD YAML:
ssh -i /home/naeel/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 \
'cd /home/naeel/terra/sless && bin/controller-gen crd paths="./api/..." \
output:crd:artifacts:config=config/crd/bases'
- Применить CRD в кластер:
ssh -i /home/naeel/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 \
'kubectl apply -f /home/naeel/terra/sless/config/crd/bases/'
Важно: CRD нужно обновлять в кластере до деплоя оператора, иначе контроллер не сможет читать/записывать новые поля из etcd.
5. Как собрать и задеплоить оператор
5.1 Полная последовательность
# Переменные
REGISTRY="pearlharbor.registryk8s.services.ngcloud.ru"
IMAGE="$REGISTRY/naeel/sless-operator"
VERSION="v0.1.42" # следующий тег
# 1. Логин в registry (один раз, credentials сохраняются)
echo "ieNocheiphaipheep1lie5johl7aqu" | docker login $REGISTRY -u admin --password-stdin
# 2. Сборка образа
docker build -t $IMAGE:$VERSION /home/naeel/terra/sless
# 3. Push
docker push $IMAGE:$VERSION
# 4. Обновить тег в operator.yaml (в файле sless/deployments/k8s/operator.yaml)
# Поле: image: pearlharbor.../naeel/sless-operator:v0.1.41 → v0.1.42
# 5. Apply в кластер
kubectl apply -f /home/naeel/terra/sless/deployments/k8s/operator.yaml
# 6. Дождаться rollout
kubectl rollout status deployment/sless-operator -n sless --timeout=180s
5.2 Что обязательно в operator.yaml
spec:
template:
spec:
imagePullSecrets:
- name: sless-registry-auth # Secret должен существовать в namespace sless
containers:
- name: operator
image: pearlharbor.registryk8s.services.ngcloud.ru/naeel/sless-operator:v0.1.42
imagePullPolicy: Always # Always — иначе k8s возьмёт старый кеш
Секрет sless-registry-auth — содержит docker credentials для pearlharbor.
Если его нет: kubectl create secret docker-registry sless-registry-auth -n sless ...
5.3 Создать Harbor-проект (если нет)
Harbor требует чтобы проект существовал до первого push:
curl -X POST "https://pearlharbor.registryk8s.services.ngcloud.ru/api/v2.0/projects" \
-u admin:ieNocheiphaipheep1lie5johl7aqu \
-H "Content-Type: application/json" \
-d '{"project_name":"naeel","public":false}'
# Ожидаем: 201 Created
6. Как собрать и опубликовать terraform-провайдер
6.1 Быстрая версия (dev override — для локального теста)
Собирает бинарник прямо в /tmp/sless-provider-dev/ — terraform подберёт его автоматически:
ssh -i /home/naeel/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 \
'cd /home/naeel/terra/sless/terraform/provider && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags "-X main.version=0.1.19" \
-o /tmp/sless-provider-dev/terraform-provider-sless_v0.1.19 . && echo OK'
Важно: в /tmp/sless-provider-dev/ должен быть только ОДИН бинарник — удалить старый!
rm /tmp/sless-provider-dev/terraform-provider-sless_v0.1.18
Dev override настроен в ~/.terraformrc:
provider_installation {
dev_overrides {
"terra.k8c.ru/naeel/sless" = "/tmp/sless-provider-dev"
}
direct {}
}
6.2 Полная публикация в S3-registry (для продакшена)
Скрипт hack/build-and-publish.sh собирает бинарники для всех платформ,
вычисляет SHA256, подписывает GPG и загружает в S3:
ssh -i /home/naeel/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 \
'cd /home/naeel/terra/sless/terraform/provider && \
S3CFG=/home/naeel/terra/terraform/secrets/.s3cfg_registry \
GPG_KEY_FILE=/home/naeel/terra/sless/secrets/private_key.asc \
bash hack/build-and-publish.sh 0.1.19'
После публикации проверить:
curl -sk https://terra.k8c.ru/v1/providers/naeel/sless/versions
6.3 Обновить версию в примерах
В examples/POSTGRES/main.tf (и других примерах):
sless = {
source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1.19" # обновить тут
}
Затем terraform init -upgrade — скачает новую версию.
7. Полный цикл: от изменения кода до terraform apply
1. Правка кода (локально через sshfs /home/naeel/remote_dev/sless/)
↓
2. git add + git commit + git push
(локально или через SSH)
↓
3. [если изменились api/v1alpha1/*.go]
controller-gen object → zz_generated.deepcopy.go
controller-gen crd → config/crd/bases/*.yaml
kubectl apply -f config/crd/bases/
↓
4. [если изменился operator Go-код]
docker build → docker push → kubectl apply operator.yaml
kubectl rollout status deployment/sless-operator -n sless
↓
5. [если изменился terraform/provider/]
go build → /tmp/sless-provider-dev/terraform-provider-sless_vX.X.X
(удалить старый бинарник из /tmp/sless-provider-dev/)
↓
6. terraform apply в examples/POSTGRES/
cd /home/naeel/terra/sless/examples/POSTGRES && terraform apply -auto-approve
8. Типичные ошибки и решения
| Ошибка | Причина | Решение |
|---|---|---|
unknown field FunctionRef при docker build |
Остарелая ссылка на удалённое поле CRD | Найти и заменить все вхождения FunctionRef в Go-коде |
Unsupported argument "timeout_sec" при terraform apply |
Провайдер не пересобран / старый бинарник в dev override | Пересобрать и удалить старый файл из /tmp/sless-provider-dev/ |
ImagePullBackOff при деплое оператора |
Нет imagePullSecrets или secret не содержит credentials для registry |
Добавить imagePullSecrets: [{name: sless-registry-auth}] в operator.yaml |
project naeel not found при docker push |
Проект в Harbor не создан | POST к Harbor API (см. раздел 5.3) |
no route to host при SSH |
Машина недоступна напрямую | Подключаться через 5.172.178.213 с SSH-ключом |
CRD имеет старые поля (functionRef вместо runtime) |
CRD не обновлён в кластере после правки types.go | kubectl apply -f config/crd/bases/ после регенерации |
9. Связанные файлы документации
- doc/run_and_logs.md — шаблоны SSH-команд, реквизиты машин
- doc/decisions/log.md — обоснование архитектурных решений
- doc/infrastructure/overview.md — инфраструктура кластера
- doc/progress.md — трекер задач