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