refactor(cluster): 通用化第四个 Worker 节点接入

将原宁波专用接入脚本改为由环境变量控制节点名称、站点和 WireGuard 端口,支持香港双物理节点 Worker 池。增加候选节点启用开关和服务商网络许可门禁,未明确批准时禁止任何 WireGuard 或 K3s 写操作。同步调整生产验收、巡检、拓扑校验和运行手册。\n\n验证:\n- scripts/cluster 全部 Shell 脚本 bash -n\n- scripts/cluster 全部 ShellCheck\n- git diff --check\n- 本地凭据值扫描\n- 当前三节点 WireGuard/K3s/PostgreSQL 只读基线检查
This commit is contained in:
2026-08-01 11:23:19 +08:00
parent e77bd17754
commit ed3b970178
7 changed files with 175 additions and 93 deletions
+21 -10
View File
@@ -2,29 +2,40 @@
## 范围与目标 ## 范围与目标
本手册覆盖宁波、香港、洛杉矶三节点迁移。宁波和香港承载 API、Web、River Worker 与 本手册覆盖宁波、香港、洛杉矶三个 K3s server 和一个可选专用 Worker agent。宁波、香港
CloudNativePG洛杉矶只作为 K3s server/etcd 仲裁节点,并带 承载 API、Web 与 CloudNativePGWorker 只调度到带 `easyai.io/worker=true` 标签的节点。
洛杉矶只作为 K3s server/etcd 仲裁节点,并带
`easyai.io/witness=true:NoSchedule` 污点。 `easyai.io/witness=true:NoSchedule` 污点。
二台宁波服务器是纯 K3s agent,不加入 etcd 或 CloudNativePG。它使用节点名 4 台服务器是站点可配置的纯 K3s agent,不加入 etcd 或 CloudNativePG。节点名和逻辑站点
`easyai-ningbo-worker-2`、WireGuard 地址 `10.77.0.4`,并归入逻辑 `ningbo` Worker 池。 分别由 `AI_GATEWAY_WORKER_NODE_4_NAME``AI_GATEWAY_WORKER_NODE_4_SITE` 指定,固定使用
WireGuard 地址 `10.77.0.4`。当前计划节点为 `easyai-hongkong-worker-2`,归入逻辑
`hongkong` Worker 池;稳定基线为宁波 0 副本、香港 2 副本,并跨香港两台物理机分布。
`easyai.io/worker-only=true:NoSchedule` 污点确保除 Worker 外的业务 Pod 不会调度到该机; `easyai.io/worker-only=true:NoSchedule` 污点确保除 Worker 外的业务 Pod 不会调度到该机;
`easyai.io/worker=true` 标签确保容量控制器只把专用 Worker 节点纳入资源预算。接入使用: `easyai.io/worker=true` 标签确保容量控制器只把专用 Worker 节点纳入资源预算。接入使用:
```bash ```bash
./scripts/cluster/add-ningbo-worker-node.sh all ./scripts/cluster/add-worker-node.sh preflight
./scripts/cluster/add-worker-node.sh all
``` ```
新节点与宁波、香港、洛杉矶使用 WireGuard 全互联直连,不经过中继。公网 K3s 直连虽然可以 新节点与宁波、香港、洛杉矶使用 WireGuard 全互联直连,不经过中继。公网 K3s 直连虽然可以
单独保护 API TLS,但当前 Flannel、Pod 和数据库网络依赖 `10.77.0.0/24`,禁止改成未加密的 单独保护 API TLS,但当前 Flannel、Pod 和数据库网络依赖 `10.77.0.0/24`,禁止改成未加密的
公网 VXLAN。巡检以 10 包丢包率低于 1%为硬门禁;宁波和香港路径 RTT 低于 80 ms,洛杉矶 公网 VXLAN。巡检以 10 包丢包率低于 1%为硬门禁;宁波和香港路径 RTT 低于 80 ms,洛杉矶
路径低于 300 ms。新节点监听 UDP/443;实测 UDP/51820 在香港方向存在 QoS 抖动,禁止在 路径低于 300 ms。新节点默认监听标准 UDP/51820,可通过
未重新完成三轮六向链路验收前改回默认端口。 `AI_GATEWAY_WORKER_NODE_4_WIREGUARD_PORT` 配置;不得为了绕过服务商风控擅自伪装端口。
本地 `.env.local` 只保存 `AI_GATEWAY_NINGBO_WORKER_HOST` 和 SSH 接入密码,必须保持 `0600` 服务商必须先通过工单书面确认“仅用于 Kubernetes 私有集群互联,不提供 VPN、代理或流量
且不得进入 Git。新节点只保留原有 SSH 入口,封锁 K3s API、kubelet、VXLAN 和全部 NodePort 转发服务”符合其政策;确认后才可在 `.env.local` 设置
公网入口。节点防火墙继续由原有 UFW 管理,接入脚本 `AI_GATEWAY_WORKER_NODE_4_NETWORK_APPROVED=true`。未设置时,脚本只允许执行只读
`preflight`,所有 WireGuard 与 K3s 写操作都会停止。
本地 `.env.local` 保存 `AI_GATEWAY_WORKER_NODE_4_HOST/NAME/SITE` 和 SSH 接入密码,必须保持
`0600` 且不得进入 Git。新节点只保留原有 SSH 入口,封锁 K3s API、kubelet、VXLAN和全部
NodePort 公网入口。节点防火墙继续由原有 UFW 管理,接入脚本
禁止安装与 UFW 冲突的 `iptables-persistent`;UFW 未安装或未启用时必须在预检阶段停止。 禁止安装与 UFW 冲突的 `iptables-persistent`;UFW 未安装或未启用时必须在预检阶段停止。
节点完成加入和验收后才设置 `AI_GATEWAY_WORKER_NODE_4_ENABLED=true`;候选服务器仅写入本地
配置但尚未加入时必须保持 `false`,避免日常巡检误把候选机计入生产拓扑。
生产上线前的双节点高媒体负载、Worker 强杀和 P24/P28/P32 容量搜索见 生产上线前的双节点高媒体负载、Worker 强杀和 P24/P28/P32 容量搜索见
[生产同构验收模式与高媒体压力测试](production-acceptance.md)。 [生产同构验收模式与高媒体压力测试](production-acceptance.md)。
@@ -8,17 +8,18 @@ source "$script_dir/common.sh"
action=${1:-all} action=${1:-all}
[[ $action == preflight || $action == network || $action == install || [[ $action == preflight || $action == network || $action == install ||
$action == verify || $action == all ]] || { $action == verify || $action == all ]] || {
echo 'usage: add-ningbo-worker-node.sh [preflight|network|install|verify|all]' >&2 echo 'usage: add-worker-node.sh [preflight|network|install|verify|all]' >&2
exit 64 exit 64
} }
load_cluster_env load_cluster_env
require_commands ssh scp shasum require_commands ssh scp shasum
: "${CLUSTER_NINGBO_WORKER_HOST:?AI_GATEWAY_NINGBO_WORKER_HOST is required}" : "${CLUSTER_WORKER_NODE_4_HOST:?AI_GATEWAY_WORKER_NODE_4_HOST is required}"
node_name=easyai-ningbo-worker-2 node_name=$CLUSTER_WORKER_NODE_4_NAME
node_site=$CLUSTER_WORKER_NODE_4_SITE
node_ip=10.77.0.4 node_ip=10.77.0.4
worker_wireguard_port=443 worker_wireguard_port=$CLUSTER_WORKER_NODE_4_WIREGUARD_PORT
k3s_version=v1.36.2+k3s1 k3s_version=v1.36.2+k3s1
k3s_sha256=65a55ec56c24eab44383086166ec620a491952b7e23941a49ddca6e8a4c4b4de k3s_sha256=65a55ec56c24eab44383086166ec620a491952b7e23941a49ddca6e8a4c4b4de
secret_root="$cluster_root/.local-secrets/cluster" secret_root="$cluster_root/.local-secrets/cluster"
@@ -39,8 +40,10 @@ assert_join_material() {
} }
preflight_node() { preflight_node() {
cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" bash -s <<'REMOTE' cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" bash -s -- "$node_name" "$node_site" <<'REMOTE'
set -euo pipefail set -euo pipefail
node_name=$1
node_site=$2
[[ $(id -u) -eq 0 && $(uname -m) == x86_64 ]] [[ $(id -u) -eq 0 && $(uname -m) == x86_64 ]]
. /etc/os-release . /etc/os-release
[[ ($ID == debian && ${VERSION_ID%%.*} -ge 12) || [[ ($ID == debian && ${VERSION_ID%%.*} -ge 12) ||
@@ -54,11 +57,19 @@ timedatectl show -p NTPSynchronized --value | grep -Fxq yes
[[ ! -e /etc/rancher/k3s/config.yaml || -f /etc/rancher/k3s/config.yaml ]] [[ ! -e /etc/rancher/k3s/config.yaml || -f /etc/rancher/k3s/config.yaml ]]
command -v ufw >/dev/null command -v ufw >/dev/null
ufw status | grep -Fqx 'Status: active' ufw status | grep -Fqx 'Status: active'
printf 'ningbo_worker_preflight=PASS cpu=%s memory_available_mib=%s disk_available_gib=%s\n' \ printf 'worker_node_preflight=PASS node=%s site=%s cpu=%s memory_available_mib=%s disk_available_gib=%s\n' \
"$(nproc)" "$((available_kib / 1024))" "$((available_bytes / 1024 / 1024 / 1024))" "$node_name" "$node_site" "$(nproc)" "$((available_kib / 1024))" \
"$((available_bytes / 1024 / 1024 / 1024))"
REMOTE REMOTE
} }
assert_network_approved() {
[[ $CLUSTER_WORKER_NODE_4_NETWORK_APPROVED == true ]] || {
echo 'dedicated Worker private-network setup is blocked until the provider approves this non-proxy Kubernetes use in writing' >&2
return 1
}
}
configure_wireguard() { configure_wireguard() {
local -a server_hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST") local -a server_hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST")
local -a server_ips=(10.77.0.1 10.77.0.2 10.77.0.3) local -a server_ips=(10.77.0.1 10.77.0.2 10.77.0.3)
@@ -66,7 +77,7 @@ configure_wireguard() {
local -a server_keys=() local -a server_keys=()
local worker_key host key local worker_key host key
worker_key=$(cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" 'bash -s' <<'REMOTE' | tail -n 1 worker_key=$(cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" 'bash -s' <<'REMOTE' | tail -n 1
set -euo pipefail set -euo pipefail
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
if ! command -v wg >/dev/null 2>&1; then if ! command -v wg >/dev/null 2>&1; then
@@ -91,7 +102,7 @@ REMOTE
server_keys+=("$key") server_keys+=("$key")
done done
cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" bash -s -- \ cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" bash -s -- \
"$worker_wireguard_port" \ "$worker_wireguard_port" \
"${server_keys[0]}" "${server_endpoints[0]}" "${server_ips[0]}" \ "${server_keys[0]}" "${server_endpoints[0]}" "${server_ips[0]}" \
"${server_keys[1]}" "${server_endpoints[1]}" "${server_ips[1]}" \ "${server_keys[1]}" "${server_endpoints[1]}" "${server_ips[1]}" \
@@ -136,7 +147,7 @@ systemctl restart wg-quick@wg0
REMOTE REMOTE
for host in "${server_hosts[@]}"; do for host in "${server_hosts[@]}"; do
cluster_ssh "$host" bash -s -- "$worker_key" "${CLUSTER_NINGBO_WORKER_HOST#*@}" \ cluster_ssh "$host" bash -s -- "$worker_key" "${CLUSTER_WORKER_NODE_4_HOST#*@}" \
"$node_ip" "$worker_wireguard_port" <<'REMOTE' "$node_ip" "$worker_wireguard_port" <<'REMOTE'
set -euo pipefail set -euo pipefail
wg set wg0 peer "$1" allowed-ips "$3/32" endpoint "$2:$4" persistent-keepalive 25 wg set wg0 peer "$1" allowed-ips "$3/32" endpoint "$2:$4" persistent-keepalive 25
@@ -159,19 +170,19 @@ verify_path() {
} }
verify_wireguard() { verify_wireguard() {
verify_path "$CLUSTER_NINGBO_HOST" "$node_ip" 80 ningbo_to_worker_2 verify_path "$CLUSTER_NINGBO_HOST" "$node_ip" 80 ningbo_to_worker_node_4
verify_path "$CLUSTER_NINGBO_WORKER_HOST" 10.77.0.1 80 worker_2_to_ningbo verify_path "$CLUSTER_WORKER_NODE_4_HOST" 10.77.0.1 80 worker_node_4_to_ningbo
verify_path "$CLUSTER_HONGKONG_HOST" "$node_ip" 80 hongkong_to_worker_2 verify_path "$CLUSTER_HONGKONG_HOST" "$node_ip" 80 hongkong_to_worker_node_4
verify_path "$CLUSTER_NINGBO_WORKER_HOST" 10.77.0.2 80 worker_2_to_hongkong verify_path "$CLUSTER_WORKER_NODE_4_HOST" 10.77.0.2 80 worker_node_4_to_hongkong
verify_path "$CLUSTER_LOS_ANGELES_HOST" "$node_ip" 300 losangeles_to_worker_2 verify_path "$CLUSTER_LOS_ANGELES_HOST" "$node_ip" 300 losangeles_to_worker_node_4
verify_path "$CLUSTER_NINGBO_WORKER_HOST" 10.77.0.3 300 worker_2_to_losangeles verify_path "$CLUSTER_WORKER_NODE_4_HOST" 10.77.0.3 300 worker_node_4_to_losangeles
local host recent local host recent
for host in "$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST" "$CLUSTER_NINGBO_WORKER_HOST"; do for host in "$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST" "$CLUSTER_WORKER_NODE_4_HOST"; do
recent=$(cluster_ssh "$host" \ recent=$(cluster_ssh "$host" \
"wg show wg0 latest-handshakes | awk '\$2 > 0 && systime()-\$2 < 180 {count++} END {print count+0}'") "wg show wg0 latest-handshakes | awk '\$2 > 0 && systime()-\$2 < 180 {count++} END {print count+0}'")
[[ $recent == 3 ]] [[ $recent == 3 ]]
done done
echo "ningbo_worker_wireguard=PASS peers=4 recent_handshakes=12 routing=direct listen_port=$worker_wireguard_port" echo "worker_node_wireguard=PASS node=$node_name site=$node_site peers=4 recent_handshakes=12 routing=direct listen_port=$worker_wireguard_port"
} }
install_agent() { install_agent() {
@@ -181,26 +192,29 @@ install_agent() {
: "${K3S_TOKEN:?}" : "${K3S_TOKEN:?}"
[[ $K3S_TOKEN =~ ^[0-9a-f]{64}$ ]] [[ $K3S_TOKEN =~ ^[0-9a-f]{64}$ ]]
cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" 'install -d -m 0700 /etc/rancher/k3s' cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" 'install -d -m 0700 /etc/rancher/k3s'
printf '%s\n' "$K3S_TOKEN" | cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" \ printf '%s\n' "$K3S_TOKEN" | cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" \
'umask 077; cat >/etc/rancher/k3s/cluster-token; chmod 0600 /etc/rancher/k3s/cluster-token' 'umask 077; cat >/etc/rancher/k3s/cluster-token; chmod 0600 /etc/rancher/k3s/cluster-token'
cluster_scp "$k3s_binary" "$CLUSTER_NINGBO_WORKER_HOST:/usr/local/bin/k3s" cluster_scp "$k3s_binary" "$CLUSTER_WORKER_NODE_4_HOST:/usr/local/bin/k3s"
cluster_scp "$registries_file" "$CLUSTER_NINGBO_WORKER_HOST:/etc/rancher/k3s/registries.yaml" cluster_scp "$registries_file" "$CLUSTER_WORKER_NODE_4_HOST:/etc/rancher/k3s/registries.yaml"
cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" bash -s -- "$k3s_version" "$k3s_sha256" <<'REMOTE' cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" bash -s -- \
"$k3s_version" "$k3s_sha256" "$node_name" "$node_site" <<'REMOTE'
set -euo pipefail set -euo pipefail
version=$1 version=$1
expected_sha256=$2 expected_sha256=$2
node_name=$3
node_site=$4
chmod 0755 /usr/local/bin/k3s chmod 0755 /usr/local/bin/k3s
echo "$expected_sha256 /usr/local/bin/k3s" | sha256sum -c - >/dev/null echo "$expected_sha256 /usr/local/bin/k3s" | sha256sum -c - >/dev/null
umask 077 umask 077
cat >/etc/rancher/k3s/config.yaml <<'EOF' cat >/etc/rancher/k3s/config.yaml <<EOF
server: https://10.77.0.1:6443 server: https://10.77.0.1:6443
token-file: /etc/rancher/k3s/cluster-token token-file: /etc/rancher/k3s/cluster-token
node-name: easyai-ningbo-worker-2 node-name: ${node_name}
node-ip: 10.77.0.4 node-ip: 10.77.0.4
flannel-iface: wg0 flannel-iface: wg0
node-label: node-label:
- easyai.io/site=ningbo - easyai.io/site=${node_site}
- easyai.io/workload=true - easyai.io/workload=true
- easyai.io/database=false - easyai.io/database=false
- easyai.io/worker=true - easyai.io/worker=true
@@ -253,12 +267,13 @@ REMOTE
verify_agent() { verify_agent() {
verify_wireguard verify_wireguard
cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$node_name" <<'REMOTE' cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$node_name" "$node_site" <<'REMOTE'
set -euo pipefail set -euo pipefail
node=$1 node=$1
site=$2
kubectl='k3s kubectl' kubectl='k3s kubectl'
[[ $($kubectl get node "$node" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') == True ]] [[ $($kubectl get node "$node" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') == True ]]
[[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/site}') == ningbo ]] [[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/site}') == "$site" ]]
[[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/workload}') == true ]] [[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/workload}') == true ]]
[[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/database}') == false ]] [[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/database}') == false ]]
[[ $($kubectl get node easyai-hongkong -o jsonpath='{.metadata.labels.easyai\.io/worker}') == true ]] [[ $($kubectl get node easyai-hongkong -o jsonpath='{.metadata.labels.easyai\.io/worker}') == true ]]
@@ -266,14 +281,14 @@ kubectl='k3s kubectl'
[[ $($kubectl get node "$node" -o json | jq '[.spec.taints[]? | select(.key=="easyai.io/worker-only" and .effect=="NoSchedule")] | length') -eq 1 ]] [[ $($kubectl get node "$node" -o json | jq '[.spec.taints[]? | select(.key=="easyai.io/worker-only" and .effect=="NoSchedule")] | length') -eq 1 ]]
[[ $($kubectl get pods -A --field-selector "spec.nodeName=$node" -o json | [[ $($kubectl get pods -A --field-selector "spec.nodeName=$node" -o json |
jq '[.items[] | select(.metadata.namespace!="kube-system")] | length') -eq 0 ]] jq '[.items[] | select(.metadata.namespace!="kube-system")] | length') -eq 0 ]]
printf 'ningbo_worker_k3s_agent=PASS node=%s role=worker-only etcd_member=false\n' "$node" printf 'worker_node_k3s_agent=PASS node=%s site=%s role=worker-only etcd_member=false\n' "$node" "$site"
REMOTE REMOTE
} }
case $action in case $action in
preflight) preflight_node ;; preflight) preflight_node ;;
network) preflight_node; configure_wireguard ;; network) assert_network_approved; preflight_node; configure_wireguard ;;
install) preflight_node; install_agent ;; install) assert_network_approved; preflight_node; install_agent ;;
verify) preflight_node; verify_agent ;; verify) preflight_node; verify_agent ;;
all) preflight_node; configure_wireguard; install_agent ;; all) assert_network_approved; preflight_node; configure_wireguard; install_agent ;;
esac esac
+8 -4
View File
@@ -12,11 +12,15 @@ hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOS
public_endpoints=("${CLUSTER_NINGBO_HOST#*@}" "${CLUSTER_HONGKONG_HOST#*@}" "${CLUSTER_LOS_ANGELES_HOST#*@}") public_endpoints=("${CLUSTER_NINGBO_HOST#*@}" "${CLUSTER_HONGKONG_HOST#*@}" "${CLUSTER_LOS_ANGELES_HOST#*@}")
wireguard_ips=(10.77.0.1 10.77.0.2 10.77.0.3) wireguard_ips=(10.77.0.1 10.77.0.2 10.77.0.3)
wireguard_ports=(51820 51820 51820) wireguard_ports=(51820 51820 51820)
if [[ -n $CLUSTER_NINGBO_WORKER_HOST ]]; then if [[ -n $CLUSTER_WORKER_NODE_4_HOST ]]; then
hosts+=("$CLUSTER_NINGBO_WORKER_HOST") [[ $CLUSTER_WORKER_NODE_4_NETWORK_APPROVED == true ]] || {
public_endpoints+=("${CLUSTER_NINGBO_WORKER_HOST#*@}") echo 'dedicated Worker private-network setup is blocked until the provider approves this non-proxy Kubernetes use in writing' >&2
exit 1
}
hosts+=("$CLUSTER_WORKER_NODE_4_HOST")
public_endpoints+=("${CLUSTER_WORKER_NODE_4_HOST#*@}")
wireguard_ips+=(10.77.0.4) wireguard_ips+=(10.77.0.4)
wireguard_ports+=(443) wireguard_ports+=("$CLUSTER_WORKER_NODE_4_WIREGUARD_PORT")
fi fi
public_keys=() public_keys=()
+41 -4
View File
@@ -28,8 +28,16 @@ load_cluster_env() {
CLUSTER_NINGBO_HOST=${AI_GATEWAY_NINGBO_HOST:-$AI_GATEWAY_DEPLOY_HOST} CLUSTER_NINGBO_HOST=${AI_GATEWAY_NINGBO_HOST:-$AI_GATEWAY_DEPLOY_HOST}
CLUSTER_LOS_ANGELES_HOST=${AI_GATEWAY_LOS_ANGELES_HOST:-$AI_GATEWAY_DEPLOY_HOST_2} CLUSTER_LOS_ANGELES_HOST=${AI_GATEWAY_LOS_ANGELES_HOST:-$AI_GATEWAY_DEPLOY_HOST_2}
CLUSTER_HONGKONG_HOST=${AI_GATEWAY_HONGKONG_HOST:-$AI_GATEWAY_DEPLOY_HOST_3} CLUSTER_HONGKONG_HOST=${AI_GATEWAY_HONGKONG_HOST:-$AI_GATEWAY_DEPLOY_HOST_3}
CLUSTER_NINGBO_WORKER_HOST=${AI_GATEWAY_NINGBO_WORKER_HOST:-} CLUSTER_WORKER_NODE_4_HOST=${AI_GATEWAY_WORKER_NODE_4_HOST:-}
export CLUSTER_NINGBO_HOST CLUSTER_LOS_ANGELES_HOST CLUSTER_HONGKONG_HOST CLUSTER_NINGBO_WORKER_HOST CLUSTER_WORKER_NODE_4_NAME=${AI_GATEWAY_WORKER_NODE_4_NAME:-}
CLUSTER_WORKER_NODE_4_SITE=${AI_GATEWAY_WORKER_NODE_4_SITE:-}
CLUSTER_WORKER_NODE_4_WIREGUARD_PORT=${AI_GATEWAY_WORKER_NODE_4_WIREGUARD_PORT:-51820}
CLUSTER_WORKER_NODE_4_NETWORK_APPROVED=${AI_GATEWAY_WORKER_NODE_4_NETWORK_APPROVED:-false}
CLUSTER_WORKER_NODE_4_ENABLED=${AI_GATEWAY_WORKER_NODE_4_ENABLED:-false}
export CLUSTER_NINGBO_HOST CLUSTER_LOS_ANGELES_HOST CLUSTER_HONGKONG_HOST
export CLUSTER_WORKER_NODE_4_HOST CLUSTER_WORKER_NODE_4_NAME CLUSTER_WORKER_NODE_4_SITE
export CLUSTER_WORKER_NODE_4_WIREGUARD_PORT CLUSTER_WORKER_NODE_4_NETWORK_APPROVED
export CLUSTER_WORKER_NODE_4_ENABLED
for host in "$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST"; do for host in "$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST"; do
[[ $host =~ ^root@[A-Za-z0-9.-]+$ ]] || { [[ $host =~ ^root@[A-Za-z0-9.-]+$ ]] || {
@@ -37,8 +45,37 @@ load_cluster_env() {
return 1 return 1
} }
done done
if [[ -n $CLUSTER_NINGBO_WORKER_HOST && ! $CLUSTER_NINGBO_WORKER_HOST =~ ^root@[A-Za-z0-9.-]+$ ]]; then if [[ -n $CLUSTER_WORKER_NODE_4_HOST ]]; then
echo "dedicated Ningbo Worker host must use root@host syntax" >&2 [[ $CLUSTER_WORKER_NODE_4_HOST =~ ^root@[A-Za-z0-9.-]+$ ]] || {
echo "dedicated Worker node 4 host must use root@host syntax" >&2
return 1
}
[[ ${#CLUSTER_WORKER_NODE_4_NAME} -le 63 &&
$CLUSTER_WORKER_NODE_4_NAME =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]] || {
echo "dedicated Worker node 4 name must be a Kubernetes DNS label" >&2
return 1
}
[[ $CLUSTER_WORKER_NODE_4_SITE == ningbo || $CLUSTER_WORKER_NODE_4_SITE == hongkong ]] || {
echo "dedicated Worker node 4 site must be ningbo or hongkong" >&2
return 1
}
[[ $CLUSTER_WORKER_NODE_4_WIREGUARD_PORT =~ ^[0-9]+$ &&
$CLUSTER_WORKER_NODE_4_WIREGUARD_PORT -ge 1 &&
$CLUSTER_WORKER_NODE_4_WIREGUARD_PORT -le 65535 ]] || {
echo "dedicated Worker node 4 WireGuard port is invalid" >&2
return 1
}
[[ $CLUSTER_WORKER_NODE_4_NETWORK_APPROVED == true ||
$CLUSTER_WORKER_NODE_4_NETWORK_APPROVED == false ]] || {
echo "dedicated Worker node 4 network approval must be true or false" >&2
return 1
}
[[ $CLUSTER_WORKER_NODE_4_ENABLED == true || $CLUSTER_WORKER_NODE_4_ENABLED == false ]] || {
echo "dedicated Worker node 4 enabled flag must be true or false" >&2
return 1
}
elif [[ -n $CLUSTER_WORKER_NODE_4_NAME || -n $CLUSTER_WORKER_NODE_4_SITE ]]; then
echo "dedicated Worker node 4 metadata requires AI_GATEWAY_WORKER_NODE_4_HOST" >&2
return 1 return 1
fi fi
[[ $ALI_REGION =~ ^[a-z0-9-]+$ && $ALI_BUCKET =~ ^[A-Za-z0-9.-]+$ ]] || { [[ $ALI_REGION =~ ^[a-z0-9-]+$ && $ALI_BUCKET =~ ^[A-Za-z0-9.-]+$ ]] || {
@@ -140,8 +140,8 @@ pool_saturated_pods() {
check_wireguard() { check_wireguard() {
local -a hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST") local -a hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST")
local -a ips=(10.77.0.1 10.77.0.2 10.77.0.3) local -a ips=(10.77.0.1 10.77.0.2 10.77.0.3)
[[ -z $CLUSTER_NINGBO_WORKER_HOST ]] || { [[ $CLUSTER_WORKER_NODE_4_ENABLED != true ]] || {
hosts+=("$CLUSTER_NINGBO_WORKER_HOST") hosts+=("$CLUSTER_WORKER_NODE_4_HOST")
ips+=(10.77.0.4) ips+=(10.77.0.4)
} }
local source target output loss average handshakes peer_count local source target output loss average handshakes peer_count
+44 -31
View File
@@ -652,28 +652,41 @@ verify_release_cas() {
-o 'jsonpath={.spec.replicas}') == "$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_NINGBO" ]] -o 'jsonpath={.spec.replicas}') == "$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_NINGBO" ]]
[[ $(remote_kubectl get deployment easyai-worker-hongkong -n "$namespace" \ [[ $(remote_kubectl get deployment easyai-worker-hongkong -n "$namespace" \
-o 'jsonpath={.spec.replicas}') == "$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_HONGKONG" ]] -o 'jsonpath={.spec.replicas}') == "$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_HONGKONG" ]]
if [[ -n $CLUSTER_NINGBO_WORKER_HOST ]]; then if [[ $CLUSTER_WORKER_NODE_4_ENABLED == true ]]; then
local expected_node expected_replicas site pod_count local expected_replicas eligible_nodes eligible_count pod_report
for site in ningbo hongkong; do local running_count invalid_count node4_count used_nodes
case $site in case $CLUSTER_WORKER_NODE_4_SITE in
ningbo) ningbo) expected_replicas=$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_NINGBO ;;
expected_node=easyai-ningbo-worker-2 hongkong) expected_replicas=$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_HONGKONG ;;
expected_replicas=$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_NINGBO esac
;; eligible_nodes=$(remote_kubectl get nodes \
hongkong) -l "easyai.io/site=$CLUSTER_WORKER_NODE_4_SITE,easyai.io/worker=true" \
expected_node=easyai-hongkong -o json | jq -c '[.items[].metadata.name]')
expected_replicas=$AI_GATEWAY_ACCEPTANCE_BASE_REPLICAS_HONGKONG eligible_count=$(jq 'length' <<<"$eligible_nodes")
;; jq -e --arg node "$CLUSTER_WORKER_NODE_4_NAME" 'index($node) != null' \
esac <<<"$eligible_nodes" >/dev/null
pod_count=$(remote_kubectl get pods -n "$namespace" \ pod_report=$(remote_kubectl get pods -n "$namespace" \
-l "app.kubernetes.io/name=easyai-worker,easyai.io/site=$site" \ -l "app.kubernetes.io/name=easyai-worker,easyai.io/site=$CLUSTER_WORKER_NODE_4_SITE" -o json)
--field-selector "spec.nodeName=$expected_node" -o json | jq \ running_count=$(jq '[.items[] | select(.status.phase=="Running")] | length' <<<"$pod_report")
'[.items[] | select(.status.phase=="Running")] | length') invalid_count=$(jq --argjson nodes "$eligible_nodes" \
[[ $pod_count == "$expected_replicas" ]] || { '[.items[] | select(.status.phase=="Running") | select(.spec.nodeName as $node | $nodes | index($node) | not)] | length' \
echo "Worker placement does not match the dedicated node: site=$site node=$expected_node running_pods=$pod_count expected=$expected_replicas" >&2 <<<"$pod_report")
return 1 node4_count=$(jq --arg node "$CLUSTER_WORKER_NODE_4_NAME" \
} '[.items[] | select(.status.phase=="Running" and .spec.nodeName==$node)] | length' <<<"$pod_report")
done used_nodes=$(jq '[.items[] | select(.status.phase=="Running") | .spec.nodeName] | unique | length' \
<<<"$pod_report")
[[ $running_count == "$expected_replicas" && $invalid_count == 0 ]] || {
echo "Worker placement is outside eligible nodes: site=$CLUSTER_WORKER_NODE_4_SITE running_pods=$running_count expected=$expected_replicas invalid=$invalid_count" >&2
return 1
}
if (( expected_replicas > 0 && node4_count == 0 )); then
echo "dedicated Worker node has no baseline pod: site=$CLUSTER_WORKER_NODE_4_SITE node=$CLUSTER_WORKER_NODE_4_NAME" >&2
return 1
fi
if (( expected_replicas >= 2 && eligible_count >= 2 && used_nodes < 2 )); then
echo "Worker baseline is not distributed across physical nodes: site=$CLUSTER_WORKER_NODE_4_SITE used_nodes=$used_nodes" >&2
return 1
fi
fi fi
} }
@@ -2417,13 +2430,13 @@ verify_cluster_links() {
verify_wireguard "$CLUSTER_LOS_ANGELES_HOST" 10.77.0.1 300 'los_angeles_to_ningbo' verify_wireguard "$CLUSTER_LOS_ANGELES_HOST" 10.77.0.1 300 'los_angeles_to_ningbo'
verify_wireguard "$CLUSTER_HONGKONG_HOST" 10.77.0.3 300 'hongkong_to_los_angeles' verify_wireguard "$CLUSTER_HONGKONG_HOST" 10.77.0.3 300 'hongkong_to_los_angeles'
verify_wireguard "$CLUSTER_LOS_ANGELES_HOST" 10.77.0.2 300 'los_angeles_to_hongkong' verify_wireguard "$CLUSTER_LOS_ANGELES_HOST" 10.77.0.2 300 'los_angeles_to_hongkong'
if [[ -n $CLUSTER_NINGBO_WORKER_HOST ]]; then if [[ $CLUSTER_WORKER_NODE_4_ENABLED == true ]]; then
verify_wireguard "$CLUSTER_NINGBO_HOST" 10.77.0.4 80 'ningbo_to_worker_2' verify_wireguard "$CLUSTER_NINGBO_HOST" 10.77.0.4 80 'ningbo_to_worker_node_4'
verify_wireguard "$CLUSTER_NINGBO_WORKER_HOST" 10.77.0.1 80 'worker_2_to_ningbo' verify_wireguard "$CLUSTER_WORKER_NODE_4_HOST" 10.77.0.1 80 'worker_node_4_to_ningbo'
verify_wireguard "$CLUSTER_HONGKONG_HOST" 10.77.0.4 80 'hongkong_to_worker_2' verify_wireguard "$CLUSTER_HONGKONG_HOST" 10.77.0.4 80 'hongkong_to_worker_node_4'
verify_wireguard "$CLUSTER_NINGBO_WORKER_HOST" 10.77.0.2 80 'worker_2_to_hongkong' verify_wireguard "$CLUSTER_WORKER_NODE_4_HOST" 10.77.0.2 80 'worker_node_4_to_hongkong'
verify_wireguard "$CLUSTER_LOS_ANGELES_HOST" 10.77.0.4 300 'los_angeles_to_worker_2' verify_wireguard "$CLUSTER_LOS_ANGELES_HOST" 10.77.0.4 300 'los_angeles_to_worker_node_4'
verify_wireguard "$CLUSTER_NINGBO_WORKER_HOST" 10.77.0.3 300 'worker_2_to_los_angeles' verify_wireguard "$CLUSTER_WORKER_NODE_4_HOST" 10.77.0.3 300 'worker_node_4_to_losangeles'
fi fi
} }
@@ -2442,8 +2455,8 @@ verify_control_plane_and_recent_logs() {
[[ $error_count == 0 ]] || return 1 [[ $error_count == 0 ]] || return 1
fi fi
done done
if [[ -n $CLUSTER_NINGBO_WORKER_HOST && -n $runtime_observation_started_at ]]; then if [[ $CLUSTER_WORKER_NODE_4_ENABLED == true && -n $runtime_observation_started_at ]]; then
error_count=$(cluster_ssh "$CLUSTER_NINGBO_WORKER_HOST" \ error_count=$(cluster_ssh "$CLUSTER_WORKER_NODE_4_HOST" \
"journalctl -u k3s-agent --since '$runtime_observation_started_at' --no-pager 2>/dev/null | awk 'BEGIN {IGNORECASE=1} /connection refused/ || /i\/o timeout/ || /node.*not ready/ || /failed to sync/ {count++} END {print count+0}'") "journalctl -u k3s-agent --since '$runtime_observation_started_at' --no-pager 2>/dev/null | awk 'BEGIN {IGNORECASE=1} /connection refused/ || /i\/o timeout/ || /node.*not ready/ || /failed to sync/ {count++} END {print count+0}'")
[[ $error_count == 0 ]] || return 1 [[ $error_count == 0 ]] || return 1
fi fi
+12 -10
View File
@@ -15,10 +15,10 @@ load_cluster_env
hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST") hosts=("$CLUSTER_NINGBO_HOST" "$CLUSTER_HONGKONG_HOST" "$CLUSTER_LOS_ANGELES_HOST")
wireguard_ips=(10.77.0.1 10.77.0.2 10.77.0.3) wireguard_ips=(10.77.0.1 10.77.0.2 10.77.0.3)
site_labels=(ningbo hongkong losangeles) site_labels=(ningbo hongkong losangeles)
if [[ -n $CLUSTER_NINGBO_WORKER_HOST ]]; then if [[ $CLUSTER_WORKER_NODE_4_ENABLED == true ]]; then
hosts+=("$CLUSTER_NINGBO_WORKER_HOST") hosts+=("$CLUSTER_WORKER_NODE_4_HOST")
wireguard_ips+=(10.77.0.4) wireguard_ips+=(10.77.0.4)
site_labels+=(ningbo-worker-2) site_labels+=("$CLUSTER_WORKER_NODE_4_NAME")
fi fi
peer_count=$((${#hosts[@]} - 1)) peer_count=$((${#hosts[@]} - 1))
for source_index in "${!hosts[@]}"; do for source_index in "${!hosts[@]}"; do
@@ -55,7 +55,7 @@ done
echo "wireguard_acceptance=PASS peers=${#hosts[@]} handshakes=$((${#hosts[@]} * peer_count)) packet_loss_lt_1=true" echo "wireguard_acceptance=PASS peers=${#hosts[@]} handshakes=$((${#hosts[@]} * peer_count)) packet_loss_lt_1=true"
expected_nodes=3 expected_nodes=3
[[ -z $CLUSTER_NINGBO_WORKER_HOST ]] || expected_nodes=4 [[ $CLUSTER_WORKER_NODE_4_ENABLED != true ]] || expected_nodes=4
cluster_state=$(cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$expected_nodes" <<'REMOTE' cluster_state=$(cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$expected_nodes" <<'REMOTE'
set -euo pipefail set -euo pipefail
expected_nodes=$1 expected_nodes=$1
@@ -72,22 +72,24 @@ printf 'nodes=%s\n' "$($kubectl get nodes --no-headers | wc -l)"
REMOTE REMOTE
) )
[[ $cluster_state == nodes="$expected_nodes" ]] [[ $cluster_state == nodes="$expected_nodes" ]]
if [[ -n $CLUSTER_NINGBO_WORKER_HOST ]]; then if [[ $CLUSTER_WORKER_NODE_4_ENABLED == true ]]; then
worker_state=$(cluster_ssh "$CLUSTER_NINGBO_HOST" 'bash -s' <<'REMOTE' worker_state=$(cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- \
"$CLUSTER_WORKER_NODE_4_NAME" "$CLUSTER_WORKER_NODE_4_SITE" <<'REMOTE'
set -euo pipefail set -euo pipefail
kubectl='k3s kubectl' kubectl='k3s kubectl'
node=easyai-ningbo-worker-2 node=$1
site=$2
[[ $($kubectl get node "$node" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') == True ]] [[ $($kubectl get node "$node" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') == True ]]
[[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/site}') == ningbo ]] [[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/site}') == "$site" ]]
[[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/worker}') == true ]] [[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/worker}') == true ]]
[[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/database}') == false ]] [[ $($kubectl get node "$node" -o jsonpath='{.metadata.labels.easyai\.io/database}') == false ]]
[[ $($kubectl get node "$node" -o json | jq '[.spec.taints[]? | select(.key=="easyai.io/worker-only" and .effect=="NoSchedule")] | length') -eq 1 ]] [[ $($kubectl get node "$node" -o json | jq '[.spec.taints[]? | select(.key=="easyai.io/worker-only" and .effect=="NoSchedule")] | length') -eq 1 ]]
[[ $($kubectl get pods -A --field-selector "spec.nodeName=$node" -o json | [[ $($kubectl get pods -A --field-selector "spec.nodeName=$node" -o json |
jq '[.items[] | select(.metadata.namespace!="kube-system" and .metadata.labels["app.kubernetes.io/name"]!="easyai-worker")] | length') -eq 0 ]] jq '[.items[] | select(.metadata.namespace!="kube-system" and .metadata.labels["app.kubernetes.io/name"]!="easyai-worker")] | length') -eq 0 ]]
printf 'worker_node=ready\n' printf 'worker_node=ready node=%s site=%s\n' "$node" "$site"
REMOTE REMOTE
) )
[[ $worker_state == worker_node=ready ]] [[ $worker_state == "worker_node=ready node=$CLUSTER_WORKER_NODE_4_NAME site=$CLUSTER_WORKER_NODE_4_SITE" ]]
fi fi
echo "k3s_control_plane=PASS servers=3 worker_nodes=$((expected_nodes - 3)) witness_business_pods=0" echo "k3s_control_plane=PASS servers=3 worker_nodes=$((expected_nodes - 3)) witness_business_pods=0"