Files
easyai-ai-gateway/scripts/cluster/failure-drill.sh
T
wangbo 7c142f5960 fix(deploy): 补齐集群备份兼容与切换前门禁
阿里云 OSS 的 S3 兼容层要求 virtual-hosted addressing,且 boto3 需要使用 Signature V2。本提交为 Barman 的归档、备份、保留与恢复统一挂载 AWS 配置,并将实际 WAL 归档和近期全备设为切换硬门禁。

同时补充 CNPG/etcdutl 固定版本安装、显式主库切换、三节点逐台停机、OSS 快照下载校验与远端临时 Secret 清理。已通过 ShellCheck、Kustomize、服务端 dry-run、迁移测试、发布脚本测试、敏感信息扫描及生产 precutover 验收。
2026-07-28 06:46:09 +08:00

90 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=scripts/cluster/common.sh
source "$script_dir/common.sh"
drill=${1:-}
confirmation=${2:-}
[[ $confirmation == --execute ]] || {
echo 'usage: failure-drill.sh {api|database|witness} --execute' >&2
exit 64
}
load_cluster_env
case $drill in
api)
started=$(date +%s)
pod=$(cluster_ssh "$CLUSTER_NINGBO_HOST" \
"k3s kubectl get pods -n easyai -l app.kubernetes.io/name=easyai-api,easyai.io/site=ningbo -o jsonpath='{.items[0].metadata.name}'")
cluster_ssh "$CLUSTER_NINGBO_HOST" \
"k3s kubectl delete pod '$pod' -n easyai --wait=false >/dev/null"
for _ in $(seq 1 20); do
if curl -fsS --max-time 3 --resolve "ai.51easyai.com:443:${CLUSTER_NINGBO_HOST#*@}" \
https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true'; then
elapsed=$(( $(date +%s) - started ))
break
fi
sleep 1
done
[[ ${elapsed:-99} -le 10 ]]
echo "api_failure_drill=PASS failover_seconds=$elapsed"
;;
database)
old_primary=$(cluster_ssh "$CLUSTER_NINGBO_HOST" \
"k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.currentPrimary}'")
started=$(date +%s)
cluster_ssh "$CLUSTER_NINGBO_HOST" \
"k3s kubectl delete pod '$old_primary' -n easyai --grace-period=0 --force >/dev/null"
for _ in $(seq 1 150); do
new_primary=$(cluster_ssh "$CLUSTER_NINGBO_HOST" \
"k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.currentPrimary}'")
if [[ -n $new_primary && $new_primary != "$old_primary" ]]; then
elapsed=$(( $(date +%s) - started ))
break
fi
sleep 2
done
[[ ${elapsed:-999} -le 300 ]]
curl -fsS --max-time 10 https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true'
cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$new_primary" <<'REMOTE'
set -euo pipefail
primary=$1
k3s kubectl exec -n easyai "$primary" -c postgres -- \
psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway -c \
"INSERT INTO system_settings(setting_key,value) VALUES('ha_acceptance_probe',jsonb_build_object('at',now())) ON CONFLICT(setting_key) DO UPDATE SET value=excluded.value,updated_at=now();" >/dev/null
for _ in $(seq 1 180); do
if [[ $(k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.readyInstances}') == 2 ]]; then
replication=$(k3s kubectl exec -n easyai "$primary" -c postgres -- \
psql -X -U postgres -d easyai_ai_gateway -At -c \
"SELECT count(*) FROM pg_stat_replication WHERE sync_state IN ('sync','quorum');")
[[ $replication == 1 ]] && break
fi
sleep 2
done
[[ ${replication:-0} == 1 ]]
k3s kubectl exec -n easyai "$primary" -c postgres -- \
psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway -c \
"DELETE FROM system_settings WHERE setting_key='ha_acceptance_probe';" >/dev/null
REMOTE
echo "database_failure_drill=PASS promotion_seconds=$elapsed old_primary=$old_primary new_primary=$new_primary"
;;
witness)
cluster_ssh "$CLUSTER_LOS_ANGELES_HOST" 'systemctl stop k3s'
trap 'cluster_ssh "$CLUSTER_LOS_ANGELES_HOST" "systemctl start k3s" || true' EXIT HUP INT TERM
cluster_ssh "$CLUSTER_NINGBO_HOST" \
"k3s kubectl get --raw='/readyz?verbose' | grep -q '\\[+\\]etcd ok'"
curl -fsS --max-time 10 https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true'
cluster_ssh "$CLUSTER_LOS_ANGELES_HOST" 'systemctl start k3s'
cluster_ssh "$CLUSTER_NINGBO_HOST" \
'k3s kubectl wait --for=condition=Ready node/easyai-los-angeles --timeout=180s'
trap - EXIT HUP INT TERM
echo 'witness_failure_drill=PASS quorum_retained=true'
;;
*)
echo 'drill must be api, database, or witness' >&2
exit 64
;;
esac