fix: rollout restart после kaniko rebuild (operator v0.1.11)
- controllers/function_controller.go: в ensureDeployment добавить аннотацию kubectl.kubernetes.io/restartedAt = fn.Status.LastBuiltAt при обновлении существующего Deployment — принудительный rolling restart после каждой сборки - deployments/k8s/operator.yaml: образ обновлён до v0.1.11 - doc/errors/log.md: задокументирована причина и решение - doc/decisions/log.md: добавлено правило restartedAt + :latest - doc/progress.md: версия обновлена до v0.1.11 Причина бага: imagePullPolicy=IfNotPresent + :latest — kubelet кешировал старый образ, pod не рестартовал после успешного kaniko build.
This commit is contained in:
@@ -211,6 +211,15 @@ func (r *FunctionReconciler) ensureDeployment(ctx context.Context, fn *slessv1al
|
|||||||
// Обновляем образ и imagePullSecrets если изменились (новая сборка или смена конфига)
|
// Обновляем образ и imagePullSecrets если изменились (новая сборка или смена конфига)
|
||||||
existing.Spec.Template.Spec.Containers[0].Image = fn.Status.ImageRef
|
existing.Spec.Template.Spec.Containers[0].Image = fn.Status.ImageRef
|
||||||
existing.Spec.Template.Spec.ImagePullSecrets = desired.Spec.Template.Spec.ImagePullSecrets
|
existing.Spec.Template.Spec.ImagePullSecrets = desired.Spec.Template.Spec.ImagePullSecrets
|
||||||
|
// Принудительный rollout restart при каждой новой сборке.
|
||||||
|
// Необходимо т.к. image tag :latest не меняется — без этой аннотации
|
||||||
|
// kubelet берёт кешированный образ и pod не видит новый код.
|
||||||
|
if fn.Status.LastBuiltAt != nil {
|
||||||
|
if existing.Spec.Template.Annotations == nil {
|
||||||
|
existing.Spec.Template.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
existing.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = fn.Status.LastBuiltAt.Time.Format(time.RFC3339)
|
||||||
|
}
|
||||||
if err := r.Update(ctx, existing); err != nil {
|
if err := r.Update(ctx, existing); err != nil {
|
||||||
return ctrl.Result{}, fmt.Errorf("update deployment: %w", err)
|
return ctrl.Result{}, fmt.Errorf("update deployment: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Состав:
|
# Состав:
|
||||||
# - ConfigMap: не-секретные env vars (S3_ENDPOINT, REGISTRY_HOST и т.д.)
|
# - ConfigMap: не-секретные env vars (S3_ENDPOINT, REGISTRY_HOST и т.д.)
|
||||||
# - Secret: секретные данные (S3 keys, postgres DSN, API token, docker auth)
|
# - Secret: секретные данные (S3 keys, postgres DSN, API token, docker auth)
|
||||||
# - Deployment: оператор naeel/sless-operator:v0.1.4 в namespace sless
|
# - Deployment: оператор naeel/sless-operator:v0.1.11 в namespace sless
|
||||||
# - Service: ClusterIP :9090 (REST API)
|
# - Service: ClusterIP :9090 (REST API)
|
||||||
# - Ingress: sless-api.kube5s.ru → :9090 (внешний доступ с TLS)
|
# - Ingress: sless-api.kube5s.ru → :9090 (внешний доступ с TLS)
|
||||||
#
|
#
|
||||||
@@ -70,7 +70,7 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: operator
|
- name: operator
|
||||||
# При обновлении версии оператора — менять тег здесь (не latest!)
|
# При обновлении версии оператора — менять тег здесь (не latest!)
|
||||||
image: naeel/sless-operator:v0.1.10
|
image: naeel/sless-operator:v0.1.11
|
||||||
# Always — чтобы всегда тянуть по точному тегу (не кешировать старый)
|
# Always — чтобы всегда тянуть по точному тегу (не кешировать старый)
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -245,3 +245,20 @@
|
|||||||
|
|
||||||
**Правило проекта:** В `sless_function.code_hash` всегда использовать `filesha256(source_file)`, не `archive_file.output_md5`.
|
**Правило проекта:** В `sless_function.code_hash` всегда использовать `filesha256(source_file)`, не `archive_file.output_md5`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-03-08 — Rollout restart после kaniko build (imagePullPolicy + :latest)
|
||||||
|
|
||||||
|
**Проблема:** После успешной kaniko сборки pod не перезапускался — kubelet брал кешированный образ `:latest` (imagePullPolicy: IfNotPresent). Функция возвращала старый код.
|
||||||
|
|
||||||
|
**Решение:** В `ensureDeployment` при обновлении существующего Deployment проставляем аннотацию:
|
||||||
|
```go
|
||||||
|
existing.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = fn.Status.LastBuiltAt.Time.Format(time.RFC3339)
|
||||||
|
```
|
||||||
|
Значение привязано к `fn.Status.LastBuiltAt` → меняется при каждой сборке → Kubernetes делает rolling restart → свежий образ гарантированно пул-ится.
|
||||||
|
|
||||||
|
**Правило проекта:** При использовании `:latest` tag всегда явно проставлять `restartedAt` annotation при обновлении кода.
|
||||||
|
|
||||||
|
**Версия:** operator `naeel/sless-operator:v0.1.11`
|
||||||
|
|||||||
@@ -301,3 +301,18 @@ k8s.io/api should be direct
|
|||||||
**Решение:** `go mod tidy` автоматически расставил правильные аннотации.
|
**Решение:** `go mod tidy` автоматически расставил правильные аннотации.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-03-08 — После kaniko rebuild функция возвращает старый код
|
||||||
|
|
||||||
|
**Проблема:** `terraform apply` с изменённым кодом успешно завершал kaniko build, но функция продолжала возвращать старую версию кода.
|
||||||
|
|
||||||
|
**Причина:** `imagePullPolicy: IfNotPresent` (default в k8s) + `:latest` тег — после успешной сборки оператор обновлял `.spec.template.spec.containers[0].image`, но image tag не менялся (всегда `:latest`). Kubernetes видел что образ уже есть на ноде и не пул-ил новый. Pod оставался запущенным со старым образом.
|
||||||
|
|
||||||
|
**Решение:** В `controllers/function_controller.go`, функция `ensureDeployment`, после обновления image добавлена аннотация:
|
||||||
|
```go
|
||||||
|
existing.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = fn.Status.LastBuiltAt.Time.Format(time.RFC3339)
|
||||||
|
```
|
||||||
|
Аннотация меняется при каждой новой сборке (значение = `lastBuiltAt`) → Kubernetes делает rolling restart → kubelet пул-ит свежий образ.
|
||||||
|
Оператор: **v0.1.11**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Прогресс разработки
|
# Прогресс разработки
|
||||||
|
|
||||||
Последнее обновление: 2026-03-08 (operator v0.1.10, provider v0.1.5)
|
Последнее обновление: 2026-03-08 (operator v0.1.11, provider v0.1.5)
|
||||||
|
|
||||||
## Статусы: ✅ готово | 🔄 в процессе | ⏳ не начато
|
## Статусы: ✅ готово | 🔄 в процессе | ⏳ не начато
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
// Используется с sless_trigger (постоянный эндпоинт).
|
// Используется с sless_trigger (постоянный эндпоинт).
|
||||||
exports.handle = async (event) => {
|
exports.handle = async (event) => {
|
||||||
const name = event.name || 'World';
|
const name = event.name || 'World';
|
||||||
return { message: `Hello, ${name}! HTTP ` };
|
return { message: `Hello, ${name}! HTTP !!!` };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -36,7 +36,7 @@ resource "sless_job" "hello_run" {
|
|||||||
function = sless_function.hello_job.name
|
function = sless_function.hello_job.name
|
||||||
event_json = jsonencode({ numbers = [1, 2, 3, 4, 5] })
|
event_json = jsonencode({ numbers = [1, 2, 3, 4, 5] })
|
||||||
wait_timeout_sec = 600
|
wait_timeout_sec = 600
|
||||||
run_id = 1
|
run_id = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
output "job_phase" {
|
output "job_phase" {
|
||||||
|
|||||||
Reference in New Issue
Block a user