Files
sless/doc/decisions/build-deploy-pipeline.md
Naeel 7f7aa44e59 chore: удаление устаревших examples, новый doc, правки funcs-service
- 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: правки из текущей сессии
2026-03-21 08:46:30 +03:00

15 KiB
Raw Permalink Blame History

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 Создать новый ресурс

  1. Создать файл terraform/provider/internal/resources/myresource_resource.go
  2. Реализовать интерфейс resource.Resource (минимум: Metadata, Schema, Create, Read, Update, Delete)
  3. Зарегистрировать в provider/provider.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:
type FunctionJobSpec struct {
    // +kubebuilder:validation:Required
    NewField string `json:"newField"`
}
  1. Регенерировать 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/..."'
  1. Регенерировать 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'
  1. Применить 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. Связанные файлы документации