背景
平时看过文章标题比较多的都是说“像容器一样运行虚拟机”,大家都想要有虚拟机的隔离性,又想要容器的便捷性,也有一些开源项目比如 Firecracker 或 KataContainer 在做。今天反过来,来看看如何“像虚拟机一样运行容器”。
为啥要把容器搞得像虚拟机一样呢?我平时用到容器比较多的地方就是在 CI 集成部分,通过 docker 快速搭建环境,进行单元测试或集成测试,测试完成后清理镜像,简单方便。但是在CD 部分,就有一点比较头疼的问题,就是调试。zouquan 同学之前在知乎上提了一个问题:容器化环境里如何方便的进行debug和测试?,回答中的一个总结很好的描述了这个问题的关键: 虽然我在本地开发,但我的应用就像在 k8s 里一样。
那怎么在容器中开发像是在本地一样呢?肯定不能每次改了代码都走一遍 build,push,deploy 的流程,上面问题的回答中给出的是借助各种工具来达成这样的效果,我不像要用那些奇奇怪怪的工作(学不动了),那么只能想办法把容器搞的跟虚拟机一样了。
最近看到了 weaveworks/footloose 项目,这个项目的简介就是我的最原始的需求:Containers that look like Virtual Machines。先来看看这个项目的示例(开源项目中examples 写的好真是上手快)。
功能示例
Ansible 远程控制
1 | [root@yiran ansible]# footloose config create --replicas 1 # 指定 machine 副本数为 1 |
可以看到,通过 footloose 创建一个 machine(容器),可以支持我们远程连接,通过 Ansible 来控制,那么我们来试试 Ansible Playbook 的效果:
1 |
|
执行结果:
1 | [root@yiran ansible]# ansible-playbook example1.yml |
可以执行 Ansible Playbook,那几乎意味着我们可以执行任何操作,我们可以通过 ansible rsync 模块直接将代码同步到容器中,也可以通过 Playbook 在容器中执行一些配置来达到我们对环境的修改,可以说是很方便了。
SSH 连接
既然可以通过 Ansible 进行控制,那么我们肯定也可以通过 ssh 进行连接,可以通过 footloose 提供的默认命令 footloose ssh
:
1 | [root@yiran ansible]# footloose ssh root@node0 |
Host 端口映射
在容器使用的过程中,我们通常需要跑一些对外提供端口的服务,这时候就需要进行 Host 端口映射,先来看下 footloose 的配置文件,这里我们指定了 machine的数量是 2,并且指定了容器的 22 端口映射到 host 的 2222端口,依次递增:
1 | cluster: |
创建对应 machine 资源:
1 | [root@yiran simple-hostPort]# footloose create |
通过 netstat 查看 Host 端口情况,这里可以看到 footloose 使用的是 docker 作为容器管理入口:
1 | [root@yiran simple-hostPort]# netstat -antp |grep 222 |
这时候就可以使用普通的 ssh 命令连接到容器中了:
1 | [root@yiran simple-hostPort]# ssh root@127.0.0.1 -p 2222 -i cluster-key hostname |
写了三个使用场景,那么我们来看看 footloose 是怎么实现的。
代码实现
machine 创建:
1 | // CreateMachine creates and starts a new machine in the cluster. |
解这看下 createMachineRunArgs
里面的实现:
1 | func (c *Cluster) createMachineRunArgs(machine *Machine, name string, i int) []string { |
这里需要注意的是,在 docker 命令行最终执行时,添加了 --tmpfs /run --tmpfs /run/lock --tmpfs /tmp:exec,mode=777
参数,并且将 Host 的 cgroup 配置路径通过只读权限传递给了容器,后面有用到。
其他的启动,停止,删除等操作也都是拼接为 docker 的命令行然后执行处理的,这里不过多描述。
那么有个问题,在容器内部,pid 为1 的进程应该是我们运行容器时传递的参数,也就时说,当我们执行的进程结束时,容器也就退出了:
1 | [root@yiran ~]# docker run centos sleep 6000 |
来看下 footloose 创建的 machine 是如何保证容器持久运行的:
1 | [root@yiran ansible]# |
可以看到在 machine 中, pid 为1 的进程是 init,这个初始化参数是写死在代码里面的,因为 machine 中存在 init 进程,也就保证了我们之后的进程都是在 init 进程树下的,我们可以通过 systemd 对服务进行管理,直到我们的从容器外部将容器杀死。
前面使用过程中,一直忽略了一点,就是我们的容器镜像内部有什么不同么?看下 Dockerfile 里面的内容:
1 | master ✗ $ cat Dockerfile |
可以看到 footloose 支持的镜像在官方的 CentOS7 的基础上进行了部分配置,比如 systemd、openssh、端口暴露等,来让容器更像是一台虚拟机。
总结
为了方便的进行持续集成,我们引入了容器;为了更方便的进行调试/测试,我们让容器装作虚拟机的样子,也是无奈。