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:
@@ -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`) запускаются
|
||||||
@@ -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
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user