数据迁移
本页介绍 SwanLab 私有化版本在不同 Kubernetes 集群之间进行全量数据迁移的完整流程。
迁移流程示意图
SwanLab 私有化服务的数据库进行跨集群数据迁移的流程,包含三个核心区域:
- 源集群 (Source):数据导出端,包含数据库和对象存储。
- 中转存储 (Transit):S3 对象存储作为临时中转站
- 目标集群 (Target):数据接收端,完成恢复。
| 数据类型 | 迁移方式 | 说明 |
|---|---|---|
| PostgreSQL | pg_dump → S3 中转 → pg_restore | 客户端直连 Service,不需要挂载 PVC |
| ClickHouse | 原生 BACKUP DATABASE → S3 → RESTORE DATABASE | 客户端直连 Service,原生 S3 备份/恢复 |
| Redis | redis-cli --rdb → S3 中转 → 落盘 PVC | 导出直连 Service,导入写入目标 PVC |
| 对象存储(MinIO / S3) | rclone sync 直传云端 S3 或直接对接云端 S3 | 存算分离时免搬运 |
图例说明:
- 🔵 Phase 1: Export — 源集群导出数据到 S3 中转存储桶(DB Export + S3 Sync)
- 🟢 Phase 2: Import — 数据库数据从 S3 导入到新集群(DB Import,物理迁移)
- 🟢 Phase 2: Direct Use(虚线)— 对象存储不移动,新集群直接通过
values.yaml同样的配置访问 public 和 private 对象存储桶
🧾 前置条件
资源准备
- 兼容 S3 协议的对象存储桶,可用空间至少大于 ClickHouse + PostgreSQL + Redis 存储总和的 1.1 倍
- 目标集群需要额外部署一套 全新未激活 的 SwanLab 服务,且必须挂载云硬盘
- 如果原始集群和目标集群版本不一致,建议先对原始集群的 chart 版本进行升级
镜像准备
| 镜像 | 用途 | 说明 |
|---|---|---|
repo.swanlab.cn/public/pg-migrator:16.1 | PostgreSQL 迁移 | 含 pg_dump / pg_restore / rclone |
repo.swanlab.cn/self-hosted/clickhouse-server:24.3 | ClickHouse 迁移 | 含 clickhouse-client,原生 BACKUP/RESTORE |
repo.swanlab.cn/public/redis-migrator:7.4.0-v8 | Redis 迁移 | 含 redis-cli / rclone |
repo.swanlab.cn/public/s3-migrator:bookworm-slim | S3 迁移 | 含 rclone,用于 MinIO → 云端 S3 同步 |
权限准备
- 对原始集群和目标集群均具有可写权限(原始集群和目标集群可以是同一个集群,但必须各自存在一套独立的 SwanLab 服务)
- 准备好
access_key和secret_key,需要具备对象存储桶的读写权限 - ClickHouse 用户需具备
BACKUP/RESTORE权限 - PostgreSQL 用户需具备
pg_dump/pg_restore权限
配置变量
使用前请确认以下信息:
| 是否准备 | 占位变量 | 说明 |
|---|---|---|
| ✅ | SOURCE_NAMESPACE / TARGET_NAMESPACE | 源集群和目标集群的命名空间 |
| ✅ | S3_REGION | 用于备份的对象存储桶的地域 |
| ✅ | S3_BUCKET | 存储桶名称 |
| ✅ | S3_ENDPOINT | S3 格式的对象存储 Endpoint,如 tos-s3-cn-beijing.volces.com |
| ✅ | S3_AK / S3_SK | 对象存储可写密钥 |
| ✅ | S3_PATH_PREFIX | S3 中的备份路径前缀,默认 origin-backup-datas |
🪜 操作步骤
WARNING
⚠️ 迁移前必须停机! 数据迁移时需要确保两套 SwanLab 应用层服务都保持停机状态,由中间 Job 执行迁移,否则会因为数据写入状态不一致等问题,造成迁移失败。
1. 修改配置文件
- 操作位置:源集群、目标集群
迁移配置按组件拆分为独立的 ConfigMap / Secret,需分别在源集群和目标集群创建。
源集群配置(config-export.yaml)
源集群需要创建以下配置:
swanlab-backup-storage-config/swanlab-backup-storage-secret— S3 中转桶凭据swanlab-pg-backup-config/swanlab-pg-backup-secret— PG 连接信息swanlab-ch-backup-config/swanlab-ch-backup-secret— CH 连接信息swanlab-redis-backup-config— Redis 连接信息
config-export.yaml
## ============================================================
## 源集群:迁移配置(export 专用)
## ============================================================
## Part 1: 备份中转桶(DB export/import 共用)
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-backup-storage-config
namespace: <SOURCE_NAMESPACE> # ⚠️ 必填:【源集群】K8s 命名空间
data:
S3_ENDPOINT: "<BACKUP_S3_ENDPOINT>" # ⚠️ 备份中转桶 endpoint,无 bucket 前缀
S3_REGION: "<BACKUP_S3_REGION>" # ⚠️ 备份中转桶 region
S3_BUCKET: "<BACKUP_S3_BUCKET>" # ⚠️ 备份中转桶名
S3_PATH_PREFIX: "<S3_PATH_PREFIX>" # ⚠️ 桶内路径前缀(import 侧需保持一致)
S3_FORCE_PATH_STYLE: "false"
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-backup-storage-secret
namespace: <SOURCE_NAMESPACE>
type: Opaque
stringData:
S3_AK: "<BACKUP_S3_AK>" # ⚠️ 备份中转桶 AK
S3_SK: "<BACKUP_S3_SK>" # ⚠️ 备份中转桶 SK
---
## Part 2: Redis 连接配置
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-redis-backup-config
namespace: <SOURCE_NAMESPACE>
data:
REDIS_HOST: "swanlab-self-hosted-redis" # ⚠️ 【源集群】Redis Service 名
REDIS_PORT: "6379" # ⚠️ Redis 端口
REDIS_BACKUP_OBJECT: "redis-restore.rdb" # ⚠️ S3 中转对象名(import 侧需一致)
---
## Part 3: PostgreSQL 连接配置
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-pg-backup-config
namespace: <SOURCE_NAMESPACE>
data:
PG_HOST: "swanlab-self-hosted-postgres" # ⚠️ 【源集群】PG Service 名
PG_USER: "swanlab" # ⚠️ PG 用户名
PG_DB: "app" # ⚠️ PG 数据库名
PG_BACKUP_OBJECT: "postgres-restore.dump" # ⚠️ S3 中转对象名(import 侧需一致)
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-pg-backup-secret
namespace: <SOURCE_NAMESPACE>
type: Opaque
stringData:
PG_PASSWORD: "<PG_PASSWORD>" # ⚠️ 与 Helm values 中 postgres password 一致
---
## Part 4: ClickHouse 连接配置
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-ch-backup-config
namespace: <SOURCE_NAMESPACE>
data:
CLICKHOUSE_HOST: "swanlab-self-hosted-clickhouse" # ⚠️ 【源集群】CH Service 名
CLICKHOUSE_DB: "app" # ⚠️ 待备份的数据库名
CLICKHOUSE_BACKUP_PATH: "clickhouse-backup" # ⚠️ S3 中转桶内备份子路径(import 侧需一致)
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-ch-backup-secret
namespace: <SOURCE_NAMESPACE>
type: Opaque
stringData:
CLICKHOUSE_USER: "<CLICKHOUSE_USER>" # ⚠️ 与 Helm values 中 CH username 一致
CLICKHOUSE_PASSWORD: "<CLICKHOUSE_PASSWORD>" # ⚠️ 与 Helm values 中 CH password 一致TIP
如果源集群使用的是 MinIO 内置存储,还需要额外创建 swanlab-cloud-s3-config / swanlab-cloud-s3-secret,详见 导出 S3 数据 章节。
kubectl apply -f config-export.yaml目标集群配置(config-import.yaml)
目标集群需要创建以下配置:
swanlab-backup-storage-config/swanlab-backup-storage-secret— S3 中转桶凭据swanlab-pg-backup-config/swanlab-pg-backup-secret— 目标 PG 连接信息swanlab-ch-backup-config/swanlab-ch-backup-secret— 目标 CH 连接信息swanlab-redis-backup-config— Redis 对象名
config-import.yaml
## ============================================================
## 目标集群:迁移配置(import 专用)
## ============================================================
## 只有 DB 数据(PG/CH/Redis)需要从中转桶 import 回来
## MinIO 数据直接上云,不经过中转桶,不需要 import 配置
## ============================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-backup-storage-config
namespace: <TARGET_NAMESPACE> # ⚠️ 必填:【目标集群】K8s 命名空间
data:
S3_ENDPOINT: "<BACKUP_S3_ENDPOINT>" # ⚠️ 备份中转桶 endpoint,无 bucket 前缀
S3_REGION: "<BACKUP_S3_REGION>" # ⚠️ 备份中转桶 region
S3_BUCKET: "<BACKUP_S3_BUCKET>" # ⚠️ 备份中转桶名
S3_PATH_PREFIX: "<S3_PATH_PREFIX>" # ⚠️ 桶内路径前缀
S3_FORCE_PATH_STYLE: "false"
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-backup-storage-secret
namespace: <TARGET_NAMESPACE>
type: Opaque
stringData:
S3_AK: "<BACKUP_S3_AK>" # ⚠️ 备份中转桶 AK
S3_SK: "<BACKUP_S3_SK>" # ⚠️ 备份中转桶 SK
---
## Redis 对象名配置(import 仅下载落盘,不连接 Redis)
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-redis-backup-config
namespace: <TARGET_NAMESPACE>
data:
REDIS_BACKUP_OBJECT: "redis-restore.rdb" # ⚠️ 需与 export 侧一致
---
## PostgreSQL 连接配置(import 需连接目标集群 PG 执行 pg_restore)
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-pg-backup-config
namespace: <TARGET_NAMESPACE>
data:
## 迁移目标二选一:
## A) 内置 PG:填 K8s Service 名,例如 swanlab-self-hosted-postgres
## B) 云端 RDS:填 RDS 域名,例如 rm-xxx.pg.rds.aliyuncs.com
## ⚠️ 选 B 时,需将 import Job 所在集群出口 IP 加入 RDS 白名单
PG_HOST: "<PG_HOST>" # ⚠️ K8s Service 名 或 云端 RDS 域名
PG_PORT: "5432" # ⚠️ PG 端口(云端 RDS 按实际填)
PG_USER: "<PG_USER>" # ⚠️ PG 用户名
PG_DB: "app" # ⚠️ PG 数据库名
PG_BACKUP_OBJECT: "postgres-restore.dump" # ⚠️ 需与 export 侧一致
PGSSLMODE: "prefer" # ⚠️ 云端 RDS 通常需 require 或更高
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-pg-backup-secret
namespace: <TARGET_NAMESPACE>
type: Opaque
stringData:
PG_PASSWORD: "<PG_PASSWORD>" # ⚠️ 与目标集群 Helm values 中 postgres password 一致
---
## ClickHouse 连接配置(Job 通过 client 连接目标 CH 执行 RESTORE)
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-ch-backup-config
namespace: <TARGET_NAMESPACE>
data:
CLICKHOUSE_HOST: "<CLICKHOUSE_HOST>" # ⚠️ 【目标集群】CH Service 名
CLICKHOUSE_DB: "app" # ⚠️ 待恢复的数据库名(需与 export 侧一致)
CLICKHOUSE_BACKUP_PATH: "clickhouse-backup" # ⚠️ 需与 export 侧一致
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-ch-backup-secret
namespace: <TARGET_NAMESPACE>
type: Opaque
stringData:
CLICKHOUSE_USER: "<CLICKHOUSE_USER>" # ⚠️ 目标集群 CH 用户名
CLICKHOUSE_PASSWORD: "<CLICKHOUSE_PASSWORD>" # ⚠️ 目标集群 CH 密码kubectl apply -f config-import.yaml同时修改导出/导入 Job YAML 中的以下字段:
namespace:对应的 K8s 命名空间claimName:对应的 PVC 名称(Redis 导入时需要)nodeSelector:本机部署场景需指定节点
2. 停服
- 操作位置:源集群、目标集群
务必按照顺序停服。
# 切断所有外部流量
kubectl scale deploy/swanlab-self-hosted --replicas=0 -n <your_namespace># 停后端核心服务
kubectl scale deploy/swanlab-self-hosted-server --replicas=0 -n <your_namespace>
# 停后端指标OLAP服务
kubectl scale deploy/swanlab-self-hosted-house --replicas=0 -n <your_namespace># 先等缓冲区消费完(看 logs 无新写入后 Ctrl+C)
kubectl logs -f swanlab-self-hosted-vector-0 -n <your_namespace> --tail=20
kubectl logs -f swanlab-self-hosted-vector-1 -n <your_namespace> --tail=20TIP
针对 「目标集群」 的 Redis 数据库,由于需要用 rdb 快照恢复服务,因此需要单独对【目标集群】的Redis服务停服:
kubectl scale deploy/swanlab-self-hosted-redis --replicas=0 -n <your_namespace>
3. 导出 DB 数据
- 操作位置:源集群
每个数据库的迁移被封装为独立的 Job,可并行执行。
导出说明
- PostgreSQL:Job 作为客户端直连 PG Service,使用
pg_dump -Fc(custom 格式)导出后通过rclone上传至 S3 中转桶 - ClickHouse:Job 作为客户端直连 CH Service,使用原生
BACKUP DATABASE ... TO S3()命令,CH 服务端保证 parts 一致性。 - Redis:Job 作为客户端直连 Redis Service,使用
redis-cli --rdb导出 RDB 后通过rclone上传。
export-postgres
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-export-postgres
namespace: <SOURCE_NAMESPACE> # ⚠️ 必填:【源集群】K8s 命名空间
labels:
swanlab: postgres
spec:
backoffLimit: 1
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
## ⚠️ /tmp 走 emptyDir + sizeLimit,避免 dump 落到节点根盘写爆触发节点级驱逐。
## sizeLimit 超限时只杀本 Pod,不殃及邻居。
volumes:
- name: tmp
emptyDir:
sizeLimit: 20Gi # ⚠️ 约 10-15 GB dump;DB 更大时按实际调,勿超节点可分配
containers:
- name: swanlab-export-postgres
image: repo.swanlab.cn/public/pg-migrator:16.1
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 4Gi
ephemeral-storage: 20Gi
volumeMounts:
- name: tmp
mountPath: /tmp
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: swanlab-pg-backup-secret
key: PG_PASSWORD
envFrom:
- configMapRef:
name: swanlab-backup-storage-config
- configMapRef:
name: swanlab-pg-backup-config
- secretRef:
name: swanlab-backup-storage-secret
command:
- /bin/bash
- -c
- |
set -e
set -o pipefail
echo "[1/5] 配置 rclone S3..."
export RCLONE_CONFIG_S3_TYPE=s3
export RCLONE_CONFIG_S3_PROVIDER=Other
export RCLONE_CONFIG_S3_ENDPOINT="${S3_ENDPOINT}"
export RCLONE_CONFIG_S3_REGION="${S3_REGION}"
export RCLONE_CONFIG_S3_ACCESS_KEY_ID="${S3_AK}"
export RCLONE_CONFIG_S3_SECRET_ACCESS_KEY="${S3_SK}"
export RCLONE_CONFIG_S3_NO_CHECK_BUCKET=true
export RCLONE_CONFIG_S3_FORCE_PATH_STYLE="${S3_FORCE_PATH_STYLE:-false}"
echo "[2/5] 连接检查..."
pg_isready -h "${PG_HOST}" -U "${PG_USER}" -d "${PG_DB}"
DUMP=/tmp/${PG_BACKUP_OBJECT}
echo "[3/5] pg_dump 导出中 (custom format)..."
(while true; do sleep 5; [ -f "${DUMP}" ] && echo " 导出中... $(du -h "${DUMP}" | cut -f1)"; done) &
PROGRESS_PID=$!
pg_dump \
-h "${PG_HOST}" \
-U "${PG_USER}" \
-d "${PG_DB}" \
-Fc \
--no-owner \
--no-acl \
-f "${DUMP}"
kill $PROGRESS_PID 2>/dev/null || true
echo " 导出完成: $(du -h "${DUMP}" | cut -f1)"
ls -lh "${DUMP}"
pg_restore -l "${DUMP}" | sed -n '1,50p'
PREFIX="${S3_PATH_PREFIX%/}"
TARGET="s3:${S3_BUCKET}/${PREFIX}"
echo "[4/5] 上传 dump 到 ${TARGET}/..."
rclone copyto "${DUMP}" "${TARGET}/${PG_BACKUP_OBJECT}" \
--s3-no-check-bucket \
--contimeout 10s \
--timeout 5m \
--low-level-retries 3 \
--retries 3 \
--transfers 1 \
--checkers 1 \
--progress \
-vv
echo "[5/5] 上传后列表:"
rclone lsf "${TARGET}/" --s3-no-check-bucket
echo "--- PostgreSQL dump 导出 + 上传完成 ---"export-clickhouse
## ============================================================
## ClickHouse 原生 BACKUP 导出 Job
## ============================================================
## 原理:Job 作为纯 client,连接源集群运行中的 CH Service,
## 通过原生 BACKUP DATABASE 命令将数据写入 S3 备份中转桶。
##
## ✅ 不需要 scale down CH Deployment,不挂 PVC。
## BACKUP 在服务端执行,CH 自身保证 parts 一致性。
##
## ⚠️ 前置条件:
## - 源集群 CH Service 对 Job 可达(9000 端口通)
## - CH 用户具备 BACKUP 权限
## - 源/目标 CH 版本一致
## ============================================================
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-export-clickhouse
namespace: <SOURCE_NAMESPACE> # ⚠️ 必填:【源集群】K8s 命名空间
labels:
swanlab: clickhouse
spec:
backoffLimit: 0 # ⚠️ TB 级备份失败不自动重跑,避免数据状态混乱
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
# # ===== 本机部署场景:指定节点 =====
# nodeSelector:
# kubernetes.io/hostname: <node_name>
# tolerations:
# - operator: Exists
# # ============================================================
containers:
- name: swanlab-export-clickhouse
image: repo.swanlab.cn/self-hosted/clickhouse-server:24.3
imagePullPolicy: IfNotPresent
env:
- name: TZ
value: "UTC"
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: swanlab-ch-backup-secret
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: swanlab-ch-backup-secret
key: CLICKHOUSE_PASSWORD
envFrom:
- configMapRef:
name: swanlab-backup-storage-config
- configMapRef:
name: swanlab-ch-backup-config
- secretRef:
name: swanlab-backup-storage-secret
command: ["/bin/bash", "-c"]
args:
- |
set -e
CH_CLIENT=(clickhouse-client
--host "${CLICKHOUSE_HOST}"
--user "${CLICKHOUSE_USER}"
--password "${CLICKHOUSE_PASSWORD}")
echo "[1/5] 连接源 ClickHouse..."
"${CH_CLIENT[@]}" --query "SELECT version(), uptime()"
echo " ${CLICKHOUSE_DB} 下表列表:"
"${CH_CLIENT[@]}" --query "SHOW TABLES FROM ${CLICKHOUSE_DB}" || true
echo "[2/5] 预估数据量..."
"${CH_CLIENT[@]}" --query "
SELECT
formatReadableSize(sum(bytes_on_disk)) AS disk_size,
formatReadableQuantity(sum(rows)) AS total_rows,
count() AS parts
FROM system.parts
WHERE database='${CLICKHOUSE_DB}' AND active
"
## 根据 S3_FORCE_PATH_STYLE 动态构造 URL
if [ "${S3_FORCE_PATH_STYLE:-false}" = "true" ]; then
S3_URL="https://${S3_ENDPOINT}/${S3_BUCKET}"
else
S3_URL="https://${S3_BUCKET}.${S3_ENDPOINT}"
fi
PREFIX="${S3_PATH_PREFIX%/}"
BACKUP_PATH="${PREFIX:+${PREFIX}/}${CLICKHOUSE_BACKUP_PATH}"
echo "[3/5] 发起 BACKUP DATABASE ${CLICKHOUSE_DB} → s3://${S3_BUCKET}/${BACKUP_PATH}..."
## ASYNC 模式发起,轮询 system.backups 跟踪进度
BACKUP_RESULT=$("${CH_CLIENT[@]}" --format=TabSeparated --query "
BACKUP DATABASE ${CLICKHOUSE_DB}
TO S3(
'${S3_URL}/${BACKUP_PATH}',
'${S3_AK}',
'${S3_SK}'
)
SETTINGS
compression_method='lz4'
ASYNC
")
BACKUP_ID=$(echo "${BACKUP_RESULT}" | awk '{print $1}')
echo " 备份任务已提交,id=${BACKUP_ID}"
echo "[4/5] 轮询备份进度..."
while true; do
ROW=$("${CH_CLIENT[@]}" --format=TabSeparated --query "
SELECT status, num_files, uncompressed_size, compressed_size, error
FROM system.backups
WHERE id='${BACKUP_ID}'
")
STATUS=$(echo "${ROW}" | awk -F'\t' '{print $1}')
NUM_FILES=$(echo "${ROW}" | awk -F'\t' '{print $2}')
UNCOMPRESSED=$(echo "${ROW}" | awk -F'\t' '{print $3}')
COMPRESSED=$(echo "${ROW}" | awk -F'\t' '{print $4}')
ERROR=$(echo "${ROW}" | awk -F'\t' '{print $5}')
UNCOMPRESSED_H=$(awk "BEGIN{printf \"%.2f GB\", ${UNCOMPRESSED:-0}/1073741824}")
COMPRESSED_H=$(awk "BEGIN{printf \"%.2f GB\", ${COMPRESSED:-0}/1073741824}")
echo " [$(date +%H:%M:%S)] status=${STATUS} files=${NUM_FILES} uncompressed=${UNCOMPRESSED_H} compressed=${COMPRESSED_H}"
case "${STATUS}" in
BACKUP_CREATED)
echo "--- ClickHouse 原生备份完成 ---"
echo "备份位置: s3://${S3_BUCKET}/${BACKUP_PATH}"
echo "总文件数: ${NUM_FILES},压缩后: ${COMPRESSED_H},未压缩: ${UNCOMPRESSED_H}"
break
;;
BACKUP_FAILED)
echo "ERROR: 备份失败:${ERROR}"
exit 1
;;
esac
sleep 10
done
echo "[5/5] 源侧行数统计(供 import 侧校验对比)..."
"${CH_CLIENT[@]}" --query "
SELECT
'${CLICKHOUSE_DB}' AS database,
sum(rows) AS total_rows,
formatReadableQuantity(sum(rows)) AS rows_readable
FROM system.parts
WHERE database='${CLICKHOUSE_DB}' AND active
"
echo "--- 导出侧完成,请记录上述行数供 import 侧校验 ---"export-redis
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-export-redis
namespace: <SOURCE_NAMESPACE> # ⚠️ 必填:【源集群】K8s 命名空间
labels:
swanlab: redis
spec:
backoffLimit: 1
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: swanlab-export-redis
image: repo.swanlab.cn/public/redis-migrator:7.4.0-v8
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 4Gi
envFrom:
- configMapRef:
name: swanlab-backup-storage-config
- configMapRef:
name: swanlab-redis-backup-config
- secretRef:
name: swanlab-backup-storage-secret
command:
- /bin/bash
- -c
- |
set -e
set -o pipefail
: "${REDIS_HOST:?REDIS_HOST 未配置}"
: "${REDIS_PORT:?REDIS_PORT 未配置}"
: "${REDIS_BACKUP_OBJECT:?REDIS_BACKUP_OBJECT 未配置}"
echo "[1/5] 配置 rclone S3..."
export RCLONE_CONFIG_S3_TYPE=s3
export RCLONE_CONFIG_S3_PROVIDER=Other
export RCLONE_CONFIG_S3_ENDPOINT="${S3_ENDPOINT}"
export RCLONE_CONFIG_S3_REGION="${S3_REGION}"
export RCLONE_CONFIG_S3_ACCESS_KEY_ID="${S3_AK}"
export RCLONE_CONFIG_S3_SECRET_ACCESS_KEY="${S3_SK}"
export RCLONE_CONFIG_S3_NO_CHECK_BUCKET=true
export RCLONE_CONFIG_S3_FORCE_PATH_STYLE="${S3_FORCE_PATH_STYLE:-false}"
echo "[2/5] 连接检查..."
redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" PING
DUMP=/tmp/${REDIS_BACKUP_OBJECT}
echo "[3/5] redis-cli --rdb 导出中..."
(while true; do sleep 5; [ -f "${DUMP}" ] && echo " 导出中... $(du -h "${DUMP}" | cut -f1)"; done) &
PROGRESS_PID=$!
redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" --rdb "${DUMP}"
kill $PROGRESS_PID 2>/dev/null || true
echo " 导出完成: $(du -h "${DUMP}" | cut -f1)"
ls -lh "${DUMP}"
PREFIX="${S3_PATH_PREFIX%/}"
TARGET="s3:${S3_BUCKET}/${PREFIX}"
echo "[4/5] 上传 RDB 到 ${TARGET}/..."
rclone copyto "${DUMP}" "${TARGET}/${REDIS_BACKUP_OBJECT}" \
--s3-no-check-bucket \
--contimeout 10s \
--timeout 5m \
--low-level-retries 3 \
--retries 3 \
--transfers 1 \
--checkers 1 \
--progress \
-vv
echo "[5/5] 上传后列表:"
rclone lsf "${TARGET}/" --s3-no-check-bucket
echo "--- Redis RDB 导出 + 上传完成 ---"# 并行执行所有导出 Job
kubectl apply -f export/
# 查看执行状态
kubectl logs -f job/swanlab-export-postgres -n <your_namespace>
kubectl logs -f job/swanlab-export-clickhouse -n <your_namespace>
kubectl logs -f job/swanlab-export-redis -n <your_namespace>
# 确认所有 Job 完成
kubectl get jobs -n <your_namespace>4. 导出 S3 数据(可选)
- 操作位置:源集群
情况 1:原始集群已集成 S3 URL
如果原本已经挂载好 S3 接入点,只需配置源集群 values.yaml 中相同的 S3 接入点配置,详见 外部 S3 集成配置。
情况 2:原始集群使用 MinIO 挂载 PVC
MinIO 数据需要通过 rclone sync 同步到公有云对象存储。此 Job 直连 MinIO Service。
前置条件
swanlab-cloud-s3-config中的MINIO_ENDPOINT指向现役 MinIO 的集群内 Service 地址swanlab-cloud-s3-secret中的MINIO_AK/SK为现役 MinIO 的 root 凭据- 业务已停写,保证读到的对象集稳定
首先创建 S3 迁移专用配置:
config-s3-export.yaml
## MinIO → 云端 S3 迁移专用配置
apiVersion: v1
kind: ConfigMap
metadata:
name: swanlab-cloud-s3-config
namespace: <SOURCE_NAMESPACE>
data:
## MinIO 本地源(直连 Service,同 namespace 用短名)
MINIO_ENDPOINT: "http://swanlab-self-hosted-s3:9000" # ⚠️ 现役 MinIO Service
MINIO_PUBLIC_BUCKET: "swanlab-public" # MinIO 中 public 桶名
MINIO_PRIVATE_BUCKET: "swanlab-private" # MinIO 中 private 桶名
## 云端 S3 目标(支持 bucket/path 格式或独立桶)
CLOUD_S3_ENDPOINT: "<CLOUD_S3_ENDPOINT>" # ⚠️ 云端 S3 endpoint
CLOUD_S3_REGION: "<CLOUD_S3_REGION>" # ⚠️ 云端 S3 region
CLOUD_S3_PUBLIC_DEST: "<CLOUD_S3_PUBLIC_DEST>" # ⚠️ 云端 public 桶/路径
CLOUD_S3_PRIVATE_DEST: "<CLOUD_S3_PRIVATE_DEST>" # ⚠️ 云端 private 桶/路径
---
apiVersion: v1
kind: Secret
metadata:
name: swanlab-cloud-s3-secret
namespace: <SOURCE_NAMESPACE>
type: Opaque
stringData:
MINIO_AK: "<MINIO_AK>" # ⚠️ 源集群 MinIO AK
MINIO_SK: "<MINIO_SK>" # ⚠️ 源集群 MinIO SK
CLOUD_S3_AK: "<CLOUD_S3_AK>" # ⚠️ 云端 S3 AK
CLOUD_S3_SK: "<CLOUD_S3_SK>" # ⚠️ 云端 S3 SKkubectl apply -f config-s3-export.yamlexport-s3 Job YAML
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-migrate-s3-export
namespace: <SOURCE_NAMESPACE> # ⚠️ 必填:【源集群】K8s 命名空间
labels:
swanlab: minio
spec:
backoffLimit: 0 # ⚠️ TB 级迁移失败不自动重跑
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: rclone-worker
image: repo.swanlab.cn/public/s3-migrator:bookworm-slim
imagePullPolicy: IfNotPresent
env:
- name: MINIO_AK
valueFrom:
secretKeyRef:
name: swanlab-cloud-s3-secret
key: MINIO_AK
- name: MINIO_SK
valueFrom:
secretKeyRef:
name: swanlab-cloud-s3-secret
key: MINIO_SK
- name: MINIO_ENDPOINT
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: MINIO_ENDPOINT
- name: MINIO_PUBLIC_BUCKET
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: MINIO_PUBLIC_BUCKET
- name: MINIO_PRIVATE_BUCKET
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: MINIO_PRIVATE_BUCKET
- name: CLOUD_S3_AK
valueFrom:
secretKeyRef:
name: swanlab-cloud-s3-secret
key: CLOUD_S3_AK
- name: CLOUD_S3_SK
valueFrom:
secretKeyRef:
name: swanlab-cloud-s3-secret
key: CLOUD_S3_SK
- name: CLOUD_S3_ENDPOINT
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: CLOUD_S3_ENDPOINT
- name: CLOUD_S3_REGION
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: CLOUD_S3_REGION
- name: CLOUD_S3_PUBLIC_DEST
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: CLOUD_S3_PUBLIC_DEST
- name: CLOUD_S3_PRIVATE_DEST
valueFrom:
configMapKeyRef:
name: swanlab-cloud-s3-config
key: CLOUD_S3_PRIVATE_DEST
command: ["/bin/bash", "-c"]
args:
- |
set -e
set -o pipefail
echo "[1/4] 探活现役 MinIO(${MINIO_ENDPOINT})..."
for i in $(seq 1 30); do
if curl -sf -o /dev/null --max-time 2 "${MINIO_ENDPOINT}/minio/health/live"; then
echo " MinIO 可达"
break
fi
[ "$i" -eq 30 ] && { echo "ERROR: MinIO 不可达"; exit 1; }
echo " probing minio... ($i/30)"
sleep 3
done
echo "[2/4] 配置 rclone S3..."
export RCLONE_CONFIG_LOCALMINIO_TYPE=s3
export RCLONE_CONFIG_LOCALMINIO_PROVIDER=Minio
export RCLONE_CONFIG_LOCALMINIO_ACCESS_KEY_ID="${MINIO_AK}"
export RCLONE_CONFIG_LOCALMINIO_SECRET_ACCESS_KEY="${MINIO_SK}"
export RCLONE_CONFIG_LOCALMINIO_ENDPOINT="${MINIO_ENDPOINT}"
export RCLONE_CONFIG_LOCALMINIO_FORCE_PATH_STYLE=true
export RCLONE_CONFIG_CLOUDS3_TYPE=s3
export RCLONE_CONFIG_CLOUDS3_PROVIDER=Other
export RCLONE_CONFIG_CLOUDS3_ENV_AUTH=false
export RCLONE_CONFIG_CLOUDS3_ACCESS_KEY_ID="${CLOUD_S3_AK}"
export RCLONE_CONFIG_CLOUDS3_SECRET_ACCESS_KEY="${CLOUD_S3_SK}"
export RCLONE_CONFIG_CLOUDS3_ENDPOINT="${CLOUD_S3_ENDPOINT}"
export RCLONE_CONFIG_CLOUDS3_REGION="${CLOUD_S3_REGION}"
export RCLONE_CONFIG_CLOUDS3_NO_CHECK_BUCKET=true
export RCLONE_CONFIG_CLOUDS3_FORCE_PATH_STYLE=false
echo "[3/4] 同步 public..."
rclone sync localminio:${MINIO_PUBLIC_BUCKET} clouds3:${CLOUD_S3_PUBLIC_DEST} \
--transfers 16 --checkers 8 --buffer-size 128M \
--multi-thread-streams 4 --s3-upload-concurrency 4 \
--retries 3 --retries-sleep 5s --progress -vv
echo "[4/4] 同步 private..."
rclone sync localminio:${MINIO_PRIVATE_BUCKET} clouds3:${CLOUD_S3_PRIVATE_DEST} \
--transfers 16 --checkers 8 --buffer-size 128M \
--multi-thread-streams 4 --s3-upload-concurrency 4 \
--retries 3 --retries-sleep 5s --progress -vv
echo "=== S3 Export 完成 ==="kubectl apply -f export-s3.yaml -n <your_namespace>
# 查看 rclone 日志
kubectl logs -f job/swanlab-migrate-s3-export -n <your_namespace>5. 导入 DB 数据
- 操作位置:目标集群
与导出类似,每个数据库有独立的导入 Job,可并行执行。
导入架构说明
- PostgreSQL:Job 从 S3 中转桶下载 dump 文件后,通过
pg_restore连接目标集群 PG Service 恢复。不挂载 PVC,不需要 scale down PG。 - ClickHouse:Job 连接目标集群 CH Service,使用原生
RESTORE DATABASE ... FROM S3()命令。恢复前自动DROP DATABASE IF EXISTS再重建空库(不可逆操作)。 - Redis:Job 从 S3 下载 RDB 落盘到目标 Redis PVC 的
/data/dump.rdb,导入完成后需手动 scale 回 Redis Deployment,Redis 启动时自动加载。
ClickHouse 恢复注意
- CH 的
RESTORE没有pg_restore --clean的等价选项,allow_non_empty_tables=1是 merge 不是 replace,会导致行数翻倍,因此脚本在 RESTORE 前执行DROP DATABASE IF EXISTS - 若目标库单表 > 50GB,CH 默认禁止 DROP,需先在 CH config 里设
<max_table_size_to_drop>0</max_table_size_to_drop>解锁
import-postgres
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-import-postgres
namespace: <TARGET_NAMESPACE> # ⚠️ 必填:【目标集群】K8s 命名空间
labels:
swanlab: postgres
spec:
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
volumes:
- name: tmp
emptyDir:
sizeLimit: 20Gi
containers:
- name: swanlab-import-postgres
image: repo.swanlab.cn/public/pg-migrator:16.1
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 4Gi
ephemeral-storage: 20Gi
volumeMounts:
- name: tmp
mountPath: /tmp
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: swanlab-pg-backup-secret
key: PG_PASSWORD
envFrom:
- configMapRef:
name: swanlab-backup-storage-config
- configMapRef:
name: swanlab-pg-backup-config
- secretRef:
name: swanlab-backup-storage-secret
command:
- /bin/bash
- -c
- |
set -e
set -o pipefail
echo "[1/4] 配置 rclone S3..."
export RCLONE_CONFIG_S3_TYPE=s3
export RCLONE_CONFIG_S3_PROVIDER=Other
export RCLONE_CONFIG_S3_ENDPOINT="${S3_ENDPOINT}"
export RCLONE_CONFIG_S3_REGION="${S3_REGION}"
export RCLONE_CONFIG_S3_ACCESS_KEY_ID="${S3_AK}"
export RCLONE_CONFIG_S3_SECRET_ACCESS_KEY="${S3_SK}"
export RCLONE_CONFIG_S3_NO_CHECK_BUCKET=true
export RCLONE_CONFIG_S3_FORCE_PATH_STYLE="${S3_FORCE_PATH_STYLE:-false}"
PREFIX="${S3_PATH_PREFIX%/}"
TARGET="s3:${S3_BUCKET}/${PREFIX}"
DUMP=/tmp/${PG_BACKUP_OBJECT}
echo "[2/4] 从 ${TARGET}/ 下载..."
rclone copyto "${TARGET}/${PG_BACKUP_OBJECT}" "${DUMP}" \
--s3-no-check-bucket \
--contimeout 10s \
--timeout 5m \
--low-level-retries 3 \
--retries 3 \
--transfers 1 \
--checkers 1 \
--progress \
-vv
echo "[3/4] 连接检查..."
ls -lh "${DUMP}"
pg_isready -h "${PG_HOST}" -p "${PG_PORT:-5432}" -U "${PG_USER}" -d "${PG_DB}"
echo "[4/4] pg_restore 恢复中..."
pg_restore \
-h "${PG_HOST}" \
-p "${PG_PORT:-5432}" \
-U "${PG_USER}" \
-d "${PG_DB}" \
--no-owner \
--no-acl \
--clean \
--if-exists \
-v \
"${DUMP}"
echo "--- PostgreSQL 恢复完成 ---"import-clickhouse
## ============================================================
## ClickHouse 原生 RESTORE 导入 Job
## ============================================================
## ⚠️ 清库行为(不可逆):
## - RESTORE 前会 DROP DATABASE IF EXISTS 再重建空库
## - 若目标库单表 > 50GB,需预先解锁 DROP 保护
## ============================================================
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-import-clickhouse
namespace: <TARGET_NAMESPACE> # ⚠️ 必填:【目标集群】K8s 命名空间
labels:
swanlab: clickhouse
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: swanlab-import-clickhouse
image: repo.swanlab.cn/self-hosted/clickhouse-server:24.3
imagePullPolicy: IfNotPresent
env:
- name: TZ
value: "UTC"
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: swanlab-ch-backup-secret
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: swanlab-ch-backup-secret
key: CLICKHOUSE_PASSWORD
envFrom:
- configMapRef:
name: swanlab-backup-storage-config
- configMapRef:
name: swanlab-ch-backup-config
- secretRef:
name: swanlab-backup-storage-secret
command: ["/bin/bash", "-c"]
args:
- |
set -e
CH_CLIENT=(clickhouse-client
--host "${CLICKHOUSE_HOST}"
--user "${CLICKHOUSE_USER}"
--password "${CLICKHOUSE_PASSWORD}")
echo "[1/5] 连接目标 ClickHouse 并清理目标库..."
"${CH_CLIENT[@]}" --query "SELECT version(), uptime()"
## 清库:DROP + CREATE 空库
EXISTING_TABLES=$("${CH_CLIENT[@]}" --query "SHOW TABLES FROM ${CLICKHOUSE_DB}" 2>/dev/null || true)
if [ -n "${EXISTING_TABLES}" ]; then
echo " 目标库 ${CLICKHOUSE_DB} 非空,执行 DROP..."
"${CH_CLIENT[@]}" --query "DROP DATABASE IF EXISTS ${CLICKHOUSE_DB}"
fi
"${CH_CLIENT[@]}" --query "CREATE DATABASE IF NOT EXISTS ${CLICKHOUSE_DB}"
echo " 目标库 ${CLICKHOUSE_DB} 已就绪(空)"
## 根据 S3_FORCE_PATH_STYLE 动态构造 URL
if [ "${S3_FORCE_PATH_STYLE:-false}" = "true" ]; then
S3_URL="https://${S3_ENDPOINT}/${S3_BUCKET}"
else
S3_URL="https://${S3_BUCKET}.${S3_ENDPOINT}"
fi
PREFIX="${S3_PATH_PREFIX%/}"
BACKUP_PATH="${PREFIX:+${PREFIX}/}${CLICKHOUSE_BACKUP_PATH}"
echo "[2/5] 发起 RESTORE DATABASE ${CLICKHOUSE_DB} ← s3://${S3_BUCKET}/${BACKUP_PATH}..."
RESTORE_RESULT=$("${CH_CLIENT[@]}" --format=TabSeparated --query "
RESTORE DATABASE ${CLICKHOUSE_DB}
FROM S3(
'${S3_URL}/${BACKUP_PATH}',
'${S3_AK}',
'${S3_SK}'
)
ASYNC
")
RESTORE_ID=$(echo "${RESTORE_RESULT}" | awk '{print $1}')
echo " 恢复任务已提交,id=${RESTORE_ID}"
echo "[3/5] 轮询恢复进度..."
while true; do
ROW=$("${CH_CLIENT[@]}" --format=TabSeparated --query "
SELECT status, num_files, uncompressed_size, compressed_size, error
FROM system.backups
WHERE id='${RESTORE_ID}'
")
STATUS=$(echo "${ROW}" | awk -F'\t' '{print $1}')
NUM_FILES=$(echo "${ROW}" | awk -F'\t' '{print $2}')
UNCOMPRESSED=$(echo "${ROW}" | awk -F'\t' '{print $3}')
COMPRESSED=$(echo "${ROW}" | awk -F'\t' '{print $4}')
ERROR=$(echo "${ROW}" | awk -F'\t' '{print $5}')
UNCOMPRESSED_H=$(awk "BEGIN{printf \"%.2f GB\", ${UNCOMPRESSED:-0}/1073741824}")
COMPRESSED_H=$(awk "BEGIN{printf \"%.2f GB\", ${COMPRESSED:-0}/1073741824}")
echo " [$(date +%H:%M:%S)] status=${STATUS} files=${NUM_FILES} uncompressed=${UNCOMPRESSED_H} compressed=${COMPRESSED_H}"
case "${STATUS}" in
RESTORED)
echo "--- ClickHouse 原生恢复完成 ---"
break
;;
RESTORE_FAILED)
echo "ERROR: 恢复失败:${ERROR}"
exit 1
;;
esac
sleep 10
done
echo "[4/5] 恢复后表列表及大小..."
"${CH_CLIENT[@]}" --query "SHOW TABLES FROM ${CLICKHOUSE_DB}"
"${CH_CLIENT[@]}" --query "
SELECT
table,
formatReadableSize(total_bytes) AS size,
formatReadableQuantity(total_rows) AS rows
FROM system.tables
WHERE database = '${CLICKHOUSE_DB}'
ORDER BY total_bytes DESC
"
echo "[5/5] 恢复后行数统计(与 export 侧对比校验)..."
"${CH_CLIENT[@]}" --query "
SELECT
'${CLICKHOUSE_DB}' AS database,
sum(rows) AS total_rows,
formatReadableQuantity(sum(rows)) AS rows_readable
FROM system.parts
WHERE database='${CLICKHOUSE_DB}' AND active
"
echo "--- 导入侧完成,请与 export 侧输出的行数对比 ---"import-redis
apiVersion: batch/v1
kind: Job
metadata:
name: swanlab-import-redis
namespace: <TARGET_NAMESPACE> # ⚠️ 必填:【目标集群】K8s 命名空间
labels:
swanlab: redis
spec:
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
volumes:
- name: swanlab-redis-data
persistentVolumeClaim:
claimName: swanlab-redis-pvc # ⚠️ 必填:【目标集群】Redis PVC 名称
containers:
- name: swanlab-import-redis
image: repo.swanlab.cn/public/redis-migrator:7.4.0-v8
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 4Gi
envFrom:
- configMapRef:
name: swanlab-backup-storage-config
- configMapRef:
name: swanlab-redis-backup-config
- secretRef:
name: swanlab-backup-storage-secret
volumeMounts:
- name: swanlab-redis-data
mountPath: /data
command:
- /bin/bash
- -c
- |
set -e
set -o pipefail
echo "[1/4] 配置 rclone S3..."
export RCLONE_CONFIG_S3_TYPE=s3
export RCLONE_CONFIG_S3_PROVIDER=Other
export RCLONE_CONFIG_S3_ENDPOINT="${S3_ENDPOINT}"
export RCLONE_CONFIG_S3_REGION="${S3_REGION}"
export RCLONE_CONFIG_S3_ACCESS_KEY_ID="${S3_AK}"
export RCLONE_CONFIG_S3_SECRET_ACCESS_KEY="${S3_SK}"
export RCLONE_CONFIG_S3_NO_CHECK_BUCKET=true
export RCLONE_CONFIG_S3_FORCE_PATH_STYLE="${S3_FORCE_PATH_STYLE:-false}"
: "${REDIS_BACKUP_OBJECT:?REDIS_BACKUP_OBJECT 未配置}"
PREFIX="${S3_PATH_PREFIX%/}"
TARGET="s3:${S3_BUCKET}/${PREFIX}"
DUMP=/tmp/${REDIS_BACKUP_OBJECT}
echo "[2/4] 从 ${TARGET}/ 下载..."
rclone copyto "${TARGET}/${REDIS_BACKUP_OBJECT}" "${DUMP}" \
--s3-no-check-bucket \
--contimeout 10s \
--timeout 5m \
--low-level-retries 3 \
--retries 3 \
--transfers 1 \
--checkers 1 \
--progress \
-vv
echo "[3/4] 落盘至 /data/dump.rdb..."
ls -lh "${DUMP}"
cp -f "${DUMP}" /data/dump.rdb
# 清理 AOF 残留,确保 Redis 启动时优先从 dump.rdb 加载
rm -f /data/appendonly.aof* 2>/dev/null || true
rm -rf /data/appendonlydir 2>/dev/null || true
ls -lh /data
echo "[4/4] --- Redis 恢复完成 ---"
echo "⚠️ 本 Job 完成后需 scale 回 Redis Deployment,Redis 启动时会自动加载 dump.rdb"# 并行执行所有导入 Job
kubectl apply -f import/
# 查看执行状态
kubectl logs -f job/swanlab-import-postgres -n <your_namespace>
kubectl logs -f job/swanlab-import-clickhouse -n <your_namespace>
kubectl logs -f job/swanlab-import-redis -n <your_namespace>
# 确认所有 Job 完成
kubectl get jobs -n <your_namespace>6. 重新开服
- 操作位置:目标集群
务必按照顺序开服。
# 恢复数据库服务(主要是 Redis) (replicas 必须为 1)
kubectl scale deployment swanlab-self-hosted-redis --replicas=1 -n <your_namespace>
# 确认数据库就绪
kubectl get pods -n <your_namespace> -w# 先恢复副本,再按需扩容
kubectl scale deploy/swanlab-self-hosted-house deploy/swanlab-self-hosted-server --replicas=1 -n <your_namespace># 恢复网关
kubectl scale deploy/swanlab-self-hosted --replicas=2 -n <your_namespace>恢复后可以观测 pod 健康状况与线上服务验证数据恢复情况。
🧹 Job 清理
原始和目标集群的 Job 完成后 24 小时自动清理(ttlSecondsAfterFinished: 86400)。如需手动清理:
# 源集群
kubectl delete job swanlab-export-postgres swanlab-export-clickhouse swanlab-export-redis swanlab-migrate-s3-export -n <SOURCE_NAMESPACE>
# 目标集群
kubectl delete job swanlab-import-postgres swanlab-import-clickhouse swanlab-import-redis -n <TARGET_NAMESPACE>