Docker CRIU Golang 项目简单迁移示例

实验项目名称

Introduction to Cloud Computing -- Live Migration

云计算介绍之热迁移

实验目的

  1. 理解虚拟机迁移的基本概念
  2. 使用 docker 容器作为一个例子来测试你的迁移技能
  3. 理解 checkpointrestore 的相关概念
  4. 成功地将你镜像从一台主机迁移到另一台主机

实验基础

  1. 一台PC机
  2. 拥有管理员权限的Linux操作系统,这里我使用的是 Ubuntu 18.04 LTS
  3. 命令行的基本操作
  4. docker使用及相关概念

实验步骤

在不出错的情况下,该实验是相对简单的,但是其中遇到了很多问题,我先将正常步骤在下面写出,关于具体的问题以及解决方案在 [实验步骤 - 问题及解决方案] 小结中进行总结。

更改 Go 程序

在实验一中,在docker上跑了一个 hello-worldGolang程序,也就是程序运行结果就只打印出一个 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.

本机测试 checkpointrestore

创建容器并运行

在之前的镜像的基础上在后台跑起一个容器,名为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,这是不安全的,比如劫持程序流后通过 execvesyscallgetshell。通过 seccomp 我们可以在程序中禁用掉某些 syscall,这样就算劫持了程序流也只能调用部分的 syscall 了。
  • 对于我们跑的 go 程序,是否加这段参数没有影响,此处是参照官网示例加上并且简单了解这个选项。

查看运行结果

使用

$ docker logs looper

即可查看程序的输出。加上参数 -f 的话可以查看持续的输出。

开启实验性功能

Docker希望管理在其容器内运行的进程的整个生命周期,因此CRIU应该由Docker运行(而不是单独运行)。这个功能在Docker的实验模式中可用。

Docker CRIU Golang 项目简单迁移示例_第1张图片
D842AF54232EA6C2788312476EA0C507.jpg

要启用实验性功能(包括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 为目录名的目录。其中内容大概如下所示:

Docker CRIU Golang 项目简单迁移示例_第2张图片
Screen Shot 2019-04-21 at 08.24.08.png

其中,默认位置为: /var/lib/docker/containers//checkpoints/

  • CONTAINER_ID: 之前运行的容器的容器 id 的全名
  • 需要以 sudoroot 用户访问目录

也可以不用存储在默认位置,只需要在创建 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 checkpointrestore

在热迁移中,往往需要保存程序状态后迁移到其他的主机上(比如某个主机需要维护升级等),因此我们需要将一些必要文件传输到另一台主机上,可以参考以下几种方式

  1. nfs
  2. 移动硬盘或U盘
  3. scp
  4. ...

这里我选用的是 scp 方式。

需要的文件:

  1. 第一步构建的 Golang 程序镜像文件
  2. 上一步创建的 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处的状态了。

有两种方式:

  1. 使用 --checkpoint-dir 指定 checkpoint 位置
  2. 自行将 checkpoint 目录加入到容器默认 checkpoints 的存储目录下

默认位置在之前已经提到过,不再赘述。

使用如下命令将容器恢复到之前的状态并继续运行:

$ docker start --checkpoint=  [--checkpoint-dir]

之后使用 docker logs 查看输出结果。


问题及解决方案

注:部分截图是于目的机的截图,有些问题出现的时候没有截图,之后解决了就只能以文字来表示问题

Q1: restore—-checkpoint-dir 不支持!

8A24F883C5D133F2DF816C4D8677A0B4.jpg

A1:最新版 dockerdocker start 命令中的 —-checkpoint-dir 选项不支持,可以手动把 checkpoint 文件目录加入到对应容器的默认位置 /var/lib/docker/containers//checkpoints/,参考: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:

  1. 与目的机之间无法ping通,尝试正确的 ip 地址
  2. 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 恢复状态时也会遇到如下错误:

FF069FDA4697445A5135056264F6D346.jpg

A3:查找了相关资料,有可能是 Moby 的问题,在 github 上该问题处于 close 状态(https://github.com/moby/moby/issues/35691),根据 issue 内讨论得知相关开发人员已经在 2018-11 月份解决了,但还没有推到 docker-ce 的新版本中,预计在 18.10 版本中解决,但是 docker ce 目前最新版本还是 18.09 ,因此当前 docker 版本无法解决该问题,也就是无法进行 checkpointrestore。在注意到其中一个回答:(https://github.com/moby/moby/issues/35691#issuecomment-384683026)

Docker CRIU Golang 项目简单迁移示例_第3张图片
Screen Shot 2019-04-21 at 10.48.27.png

因此想到将 docker 版本降级至 17.06.0 尝试。通过官网中从二进制文件安装 Docker CE 的方式(https://docs.docker.com/install/linux/docker-ce/binaries/)将当前版本替换为17.06.0。通过 docker info可以查看版本信息:

Docker CRIU Golang 项目简单迁移示例_第4张图片
Screen Shot 2019-04-21 at 10.51.38.png

之后,所有问题都解决了,包括 --checkpoint-dir 不可用等问题,此方法为解决问题的最终方案。(ps:与别的同学交流的时候,他说它使用 18.09 版本也可以使用,其中 Golang 程序是一个 Web 程序,可以在别人的主机上恢复状态,但是别人的程序无法在其主机上恢复状态。暂时不清楚程序代码和 dockercheckpointrestore之间的关系)

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 版本

源主机

Docker CRIU Golang 项目简单迁移示例_第5张图片
Screen Shot 2019-04-21 at 10.51.38.png

目的主机

Docker CRIU Golang 项目简单迁移示例_第6张图片
Screen Shot 2019-04-21 at 11.07.49.png

镜像信息

源主机

Docker CRIU Golang 项目简单迁移示例_第7张图片
Screen Shot 2019-04-21 at 11.12.16.png

目的主机

Screen Shot 2019-04-21 at 11.12.28.png

容器信息

源主机

Screen Shot 2019-04-21 at 11.13.32.png

目的主机

Screen Shot 2019-04-21 at 11.13.53.png

断点信息

源主机(使用了 --checkpoint-dir)

Docker CRIU Golang 项目简单迁移示例_第8张图片
Screen Shot 2019-04-21 at 11.15.41.png

目的主机(没使用 —-checkpoint-dir)

Screen Shot 2019-04-21 at 11.15.58.png

⭐️运行状态(实验结果证明)

源主机

Docker CRIU Golang 项目简单迁移示例_第9张图片
Screen Shot 2019-04-21 at 11.20.39.png

目的主机

Docker CRIU Golang 项目简单迁移示例_第10张图片
Screen Shot 2019-04-21 at 11.21.07.png

源主机在运行打印到55时 checkpoint,在目的主机后恢复后从56开始继续打印。

实验思考

本次实验从逻辑上来说十分简单:

  1. host1 上创建一个 golang 的镜像
  2. host1 跑一个该镜像的容器,并且创建一个checkpoint,拿到checkpoint文件
  3. host1 上用scpcheckpoint文件传到host2
  4. host1 上把镜像文件scphost2
  5. host2 上利用镜像创建一个容器
  6. host2 利用checkpoint恢复容器状态

但是由于天坑的官方bug,花了大概4-6个小时去查找在docker ce 18.09.5checkpoint&restore的方法,但都以失败告终,最后只能切换 17.06.0 版本的 docker ce

对于两个不同主机之间的虚拟机互联问题,需要对 VMWare 进行一些特殊的配置,由于我的主系统为 OS X,使用的 VMWare Fusion 与网上的操作不一致,在短时间内无法使用 scp 进行不同主机上虚拟机的互联,但我的电脑上有两个 Ubuntu 18.04 的虚拟机(其中一个是因室友由于电脑问题无法安装 kvm 然后让他在我电脑上装Ubuntu然后使用VNC连接虚拟机后再进行实验),所以我直接使用了同一主机上的两个不同的虚拟机进行实验,但本质上和两台不同主机的虚拟机之间差别不大,只是传输所需的镜像文件以及 checkpoint 文件方式不同。

参考资料

  1. docker — 从入门到实践
  2. docker CLI 命令
  3. Criu Docker
  4. 菜鸟教程 - scp
  5. Github Issue - Can’t restore containers
  6. 使用CRIU实现简单容器的迁移
  7. 从二进制文件安装Docker CE
  8. How to fix docker: Got permission denied issue
  9. Github Issue - Restoring containers from a custom checkpoint-dir is broken
  10. Github Issue - docker start checkpoint failed
  11. Failed to start docker.service: Unit docker.service is masked
  12. Unable to create docker checkpoint in docker experimental
  13. Docker Checkpoint/Restore

你可能感兴趣的:(Docker CRIU Golang 项目简单迁移示例)