JuiceFS 生产迁移执行手册(纯命令版·无变量)

使用前提:调研完成、演练完成、窗口已批准。
用法:先在 §0 把每个 <占位符> 的真实值查好写在旁边 → 执行时整条命令复制、替换尖括号再敲。
每条命令自包含,换终端、换窗口都不受影响。每段【GATE】不通过 → 立即走 §7 对应段回滚,不硬闯。
场景标记:【K8s】= CSI 挂载环境;【宿主】= 宿主机 FUSE 挂载环境;都有就都执行。

0. 占位符对照表(执行前全部填好)

占位符填什么去哪查
<REDIS密码>元数据引擎密码CSI Secret metaurl 解码 / 宿主机挂载进程
<REDIS地址> <REDIS端口> <REDIS_DB>Redis 地址/端口/库号同上,如 xxx:6379 0
<PD1_IP> <PD2_IP> <PD3_IP>三台 TiKV 节点 ECS 内网 IP云控制台
<前缀>TiKV 命名前缀,默认 jfs与演练验证过的一致
<REDIS_NS> <REDIS_POD>Redis 所在命名空间/Pod 名`kubectl get pods -A \grep redis`(集群内场景)
<MOUNT_POD>任一 Mount Pod 名kubectl -n <NS> get pod -l app.kubernetes.io/name=juicefs-mount
<SECRET命名空间> <SECRET名1/2/3>所有含 metaurl 的 CSI Secret逐个 PV 查 nodePublishSecretRef + SC 查
<业务NS> <业务DEPLOY名> <原副本数>全部使用该文件系统的业务`kubectl describe pvc \grep "Used By"`
<挂载点> <挂载参数>宿主机挂载点和完整参数`ps aux \grep "juicefs mount"` 原样抄
<业务服务名>宿主机上业务进程名systemctl / supervisor / compose
<AK> <SK>对象存储密钥Secret 解码 / 云平台
<SSH密码>ECS 的 SSH 密码自有记录

1. 部署 TiKV 集群(窗口前完成,ECS ×3)

# 1.1 三台 ECS 系统初始化(每台都执行)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
echo 'vm.swappiness = 0' >> /etc/sysctl.conf && sysctl -p
mkfs.ext4 /dev/vdb && mkdir -p /data && mount /dev/vdb /data
echo '/dev/vdb /data ext4 nodelalloc,noatime 0 0' >> /etc/fstab && df -h /data

# 1.2 中控机装 tiup(任选一台能 ssh 到三台 ECS 的机器)
curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
source ~/.bash_profile 2>/dev/null || source ~/.bashrc

# 1.3 拓扑:PD×3 + TiKV×3 + 监控(不装 TiDB),生成后按实际 IP 改
tiup cluster template --minimal > topo.yaml
# 编辑 topo.yaml:pd_servers 填三台 IP、tikv_servers 填三台 IP、monitoring/grafana 填其中一台

tiup cluster check ./topo.yaml --user root -p '<SSH密码>'        # 有 fail 项全部修完再往下
tiup cluster deploy jfs-tikv v8.1.0 ./topo.yaml --user root -p '<SSH密码>'
tiup cluster start jfs-tikv --init
tiup cluster display jfs-tikv                                    # 全部 Up

【GATE-1】display 全 Up;df -h /data 是独立盘;从任一客户端机器 nc -zv <PD1_IP> 2379 通(安全组已对客户端网段放行 2379/2380/20160)。


2. T-1 备份(窗口前一天)

mkdir -p ~/jfs-migration && cd ~/jfs-migration

# 【K8s】2.1 备份全部 CSI Secret(有几个写几行)
kubectl -n <SECRET命名空间> get secret <SECRET名1> -o yaml > secret-<SECRET名1>.yaml
kubectl -n <SECRET命名空间> get secret <SECRET名2> -o yaml > secret-<SECRET名2>.yaml
kubectl -n <SECRET命名空间> get secret <SECRET名3> -o yaml > secret-<SECRET名3>.yaml
ls -la                                          # 确认每个文件非空

# 【宿主】2.2 备份 fstab + 挂载参数
grep -i juicefs /etc/fstab > fstab.bak
ps aux | grep "juicefs mount" | grep -v grep > mount-cmd.bak
mount | grep -i juicefs >> mount-cmd.bak

# 2.3 Redis 落盘备份(两种通道按环境选一种)
# 通道一:Redis 在 K8s 里
kubectl -n <REDIS_NS> exec <REDIS_POD> -- sh -c "redis-cli -a '<REDIS密码>' --no-auth-warning save && cat /data/dump.rdb | base64" | base64 -d > redis-backup.rdb
# 通道二:云 Redis / 直连
redis-cli -h <REDIS地址> -p <REDIS端口> -a '<REDIS密码>' --no-auth-warning --rdb redis-backup.rdb
ls -lh redis-backup.rdb                         # 非空

# 2.4 客户端版本(≥1.3.0 才能 --binary,低了去掉 --binary 改用 meta-prod.json)
kubectl -n <NS> exec <MOUNT_POD> -- juicefs version

【GATE-2】备份文件齐全非空;版本达标。


3. T0 停写(窗口开始)

# 【K8s】3A.1 缩容全部业务(有几个写几行)
kubectl -n <业务NS> scale deploy <业务DEPLOY名1> --replicas=0
kubectl -n <业务NS> scale deploy <业务DEPLOY名2> --replicas=0

# 【K8s】3A.2 确认 Mount Pod 全部消失(CSI 按需创建,业务停了挂载即释放)
watch kubectl get pods -A | grep juicefs-mount          # 等到输出为空,Ctrl+C 退出

# 【宿主】3B.1 停业务 + 卸载
systemctl stop <业务服务名>
umount <挂载点>
mount | grep -i juicefs                                 # 无输出 = 干净

4. 三道闸(全绿才能导)

# 闸1:Sessions 为空
kubectl -n <NS> exec <MOUNT_POD所在NS的任一含客户端Pod> -- juicefs status 'redis://:<REDIS密码>@<REDIS地址>:<REDIS端口>/<REDIS_DB>'
# 输出中 Sessions 段必须没有任何条目
# (Mount Pod 已随停写消失时:在执行机上直接跑 juicefs status 同命令)

# 闸2:写计数两次采样不涨(间隔 30 秒,人工对比两次输出一致)
kubectl -n <REDIS_NS> exec <REDIS_POD> -- redis-cli -a '<REDIS密码>' --no-auth-warning info stats | grep -E 'total_writes|instantaneous_ops'
sleep 30
kubectl -n <REDIS_NS> exec <REDIS_POD> -- redis-cli -a '<REDIS密码>' --no-auth-warning info stats | grep -E 'total_writes|instantaneous_ops'
# total_writes 差值 = 0 且 ops ≈ 0

# 闸3:业务方书面确认全部停写(群里文字确认,截图留证)

【GATE-3】三道全过。闸1/闸2 不过 → 找出还在写的客户端处理掉重新过闸;找不到就回滚(§7.0)。

5. 正式 dump + load + 验证

# 5.1 导出(一致性版本。K8s 内:迁移 Pod 里跑;集群外:执行机直接跑)
time juicefs dump 'redis://:<REDIS密码>@<REDIS地址>:<REDIS端口>/<REDIS_DB>' /tmp/meta-prod.zstd --binary --keep-secret-key

# 5.2 校验 + 双异地备份
md5sum /tmp/meta-prod.zstd
kubectl cp <NS>/juicefs-migrator:/tmp/meta-prod.zstd ~/jfs-migration/meta-prod.zstd    # Pod 场景
cp /tmp/meta-prod.zstd ~/jfs-migration/                                                # 执行机场景
# 再拷一份到第二台机器:scp ~/jfs-migration/meta-prod.zstd root@<另一台机器IP>:~/

# 5.3 导入 TiKV
time juicefs load 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>' /tmp/meta-prod.zstd
# 中断处理:tiup cluster display jfs-tikv 定位 → 修复 →(新集群可 destroy 重建)→ 用同一份文件重试

# 5.4 补密钥(5.1 没带 --keep-secret-key 时才执行)
juicefs config 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>' --access-key '<AK>' --secret-key '<SK>'

# 5.5 验证四件套
juicefs fsck 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>' /
juicefs ls -R 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>' / > /tmp/new-list.txt
juicefs ls -R 'redis://:<REDIS密码>@<REDIS地址>:<REDIS端口>/<REDIS_DB>' / > /tmp/old-list.txt
wc -l /tmp/new-list.txt /tmp/old-list.txt              # 行数必须一致
diff <(head -50 /tmp/new-list.txt) <(head -50 /tmp/old-list.txt)   # 抽样比对无输出=一致

【GATE-4】fsck 无 ERROR、条目数一致、抽样无差异。不过 → §7.1 回滚。

6. 切换 + 拉起

# 【K8s】6A.1 生成新 metaurl 的 base64(复制输出备用)
echo -n 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>' | base64 -w0; echo

# 【K8s】6A.2 patch 全部 CSI Secret(有几个写几行,<上一步的base64>原样粘贴)
kubectl -n <SECRET命名空间> patch secret <SECRET名1> --type merge -p '{"data":{"metaurl":"<上一步的base64>"}}'
kubectl -n <SECRET命名空间> patch secret <SECRET名2> --type merge -p '{"data":{"metaurl":"<上一步的base64>"}}'
kubectl -n <SECRET命名空间> patch secret <SECRET名3> --type merge -p '{"data":{"metaurl":"<上一步的base64>"}}'

# 【K8s】6A.3 抽查确认(应显示 tikv://...)
kubectl -n <SECRET命名空间> get secret <SECRET名1> -o jsonpath='{.data.metaurl}' | base64 -d; echo

# 【K8s】6A.4 拉起业务(CSI 自动用新 metaurl 重建 Mount Pod)
kubectl -n <业务NS> scale deploy <业务DEPLOY名1> --replicas=<原副本数>
kubectl -n <业务NS> scale deploy <业务DEPLOY名2> --replicas=<原副本数>
sleep 20 && kubectl get pods -A | grep juicefs-mount    # 新 Mount Pod 出现

# 【K8s】6A.5 验证:挂载命令行已是 tikv://;业务读写正常
kubectl -n <NS> get pod -l app.kubernetes.io/name=juicefs-mount
kubectl -n <NS> exec <新MOUNT_POD> -- sh -c "cat /proc/1/cmdline | tr '\0' ' '; echo"

# 【宿主】6B.1 原参数 + 新 URL 重挂(<挂载参数>逐字沿用 mount-cmd.bak)
juicefs mount 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>' <挂载点> <挂载参数> &
sleep 3 && mount | grep -i juicefs

# 【宿主】6B.2 改 fstab(用编辑器把 redis:// 那行的地址整段换成 tikv:// 新地址)
vi /etc/fstab

# 【宿主】6B.3 拉起业务 + 读写验证
systemctl start <业务服务名>
ls <挂载点>
echo 切换验证 > <挂载点>/.migration-test && cat <挂载点>/.migration-test && rm <挂载点>/.migration-test

【GATE-5】挂载指向 TiKV、业务读写正常、业务方确认。不过 → §7.2 回滚。


7. 回滚(按所处阶段选一段执行)

# 7.0 闸口未过 / 停写未完成:恢复业务即可(Redis 没动过)
kubectl -n <业务NS> scale deploy <业务DEPLOY名1> --replicas=<原副本数>

# 7.1 dump/load/验证失败(还没改 Secret/没重挂):同 7.0,业务回 Redis 零影响

# 7.2 已切换后失败:
# 【K8s】缩容 → 从备份恢复 Secret → 拉起 → 确认回到 redis://
kubectl -n <业务NS> scale deploy <业务DEPLOY名1> --replicas=0
kubectl replace -f ~/jfs-migration/secret-<SECRET名1>.yaml --force
kubectl replace -f ~/jfs-migration/secret-<SECRET名2>.yaml --force
kubectl -n <业务NS> scale deploy <业务DEPLOY名1> --replicas=<原副本数>
kubectl -n <NS> exec <MOUNT_POD> -- sh -c "cat /proc/1/cmdline | tr '\0' ' '; echo"

# 【宿主】卸载 → 挂回 Redis → 恢复 fstab → 拉起
umount <挂载点>
juicefs mount 'redis://:<REDIS密码>@<REDIS地址>:<REDIS端口>/<REDIS_DB>' <挂载点> <挂载参数> &
cp ~/jfs-migration/fstab.bak /etc/fstab
systemctl start <业务服务名>
# 注意:切换期间写入 TiKV 的新数据不随回滚带回,先和业务确认窗口内有无写入

8. 观察期(T+1 ~ T+14,每日)

tiup cluster display jfs-tikv                                   # 节点全 Up
kubectl get pods -A | grep -v Running                           # 无异常(K8s 场景)
kubectl -n <NS> logs <MOUNT_POD> --tail=100 | grep -iE 'error'  # 无持续增长报错
juicefs status 'tikv://<PD1_IP>:2379,<PD2_IP>:2379,<PD3_IP>:2379/<前缀>'
ssh <PD1_IP> 'iostat -x 1 3'                                    # TiKV 磁盘 util 不饱和
# 另看 Grafana:TiKV CPU/内存/磁盘延迟/Leader 分布

9. 收尾(观察期满)

# 9.1 TiKV 节点遗留清理(如经历过 destroy 重建)
ssh <PD1_IP> 'rm -rf /data/tikv-old /data/pd-old 2>/dev/null'

# 9.2 Redis 下线(确认无回滚需求后;先停,rdb 备份留 ≥1 个月再删资源)
kubectl -n <REDIS_NS> scale statefulset <REDIS_POD去掉-0> --replicas=0     # 集群内场景
# 云 Redis:控制台退订前先导出备份

# 9.3 归档:dump 文件 + md5 + Secret 备份 + 各 GATE 输出截图 → 变更记录归档
ls -la ~/jfs-migration/

标签: none

添加新评论