- 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: правки из текущей сессии
360 lines
15 KiB
Markdown
360 lines
15 KiB
Markdown
# 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) — трекер задач
|