本文介绍如何实现阿里云文件存储NAS SMB协议文件系统之间的数据迁移。

前提条件

拥有一个存有数据的SMB协议文件系统,并且拥有一个专有网络类型挂载点。

背景信息

Robocopy是Windows系统自带的目录复制命令,该功能可以创建两个文件结构完全的镜像副本而不复制任何不需要的重复文件,同时还允许您保留所有相关文件信息,包括日期、时间戳等等。

准备工作

  1. 查看源挂载点信息。
    迁移之前请记录源文件系统的挂载点和所属的专有网络VPC信息。更多信息,请参见查看挂载点列表
    说明 如果您的文件系统只有经典网络挂载点,需要创建一个专有网络挂载点。具体操作,请参见添加挂载点
  2. 配置目标挂载点。
    说明
    • 如果目标文件系统和源文件系统在同一地域,为了方便迁移操作,请尽量保证目标挂载点与源挂载点在同一个VPC网络内。
    • 如果不在同一个VPC内,ECS与两个文件系统挂载点之间的网络必须连通,且能正常访问两个文件系统。
    给目标SMB文件系统准备挂载点,可以采用以下三种方式:
    • 在目标地域和可用区创建新的文件系统,自动创建新的挂载点。具体操作,请参见通过控制台创建通用型NAS文件系统
      说明 如果您购买按量付费的通用型(容量型/性能型)SMB文件系统,请选择与源挂载点相同的VPC网络和虚拟交换机,即可自动生成目标挂载点。在新的文件系统创建之后,可以购买资源包,以节省费用。
    • 在已有的文件系统上找到已有的挂载点。具体操作,请参见查看挂载点地址
    • 在已有的文件系统上创建新的挂载点。具体操作,请参见添加挂载点
      以下情况需要添加挂载点:
      • 如果您希望将数据迁移至已有的文件系统,而已有的挂载点与源挂载点属于不同的VPC网络且两个VPC网络未连通。
      • 创建新的文件系统后,没有自动生成挂载点。

实施迁移

在准备好源和目标挂载点后,创建新的ECS,同时挂载两个SMB文件系统后,使用Robocopy工具进行复制即可实现数据迁移。迁移数据的操作如下所示。

  1. 挂载源和目标文件系统。
    重要 推荐购买新的临时ECS执行迁移操作。如果使用已有的ECS执行迁移操作,会与正在运行的业务争抢CPU和网络带宽资源。
    登录ECS管理控制台单击创建实例后,在基础配置页面配置如下信息。
    • 地域及可用区:选择源文件系统所在的地域及可用区。
    • 实例规格:一般选择最低规格即可。
    • 镜像:选择Windows Server版本,建议您选择2019版本 。
    • 存储:单击共享盘NAS下方的增加文件存储进行配置,详情请参考下图示例。
      说明
      • 如果源和目标挂载点都在同一个VPC网络中,可以在ECS购买页面中配置NAS挂载信息,ECS启动后,源和目标NAS文件系统会自动挂载。
      • 如果源和目标挂载点不在同一个VPC网络中,在ECS购买页面中只需配置源文件系统。在ECS完成创建后,手动挂载目标文件系统,详情请参见同地域跨VPC挂载文件系统
      挂载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
    挂载成功
  2. 迁移数据。
    执行以下命令,将源文件系统(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协议文件系统
  3. 检查迁移结果。
    迁移完后,执行以下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文件系统。
    1. 执行net use记录现有NAS挂载信息,注意NAS挂载到的本地盘符。
    2. 执行命令,卸载旧的文件系统。
      net use Z: /delete
      挂载命令中的盘符(Z:),请根据实际挂载盘符进行替换。
      说明
      • 执行 net use * /delete命令,手动卸载Windows系统中所有已挂载的文件系统。
      • 执行net use * /delete /y命令,自动卸载Windows系统中所有已挂载的文件系统。
    3. 挂载新文件系统到原本的盘符。更多有关挂载参数的信息,请参见Windows系统挂载SMB文件系统
    4. 启动访问NAS的进程,确认读写正常。
    5. 修改auto_mount.bat中的自动挂载信息,将旧的挂载点替换为新的挂载点。
  • 使用Windows容器挂载NAS文件系统。
    1. 修改现有YAML配置文件,将旧的挂载点替换为新挂载点。
    2. 用修改后的配置文件生成新pod,确认其挂载新的文件系统成功并可正常读写。
    3. 回收使用旧的文件系统的所有pod。
重要 在业务切换到新的文件系统后,请继续保留旧的文件系统的数据至少一个星期。不要立刻删除旧的文件系统里的数据,以避免因数据误删除或误同步而造成数据丢失。

常见问题

如何加速迁移数据至NAS SMB协议文件系统

如果您需要加速迁移海量数据(例如,10 T以上的上亿小文件,单个文件100K左右的文件),同时也在往同样的SMB文件系统中写入大量的业务数据。您可通过在windows ECS上安装最新的Python程序执行迁移。具体操作如下:

  1. 下载并安装最新的Python程序。
  2. 设置Python执行路径到系统环境变量PATH中(例如,C:\Python27)。
    set PATH=%PATH%;C:\python27
    您也可以执行where python命令,查看python的安装路径。如下图所示。Python路径
  3. 将如下所示的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:])
  4. 迁移数据。命令格式:python ./migration.py -s <source path> -d <destination path> [-r BeginIndex:EndIndex | -f] [-i ignoredFile]

    命令格式中主要参数说明如下表所示。

    参数说明
    <source path>指定源文件系统挂载点的目录。例如,Z盘。
    <destination path>指定目标文件系统挂载点目录。例如,Y盘
    -r指定只迁移源文件系统目录的子目录。
    -f指定只迁移源文件系统目录中的第一层的普通文件。
    -i指定源文件系统目录中无需迁移的文件。
    说明 缺省时,不需要设置-r-f,script会顺序Robocopy所有子目录后再copy第一层的普通文件。如果希望利用多客户端的cpu/network来并发加速迁移不同的子目录,在所有客户端都可以访问同一个源盘时,可以指定迁移的子目录的range,比如第一个客户端使用-r 0:9999, 第二个客户端使用-r 10000:19999等等。

    例如,将源文件系统Z:\中的数据迁移至目标文件系统的Y:\。示例如下:

    python ./migration.py -s Z:\ -d Y:\