# 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`: ```go type JobRequest struct { // ... существующие поля TimeoutSec int32 `json:"timeout_sec,omitempty"` } ``` **Шаг 2 — job_resource.go**: добавить поле в `JobModel`: ```go type JobModel struct { // ... существующие поля TimeoutSec types.Int64 `tfsdk:"timeout_sec"` } ``` **Шаг 3 — job_resource.go**: добавить в `Schema()`: ```go "timeout_sec": schema.Int64Attribute{ Optional: true, Computed: true, Default: int64default.StaticInt64(30), MarkdownDescription: "Таймаут выполнения в секундах.", }, ``` **Шаг 4 — job_resource.go**: использовать в `Create()` при формировании запроса: ```go req := client.JobRequest{ // ... TimeoutSec: int32(plan.TimeoutSec.ValueInt64()), } ``` ### 3.3 Создать новый ресурс 1. Создать файл `terraform/provider/internal/resources/myresource_resource.go` 2. Реализовать интерфейс `resource.Resource` (минимум: `Metadata`, `Schema`, `Create`, `Read`, `Update`, `Delete`) 3. Зарегистрировать в `provider/provider.go`: ```go func (p *SlessProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ resources.NewJobResource, resources.NewMyResource, // добавить сюда } } ``` ### 3.4 Что НЕ делать - Не добавлять поле в `Schema()` без добавления его в `Model` struct — паника при `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 1. Добавить в `job_types.go`: ```go type FunctionJobSpec struct { // +kubebuilder:validation:Required NewField string `json:"newField"` } ``` 2. Регенерировать deepcopy: ```bash 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/..."' ``` 3. Регенерировать CRD YAML: ```bash 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' ``` 4. Применить CRD в кластер: ```bash 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 Полная последовательность ```bash # Переменные 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 ```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: ```bash 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 подберёт его автоматически: ```bash 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/` должен быть только ОДИН бинарник — удалить старый! ```bash rm /tmp/sless-provider-dev/terraform-provider-sless_v0.1.18 ``` Dev override настроен в `~/.terraformrc`: ```hcl 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: ```bash 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' ``` После публикации проверить: ```bash curl -sk https://terra.k8c.ru/v1/providers/naeel/sless/versions ``` ### 6.3 Обновить версию в примерах В `examples/POSTGRES/main.tf` (и других примерах): ```hcl 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](../run_and_logs.md) — шаблоны SSH-команд, реквизиты машин - [doc/decisions/log.md](log.md) — обоснование архитектурных решений - [doc/infrastructure/overview.md](../infrastructure/overview.md) — инфраструктура кластера - [doc/progress.md](../progress.md) — трекер задач