feat: storage benchmark — local-path PV, vcd-disk-ext4 PV, S3 (10 iter each)
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# Fission storage benchmark: local-path PV / vcd-disk-ext4 PV / S3
|
||||
# Каждый вызов = полный путь: curl → Fission Router → Executor → Pod → Storage
|
||||
|
||||
set -e
|
||||
N=${1:-10} # число итераций, по умолчанию 10
|
||||
OUTDIR="$(cd "$(dirname "$0")/../../test-results" 2>/dev/null && pwd || echo /tmp)"
|
||||
OUTFILE="$OUTDIR/bench-storage-$(date +%Y-%m-%d_%H-%M-%S).txt"
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# Port-forward router
|
||||
kubectl port-forward svc/router 8889:80 -n fission &>/tmp/pf-bench.log &
|
||||
PF_PID=$!
|
||||
trap "kill $PF_PID 2>/dev/null" EXIT
|
||||
sleep 3
|
||||
|
||||
ROUTER="http://localhost:8889"
|
||||
|
||||
# JWT auth
|
||||
PASSWORD=$(kubectl get secret router -n fission -o jsonpath="{.data.password}" | base64 -d)
|
||||
USERNAME=$(kubectl get secret router -n fission -o jsonpath="{.data.username}" | base64 -d)
|
||||
TOKEN=$(curl -s -X POST "$ROUTER/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['accesstoken'])")
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "ERROR: не удалось получить JWT токен" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTH="-H \"Authorization: Bearer $TOKEN\""
|
||||
|
||||
# Функция запуска N итераций и сбора статистики
|
||||
bench_endpoint() {
|
||||
local LABEL="$1"
|
||||
local URL="$2"
|
||||
local times=()
|
||||
local errors=0
|
||||
|
||||
echo ""
|
||||
echo "=== $LABEL ==="
|
||||
printf "%-5s %-12s %s\n" "iter" "ms" "response"
|
||||
|
||||
for i in $(seq 1 $N); do
|
||||
START=$(date +%s%N)
|
||||
RESP=$(curl -s --max-time 60 -H "Authorization: Bearer $TOKEN" "$URL" 2>/dev/null)
|
||||
END=$(date +%s%N)
|
||||
MS=$(( (END - START) / 1000000 ))
|
||||
|
||||
if echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['status']=='ok'" 2>/dev/null; then
|
||||
STATUS="ok"
|
||||
INNER=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); k=list(d.keys()); print(' '.join(f'{k}={d[k]}' for k in k if k not in ['status']))" 2>/dev/null)
|
||||
else
|
||||
STATUS="ERR"
|
||||
INNER="$RESP"
|
||||
(( errors++ )) || true
|
||||
fi
|
||||
|
||||
times+=($MS)
|
||||
printf "%-5s %-12s %s\n" "$i" "${MS}ms" "$STATUS $INNER"
|
||||
done
|
||||
|
||||
# Статистика
|
||||
local sum=0 min=999999999 max=0
|
||||
for t in "${times[@]}"; do
|
||||
sum=$((sum + t))
|
||||
[ $t -lt $min ] && min=$t
|
||||
[ $t -gt $max ] && max=$t
|
||||
done
|
||||
local avg=$((sum / N))
|
||||
local ok=$((N - errors))
|
||||
|
||||
echo "---"
|
||||
printf " Успешно: %d/%d | min=%dms avg=%dms max=%dms\n" "$ok" "$N" "$min" "$avg" "$max"
|
||||
}
|
||||
|
||||
# Заголовок отчёта
|
||||
{
|
||||
echo "============================================================"
|
||||
echo " Fission Storage Benchmark"
|
||||
echo " Дата: $(date)"
|
||||
echo " Итераций: $N на endpoint"
|
||||
echo " Стек: curl → Router → Executor → Pod → Storage"
|
||||
echo "============================================================"
|
||||
|
||||
bench_endpoint "local-path PV (100Mi, rawfile CSI, WaitForFirstConsumer)" \
|
||||
"$ROUTER/check/local"
|
||||
|
||||
bench_endpoint "vcd-disk-ext4 PV (10Gi, VMware Cloud Director)" \
|
||||
"$ROUTER/check/vcd"
|
||||
|
||||
bench_endpoint "S3 ngcloud (s3.msk-1.ngcloud.ru, bucket=sless-functions)" \
|
||||
"$ROUTER/check/s3"
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " Завершено: $(date)"
|
||||
echo "============================================================"
|
||||
|
||||
} | tee "$OUTFILE"
|
||||
|
||||
echo ""
|
||||
echo "Результаты записаны: $OUTFILE"
|
||||
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
def main(event, context):
|
||||
path = "/mnt/data/check.txt"
|
||||
test_data = "storage-check-ok"
|
||||
|
||||
try:
|
||||
# Write
|
||||
t0 = time.time()
|
||||
with open(path, "w") as f:
|
||||
f.write(test_data)
|
||||
write_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Read
|
||||
t0 = time.time()
|
||||
with open(path, "r") as f:
|
||||
result = f.read()
|
||||
read_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Cleanup
|
||||
os.remove(path)
|
||||
|
||||
if result == test_data:
|
||||
return {"status": "ok", "write_ms": write_ms, "read_ms": read_ms, "mount": path}
|
||||
else:
|
||||
return {"status": "error", "detail": "data mismatch"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "detail": str(e)}
|
||||
@@ -0,0 +1,48 @@
|
||||
import time
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
|
||||
|
||||
def main(event, context):
|
||||
# Credentials mounted by Fission at /secrets/<namespace>/<secretname>/<key>
|
||||
try:
|
||||
with open("/secrets/default/bench-s3-secret/access-key") as f:
|
||||
access_key = f.read().strip()
|
||||
with open("/secrets/default/bench-s3-secret/secret-key") as f:
|
||||
secret_key = f.read().strip()
|
||||
except Exception as e:
|
||||
return {"status": "error", "detail": f"secret read: {e}"}
|
||||
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
endpoint_url="https://s3.msk-1.ngcloud.ru",
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
config=Config(signature_version="s3v4"),
|
||||
)
|
||||
|
||||
bucket = "sless-functions"
|
||||
key = "storage-check/check.txt"
|
||||
test_data = b"storage-check-ok"
|
||||
|
||||
try:
|
||||
# Write
|
||||
t0 = time.time()
|
||||
s3.put_object(Bucket=bucket, Key=key, Body=test_data)
|
||||
write_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Read
|
||||
t0 = time.time()
|
||||
result = s3.get_object(Bucket=bucket, Key=key)["Body"].read()
|
||||
read_ms = round((time.time() - t0) * 1000, 2)
|
||||
|
||||
# Cleanup
|
||||
s3.delete_object(Bucket=bucket, Key=key)
|
||||
|
||||
if result == test_data:
|
||||
return {"status": "ok", "write_ms": write_ms, "read_ms": read_ms, "bucket": bucket}
|
||||
else:
|
||||
return {"status": "error", "detail": "data mismatch"}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "detail": str(e)}
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
NS=default
|
||||
|
||||
echo "=== Removing HTTP triggers ==="
|
||||
fission httptrigger delete --name check-local-t --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger delete --name check-vcd-t --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger delete --name check-s3-t --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing functions ==="
|
||||
fission fn delete --name check-local --namespace $NS 2>/dev/null || true
|
||||
fission fn delete --name check-vcd --namespace $NS 2>/dev/null || true
|
||||
fission fn delete --name check-s3 --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing environment ==="
|
||||
fission env delete --name bench-py --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing PVCs ==="
|
||||
kubectl delete pvc storage-check-local -n $NS 2>/dev/null || true
|
||||
kubectl delete pvc storage-check-vcd -n $NS 2>/dev/null || true
|
||||
|
||||
echo "=== Removing secret ==="
|
||||
kubectl delete secret bench-s3-secret -n $NS 2>/dev/null || true
|
||||
|
||||
echo "Done."
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
NS=default
|
||||
S3_KEY=0GLQRD38H4I6RBDB0EWJ
|
||||
S3_SECRET=eTFibiHmBd96IApj9PYsboTR6OBoD7osxoarHykw
|
||||
|
||||
echo "=== PVCs ==="
|
||||
kubectl apply -f pvc-local.yaml
|
||||
kubectl apply -f pvc-vcd.yaml
|
||||
|
||||
echo "=== S3 Secret ==="
|
||||
kubectl create secret generic bench-s3-secret \
|
||||
--from-literal=access-key="$S3_KEY" \
|
||||
--from-literal=secret-key="$S3_SECRET" \
|
||||
-n $NS 2>/dev/null || echo "secret already exists"
|
||||
|
||||
echo "=== Environment (no resource limits to avoid quota) ==="
|
||||
fission env create --name bench-py --image naeel/fission-python-env:v1.1 \
|
||||
--mincpu 0 --maxcpu 0 --minmemory 0 --maxmemory 0 \
|
||||
--poolsize 1 --namespace $NS 2>/dev/null || echo "env already exists"
|
||||
|
||||
echo "=== Functions ==="
|
||||
# check-local и check-vcd используют newdeploy: 1 под = 1 PVC (RWO работает)
|
||||
fission fn create --name check-local --env bench-py --code check_pv.py \
|
||||
--executortype newdeploy --minscale 1 --maxscale 1 --namespace $NS 2>/dev/null || \
|
||||
fission fn update --name check-local --code check_pv.py --namespace $NS
|
||||
|
||||
fission fn create --name check-vcd --env bench-py --code check_pv.py \
|
||||
--executortype newdeploy --minscale 1 --maxscale 1 --namespace $NS 2>/dev/null || \
|
||||
fission fn update --name check-vcd --code check_pv.py --namespace $NS
|
||||
|
||||
# check-s3 использует poolmgr
|
||||
fission fn create --name check-s3 --env bench-py --code check_s3.py \
|
||||
--namespace $NS --secret bench-s3-secret 2>/dev/null || \
|
||||
fission fn update --name check-s3 --code check_s3.py --namespace $NS --secret bench-s3-secret
|
||||
|
||||
echo "=== Patching podspec (PVC mounts) ==="
|
||||
kubectl patch function check-local -n $NS --type=merge -p "$(cat patch-local.json)"
|
||||
kubectl patch function check-vcd -n $NS --type=merge -p "$(cat patch-vcd.json)"
|
||||
|
||||
echo "=== HTTP Triggers ==="
|
||||
fission httptrigger create --name check-local-t --url /check/local --function check-local --method GET --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger create --name check-vcd-t --url /check/vcd --function check-vcd --method GET --namespace $NS 2>/dev/null || true
|
||||
fission httptrigger create --name check-s3-t --url /check/s3 --function check-s3 --method GET --namespace $NS 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "Done! Жди ~60s (newdeploy pods + PVC binding), затем: ./test.sh"
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: fission.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: check-local
|
||||
namespace: default
|
||||
spec:
|
||||
InvokeStrategy:
|
||||
ExecutionStrategy:
|
||||
ExecutorType: newdeploy
|
||||
MinScale: 1
|
||||
MaxScale: 1
|
||||
StrategyType: execution
|
||||
environment:
|
||||
name: bench-py
|
||||
namespace: default
|
||||
package:
|
||||
functionName: main.main
|
||||
packageref:
|
||||
name: check-local-7d087421-7802-4af1-b111-03d71fe738cd
|
||||
namespace: default
|
||||
podspec:
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: storage-check-local
|
||||
containers:
|
||||
- name: bench-py
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /mnt/data
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: fission.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: check-vcd
|
||||
namespace: default
|
||||
spec:
|
||||
InvokeStrategy:
|
||||
ExecutionStrategy:
|
||||
ExecutorType: newdeploy
|
||||
MinScale: 1
|
||||
MaxScale: 1
|
||||
StrategyType: execution
|
||||
environment:
|
||||
name: bench-py
|
||||
namespace: default
|
||||
package:
|
||||
functionName: main.main
|
||||
packageref:
|
||||
name: check-vcd-6e6076b2-2207-49f3-a12d-83fb50633285
|
||||
namespace: default
|
||||
podspec:
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: storage-check-vcd
|
||||
containers:
|
||||
- name: bench-py
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /mnt/data
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"spec": {
|
||||
"podspec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "data",
|
||||
"persistentVolumeClaim": {
|
||||
"claimName": "storage-check-local"
|
||||
}
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "bench-py",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "data",
|
||||
"mountPath": "/mnt/data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"spec": {
|
||||
"podspec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "data",
|
||||
"persistentVolumeClaim": {
|
||||
"claimName": "storage-check-vcd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "bench-py",
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "data",
|
||||
"mountPath": "/mnt/data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: storage-check-local
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Mi
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: storage-check-vcd
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: vcd-disk-ext4
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Port-forward router to localhost:8888
|
||||
kubectl port-forward svc/router 8888:80 -n fission &>/tmp/pf.log &
|
||||
PF_PID=$!
|
||||
trap "kill $PF_PID 2>/dev/null" EXIT
|
||||
sleep 3
|
||||
|
||||
ROUTER="localhost:8888"
|
||||
echo "Router via port-forward: $ROUTER"
|
||||
echo ""
|
||||
|
||||
echo "=== [1/3] local-path ==="
|
||||
curl -sf --max-time 30 "http://$ROUTER/check/local" | python3 -m json.tool || echo "ERROR: no response"
|
||||
|
||||
echo ""
|
||||
echo "=== [2/3] vcd-disk-ext4 ==="
|
||||
curl -sf --max-time 30 "http://$ROUTER/check/vcd" | python3 -m json.tool || echo "ERROR: no response"
|
||||
|
||||
echo ""
|
||||
echo "=== [3/3] S3 (ngcloud) ==="
|
||||
curl -sf --max-time 30 "http://$ROUTER/check/s3" | python3 -m json.tool || echo "ERROR: no response"
|
||||
Reference in New Issue
Block a user