实验项目名称
Introduction to Cloud Computing -- Live Migration
云计算介绍之热迁移
实验目的
- 理解虚拟机迁移的基本概念
- 使用
docker
容器作为一个例子来测试你的迁移技能 - 理解
checkpoint
和restore
的相关概念 - 成功地将你镜像从一台主机迁移到另一台主机
实验基础
- 一台PC机
- 拥有管理员权限的Linux操作系统,这里我使用的是 Ubuntu 18.04 LTS
- 命令行的基本操作
-
docker
使用及相关概念
实验步骤
在不出错的情况下,该实验是相对简单的,但是其中遇到了很多问题,我先将正常步骤在下面写出,关于具体的问题以及解决方案在 [实验步骤 - 问题及解决方案] 小结中进行总结。
更改 Go 程序
在实验一中,在docker
上跑了一个 hello-world
的 Golang
程序,也就是程序运行结果就只打印出一个 hello-world
。在这次实验中,由于需要验证热迁移的结果是否成功,我将原来的 hello-world
程序更改为实现 “每隔一秒递增的输出一个数字” 无限循环的程序,只要观察前后的输出是否衔接上即可判断热迁移是否成功。
程序代码如下:
package main
import (
"fmt"
"time"
)
func main() {
const id = "6130116165"
i := 1
for {
fmt.Printf("[%s] - %d\n", id, i)
i += 1
time.Sleep(time.Duration(1) * time.Second)
}
}
之后再使用同样的 dockerfile
构建成镜像即可。
对于有强迫症的我,由于同名镜像造成了一些虚浮镜像,我使用如下命令清除掉相关的容器及镜像:
$ docker container prune
$ docker image prune
安装 CRIU
工具
使用如下命令安装相关依赖以及 CRIU
$ apt update
$ apt upgrade
$ apt install build-essential
$ apt install pkg-config
$ apt install libnet-dev python-yaml libaio-dev
$ apt install libprotobuf-dev libprotobuf-c0-dev protobuf-c-compiler protobuf-compiler python-protobuf libnl-3-dev libcap-dev python-future
# criu install
$ curl -O -sSL http://download.openvz.org/criu/criu-3.11.tar.bz2
$ tar xjf criu-3.11.tar.bz2
$ cd criu-3.11
$ make
$ cp ./criu/criu /usr/local/bin
使用如下命令测试安装是否完成
$ criu check
Looks good.
本机测试 checkpoint
和 restore
创建容器并运行
在之前的镜像的基础上在后台跑起一个容器,名为looper
$ docker run -d --name looper --security-opt seccomp:unconfined hello-go
- 其中
--security-opt
选项是配置进程内安全策略的,可参阅 https://docs.docker.com/engine/reference/run/ 中 Security configuration 小结。 -
--security-opt setccomp:unconfined
代表关闭容器的seccomp
限制。 -
seccomp
是一种内核中的安全机制,正常情况下,程序可以使用所有的syscall
,这是不安全的,比如劫持程序流后通过execve
的syscall
来getshell
。通过seccomp
我们可以在程序中禁用掉某些syscall
,这样就算劫持了程序流也只能调用部分的syscall
了。 - 对于我们跑的
go
程序,是否加这段参数没有影响,此处是参照官网示例加上并且简单了解这个选项。
查看运行结果
使用
$ docker logs looper
即可查看程序的输出。加上参数 -f
的话可以查看持续的输出。
开启实验性功能
Docker希望管理在其容器内运行的进程的整个生命周期,因此CRIU应该由Docker运行(而不是单独运行)。这个功能在Docker的实验模式中可用。
要启用实验性功能(包括CRIU),需要执行以下操作:
$ echo "{\"experimental\": true}" >> /etc/docker/daemon.json
$ systemctl restart docker
不过我还是使用 vim
来编辑这个 json
文件然后手动加入。
可以使用 docker info
查看 Experimental
属性是否为 true
创建 checkpoint
当创建 checkpoint
后,默认情况下,在 docker
中运行中的指定容器将会停止运行。
使用如下命令创建 checkpoint
$ docker checkpoint create looper ckp1
此时,将会在默认位置创建一个以 ckp1
为目录名的目录。其中内容大概如下所示:
其中,默认位置为: /var/lib/docker/containers/
-
CONTAINER_ID
: 之前运行的容器的容器id
的全名 - 需要以
sudo
或root
用户访问目录
也可以不用存储在默认位置,只需要在创建 checkpoint
目录时的命令修改为如下:
$ docker checkpoint create --checkpoint-dir=/tmp looper ckp2
这条命令将会在 /tmp
下创建一个名为 ckp2
的目录,其中内容与上图的一致。
Restore
在 docker
中,该命令如下所示:
$ docker start --checkpoint ckp1 looper
使用前可以使用 docker checkpoint ls [container]
来查看该容器的 checkpoint
信息。
需要注意的是:
- 如果我们在创建时指定了
--checkpoint-dir
选项的话,在restore
时也需要指定--checkpoint-dir
来指定之前创建的断点位置。同时docker checkpoint ls [container]
也无法得到你自定义的checkpoint
目录中的断点信息。
跨 Host checkpoint
和 restore
在热迁移中,往往需要保存程序状态后迁移到其他的主机上(比如某个主机需要维护升级等),因此我们需要将一些必要文件传输到另一台主机上,可以参考以下几种方式
nfs
- 移动硬盘或U盘
scp
- ...
这里我选用的是 scp
方式。
需要的文件:
- 第一步构建的
Golang
程序镜像文件 - 上一步创建的
checkpoint
文件
导出Golang程序镜像
docker save
命令可以保存一个或多个镜像为 tar
包,具体使用如下
$ docker save > .tar
-
IMAGE
: 镜像名或镜像id -
IMAGE_NAME
: 镜像名
之后在命令执行的目录下生成一个 tar
包
scp
传输所需文件
先将之前的 checkpoint
文件使用 tar
命令打包。使用 scp
命令将两个 tar
包传输到目的足主机上:
$ scp @:
但实际上 checkpoint
目录可以直接传过去,给 scp
加上 -r
选项即可。
在目的主机上 Restore
先将镜像加载到目的主机的 docker
中,使用如下命令:
$ docker load -i /path/to/.tar
加载后可以使用。
$ docker images
查看到镜像列表上新增的镜像。
之后在该镜像上创建(只创建不运行)一个容器,使用如下命令:
$ docker create --name --security-opt seccomp:unconfined
之后便是将该容器恢复到另一台主机checkpoint
处的状态了。
有两种方式:
- 使用
--checkpoint-dir
指定checkpoint
位置 - 自行将
checkpoint
目录加入到容器默认checkpoints
的存储目录下
默认位置在之前已经提到过,不再赘述。
使用如下命令将容器恢复到之前的状态并继续运行:
$ docker start --checkpoint= [--checkpoint-dir]
之后使用 docker logs
查看输出结果。
问题及解决方案
注:部分截图是于目的机的截图,有些问题出现的时候没有截图,之后解决了就只能以文字来表示问题
Q1: restore
时 —-checkpoint-dir
不支持!
A1:最新版 docker
对 docker start
命令中的 —-checkpoint-dir
选项不支持,可以手动把 checkpoint
文件目录加入到对应容器的默认位置 /var/lib/docker/containers/
,参考:https://github.com/moby/moby/issues/37344#issuecomment-450782189
Q2:使用 docker 相关命令时出现如下错误:
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/json: dial unix /var/run/docker.sock: connect: permission denied
A2:通过设置 docker.sock
权限解决,具体解决方案: https://stackoverflow.com/a/51362528
Q3:使用 scp 相关命令时出现如下错误:
ssh: connect to host xxx.xxx.xxx.xxx port 22: Connection refusedlost connection
lost connection
A3:
- 与目的机之间无法ping通,尝试正确的
ip
地址 -
sshd
服务未启动,目的主机安装openssh-server
Q4:指定 checkpoint
恢复运行状态时出现如下错误:
Error response from daemon: checkpoint does not exists for container
A4:没有指定 --checkpoint-dir
(虽然指定了会出现另一个错),采用Q1的解决方式解决。
⭐️Q5:执行 docker checkpoint ls
时,无法正常获取指定容器已有的checkpoint及信息,显示
Error response from daemon: open /var/lib/docker/containers/[CONTAINER_ID]/checkpoints/[CHECKPOINT_ID]/config.json: no such file or directory
同时,尝试根据 checkpoint
恢复状态时也会遇到如下错误:
A3:查找了相关资料,有可能是 Moby
的问题,在 github
上该问题处于 close
状态(https://github.com/moby/moby/issues/35691),根据 issue
内讨论得知相关开发人员已经在 2018-11 月份解决了,但还没有推到 docker-ce
的新版本中,预计在 18.10
版本中解决,但是 docker ce
目前最新版本还是 18.09
,因此当前 docker
版本无法解决该问题,也就是无法进行 checkpoint
的 restore
。在注意到其中一个回答:(https://github.com/moby/moby/issues/35691#issuecomment-384683026)
因此想到将 docker
版本降级至 17.06.0
尝试。通过官网中从二进制文件安装 Docker CE
的方式(https://docs.docker.com/install/linux/docker-ce/binaries/)将当前版本替换为17.06.0
。通过 docker info
可以查看版本信息:
之后,所有问题都解决了,包括 --checkpoint-dir
不可用等问题,此方法为解决问题的最终方案。(ps:与别的同学交流的时候,他说它使用 18.09
版本也可以使用,其中 Golang
程序是一个 Web
程序,可以在别人的主机上恢复状态,但是别人的程序无法在其主机上恢复状态。暂时不清楚程序代码和 docker
的 checkpoint
和restore
之间的关系)
Q6:安装旧版本后启动 docker
出现如下错误:
Failed to start docker.service: Unit docker.service is masked.
A6:使用如下命令解决:
$ systemctl unmask docker.service
$ systemctl unmask docker.socket
$ systemctl start docker.service
Q7:根据 Q5 更改版本后,使用之前的 checkpoint
进行恢复发现无法启动容器
A7:需要重新打断点,之前 18.09
版本打的断点无效。
实验数据或结果
docker 版本
源主机
目的主机
镜像信息
源主机
目的主机
容器信息
源主机
目的主机
断点信息
源主机(使用了 --checkpoint-dir
)
目的主机(没使用 —-checkpoint-dir
)
⭐️运行状态(实验结果证明)
源主机
目的主机
源主机在运行打印到55时 checkpoint
,在目的主机后恢复后从56
开始继续打印。
实验思考
本次实验从逻辑上来说十分简单:
-
host1
上创建一个golang
的镜像 -
host1
跑一个该镜像的容器,并且创建一个checkpoint
,拿到checkpoint
文件 -
host1
上用scp
把checkpoint
文件传到host2
上 -
host1
上把镜像文件scp
到host2
上 -
host2
上利用镜像创建一个容器 -
host2
利用checkpoint
恢复容器状态
但是由于天坑的官方bug,花了大概4-6个小时去查找在docker ce 18.09.5
上 checkpoint&restore
的方法,但都以失败告终,最后只能切换 17.06.0
版本的 docker ce
。
对于两个不同主机之间的虚拟机互联问题,需要对 VMWare
进行一些特殊的配置,由于我的主系统为 OS X
,使用的 VMWare Fusion
与网上的操作不一致,在短时间内无法使用 scp
进行不同主机上虚拟机的互联,但我的电脑上有两个 Ubuntu 18.04
的虚拟机(其中一个是因室友由于电脑问题无法安装 kvm
然后让他在我电脑上装Ubuntu
然后使用VNC
连接虚拟机后再进行实验),所以我直接使用了同一主机上的两个不同的虚拟机进行实验,但本质上和两台不同主机的虚拟机之间差别不大,只是传输所需的镜像文件以及 checkpoint
文件方式不同。
参考资料
- docker — 从入门到实践
- docker CLI 命令
- Criu Docker
- 菜鸟教程 - scp
- Github Issue - Can’t restore containers
- 使用CRIU实现简单容器的迁移
- 从二进制文件安装Docker CE
- How to fix docker: Got permission denied issue
- Github Issue - Restoring containers from a custom checkpoint-dir is broken
- Github Issue - docker start checkpoint failed
- Failed to start docker.service: Unit docker.service is masked
- Unable to create docker checkpoint in docker experimental
- Docker Checkpoint/Restore