一、背景
信创即信息技术应用创新的简称,涵盖了国产软件、国产芯片以及云计算等各个方向,也可以理解为常说的“ZZKK(自主可控)”, ZZKK是指对国内企事业单位应用系统中关键软硬件部件的安全性、可靠性、性能稳定性、安全接入等方面进行评估和测试的过程。信创的发展核心就在于通过行业应用拉动构建国产化信息技术,软硬件底层架构体系,全周期生态体系,解决核心技术被国外“卡脖子”的问题。
信创的目标就是在核心芯片、基础硬件、电脑、操作系统、中间件、数据服务器等领域实现国产替代。本单位某系统在信创ZZKK OS改造过程中,涉及部分主机采用了docker容器化部署,本文对此讨论,如何对OS改造前主机上的容器实例进行迁移和恢复,以提供参考。
二、docker容器数据
首先docker容器是分层架构,实际的数据就是底层的不可变image层+一层顶部可读写的用户空间容器层,而底层的image又可由多层构建,最底层为内核层,其上为base image层,之后每一层都可由用户构建容器时自定义或通过Dockerfile的RUN命令指定创建,即每一次在base image上执行的安装软件或进行修改或增加新的内容时,就会在当前镜像层之上创建新的镜像层,而每一层新镜像都是上一层的父镜像,再dockerfile里体现为FROM指定引入的那个镜像就是parent image(比如:FROM ubuntu:14.04 ),最常见的就是最开始的从镜像库pull下来的镜像就是父镜像,对于Base image对应就是在Dockerfile 中没有 FROM 行,或 FROM scratch(从最小镜像开始)开头的,通常base 镜像都是各种 Linux 发行版的 Docker 镜像。综上,docker采用这种分层次的文件系统结构来实现container的文件系统。
基于此,docker镜像层+容器层,我们是可以提交成新镜像导出的;但是还有可能出现容器卷和宿主数据卷映射/挂载到容器内的情况,这部分的数据也需要考虑进去:
- 数据卷:是一个供容器使用的特殊目录,位于容器中。可将宿主机的目录挂载到数据卷上,对数据卷的修改操作即时生效立刻可见的,且更新数据不会影响镜像,从而实现数据在宿主机与容器之间的迁移。数据卷的使用类似于Linux下对目录进行的mount操作。eg:
docker run -v /var/www:/data1 --name web -it centos:7 /bin/bash
,其中-v选项可以在容器内创建宿主机的/var/www数据卷于/data1/的映射,相当于直接挂载了 /var/www,这2个目录如同一个目录,一个理解后者是前者宿主机再容器内的“软连接”;- 容器卷:就是数据卷容器,它是一个普通的容器,专门提供数据卷给其他容器挂载使用用在需要在容器之间共享一些数据,最简单的方法就是使用数据卷容器。eg:
docker run --name data -v /data1 -v /data2 -it centos:7 /bin/bash
,这样就创建了一个数据卷容器,之后就可以把它挂载给其他容器使用了,容器挂载使用时需借用–volumes-from参数来挂载data容器中的数据卷到新的容器:docker run -it --volumes-from data --name web centos:7 /bin/bash
;
另外再回顾下底层这些东西,如上图所示,docker镜像层位于内核层之上,其中内核层中的AUFS,LXC,Bootfs(boot file system)一起为上层的镜像提供kernel内核支持;其中,AUFS(联合文件系统)使用同一个Linux 宿主机上的多个目录,逐个堆叠起来,在容器层面对外呈现出一个统一的文件系统。我们知道base image Linux启动需要加载bootfs + rootfs,其中bootfs负责与内核交互,它包含boot loader 和 kernel,主要负责容器启动时引导加载kernel,这时会加载bootfs文件系统,当加载完成之后整个内核就都在内存中了,此时内存的使用权由bootfs转交给内核,此时系统就会卸载bootfs,从而释放出所占用的内存,之后由用户空间的文件系统 rootfs接管,如/dev, /proc, /bin, /etc, /lib, /usr, and /tmp 等再加上要运行用户应用所需要的所有配置文件,二进制文件和库文件。容器的创建启动过程如下:
- docker client(即:docker终端命令行)会调用docker daemon请求启动一个容器,
- docker daemon会向host os(即:linux)请求创建容器
- linux会创建一个空的容器(可以简单理解为:一个未安装操作系统的裸机,只有虚拟出来的CPU、内存等硬件资源)
- docker daemon请检查本机是否存在docker镜像文件,如果有,则加载到容器中(准备安装操作系统)
- 将镜像文件加载到容器中(安装好操作系统)
- 将安装好的容器启动,并返回系统状态; Docker 启动一个容器时,它会从镜像中加载所有层,并使用联合文件系统(Union File System)将它们堆叠起来形成容器的文件系统(以unionfs方式加载文件系统),这意味着容器的文件系统是由 bootfs、根文件系统和任何其他挂载的卷的联合视图组成的,这个过程是只读的。即Docker的镜像是借用宿主机的bootfs层,在 Docker 镜像的最底层是 bootfs,Docker的上下文中,指明了Docker容器使用的是宿主机上的bootfs镜像,当Docker容器启动时,它会从镜像中读取bootfs,加载其上必要的bootloader和kernel组件加载内核和完成自身启动,其中,bootloader主要是引导加载kernel,初始化容器硬件及启动准备,当内核加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系统也会卸载 bootfs ,rootfs在 bootfs之上,容器启动后就会加载rootfs,rootfs 一般很小,只需要包合最基本的命令,工具和程序库就可以了;启动后,容器首先将 rootfs 设置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite”供用户使用。这使得镜像本身只需要包含rootfs层所需的文件和工具即可,而bootfs供全部容器共用 。因此,镜像占用的存储空间比较少,有的极精简的镜像只有几MB大小,
三、docker 容器迁移方式
//方式一:借用容器导出和导入工具,将Docker容器迁移到另一台服务器
#该方式:不会导出容器的端口和变量,也不导出包含容器的底层数据,但是容器内的数据会一并导出
docker export container-name | gzip > container-name.gz #导出容器快照文件到本地,会丢失历史记录和元数据
#导入,格式:docker import xxx.tar newname:tag
zcat container-name.gz | docker import - repository-name:tag #导入容器快照到本地镜像库
#之后docker run重新运行容器,启动export与import命令导出导入的镜像必须加/bin/bash或者其他/bin/sh,适用于重新部署的场景,多用来制作基础镜像
#验证
docker images
#docker run #加bash,否则报错docker: Error response from daemon: No command specified
docker run -itd --name vpm.nvs.2.5.22 vpm-nvs:v1.5.0sp2h1_20230522 #报错如下
docker: Error response from daemon: No command specified.
See 'docker run --help'.
#重新运行
docker run -itd --name vpm.nvs.2.5.22 vpm-nvs:v1.5.0sp2h1_20230522 /usr/bin/bash #这时就可正常启动容器了
docker ps #显示如下
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c72dea87aeb vpm-nvs:v1.5.0sp2h1_20230522 "/usr/bin/bash" 34 seconds ago Up 32 seconds vpm.nvs.2.5.22
#附录:docker run参数说明
no 不自动重启容器. (默认value)
on-failure 容器发生error而退出(容器退出状态不为0)重启容器
unless-stopped 在容器已经stop掉或Docker stoped/restarted的时候才重启容器
always 在容器已经stop掉或Docker stoped/restarted的时候才重启容器
//方式2:容器镜像提交方式--共用同一个镜像库,该方式也不会迁移基础数据卷;这种方式可实现保存读写层数据
#这也是将Docker中进行容器及数据进行全部迁移的方式,你可以使用docker commit命令来创建一个容器的镜像,然后使用docker save和docker load来迁移整个容器
#格式:docker commit [容器名称|ID] 生成新的镜像名字
docker commit container-id image-name #将当前镜像的数据落盘生成新镜像上传到镜像库,即将已存在容器中的镜像和最上层的可读写层中修改内容合并一起提交为一个新的镜像,使用-p:在commit的时候,将正在运行的容器暂停,-m:提交时的说明文字
docker save -o myQY.tar 镜像name # 把镜像做成tar文件
docker load -i myQY.tar # 把压缩包load成镜像
docker run -id --name=xxx -p 容器端口:宿主机端口 myQY:latest #使用上述镜像创建一个新容器,就把镜像做成一个容器
//方式3:镜像存储文件导出导入
#相比快照的导入导出,容器快照文件将丢弃所有的历史记录和元数据信息(仅保存容器导出当时的快照状态),从容器快照文件导入时可以重新指定标签等元数据信息,而镜像存储文件将保存完整记录,体积也要大
#该方式:不会丢弃历史记录和元数据,并可以回滚版本;另外启动时不用加/bin/bash
#格式:docker save -o xxx.tar 镜像名 或 docker save 镜像名 > xxx.tar
docker save image-name > image-name.tar #将镜像文件压缩导出到外部文件
cat image-name.tar | docker load #新服务器中,使用docker load 将压缩镜像文件导入到新服务器本地镜像库用于创建新镜像
#迁移数据卷
#docker run -it -v 主机目录:容器内目录:ro/rw 镜像名
docker run --rm --volumes-from datavolume-name -v $(pwd):/backup image-name tar cvf backup.tar /path-to-datavolume #删除容器前将容器内
ocker run --rm --volumes-from datavolume-name -v $(pwd):/backup image-name bash -c "cd /path-to-datavolume && tar xvf /backup/backup.tar --strip 1" #还原
#共享数据卷
docker run -it