这个问题其实还是比较有意思的,这几天又遇到一定要在没有root的机器上不能装docker的情况下build container的image…
几年前尝试过把docker image转化成一个img文件再加上linux kernel binary让虚拟机可以跑这个image。这次看了看,哦,听说Google那边有repo可以在k8s里直接build image,这么神奇?名字是kaniko,于是就去看了下使用方法:
FROM gcr.io/kaniko-project/executor:debug
COPY . /workspace/src
RUN /kaniko/executor \
--context /workspace/src \
--dockerfile /workspace/src/Dockerfile \
--destination dockerrepo.example.com/image-name:version
这样运行完了,image就build出来并且push好了,是不是很简单。
它不就是执行了一个executor么?那现在没有docker,我可以运行它来帮我build image么?
我们docker save
这个 executor 的image以后,发现里面主要就2个文件夹,一个是kaniko一个是busybox。那说明executor是static-linked呀,这就是说复制到任意一台linux机器上基本都能跑啊!
二话不说,找了一台机器,把dump出来的kaniko文件夹复制上去,然后开始运行。报错是理所当然的——kaniko的executor会告诉你这不是一个container环境。然后-h
发现它有一个--force
的flag,那还是赶紧加上运行。本来并没有看过kaniko的工作原理,直接壮着胆子就在机器上用root权限运行起了executor。当!很快executor就又报错了,说没有apt命令…我Dockerfile里的base image是ubuntu,那没有apt命令是几个意思?
这回真的得看看kaniko的源代码了。哪里错了我grep哪里。发现kaniko在pkg/constants.go
里写死了几个path,其中有一个是ignore list,指向/proc/self/mountinfo
。于是把这些hard code的path从const里拉出来变成var,然后可以通过env var赋值,下载golang编译。能自己指定ROOTDIR了,然后就报mountinfo
no such file的错误,于是直接touch一个空文件给它,这下好了,executor终于往后走了。
过了一会,报错说什么网络连接timeout,连接53端口有问题…啥?DNS有问题?我的机器应该没问题呀?于是直接打开 /etc/resolv.conf
确认,发现nameserver 1.1.1.1
,难道是我DNS原来设置就有问题么?于是改成知道的一个server IP,继续跑。前面的网络连接过了,后面又来timeout了,再去看/etc/resolv.conf
发现它又变成nameserver 1.1.1.1
了。oh,no,kaniko原来是直接把image下载下来然后覆盖root…回味着ignore list然后让executor进入trace模式打log,发现原来它是这样的原理:
- 下载base image,将image的内容填充覆盖到
/
;跳过ignore list里的 - 每次RUN都是直接运行当前环境的程序,然后结尾的时候列出当前
/
下的所有文件,和上一步的比对,看看哪些文件作了增改删;把这些改变记录再layer里 - 最终得到一个一个layer的snapshot,拼成一个image的,如果设置了
--no-push
,可以将image通过--tarPath
保存成.tar
文件。
好了,知道了原理,也知道了我的这台机器即将报废,只要我一退出ssh估计就再也进不来了…
为了让它最后发光发热,我琢磨着不是有一个叫chroot的东西可以改root folder么,改掉了root folder是不是它就只能把东西覆盖到一个指定文件夹了?不过chroot好像需要root权限才能运行…有没有更高级的能再user mode运行的呢?啊,就是它了,proot。进入它的github,发现需要2个主要的dependencies,一个是libarchive,一个是talloc,下载默认编译,出来一个proot可以用了,尝试了一下,哇,可以用了,只要proot -r /path/to/fake/root -0 /kaniko/executor ...
就能完美执行executor了。于是开开心心退出ssh让这台VM报废了。
之后我换了一台机器,弄了一个非root用户,把proot静态编译了一遍,这样executor和proot基本上kernel支持的所有linux就都能跑了。跑了一些用户的build case,发现了一些问题,比如在Dockerfile里RUN python就会挂,说什么random init error,这一看就是/dev的问题啊;好在proot给出了解决方案,-b
就能把/dev下需要的设备mount到非root下的dev文件夹里的,比如-b /dev/null:/dev/null
-b /dev/urandom:/dev/urandom
这下应该没有什么问题了,后面就是用户遇到什么问题帮他们解决什么问题了,没有docker没有root照样build image…
后记:其实现在的去docker去clone技术已经成熟,在security需求旺盛的当下工具也是无穷的多…比如这个udocker可以login pull create然后一行一行run command,最后save成tar就可以load到docker里了,虽然没有中间层了,但是也是一种方式,没有docker没有root的日子我fake root~