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的实验模式中可用。

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 为目录名的目录。其中内容大概如下所示:

Screen Shot 2019-04-21 at 08.24.08.png

其中,默认位置为: /var/lib/docker/containers/<CONTAINER_ID>/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 <IMAGE> > <IMAGE_NAME>.tar
  • IMAGE: 镜像名或镜像id
  • IMAGE_NAME: 镜像名

之后在命令执行的目录下生成一个 tar

scp 传输所需文件

先将之前的 checkpoint 文件使用 tar 命令打包。使用 scp 命令将两个 tar 包传输到目的足主机上:

$ scp <local_file> <remote_username>@<remote_ip>:<remote_folder> 

但实际上 checkpoint 目录可以直接传过去,给 scp 加上 -r 选项即可。

在目的主机上 Restore

先将镜像加载到目的主机的 docker 中,使用如下命令:

$ docker load -i /path/to/<IMAGE_NAME>.tar

加载后可以使用。

$ docker images

查看到镜像列表上新增的镜像。

之后在该镜像上创建(只创建不运行)一个容器,使用如下命令:

$ docker create --name <CONTAINER_NAME> --security-opt seccomp:unconfined <IMAGE_NAME> 

之后便是将该容器恢复到另一台主机checkpoint处的状态了。

有两种方式:

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

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

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

$ docker start --checkpoint=<CHECKPOINT_NAME> <CONTAINER_NAME> [--checkpoint-dir]

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


问题及解决方案

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

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

8A24F883C5D133F2DF816C4D8677A0B4.jpg

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

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可以查看版本信息:

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

源主机

Screen Shot 2019-04-21 at 10.51.38.png

目的主机

Screen Shot 2019-04-21 at 11.07.49.png

镜像信息

源主机

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)

Screen Shot 2019-04-21 at 11.15.41.png

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

Screen Shot 2019-04-21 at 11.15.58.png

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

源主机

Screen Shot 2019-04-21 at 11.20.39.png

目的主机

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容