从 Docker 镜像中提取 Dockerfile

其实上篇已经详细介绍了如何从 alinode 官方镜像中提取 Dockerfile 的完整过程。
但在提取的时候,我写了个工具,方便以后使用,既然写了,就分享下。

从镜像中提取 Dockerfile

其实这个需求不应该存在,他是个不合理的需求。
没有 Dockerfile 的镜像,我们不用才对,为什么要去提取他的 Dockerfile 呢?
为了(折)(腾)。。

因为某些不可抗拒的原因,人家没给 Dockerfile,但你又非常想用他的镜像,然而严谨的你,不放心用一个黑盒在线上跑,那怎么办呢?
找他们要?不一定给。下班堵他们门口,逼他们给?不一定打得过他们。把所有想要配置的人集合起来,应该可以打的过。

这里说的就是 alinode 官网镜像,指名道姓说,没啥不好意思的。
alinode 年初免费后,我就直接接入用了,确实省了我不少麻烦,年中的时候上了 docker,但官网镜像太大,于是自己封装了个镜像,跑着还算稳定。
这些天百谷歌度了一大圈,也学了点姿势,也成功提取了 alinode 官网镜像的 Dockerfile,也没看到什么秘密啊,完全可以直接公布出来。

详细的提取过程上一篇介绍了,这里说 node 提取,并格式化最终结果。

写成 node 工具

其实工具有现成的,CenturyLinkLabs/dockerfile-from-image

1
2
$ docker run -v /var/run/docker.sock:/var/run/docker.sock \
centurylink/dockerfile-from-image <IMAGE_TAG_OR_ID>

OJB,,,报错。。。

在 issue 中看到有人 fork 并更新了代码,但依然报错,找到个 py 重写的,但没镜像,只有 dockerfile,我本地编译测试可用,但麻烦。
于是乎自称 “造轮之王” 的我就用 node 重写了个,并封装了 docker 镜像,你用 docker 或用 npx 都可以,非常爽。

项目在这个 52cik/dockerfile-from-image

直接看下源码核心部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const Docker = require('dockerode');
const docker = new Docker();

parse(id);

/**
* 分析镜像
* @param id 镜像名或ID
*/
async function parse(id) {
const image = await docker.getImage(id);

const inspect = await image.inspect();
const tag = inspect.RepoTags[0];
console.log('FROM', tag);

let history = await image.history();

history = history.reverse().map((it, idx) => {
let line = it.CreatedBy;
if (line.includes('#(nop)')) {
line = line.replace(/\/bin\/\w+ -c #\(nop\)\s+/, '');
} else {
line = line
.replace(/\/bin\/\w+ -c\s+/, 'RUN ') // 添加 RUN 指令
.replace(/ ( {2,})/g, ' \\\n$1') // 处理多空格换行
.replace(/(\t+)/g, '\\\n$1'); // 处理多空格换行
}
return line;
});

console.log(history.join('\n'));
}

dockerode 是个 node 下调用 docker api 的模块。

这里几乎只用了 history 方法,因为确实就够了。
如果没有 #(nop) 层,那就是命令行,需要镜像格式化。
这里分两种情况,一种是空格党,一种是 tab 党。
最终得到格式化后的结果。

是不是感觉简单的一逼。

FROM 是自身,而不是他依赖的镜像,因为无法得知依赖的镜像是什么。
不过这块可以优化,我已经简单分析了各个基础镜像的版本信息,可以得到底层镜像。
这种 node:8, buildpack-deps:jessie 二次封装的镜像,我没办法解析,
但可以解析出底层镜像,如 node:8 -> debian:8.11, node:8-alpine -> alpine:3.8.1 这样得到具体版本。

这个功能坐等我有空的时候加上吧,目前手动操作下也可以得到的。

1
2
3
4
5
6
7
$ docker run --rm node:8-alpine cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.8.1
PRETTY_NAME="Alpine Linux v3.8"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"

多详细,而且简单方便。

1
2
3
4
5
6
7
8
9
10
11
$ docker run --rm node:8 cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
NAME="Debian GNU/Linux"
VERSION_ID="8"
VERSION="8 (jessie)"
ID=debian
HOME_URL="http://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
$ docker run --rm node:8 cat /etc/debian_version
8.11

适配下不同镜像获取具体版本信息即可。

使用方法

因为有 npm 模块和 docker 镜像两种,所以使用方法也是两种。

1
2
$ npx dockerfile-from-image nginx:alpine > Dockerfile # 输出到文件
$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock toomee/dockerfile-from-image nginx:alpine > Dockerfile # 输出到文件

npx 真香。

小结

这是一篇水文,总结加推广为主,因为详细的提取流程,上一篇已经介绍了,这里只是写成了代码。
唯一不一样的只是加了正则,格式化了输出结果,看起来跟源配置很接近,更加容易阅读。