本文介绍如何使用robocopy工具实现阿里云文件存储NAS SMB协议文件系统之间的数据迁移。
前提条件
拥有一个存有数据的SMB协议文件系统,并且拥有一个专有网络类型挂载点。
背景信息
Robocopy是Windows系统自带的目录复制命令,该功能可以创建两个文件结构完全的镜像副本而不复制任何不需要的重复文件,同时还允许您保留所有相关文件信息,包括日期、时间戳等等。
费用说明
NAS与NAS之间的迁移,会涉及如下费用:
准备工作
NAS文件系统之间进行数据迁移时,需要云服务器可以同时访问源NAS和目的NAS。因此,您需要确保通过同一个阿里云专有网络VPC可以同时访问两个NAS。
- 查看源文件系统挂载点信息。 
- 配置目标文件系统挂载点。 - 文件系统同地域 - 目标文件系统与源文件系统的挂载点属于同一VPC下时,在获取目标挂载点信息后,即可参照实施迁移进行数据迁移。 
- 目标文件系统与源文件系统的挂载点不属于同一VPC下时,可以采用以下三种方式准备挂载点: - 在目标地域和可用区创建新的文件系统,自动创建新的挂载点。具体操作,请参见通过控制台创建通用型NAS文件系统。 说明- 如果您购买按量付费的通用型NAS(容量型/性能型/高级型)SMB协议文件系统,请选择与源挂载点相同的VPC网络和虚拟交换机,即可自动生成目标挂载点。在新的文件系统创建之后,可以购买资源包进行抵扣,以节省费用。 
- 在已有的文件系统上创建新的挂载点。具体操作,请参见添加挂载点。 
- 通过云企业网将目标挂载点与源挂载点的VPC网络互相连通。具体操作,请参见通过云企业网实现同地域跨VPC挂载NAS。 
 
 
- 文件系统跨账号或跨地域 - 如果您的目标文件系统与源文件系统的挂载点信息不在同一个账号或同地域下时,则需通过云企业网将您的VPC网络互相连通。具体操作,请参见通过云企业网实现跨账号跨地域挂载NAS。 
 
实施迁移
在准备好源和目标挂载点后,创建新的ECS,同时挂载两个SMB协议文件系统后,使用Robocopy工具进行复制即可实现数据迁移。迁移数据的操作如下所示。
- 挂载源和目标文件系统。 重要- 推荐购买新的临时ECS执行迁移操作。如果使用已有的ECS执行迁移操作,会与正在运行的业务争抢CPU和网络带宽资源。 - 登录ECS管理控制台单击创建实例后,配置如下重要信息。 - 地域:选择源文件系统所在的地域。 
- 网络及可用区:选择源文件系统所在的VPC、可用区及交换机。 
- 实例规格:一般选择最低规格即可。 
- 镜像:选择Windows Server版本,建议您选择2019版本 。 
- 存储:单击弹性临时盘|文件存储 NAS|专属块存储集群(选填)折叠块下的添加文件存储进行配置,详情请参考下图示例。 说明- 如果源和目标挂载点都在同一个VPC中,可以在ECS购买页面中配置NAS挂载信息,ECS启动后,源和目标NAS文件系统会自动挂载。 
- 如果源和目标挂载点不在同一个VPC或同一地域或同一账号中,在ECS购买页面中只需配置源文件系统。在ECS完成创建后,手动挂载目标文件系统。关于手动挂载目标文件系统的操作,请参见挂载SMB协议文件系统。 
  
 - 在ECS创建成功后,源和目标NAS文件系统挂载完成,请执行以下命令确认。 - net use- 如果挂载成功,界面会显示以下信息。源文件系统挂载到了Z盘,目标文件系统挂载到了Y盘。 - 状态 本地 远程 网络 ------------------------------------------------------------------------------ OK Y: \\29e9c24****-eab13.cn-wulanchabu.nas.aliyuncs.com\myshare MicrosoftWindowgNetwork OK Z: \\29fe7f4****-txr31.cn-wulanchabu.nas.aliyuncs.com\myshare MicrosoftWindowgNetwork 
- 迁移数据。 - 执行以下命令,将源文件系统(Z盘)中的数据迁移到目标文件系统(Y盘)中。 - robocopy Z:\ Y:\ /e /w:5 /z /mt:32说明- 仅迁移指定目录下的数据,不包括指定目录。 - 重要字段说明如下,请根据实际情况替换。 - 参数 - 说明 - /mt - 设置并发的线程数。默认值为8。 - 取值为1~128。 - 本文示例32个线程进行多线程复制。 - /w - 设置每次错误重试的间隔秒数。 - /z - 开启断点续传。 - /e - 拷贝所有子目录(包括空目录)。 - /copyall - 复制所有的文件信息。包含: - 数据 
- 属性 
- 时间戳 
- 访问控制列表(ACL) 
- 所有者信息 
- 审计信息 
 说明- 如果您想加速迁移海量数据(例如,10 T以上的上亿小文件),可通过在windows ECS上安装最新的Python程序执行迁移。具体操作,请参见如何加速迁移数据至NAS SMB协议文件系统。 
- 检查迁移结果。 - 迁移完后,执行以下Robocopy命令,检查目标文件系统是否与源文件系统一致。 - ROBOCOPY Z:\ Y:\ /e /l /ns /njs /njh /ndl /fp /log:reconcile.txt- 重要字段说明如下,请根据实际情况替换。 - /e:仅列出目录(包括空目录)。 
- /l:不修改或复制文件,仅记录差异。 
- /fp:指在日志中包括文件的完整路径(仅在省略/ndl时有必要)。 
- /ns:指不在日志中包括文件大小。 
- /ndl:指不在日志中包括文件夹。 
- /njs:指不包括作业摘要。 
- /njh:不包括作业头。 
- /log:reconcile.txt:将迁移结果写入reconcile.txt日志中。如果已存在,将覆盖现有日志。 
 
切换应用到新的文件系统
在数据迁移完成后,如果您需要将现有业务从旧的文件系统切换到新的文件系统上,请在所有ECS和容器上卸载旧的文件系统,然后挂载新的文件系统。
- 使用ECS直接挂载NAS文件系统。 - 执行 - net use记录现有NAS挂载信息,注意NAS挂载到的本地盘符。
- 执行命令,卸载旧的文件系统。 - net use Z: /delete- 挂载命令中的盘符(Z:),请根据实际挂载盘符进行替换。 说明- 执行 net use * /delete命令,手动卸载Windows系统中所有已挂载的文件系统。 
- 执行net use * /delete /y命令,自动卸载Windows系统中所有已挂载的文件系统。 
 
- 挂载新文件系统到原本的盘符。更多有关挂载参数的信息,请参见挂载SMB协议文件系统。 
- 启动访问NAS的进程,确认读写正常。 
- 修改auto_mount.bat中的自动挂载信息,将旧的挂载点替换为新的挂载点。 
 
- 使用Windows容器挂载NAS文件系统。 - 修改现有YAML配置文件,将旧的挂载点替换为新挂载点。 
- 用修改后的配置文件生成新pod,确认其挂载新的文件系统成功并可正常读写。 
- 回收使用旧的文件系统的所有pod。 
 
在业务切换到新的文件系统后,请继续保留旧的文件系统的数据至少一个星期。不要立刻删除旧的文件系统里的数据,以避免因数据误删除或误同步而造成数据丢失。
常见问题
如何加速迁移数据至NAS SMB协议文件系统
如果您需要加速迁移海量数据(例如,10 T以上的上亿小文件,单个文件100K左右的文件),同时也在往同样的SMB文件系统中写入大量的业务数据。您可通过在windows ECS上安装最新的Python程序执行迁移。具体操作如下:
- 下载并安装最新的Python程序。 
- 设置Python执行路径到系统环境变量PATH中(例如,C:\Python27)。 - set PATH=%PATH%;C:\python27- 您也可以执行 - where python命令,查看python的安装路径。如下图所示。 
- 将如下所示的migration.py脚本拷贝到阿里云ECS实例的本地目录。例如C:\。 - #!/usr/bin/python import os import random import string import sys, getopt import datetime import time def execute_cmd(cmd): print('\tExecuting cmd: %s' % cmd) count = 0 rc = 0 while (count < 3*60): rc = os.system(cmd) if (rc != 0): count += 1 time.sleep(1) continue else: break if rc != 0: print('\tFailed to execute cmd: %s. rc:%d' %(cmd, rc)) return def migrate_subdirs(srcPath, dstPath, rangeBegin, rangeEnd, ignoreFile): currTimeStr = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S") print('Start to migrate from %s to %s for subdir range[%s, %s] at %s.\n' %(srcPath, dstPath, rangeBegin, rangeEnd, currTimeStr)) index = 0 for entry in os.listdir(srcPath): if os.path.isdir(os.path.join(srcPath, entry)): if index >= rangeBegin and index <= rangeEnd: srcSubDir = srcPath + "\\" + entry dstSubDir = dstPath + "\\" + entry print('\tBegin of migrating from the %d th dir %s to %s.' %(index, srcSubDir, dstSubDir)) cmd = "robocopy \"" + srcSubDir + "\" \"" + dstSubDir + "\" /e /w:5 /z /mt:32" if ignoreFile.strip(): cmd += " /XF \"" + ignoreFile + "\"" cmd += " >> robocopy.log" execute_cmd(cmd) print('\tEnd of migrating from %s to %s.\n' %(srcSubDir, dstSubDir)) index += 1 print('Finish to migrate from %s to %s for subdir range[%s, %s] at %s.\n' %(srcPath, dstPath, rangeBegin, rangeEnd, datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S"))) def migrate_regfiles(srcPath, dstPath, ignoreFile): currTimeStr = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S") print('Start to migrate from %s to %s for regular files at %s.\n' %(srcPath, dstPath, currTimeStr)) for entry in os.listdir(srcPath): if os.path.isfile(os.path.join(srcPath, entry)): print('\tBegin of migrating %s from %s to %s.\n' %(entry, srcPath, dstPath)) cmd = "attrib -R \"" + dstPath + "\\\\" + entry + "\"" execute_cmd(cmd) cmd = "copy \"" + srcPath + "\\\\" + entry + "\" \"" + dstPath + "\" /Y" if ignoreFile.strip(): cmd += " /XF \"" + ignoreFile + "\"" cmd += " >> robocopy.log" execute_cmd(cmd) print('\tEnd of migrating %s from %s to %s' %(entry, srcPath, dstPath)) print('Finish to migrate from %s to %s for regular files at %s.\n' %(srcPath, dstPath, datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S"))) def main(argv): srcPath = '' dstPath = '' range = '' ignoreFile = '' try: opts, args = getopt.getopt(argv,"hs:d:r:i:f",["srcPath=","dstPath=","range=","ignore="]) except getopt.GetoptError: print('migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]') print('example: migration.py -s x:\pic -d z:\pic [-r 0:100]') sys.exit(2) subdironly = False fileonly = False for opt, arg in opts: if opt == '-h': print('migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]') sys.exit() elif opt in ("-s", "--srcPath"): srcPath = arg elif opt in ("-d", "--dstPath"): dstPath = arg elif opt in ("-r", "--range"): range = arg subdironly = True elif opt in ("-f", "--file"): fileonly = True elif opt in ("-i", "--ignore"): ignoreFile = arg if not srcPath.strip() or not dstPath.strip(): print('migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]') sys.exit() if subdironly and fileonly: print('migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]') sys.exit() if not fileonly: if not range.strip(): rangeBegin = 0 rangeEnd = sys.maxsize-1 else: rangeBegin, rangeEnd = (int(x) for x in range.split(":")) if rangeBegin < 0 or rangeEnd >= sys.maxsize or rangeEnd < rangeBegin: print('migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]') print('example: migration.py -s x:\pic -d z:\pic -r 0:99') sys.exit() migrate_subdirs(srcPath, dstPath, rangeBegin, rangeEnd, ignoreFile) if not subdironly: migrate_regfiles(srcPath, dstPath, ignoreFile) if __name__ == "__main__": main(sys.argv[1:])
- 迁移数据。命令格式: - python ./migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]。- 命令格式中主要参数说明如下表所示。 - 参数 - 说明 - -s <source path> - 指定源文件系统挂载点的目录。例如 - -s Z:\,表示为源文件系统挂载点的目录为Z盘根目录。- -d <destination path> - 指定目标文件系统的挂载点目录。例如 - -d Y:\,表示目标文件系统的挂载点目录为Y盘根目录。- -r BeginIndex:EndIndex - 指定要迁移的文件范围,从 - BeginIndex到- EndIndex,例如- -r 1:100,表示要迁移的文件列表为1到100。- -f - 指定只迁移源文件系统目录中的第一层的普通文件。 - 迁移源文件系统目录中的所有文件,忽略范围限制。 - -i ignoredFile - 指定源文件系统目录中无需迁移的文件。例如 - -i ignored.txt,表示不迁移文件名为- ignored.txt的文件。说明- 缺省时,不需要设置-r或-f,script会顺序Robocopy所有子目录后再copy第一层的普通文件。如果希望利用多客户端的cpu/network来并发加速迁移不同的子目录,在所有客户端都可以访问同一个源盘时,可以指定迁移的子目录的range,比如第一个客户端使用 - -r 0:9999, 第二个客户端使用- -r 10000:19999等等。- 例如,将源文件系统Z:\中的数据迁移至目标文件系统的Y:\。示例如下: - python ./migration.py -s Z:\ -d Y:\