feat(deploy): 增加三节点 K3s 高可用迁移能力
新增 WireGuard 全互联、三 server embedded-etcd K3s、CloudNativePG 双实例、Barman OSS 备份、双 NGINX、Kubernetes Secret/RBAC 与本地旧文件按严格 24 小时清理。 新增维护窗口数据迁移、digest 固定滚动发布、应用回滚、跨节点文件 E2E、节点和数据库故障演练、CNPG 恢复与 etcd 快照验收脚本;洛杉矶仅作为带 NoSchedule 污点的仲裁节点。 所有生产 Secret 只在执行时从 0600 本地环境和旧生产容器导入,仓库不保存凭据;公网入口保持人工 DNS 故障切换边界。 验证:bash -n、ShellCheck、kubectl kustomize、Node 语法检查、Secret 扫描、OSS put/head/delete 实测。
This commit is contained in:
+279
@@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
[[ $(id -u) -eq 0 ]] || { echo 'run as root' >&2; exit 1; }
|
||||
|
||||
config_file=${AI_GATEWAY_CLUSTER_RELEASE_CONFIG:-/etc/easyai-ai-gateway-cluster-release.conf}
|
||||
manifest_tool=${AI_GATEWAY_RELEASE_MANIFEST_TOOL:-/usr/local/lib/easyai-ai-gateway-release/release-manifest.mjs}
|
||||
[[ -f $config_file && ! -L $config_file ]] || { echo 'missing cluster release config' >&2; exit 1; }
|
||||
[[ -f $manifest_tool && ! -L $manifest_tool ]] || { echo 'missing release manifest validator' >&2; exit 1; }
|
||||
# shellcheck source=/dev/null
|
||||
source "$config_file"
|
||||
|
||||
: "${RELEASES_DIR:?}"
|
||||
: "${NAMESPACE:=easyai}"
|
||||
: "${PUBLIC_BASE_URL:=https://ai.51easyai.com}"
|
||||
: "${K3S_BIN:=/usr/local/bin/k3s}"
|
||||
[[ $RELEASES_DIR == /* && $NAMESPACE =~ ^[a-z0-9-]+$ ]] || {
|
||||
echo 'invalid cluster release configuration' >&2
|
||||
exit 1
|
||||
}
|
||||
[[ $PUBLIC_BASE_URL =~ ^https://[A-Za-z0-9.-]+$ ]] || {
|
||||
echo 'invalid public base URL' >&2
|
||||
exit 1
|
||||
}
|
||||
for command in "$K3S_BIN" curl flock install jq node; do
|
||||
command -v "$command" >/dev/null 2>&1 || { echo "$command is required" >&2; exit 1; }
|
||||
done
|
||||
|
||||
kubectl=("$K3S_BIN" kubectl)
|
||||
install -d -m 0700 "$RELEASES_DIR"
|
||||
exec 9>"$RELEASES_DIR/.release.lock"
|
||||
flock -n 9 || { echo 'another cluster release operation is running' >&2; exit 1; }
|
||||
|
||||
manifest_get() {
|
||||
node "$manifest_tool" get "$1" "$2"
|
||||
}
|
||||
|
||||
current_to_file() {
|
||||
local output=$1
|
||||
"${kubectl[@]}" get configmap easyai-gateway-release -n "$NAMESPACE" \
|
||||
-o jsonpath='{.data.manifest\.json}' >"$output"
|
||||
[[ -s $output ]]
|
||||
node "$manifest_tool" validate "$output" >/dev/null
|
||||
}
|
||||
|
||||
wait_for_url() {
|
||||
local label=$1
|
||||
local url=$2
|
||||
local expected=${3:-}
|
||||
local body
|
||||
for _ in $(seq 1 60); do
|
||||
if body=$(curl -fsS --max-time 5 "$url" 2>/dev/null); then
|
||||
if [[ -z $expected || $body == *"$expected"* ]]; then
|
||||
echo "[cluster-release] verified $label"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "release probe failed: $label ($url)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_site() {
|
||||
local site=$1
|
||||
local address
|
||||
case $site in
|
||||
hongkong) address=10.77.0.2 ;;
|
||||
ningbo) address=10.77.0.1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
wait_for_url "$site API health" "http://$address:30088/api/v1/healthz" easyai-ai-gateway
|
||||
wait_for_url "$site API readiness" "http://$address:30088/api/v1/readyz" '"ok":true'
|
||||
wait_for_url "$site Web" "http://$address:30178/" 'EasyAI AI Gateway'
|
||||
}
|
||||
|
||||
rollout_site() {
|
||||
local site=$1
|
||||
local api_image=$2
|
||||
local web_image=$3
|
||||
local api_changed=$4
|
||||
local web_changed=$5
|
||||
if [[ $api_changed == true ]]; then
|
||||
"${kubectl[@]}" set image "deployment/easyai-api-$site" -n "$NAMESPACE" \
|
||||
"api=$api_image"
|
||||
"${kubectl[@]}" rollout status "deployment/easyai-api-$site" -n "$NAMESPACE" --timeout=300s
|
||||
fi
|
||||
if [[ $web_changed == true ]]; then
|
||||
"${kubectl[@]}" set image "deployment/easyai-web-$site" -n "$NAMESPACE" \
|
||||
"web=$web_image"
|
||||
"${kubectl[@]}" rollout status "deployment/easyai-web-$site" -n "$NAMESPACE" --timeout=300s
|
||||
fi
|
||||
verify_site "$site"
|
||||
}
|
||||
|
||||
run_backup() {
|
||||
local source_sha=$1
|
||||
local backup=easyai-pre-release-${source_sha:0:12}
|
||||
cat <<EOF | "${kubectl[@]}" apply -f - >/dev/null
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Backup
|
||||
metadata:
|
||||
name: $backup
|
||||
namespace: $NAMESPACE
|
||||
spec:
|
||||
cluster:
|
||||
name: easyai-postgres
|
||||
method: plugin
|
||||
pluginConfiguration:
|
||||
name: barman-cloud.cloudnative-pg.io
|
||||
EOF
|
||||
for _ in $(seq 1 120); do
|
||||
phase=$("${kubectl[@]}" get backup "$backup" -n "$NAMESPACE" \
|
||||
-o jsonpath='{.status.phase}' 2>/dev/null || true)
|
||||
[[ ${phase,,} == completed ]] && return 0
|
||||
[[ ${phase,,} == failed ]] && break
|
||||
sleep 5
|
||||
done
|
||||
echo "pre-migration backup failed: $backup phase=${phase:-unknown}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
run_migrator() {
|
||||
local source_sha=$1
|
||||
local api_image=$2
|
||||
local job=easyai-migrator-${source_sha:0:12}
|
||||
"${kubectl[@]}" delete job "$job" -n "$NAMESPACE" \
|
||||
--ignore-not-found=true --wait=true >/dev/null
|
||||
cat <<EOF | "${kubectl[@]}" apply -f - >/dev/null
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: $job
|
||||
namespace: $NAMESPACE
|
||||
labels:
|
||||
easyai.io/release: $source_sha
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
ttlSecondsAfterFinished: 86400
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
imagePullSecrets:
|
||||
- name: easyai-registry
|
||||
nodeSelector:
|
||||
easyai.io/site: ningbo
|
||||
containers:
|
||||
- name: migrator
|
||||
image: $api_image
|
||||
command: ["/app/easyai-ai-gateway-migrate"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: easyai-ai-gateway-runtime
|
||||
EOF
|
||||
"${kubectl[@]}" wait --for=condition=complete "job/$job" \
|
||||
-n "$NAMESPACE" --timeout=300s
|
||||
}
|
||||
|
||||
record_current() {
|
||||
local manifest=$1
|
||||
local action=$2
|
||||
local started_at=$3
|
||||
local source_sha recorded temporary
|
||||
source_sha=$(manifest_get "$manifest" sourceSha)
|
||||
recorded=$RELEASES_DIR/$source_sha.json
|
||||
temporary=$(mktemp "$RELEASES_DIR/.recorded.XXXXXX")
|
||||
rm -f -- "$temporary"
|
||||
RELEASE_DEPLOYED_AT=$(date -u '+%Y-%m-%dT%H:%M:%SZ') \
|
||||
RELEASE_DEPLOY_DURATION_SECONDS=$(( $(date +%s) - started_at )) \
|
||||
RELEASE_DEPLOY_ACTION=$action \
|
||||
node "$manifest_tool" record-deployment "$manifest" "$temporary" >/dev/null
|
||||
install -m 0600 "$temporary" "$recorded"
|
||||
"${kubectl[@]}" create configmap easyai-gateway-release -n "$NAMESPACE" \
|
||||
--from-file=manifest.json="$recorded" --dry-run=client -o yaml |
|
||||
"${kubectl[@]}" apply -f - >/dev/null
|
||||
rm -f -- "$temporary"
|
||||
}
|
||||
|
||||
activate_manifest() {
|
||||
local manifest=$1
|
||||
local action=${2:-deploy}
|
||||
local source_sha base_sha api_image web_image migrations_changed components
|
||||
local api_changed=false web_changed=false current_file previous_api previous_web started_at
|
||||
[[ -f $manifest && ! -L $manifest ]] || { echo 'manifest must be a regular file' >&2; return 1; }
|
||||
node "$manifest_tool" validate "$manifest" >/dev/null
|
||||
source_sha=$(manifest_get "$manifest" sourceSha)
|
||||
base_sha=$(manifest_get "$manifest" baseReleaseSha)
|
||||
api_image=$(manifest_get "$manifest" images.api)
|
||||
web_image=$(manifest_get "$manifest" images.web)
|
||||
migrations_changed=$(manifest_get "$manifest" migrationsChanged)
|
||||
components=$(manifest_get "$manifest" components)
|
||||
[[ $api_image =~ @sha256:[0-9a-f]{64}$ && $web_image =~ @sha256:[0-9a-f]{64}$ ]] || {
|
||||
echo 'release images must be digest-pinned' >&2
|
||||
return 1
|
||||
}
|
||||
[[ $components == *'"api"'* ]] && api_changed=true
|
||||
[[ $components == *'"web"'* ]] && web_changed=true
|
||||
if [[ $action == rollback ]]; then
|
||||
api_changed=true
|
||||
web_changed=true
|
||||
fi
|
||||
|
||||
current_file=$(mktemp "$RELEASES_DIR/.current.XXXXXX")
|
||||
if current_to_file "$current_file"; then
|
||||
current_sha=$(manifest_get "$current_file" sourceSha)
|
||||
previous_api=$(manifest_get "$current_file" images.api)
|
||||
previous_web=$(manifest_get "$current_file" images.web)
|
||||
if [[ $action == deploy && $current_sha != "$base_sha" ]]; then
|
||||
echo "stale release manifest: production=$current_sha manifest_base=$base_sha" >&2
|
||||
rm -f -- "$current_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
if [[ $action == deploy && -n $base_sha ]]; then
|
||||
echo 'cluster release status is unavailable for a non-bootstrap manifest' >&2
|
||||
rm -f -- "$current_file"
|
||||
return 1
|
||||
fi
|
||||
previous_api=
|
||||
previous_web=
|
||||
fi
|
||||
started_at=$(date +%s)
|
||||
|
||||
if [[ $action == deploy && $migrations_changed == true ]]; then
|
||||
run_backup "$source_sha"
|
||||
run_migrator "$source_sha" "$api_image"
|
||||
fi
|
||||
|
||||
if ! rollout_site hongkong "$api_image" "$web_image" "$api_changed" "$web_changed" ||
|
||||
! rollout_site ningbo "$api_image" "$web_image" "$api_changed" "$web_changed" ||
|
||||
! wait_for_url 'public readiness' "$PUBLIC_BASE_URL/api/v1/readyz" '"ok":true'; then
|
||||
if [[ -n $previous_api && -n $previous_web ]]; then
|
||||
echo '[cluster-release] restoring previous application images' >&2
|
||||
rollout_site hongkong "$previous_api" "$previous_web" "$api_changed" "$web_changed" || true
|
||||
rollout_site ningbo "$previous_api" "$previous_web" "$api_changed" "$web_changed" || true
|
||||
fi
|
||||
rm -f -- "$current_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
record_current "$manifest" "$action" "$started_at"
|
||||
rm -f -- "$current_file"
|
||||
echo "production_release=PASS action=$action release=$source_sha"
|
||||
}
|
||||
|
||||
case ${1:-} in
|
||||
status)
|
||||
[[ $# -eq 1 ]] || exit 64
|
||||
status_file=$(mktemp "$RELEASES_DIR/.status.XXXXXX")
|
||||
current_to_file "$status_file"
|
||||
cat "$status_file"
|
||||
rm -f -- "$status_file"
|
||||
;;
|
||||
adopt)
|
||||
[[ $# -eq 2 ]] || exit 64
|
||||
node "$manifest_tool" validate "$2" >/dev/null
|
||||
api_image=$(manifest_get "$2" images.api)
|
||||
web_image=$(manifest_get "$2" images.web)
|
||||
[[ $("${kubectl[@]}" get deployment easyai-api-ningbo -n "$NAMESPACE" \
|
||||
-o jsonpath='{.spec.template.spec.containers[?(@.name=="api")].image}') == "$api_image" ]]
|
||||
[[ $("${kubectl[@]}" get deployment easyai-web-ningbo -n "$NAMESPACE" \
|
||||
-o jsonpath='{.spec.template.spec.containers[?(@.name=="web")].image}') == "$web_image" ]]
|
||||
record_current "$2" deploy "$(date +%s)"
|
||||
;;
|
||||
deploy)
|
||||
[[ $# -eq 2 ]] || exit 64
|
||||
activate_manifest "$2" deploy
|
||||
;;
|
||||
rollback)
|
||||
[[ $# -eq 2 && $2 =~ ^[0-9a-f]{40}$ ]] || exit 64
|
||||
target=$RELEASES_DIR/$2.json
|
||||
[[ -f $target && ! -L $target ]] || { echo 'unknown historical release' >&2; exit 1; }
|
||||
activate_manifest "$target" rollback
|
||||
;;
|
||||
*)
|
||||
echo 'usage: easyai-ai-gateway-cluster-release {status|adopt <manifest>|deploy <manifest>|rollback <sha>}' >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,4 @@
|
||||
RELEASES_DIR=/var/lib/easyai-ai-gateway-cluster-release/releases
|
||||
NAMESPACE=easyai
|
||||
PUBLIC_BASE_URL=https://ai.51easyai.com
|
||||
K3S_BIN=/usr/local/bin/k3s
|
||||
@@ -0,0 +1,39 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: easyai-ai-gateway-config
|
||||
labels:
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
data:
|
||||
APP_ENV: production
|
||||
HTTP_ADDR: ":8088"
|
||||
IDENTITY_MODE: hybrid
|
||||
IDENTITY_SECRET_STORE: kubernetes
|
||||
IDENTITY_KUBERNETES_NAMESPACE: easyai
|
||||
IDENTITY_KUBERNETES_SECRET_NAME: easyai-gateway-security-events
|
||||
IDENTITY_SECURITY_EVENTS_HEARTBEAT_INTERVAL_SECONDS: "60"
|
||||
IDENTITY_SECURITY_EVENTS_STALE_AFTER_SECONDS: "180"
|
||||
IDENTITY_SECURITY_EVENTS_CLOCK_SKEW_SECONDS: "60"
|
||||
SERVER_MAIN_BASE_URL: http://10.77.0.1:3000
|
||||
TASK_PROGRESS_CALLBACK_ENABLED: "true"
|
||||
TASK_PROGRESS_CALLBACK_URL: http://10.77.0.1:3000/internal/platform/task-progress-callbacks
|
||||
TASK_PROGRESS_CALLBACK_TIMEOUT_MS: "5000"
|
||||
TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS: "10"
|
||||
AI_GATEWAY_TASK_CLEANUP_ENABLED: "false"
|
||||
AI_GATEWAY_TASK_RETENTION_DAYS: "30"
|
||||
AI_GATEWAY_TASK_ANALYSIS_RETENTION_DAYS: "7"
|
||||
AI_GATEWAY_TASK_CLEANUP_INTERVAL_SECONDS: "300"
|
||||
AI_GATEWAY_TASK_CLEANUP_BATCH_SIZE: "1000"
|
||||
CORS_ALLOWED_ORIGIN: https://ai.51easyai.com
|
||||
AI_GATEWAY_PUBLIC_BASE_URL: https://ai.51easyai.com
|
||||
AI_GATEWAY_WEB_BASE_URL: https://ai.51easyai.com
|
||||
AI_GATEWAY_GENERATED_STORAGE_DIR: /app/data/static/generated
|
||||
AI_GATEWAY_UPLOADED_STORAGE_DIR: /app/data/static/uploaded
|
||||
AI_GATEWAY_LOCAL_TEMP_ASSET_TTL_HOURS: "24"
|
||||
AI_GATEWAY_LOCAL_RESULT_TTL_HOURS: "24"
|
||||
AI_GATEWAY_LOCAL_RESULT_MIN_FREE_BYTES: "10737418240"
|
||||
AI_GATEWAY_LOCAL_RESULT_MAX_BYTES: "268435456"
|
||||
AI_GATEWAY_LOCAL_RESULT_MAX_TASK_BYTES: "536870912"
|
||||
AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED: "true"
|
||||
AI_GATEWAY_ASYNC_WORKER_HARD_LIMIT: "2048"
|
||||
AI_GATEWAY_ASYNC_WORKER_REFRESH_INTERVAL_SECONDS: "5"
|
||||
@@ -0,0 +1,439 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: easyai-api-ningbo
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
app.kubernetes.io/component: api
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
easyai.io/site: ningbo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
app.kubernetes.io/component: api
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
easyai.io/site: ningbo
|
||||
spec:
|
||||
serviceAccountName: easyai-ai-gateway
|
||||
terminationGracePeriodSeconds: 90
|
||||
nodeSelector:
|
||||
easyai.io/site: ningbo
|
||||
easyai.io/workload: "true"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: api
|
||||
image: easyai-api
|
||||
imagePullPolicy: IfNotPresent
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: easyai-ai-gateway-config
|
||||
- secretRef:
|
||||
name: easyai-ai-gateway-runtime
|
||||
env:
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8088
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/readyz
|
||||
port: http
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/healthz
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "sleep 10"]
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
readOnlyRootFilesystem: true
|
||||
volumeMounts:
|
||||
- name: application-data
|
||||
mountPath: /app/data
|
||||
- name: temporary-files
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: application-data
|
||||
emptyDir:
|
||||
sizeLimit: 5Gi
|
||||
- name: temporary-files
|
||||
emptyDir:
|
||||
sizeLimit: 1Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: easyai-api-hongkong
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
app.kubernetes.io/component: api
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
easyai.io/site: hongkong
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
app.kubernetes.io/component: api
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
easyai.io/site: hongkong
|
||||
spec:
|
||||
serviceAccountName: easyai-ai-gateway
|
||||
terminationGracePeriodSeconds: 90
|
||||
nodeSelector:
|
||||
easyai.io/site: hongkong
|
||||
easyai.io/workload: "true"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: api
|
||||
image: easyai-api
|
||||
imagePullPolicy: IfNotPresent
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: easyai-ai-gateway-config
|
||||
- secretRef:
|
||||
name: easyai-ai-gateway-runtime
|
||||
env:
|
||||
- name: POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8088
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/readyz
|
||||
port: http
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/healthz
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "sleep 10"]
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
readOnlyRootFilesystem: true
|
||||
volumeMounts:
|
||||
- name: application-data
|
||||
mountPath: /app/data
|
||||
- name: temporary-files
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: application-data
|
||||
emptyDir:
|
||||
sizeLimit: 5Gi
|
||||
- name: temporary-files
|
||||
emptyDir:
|
||||
sizeLimit: 1Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: easyai-web-ningbo
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
app.kubernetes.io/component: web
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
easyai.io/site: ningbo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
app.kubernetes.io/component: web
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
easyai.io/site: ningbo
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
terminationGracePeriodSeconds: 30
|
||||
nodeSelector:
|
||||
easyai.io/site: ningbo
|
||||
easyai.io/workload: "true"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
fsGroup: 101
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: web
|
||||
image: easyai-web
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
add: ["NET_BIND_SERVICE"]
|
||||
readOnlyRootFilesystem: true
|
||||
volumeMounts:
|
||||
- name: nginx-cache
|
||||
mountPath: /var/cache/nginx
|
||||
- name: nginx-run
|
||||
mountPath: /var/run
|
||||
volumes:
|
||||
- name: nginx-cache
|
||||
emptyDir:
|
||||
sizeLimit: 128Mi
|
||||
- name: nginx-run
|
||||
emptyDir:
|
||||
sizeLimit: 16Mi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: easyai-web-hongkong
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
app.kubernetes.io/component: web
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
easyai.io/site: hongkong
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
app.kubernetes.io/component: web
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
easyai.io/site: hongkong
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
terminationGracePeriodSeconds: 30
|
||||
nodeSelector:
|
||||
easyai.io/site: hongkong
|
||||
easyai.io/workload: "true"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
fsGroup: 101
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: web
|
||||
image: easyai-web
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
add: ["NET_BIND_SERVICE"]
|
||||
readOnlyRootFilesystem: true
|
||||
volumeMounts:
|
||||
- name: nginx-cache
|
||||
mountPath: /var/cache/nginx
|
||||
- name: nginx-run
|
||||
mountPath: /var/run
|
||||
volumes:
|
||||
- name: nginx-cache
|
||||
emptyDir:
|
||||
sizeLimit: 128Mi
|
||||
- name: nginx-run
|
||||
emptyDir:
|
||||
sizeLimit: 16Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
app.kubernetes.io/component: api
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
type: NodePort
|
||||
externalTrafficPolicy: Local
|
||||
selector:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
ports:
|
||||
- name: http
|
||||
port: 8088
|
||||
targetPort: http
|
||||
nodePort: 30088
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
app.kubernetes.io/component: web
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
type: NodePort
|
||||
externalTrafficPolicy: Local
|
||||
selector:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
nodePort: 30178
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: easyai-api
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: easyai-api
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: easyai-web
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: easyai-web
|
||||
@@ -0,0 +1,113 @@
|
||||
apiVersion: barmancloud.cnpg.io/v1
|
||||
kind: ObjectStore
|
||||
metadata:
|
||||
name: easyai-postgres-oss
|
||||
labels:
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
retentionPolicy: 14d
|
||||
configuration:
|
||||
destinationPath: s3://wangbo0808/easyai-ai-gateway/production/postgresql
|
||||
endpointURL: https://oss-cn-shanghai.aliyuncs.com
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: easyai-oss-backup
|
||||
key: ACCESS_KEY_ID
|
||||
secretAccessKey:
|
||||
name: easyai-oss-backup
|
||||
key: ACCESS_SECRET_KEY
|
||||
wal:
|
||||
compression: gzip
|
||||
maxParallel: 2
|
||||
data:
|
||||
compression: gzip
|
||||
jobs: 2
|
||||
instanceSidecarConfiguration:
|
||||
env:
|
||||
- name: AWS_DEFAULT_REGION
|
||||
value: oss-cn-shanghai
|
||||
- name: AWS_REQUEST_CHECKSUM_CALCULATION
|
||||
value: when_required
|
||||
- name: AWS_RESPONSE_CHECKSUM_VALIDATION
|
||||
value: when_required
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: easyai-postgres
|
||||
labels:
|
||||
app.kubernetes.io/name: easyai-postgres
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
instances: 2
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:18.4-standard-trixie@sha256:4587df73024408f5b2be9b4dd6ba2ccee8c9e5dc0c9a87c274c292291cc8a68c
|
||||
imagePullPolicy: IfNotPresent
|
||||
primaryUpdateStrategy: unsupervised
|
||||
primaryUpdateMethod: switchover
|
||||
failoverDelay: 0
|
||||
switchoverDelay: 60
|
||||
stopDelay: 90
|
||||
smartShutdownTimeout: 30
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: easyai_ai_gateway
|
||||
owner: easyai
|
||||
secret:
|
||||
name: easyai-postgres-app
|
||||
postInitSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
storage:
|
||||
storageClass: local-path
|
||||
size: 20Gi
|
||||
walStorage:
|
||||
storageClass: local-path
|
||||
size: 5Gi
|
||||
affinity:
|
||||
enablePodAntiAffinity: true
|
||||
podAntiAffinityType: required
|
||||
topologyKey: easyai.io/site
|
||||
nodeSelector:
|
||||
easyai.io/database: "true"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 3Gi
|
||||
postgresql:
|
||||
synchronous:
|
||||
method: any
|
||||
number: 1
|
||||
dataDurability: preferred
|
||||
parameters:
|
||||
archive_timeout: 60s
|
||||
max_connections: "200"
|
||||
shared_buffers: 512MB
|
||||
wal_compression: "on"
|
||||
wal_keep_size: 1GB
|
||||
monitoring:
|
||||
enablePodMonitor: false
|
||||
plugins:
|
||||
- name: barman-cloud.cloudnative-pg.io
|
||||
isWALArchiver: true
|
||||
parameters:
|
||||
barmanObjectName: easyai-postgres-oss
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: ScheduledBackup
|
||||
metadata:
|
||||
name: easyai-postgres-daily
|
||||
labels:
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
spec:
|
||||
# Six-field cron, UTC: 18:30 UTC is 02:30 Asia/Shanghai.
|
||||
schedule: "0 30 18 * * *"
|
||||
backupOwnerReference: self
|
||||
cluster:
|
||||
name: easyai-postgres
|
||||
method: plugin
|
||||
pluginConfiguration:
|
||||
name: barman-cloud.cloudnative-pg.io
|
||||
immediate: false
|
||||
suspend: false
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: easyai
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- service-account-rbac.yaml
|
||||
- application-config.yaml
|
||||
- application.yaml
|
||||
- database.yaml
|
||||
- network-policy.yaml
|
||||
images:
|
||||
# Deployment tooling must replace both sentinel digests with release-manifest
|
||||
# digests before server-side apply.
|
||||
- name: easyai-api
|
||||
newName: registry.cn-shanghai.aliyuncs.com/easyaigc/ai-gateway
|
||||
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
|
||||
- name: easyai-web
|
||||
newName: registry.cn-shanghai.aliyuncs.com/easyaigc/ai-gateway-web
|
||||
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: easyai
|
||||
labels:
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
pod-security.kubernetes.io/enforce: baseline
|
||||
pod-security.kubernetes.io/audit: restricted
|
||||
pod-security.kubernetes.io/warn: restricted
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: easyai-application-ingress
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values: [easyai-api, easyai-web]
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: easyai
|
||||
- ipBlock:
|
||||
cidr: 10.77.0.0/24
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8088
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: easyai-ai-gateway
|
||||
labels:
|
||||
app.kubernetes.io/part-of: easyai-ai-gateway
|
||||
automountServiceAccountToken: true
|
||||
imagePullSecrets:
|
||||
- name: easyai-registry
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: easyai-gateway-security-events
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["easyai-gateway-security-events"]
|
||||
verbs: ["get", "update", "patch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: easyai-gateway-security-events
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: easyai-ai-gateway
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: easyai-gateway-security-events
|
||||
@@ -0,0 +1,106 @@
|
||||
upstream easyai_gateway_api {
|
||||
least_conn;
|
||||
keepalive 64;
|
||||
server 10.77.0.1:30088 max_fails=2 fail_timeout=5s;
|
||||
server 10.77.0.2:30088 max_fails=2 fail_timeout=5s;
|
||||
}
|
||||
|
||||
upstream easyai_gateway_web {
|
||||
least_conn;
|
||||
keepalive 32;
|
||||
server 10.77.0.1:30178 max_fails=2 fail_timeout=5s;
|
||||
server 10.77.0.2:30178 max_fails=2 fail_timeout=5s;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ai.51easyai.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name ai.51easyai.com;
|
||||
|
||||
ssl_certificate /etc/nginx/tls/ai.51easyai.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/tls/ai.51easyai.com/privkey.pem;
|
||||
ssl_session_cache shared:easyai_gateway_tls:10m;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
client_max_body_size 200m;
|
||||
client_body_timeout 300s;
|
||||
|
||||
include /etc/nginx/easyai-ai-gateway/legacy-static.inc;
|
||||
|
||||
location = /api/v1/metrics {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ^~ /api/v1/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_next_upstream error timeout http_502 http_503 http_504;
|
||||
proxy_next_upstream_tries 2;
|
||||
proxy_pass http://easyai_gateway_api;
|
||||
}
|
||||
|
||||
location /gateway-api/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_next_upstream error timeout http_502 http_503 http_504;
|
||||
proxy_next_upstream_tries 2;
|
||||
proxy_pass http://easyai_gateway_api/;
|
||||
}
|
||||
|
||||
location ^~ /static/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_buffering off;
|
||||
proxy_next_upstream error timeout http_502 http_503 http_504;
|
||||
proxy_next_upstream_tries 2;
|
||||
proxy_pass http://easyai_gateway_api;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_next_upstream error timeout http_502 http_503 http_504;
|
||||
proxy_next_upstream_tries 2;
|
||||
proxy_pass http://easyai_gateway_web;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ai.51easyai.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name ai.51easyai.com;
|
||||
|
||||
ssl_certificate /etc/nginx/tls/ai.51easyai.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/tls/ai.51easyai.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
location ^~ /api/ {
|
||||
default_type application/json;
|
||||
add_header Retry-After 600 always;
|
||||
return 503 '{"error":{"message":"Gateway database maintenance is in progress","type":"service_unavailable"}}';
|
||||
}
|
||||
|
||||
location / {
|
||||
default_type text/html;
|
||||
add_header Retry-After 600 always;
|
||||
return 503 '<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>系统维护</title><style>body{font-family:sans-serif;max-width:40rem;margin:15vh auto;padding:2rem;color:#1f2937}h1{font-size:1.8rem}</style><h1>系统正在进行高可用升级</h1><p>预计 5–10 分钟恢复,请稍后重试。</p></html>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Private-only deterministic path used by the cross-node acceptance test.
|
||||
server {
|
||||
listen 10.77.0.1:18088;
|
||||
server_name _;
|
||||
|
||||
allow 10.77.0.0/24;
|
||||
deny all;
|
||||
client_max_body_size 200m;
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host ai.51easyai.com;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_pass http://10.77.0.1:30088;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
# Hong Kong reads unexpired legacy files from the private Ningbo origin.
|
||||
location ^~ /static/generated/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host ai.51easyai.com;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_buffering off;
|
||||
proxy_pass http://10.77.0.1:18080;
|
||||
}
|
||||
|
||||
location ^~ /static/uploaded/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host ai.51easyai.com;
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 2s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_buffering off;
|
||||
proxy_pass http://10.77.0.1:18080;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Temporary compatibility route. Remove after all files are strictly older
|
||||
# than 86,400 seconds and the cleanup acceptance check reports zero survivors.
|
||||
location ^~ /static/generated/ {
|
||||
alias /var/lib/docker/volumes/easyai-ai-gateway_api_data/_data/static/generated/;
|
||||
add_header Cache-Control "private, max-age=300";
|
||||
try_files $request_filename =410;
|
||||
}
|
||||
|
||||
location ^~ /static/uploaded/ {
|
||||
alias /var/lib/docker/volumes/easyai-ai-gateway_api_data/_data/static/uploaded/;
|
||||
add_header Cache-Control "private, max-age=300";
|
||||
try_files $request_filename =410;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
server {
|
||||
listen 10.77.0.1:18080;
|
||||
server_name _;
|
||||
|
||||
allow 10.77.0.0/24;
|
||||
deny all;
|
||||
|
||||
location ^~ /static/generated/ {
|
||||
alias /var/lib/docker/volumes/easyai-ai-gateway_api_data/_data/static/generated/;
|
||||
add_header Cache-Control "private, max-age=300";
|
||||
try_files $request_filename =410;
|
||||
}
|
||||
|
||||
location ^~ /static/uploaded/ {
|
||||
alias /var/lib/docker/volumes/easyai-ai-gateway_api_data/_data/static/uploaded/;
|
||||
add_header Cache-Control "private, max-age=300";
|
||||
try_files $request_filename =410;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Prune EasyAI Gateway legacy local files older than 24 hours
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/easyai-prune-legacy-generated --apply
|
||||
User=root
|
||||
Group=root
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/lib/docker/volumes/easyai-ai-gateway_api_data/_data/static
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Run EasyAI Gateway legacy local file cleanup hourly
|
||||
|
||||
[Timer]
|
||||
OnCalendar=hourly
|
||||
Persistent=true
|
||||
RandomizedDelaySec=120
|
||||
Unit=easyai-legacy-generated-cleanup.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user