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:
2026-07-28 04:37:31 +08:00
parent edf7e66941
commit 971540a2a4
38 changed files with 3386 additions and 3 deletions
+279
View File
@@ -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
+113
View File
@@ -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