139 lines
4.8 KiB
YAML
139 lines
4.8 KiB
YAML
# ===================================================================
|
|
# PostgreSQL 序列同步 Job 模板
|
|
# ===================================================================
|
|
# 这个是"裸"模板, 部署时 helm hook 在 pre-install / pre-upgrade 时跑一次。
|
|
# Chart 内部 templates/ 也有一个对应的模板走 helm hook, 两种用法:
|
|
#
|
|
# 1. helm install/upgrade 自动触发 (推荐)
|
|
# → 见 templates/pg-sequence-sync.yaml (已挂 helm hook)
|
|
#
|
|
# 2. 单独手动跑 (紧急 / 调试 / 补单)
|
|
# → kubectl apply -f k8s/jobs/pg-sequence-sync/manual-job.yaml
|
|
# (manual-job.yaml 从本模板渲染, 把 ${TABLES_CSV} 替换成实际值)
|
|
#
|
|
# ⚠️ 切流量前必跑 (CLAUDE.md 强制规范, 设计文档 §10.3 hard blocker)
|
|
# ===================================================================
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: topfans-pg-sequence-sync-script
|
|
namespace: topfans
|
|
labels:
|
|
app.kubernetes.io/component: pg-sequence-sync
|
|
app.kubernetes.io/part-of: topfans
|
|
data:
|
|
# 完全展开后的脚本: kubectl create cm -n topfans --from-file=script.sh=script.sh
|
|
script.sh: |
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
PG_HOST="${PG_HOST:-postgres}"
|
|
PG_PORT="${PG_PORT:-5432}"
|
|
PG_USER="${PG_USER:-postgres}"
|
|
PG_DB="${PG_DB:-topfans}"
|
|
PG_PASSWORD="${PG_PASSWORD:?PG_PASSWORD 必须通过 envFrom secret 注入}"
|
|
TABLES_CSV="${TABLES_CSV:-assets,asset_registry,users,stars,activity_assets,collection_assets,materials,exhibitions,galleries}"
|
|
|
|
IFS=',' read -ra TABLES <<< "$TABLES_CSV"
|
|
echo "Tables to sync: ${#TABLES[@]}"
|
|
|
|
for tbl in "${TABLES[@]}"; do
|
|
tbl=$(echo "$tbl" | tr -d ' ')
|
|
[ -z "$tbl" ] && continue
|
|
max_id=$(PGPASSWORD="$PG_PASSWORD" psql \
|
|
-h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
|
|
-tAc "SELECT COALESCE(MAX(id), 0) FROM ${tbl};")
|
|
[ "$max_id" = "0" ] && { echo " ${tbl}: empty, skip"; continue; }
|
|
PGPASSWORD="$PG_PASSWORD" psql \
|
|
-h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
|
|
-c "SELECT setval('${tbl}_id_seq', ${max_id}, true);"
|
|
echo " ${tbl}: setval(${tbl}_id_seq, ${max_id}, true)"
|
|
done
|
|
|
|
# Verify
|
|
bad=0
|
|
for tbl in "${TABLES[@]}"; do
|
|
tbl=$(echo "$tbl" | tr -d ' ')
|
|
[ -z "$tbl" ] && continue
|
|
h=$(PGPASSWORD="$PG_PASSWORD" psql \
|
|
-h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \
|
|
-tAc "SELECT last_value >= COALESCE((SELECT MAX(id) FROM ONLY ${tbl}), 0) FROM pg_sequences WHERE sequencename = '${tbl}_id_seq';")
|
|
if [ "$h" != "t" ]; then
|
|
echo " ❌ ${tbl}_id_seq unhealthy"
|
|
bad=$((bad+1))
|
|
else
|
|
echo " ✅ ${tbl}_id_seq healthy"
|
|
fi
|
|
done
|
|
[ "$bad" -gt 0 ] && { echo "❌ ${bad} unhealthy"; exit 1; }
|
|
echo "✅ all healthy"
|
|
---
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: pg-sequence-sync-pg-password
|
|
namespace: topfans
|
|
labels:
|
|
app.kubernetes.io/component: pg-sequence-sync
|
|
type: Opaque
|
|
stringData:
|
|
PG_PASSWORD: "" # ← 必须从 db-credentials 同步或 CI 注入
|
|
---
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: topfans-pg-sequence-sync
|
|
namespace: topfans
|
|
labels:
|
|
app.kubernetes.io/component: pg-sequence-sync
|
|
app.kubernetes.io/part-of: topfans
|
|
annotations:
|
|
"helm.sh/hook": pre-install,pre-upgrade
|
|
"helm.sh/hook-weight": "-5" # 在 oss-cors-init (-10) 之后跑, 但顺序不重要
|
|
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
|
spec:
|
|
ttlSecondsAfterFinished: 600
|
|
backoffLimit: 0
|
|
template:
|
|
spec:
|
|
restartPolicy: Never
|
|
# postgresql-client 镜像: 用 bitnami/postgresql:latest 仅 client 部分, 不带 server
|
|
# 或 alpine + apk add postgresql-client。 这里用 Google 维护的轻量镜像
|
|
containers:
|
|
- name: psql
|
|
image: alpine:3.19
|
|
command: ["/bin/sh", "-c"]
|
|
args:
|
|
- |
|
|
set -e
|
|
apk add --no-cache postgresql16-client bash >/dev/null
|
|
bash /scripts/script.sh
|
|
env:
|
|
- name: PG_HOST
|
|
value: "postgres"
|
|
- name: PG_PORT
|
|
value: "5432"
|
|
- name: PG_USER
|
|
value: "postgres"
|
|
- name: PG_DB
|
|
value: "topfans"
|
|
- name: PG_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: db-credentials
|
|
key: DB_PASSWORD
|
|
- name: TABLES_CSV
|
|
value: "assets,asset_registry,users,stars,activity_assets,collection_assets,materials,exhibitions,galleries"
|
|
volumeMounts:
|
|
- name: script
|
|
mountPath: /scripts
|
|
readOnly: true
|
|
volumes:
|
|
- name: script
|
|
configMap:
|
|
name: topfans-pg-sequence-sync-script
|
|
defaultMode: 0o755
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65534 # nobody
|