feat(v0.1.58): ImageExists cache hit, timing analysis, in-cluster registry plan
This commit is contained in:
@@ -4,6 +4,68 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-22 — БАГ: CreateService/CreateFunction возвращает 409 при `terraform apply -replace` (ИСПРАВЛЕН)
|
||||
|
||||
### Симптом
|
||||
|
||||
```
|
||||
terraform apply -replace=sless_service.pg_info
|
||||
sless_service.pg_info: Destroying... [name=pg-info]
|
||||
sless_service.pg_info: Destruction complete
|
||||
sless_service.pg_info: Creating...
|
||||
Error: create service: status 409: {"error":"service already exists"}
|
||||
```
|
||||
|
||||
Все 22+ сервиса из `-replace` падают с 409 при пересоздании.
|
||||
|
||||
### Точная причина
|
||||
|
||||
**Цепочка событий** (воспроизводится только при наличии finalizer):
|
||||
|
||||
1. `terraform` вызывает `DELETE /services/pg-info`
|
||||
2. API делает `h.K8s.Delete(svc)` → k8s **НЕ удаляет объект** немедленно.
|
||||
Вместо этого — выставляет `DeletionTimestamp` на объекте и ждёт.
|
||||
3. `service_controller.go` (асинхронно!) обрабатывает удаление:
|
||||
- сносит Deployment, k8s Service, Ingress
|
||||
- затем вызывает `svc.Finalizers = removeString(svc.Finalizers, serviceFinalizerName)`
|
||||
- только после этого k8s реально удаляет CRD объект из etcd
|
||||
- **латентность: 1–5 секунд**
|
||||
4. `terraform` **немедленно** вызывает `POST /services/pg-info`
|
||||
5. API делает `h.K8s.Create(svc)` → etcd возвращает `IsAlreadyExists`
|
||||
6. Старый код проверял: `phase == Failed`? → нет, было `Ready` → `shouldRecreate=false` → **409**
|
||||
|
||||
**Ключевое: объект существует с `DeletionTimestamp != zero`, то есть он уже "мёртвый", но finalizer ещё не снят. Старый код этого не проверял.**
|
||||
|
||||
### Файл с багом
|
||||
|
||||
`internal/api/handler/services.go` → `CreateService()` — строка `shouldRecreate`
|
||||
`internal/api/handler/functions.go` → `CreateFunction()` — аналогичная логика
|
||||
|
||||
### Исправление
|
||||
|
||||
В обоих файлах добавлена проверка `!existing.DeletionTimestamp.IsZero()` **до** проверки `shouldRecreate`:
|
||||
|
||||
```go
|
||||
if getErr == nil && !existing.DeletionTimestamp.IsZero() {
|
||||
// Объект удаляется: polling каждую секунду до 30 сек
|
||||
for i := 0; i < 30; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
if errors.IsNotFound(h.K8s.Get(...)) {
|
||||
// исчez — создаём
|
||||
}
|
||||
}
|
||||
// таймаут — возвращаем 409 с "try again later"
|
||||
}
|
||||
```
|
||||
|
||||
### Почему именно polling, а не Watch
|
||||
|
||||
`http.ResponseWriter` не поддерживает long-poll без дополнительного механизма.
|
||||
Watch на объект внутри HTTP handler — антипаттерн (использует goroutine leak при отмене).
|
||||
30 × 1s — достаточно для любого разумного кластера; terraform имеет свой retry.
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-22 — ПОВЕДЕНИЕ: оператор выставляет Ready до готовности pod (known limitation)
|
||||
|
||||
### Симптом
|
||||
|
||||
Reference in New Issue
Block a user