feat: PV storage backend + fix fetcher image v1.23.0

- helm/fission-values.yaml: storagesvc на vcd-disk-ext4 PV 2Gi
  fetcher image: naeel/fetcher:v1.23.0 (собран из fission-src/cmd/fetcher)
- scripts/migrate_s3_to_pv.py: скрипт перелива S3→storagesvc (no-op: 0 пакетов с s3://)
- doc/report-2026-05-19-pv-migration-bugs.md: баги helm upgrade, FETCHER_IMAGE, field manager
This commit is contained in:
“Naeel”
2026-05-19 09:46:57 +04:00
parent 865d33a0e1
commit 3eb4f5669b
3 changed files with 279 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
# Отчёт: миграция S3→PV + баги helm upgrade — 2026-05-19
## Что делали
1. Бенчмарк 3 хранилищ (local-path PV, vcd-disk-ext4 PV, S3)
2. Переехали storagesvc с S3 на PV (vcd-disk-ext4, 2Gi)
3. Обнаружили что S3 фактически не использовался (все Package URL — http://storagesvc..., не s3://)
4. Миграция была no-op: перенесено=0, пропущено=44
---
## Баги и ошибки
### БАГ 1: helm upgrade откатил executor/router с v1.23.0 → v1.22.1
**Причина:** в `helm/fission-values.yaml` был прописан `imageTag: v1.22.1` вместо `v1.23.0`.
До helm upgrade executor/router жили вне helm (задеплоены через `kubectl set image`).
**Симптом:** консоль не показывала функции.
**Исправление:** изменён `imageTag: v1.23.0` в values, пересоздан executor/router.
---
### БАГ 2: field manager конфликт — fission-console vs helm
**Причина:** fission-console патчит `FISSION_RESOURCE_NAMESPACES` в executor и router через server-side apply.
Когда helm пытается обновить те же deployment-ы — конфликт field manager.
**Симптом:**
```
conflict with "fission-console" using apps/v1:
.spec.template.spec.containers[name="executor"].env[name="FISSION_RESOURCE_NAMESPACES"].value
```
**Обход:** перед `helm upgrade` удалять executor и router вручную:
```bash
kubectl delete deploy executor router -n fission
helm upgrade fission fission-charts/fission-all --version 1.22.1 \
-n fission -f helm/fission-values.yaml --no-hooks --timeout 180s
```
**Системное решение:** нужно чтобы fission-console не трогал deployments executor/router напрямую (FISSION_RESOURCE_NAMESPACES должен читаться из ConfigMap или CRD, а не инжектироваться в deployment env).
---
### БАГ 3: FETCHER_IMAGE = fission/fetcher:v1.22.0 — образ не существует
**Причина:** helm chart `fission-all:1.22.1` имеет дефолтное значение:
```yaml
fetcher:
image: fission/fetcher
imageTag: v1.22.0
```
Этот образ на Docker Hub не существует (fission переехал на ghcr.io).
До наших helm upgrade executor не управлялся helm — FETCHER_IMAGE либо не был задан (код падал на дефолт `ghcr.io/fission/fetcher`), либо был задан правильно вручную.
После helm upgrade — chart прописал неправильный FETCHER_IMAGE.
**Симптом:**
- Pool pods: `ImagePullBackOff` на контейнере `fetcher`
- `fission/fetcher:v1.22.0: not found` на Docker Hub
- Функции не запускаются (504 при invoke)
**Правильное значение:** `naeel/fission-bundle:v1.23.0`
Fetcher — часть fission-bundle, отдельного образа нет.
Находится в: `fission-src/pkg/fetcher/`
**Неправильные ключи в values (игнорируются чартом):**
```yaml
fetcherImage: naeel/fission-bundle # ← НЕПРАВИЛЬНО, такого ключа нет
fetcherImageTag: v1.23.0 # ← НЕПРАВИЛЬНО
```
**Правильные ключи:**
```yaml
fetcher:
image: naeel/fission-bundle # ← ПРАВИЛЬНО
imageTag: v1.23.0 # ← ПРАВИЛЬНО
```
Шаблон чарта (`charts/fission-all/templates/_helpers.tpl`):
```
{{- define "fetcherImage" -}}
{{- $args := list (.Values.fetcher.repository | default .Values.repository) .Values.fetcher.image .Values.fetcher.imageTag -}}
```
---
### БАГ 4: router JWT пароль сменился при helm upgrade
**Причина:** helm upgrade пересоздал secret `router` с новым паролем.
| До | После |
|----|-------|
| `slxGj3G3FAD7l5ms2tJ9` | `58oKMuBIvmBBXHXqaLHw` |
fission-console читает пароль из secret динамически (`valueFrom: secretKeyRef`) — поэтому сам работает.
CLI-инструменты с сохранённым токеном требуют перегенерации токена.
---
## Текущее состояние (на момент отчёта)
| Компонент | Образ | Статус |
|-----------|-------|--------|
| executor, router, buildermgr, storagesvc, timer, webhook, kubewatcher | `naeel/fission-bundle:v1.23.0` | ✅ Running |
| fission-console | `naeel/fission-console:v1.3.92` | ✅ Running |
| FETCHER_IMAGE (env в executor) | `fission/fetcher:v1.22.0` | ❌ НЕПРАВИЛЬНО |
| fission-storage-pvc | 2Gi, vcd-disk-ext4, Bound | ✅ OK |
## Что нужно сделать
1. Исправить `helm/fission-values.yaml` — заменить `fetcherImage`/`fetcherImageTag` на правильную секцию `fetcher:`
2. Сделать helm upgrade (с предварительным удалением executor/router)
3. Проверить что pool pods поднимаются без ImagePullBackOff
4. Проверить что функции nubes1 (`a1`, `c1`) запускаются
+30
View File
@@ -0,0 +1,30 @@
# Fission helm values — PV (local storage) backend
# Применять: helm upgrade fission fission-charts/fission-all --version 1.22.1 \
# -n fission -f helm/fission-values.yaml
#
# Секретов нет — local storage не требует credentials.
# При деплое в новый кластер — только этот файл.
authentication:
enabled: true
defaultNamespace: fission-functions
image: naeel/fission-bundle
imageTag: v1.23.0
fetcher:
image: naeel/fetcher
imageTag: v1.23.0
persistence:
enabled: true
storageType: local
storageClass: vcd-disk-ext4 # сетевой block-device VMware Cloud Director (надёжнее local-path)
size: 2Gi
accessMode: ReadWriteOnce
repository: ""
routerServiceType: ClusterIP
serviceType: ClusterIP
analytics: false
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""
migrate_s3_to_pv.py — перелив архивов функций из S3 → storagesvc (PV-backend)
Запуск: kubectl exec -n fission deploy/storagesvc -- python3 /tmp/migrate.py
ИЛИ: запустить как Job в кластере
Что делает:
1. Читает все Package CRD во всех namespace
2. Для каждого пакета с URL s3://... скачивает архив из S3
3. Загружает в storagesvc HTTP API → получает новый URL
4. Обновляет Package CRD: .spec.deployment.url = новый URL
"""
import os
import sys
import json
import time
import urllib.request
import urllib.parse
# S3 credentials (из env или хардкод для ручного запуска)
S3_ENDPOINT = os.environ.get("S3_ENDPOINT", "https://s3.msk-1.ngcloud.ru")
S3_BUCKET = os.environ.get("S3_BUCKET", "sless-functions")
S3_ACCESS_KEY = os.environ.get("S3_ACCESS_KEY", "0GLQRD38H4I6RBDB0EWJ")
S3_SECRET_KEY = os.environ.get("S3_SECRET_KEY", "eTFibiHmBd96IApj9PYsboTR6OBoD7osxoarHykw")
S3_REGION = os.environ.get("S3_REGION", "msk-1")
STORAGESVC_URL = os.environ.get("STORAGESVC_URL", "http://storagesvc.fission.svc.cluster.local")
import subprocess
def kubectl(args, input_data=None):
cmd = ["kubectl"] + args
r = subprocess.run(cmd, capture_output=True, text=True, input=input_data)
if r.returncode != 0:
raise RuntimeError(f"kubectl {' '.join(args)} failed: {r.stderr}")
return r.stdout
def get_all_packages():
out = kubectl(["get", "packages", "--all-namespaces", "-o", "json"])
return json.loads(out)["items"]
def is_s3_url(url):
return url and url.startswith("s3://")
def s3_key_from_url(url):
# s3://sless-functions/fission/abc123 → fission/abc123
path = url[len(f"s3://{S3_BUCKET}/"):]
return path
def download_from_s3(s3_key):
import boto3
s3 = boto3.client(
"s3",
endpoint_url=S3_ENDPOINT,
aws_access_key_id=S3_ACCESS_KEY,
aws_secret_access_key=S3_SECRET_KEY,
region_name=S3_REGION,
)
obj = s3.get_object(Bucket=S3_BUCKET, Key=s3_key)
return obj["Body"].read()
def upload_to_storagesvc(data):
import urllib.request, uuid
boundary = uuid.uuid4().hex
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="uploadfile"; filename="archive.zip"\r\n'
f"Content-Type: application/octet-stream\r\n\r\n"
).encode() + data + f"\r\n--{boundary}--\r\n".encode()
req = urllib.request.Request(
f"{STORAGESVC_URL}/v1/archive",
data=body,
method="POST",
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
with urllib.request.urlopen(req, timeout=60) as resp:
result = json.loads(resp.read())
return result["id"] # storagesvc возвращает {"id": "http://storagesvc.../v1/archive?id=..."}
def update_package_url(ns, name, new_url):
patch = json.dumps({"spec": {"deployment": {"url": new_url, "type": "url"}}})
kubectl(["patch", "package", name, "-n", ns, "--type=merge", f"--patch={patch}"])
def main():
try:
import boto3
except ImportError:
print("ERROR: boto3 не установлен. Запусти: pip install boto3")
sys.exit(1)
packages = get_all_packages()
total = len(packages)
migrated = 0
skipped = 0
errors = 0
print(f"Всего пакетов: {total}")
print()
for pkg in packages:
ns = pkg["metadata"]["namespace"]
name = pkg["metadata"]["name"]
url = pkg.get("spec", {}).get("deployment", {}).get("url", "")
if not is_s3_url(url):
print(f" SKIP {ns}/{name} — url: {url or '(empty)'}")
skipped += 1
continue
s3_key = s3_key_from_url(url)
print(f" MIG {ns}/{name} s3://{S3_BUCKET}/{s3_key}", end=" ", flush=True)
try:
data = download_from_s3(s3_key)
new_id = upload_to_storagesvc(data)
new_url = f"{STORAGESVC_URL}/v1/archive?id={new_id}" if not new_id.startswith("http") else new_id
update_package_url(ns, name, new_url)
print(f"{new_url}")
migrated += 1
except Exception as e:
print(f" ERROR: {e}")
errors += 1
print()
print(f"Готово: перенесено={migrated} пропущено={skipped} ошибок={errors}")
if __name__ == "__main__":
main()