返回 登录
0

一个仅有1KB大小的Docker容器

阅读9766

原文A 1 KB Docker Container
作者:Nathan Osman
翻译:雁惊寒

摘要:本文介绍了如何使用汇编程序编写一个极小的docker容器。以下是译文。

不,这不是打错字,也不是玩笑。我创建了一个Docker容器,该容器包含一个Unix可执行文件,没有其他依赖关系,磁盘空间占用不足1KB。容器中没有其他文件,甚至没有libc。

这就是证明。

为什么?

在解释如何实现这个容器之前,应该先解释下为什么要这么做。 caddy-docker(这是我写的另外一个工具,在这里有详细的说明)将传入的请求根据其标签路由到其他运行的容器中去。

我需要用caddy-docker作为特定主机的反向代理,要实现这个最简单的方法就是启动一个容器,这个容器唯一的目的就是包含两个特殊的标签,并且容器在停止之前不应该做任何事情。

就在那个时候我想到了这个绝妙透顶的主意。

什么?

我立即开始研究这个应用程序,并因为其不寻常的目的而命名为“hang”。 Go可以轻松生成没有依赖关系的可执行文件,允许Docker容器从scratch继承。唯一的缺点是,Go可执行文件相当的大,大小通常会超过8MB。

这绝对不行。

我认为,编写一个这样的C程序很容易:为SIGTERM注册一个信号处理程序,并在收到这个信号的时候退出。不幸的是,这意味着我需要使用libc,这样,容器很快会变得与Go可执行文件差不多大小。这根本就没有任何优势。

汇编?

是的,生成一个没有依赖关系的小型可执行文件的最快方法是用汇编编写。我更喜欢Intel风格的语法,所以,NASM是不二选择。

系统调用

曾几何时,在x86架构出现的早些年间,系统调用看起来是这样的:

mov eax, 0x01
mov ebx, 0x00
int 0x80

第一行指定要执行哪个系统调用 - sys_exit。第二行指定返回值(0)。第三行产生一个内核后续会处理的中断。

x86操作系统后来转为使用sysenter/sysret,而x86_64则引入了一个新的操作码:syscall。与上面的例子类似,rax寄存器用于指定要调用的特定系统调用。上面的示例可以在x86_64程序集中进行重写,如下所示:

mov rax, 0x3c
mov rdi, 0x00
syscall

请注意,sys_exit的系统调用号在x86_64上是不同的。

信号处理程序

在C中注册信号处理程序很普通:

#include <signal.h>

void handler(int param) {}

int main() {
    struct sigaction sa;
    sa.sa_handler = handler;
    sigaction(SIGTERM, &sa, 0);
    return 0;
}

不幸的是,C标准库隐瞒了以下这几件事情:

  • 标志SA_RESTORER添加到了sa.sa_flags
  • sa.sa_restorer成员上设置了一个特殊的函数

我们不能直接将C代码转换成汇编,因为sigaction的结构与sys_rt_sigaction所期望的不一致。以下是NASM中内核结构的样子:

struc sigaction
    .sa_handler  resq 1
    .sa_flags    resq 1
    .sa_restorer resq 1
    .sa_mask     resq 1
endstruc

每个成员的大小为8字节。

设置信号处理程序

首先,我们必须在.bss段中为该结构体分配空间:

section .bss

    act resb sigaction_size

请注意,sigaction_size是汇编程序为我们创建的特殊值 - 它等于sigaction的大小(以字节为单位)。然后可以在.text段中初始化该结构体,如下所示:

section .text
global _start

    lea rax, [handler]
    mov [act + sigaction.sa_handler], rax
    mov [act + sigaction.sa_flags], dword 0x04000000  ; SA_RESTORER
    lea rax, [restorer]
    mov [act + sigaction.sa_restorer], rax

handlerrestorer这两个标签我们稍后会提到。现在我们可以调用sys_rt_sigaction这个系统调用了:

    mov rax, 0x0d  ; sys_rt_sigaction
    mov rdi, 0x0f  ; SIGTERM
    lea rsi, [act]
    mov rdx, 0x00
    mov r10, 0x08
    syscall

处理信号

下一步是等待SIGTERM信号的到来。 sys_pause这个系统调用可以用下面这种方式轻松地实现:

    mov rax, 0x22  ; sys_pause
    syscall

处理程序本身很普通,它没有做任何事情:

handler:

    ret

恢复器(restorer)也很简单,虽然它需要调用sys_rt_sigreturn系统调用:

restorer:

    mov rax, 0x0f  ; sys_rt_sigreturn
    syscall

构建

需要两个命令来构建应用程序。假定源文件名为hang.asm,则命令是:

nasm -f elf64 hang.asm
ld -s -o hang hang.o

这将产生一个名为hang的可执行文件,它很小:

$ stat hang
  File: hang
  Size: 736

是的,它只有736字节。

Dockerfile相当简单,只需要两个命令:

FROM scratch
ADD hang /usr/bin/hang
ENTRYPOINT ["/usr/bin/hang"]

测试

我们来看看容器是否能工作:

$ docker build -t nathanosman/hang .
$ docker run -d --name hang nathanosman/hang

此时,容器应该保持运行状态:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             STATUS
f1861f628ea8        nathanosman/hang    "/usr/bin/hang"     Up 3 seconds

当执行docker stop时,应该立即停止:

$ docker stop hang
hang

有用!我们来确认以下容器的大小是否和可执行文件的大小一致:

$ docker images
REPOSITORY               TAG                 CREATED             SIZE
nathanosman/hang         latest              2 minutes ago       736B

是的!一个非常小的容器!

链接

你可以在这里找到源代码:

https://github.com/nathan-osman/hang


图片描述
2017年10月14日,SDCC 2017之大数据技术实战线上峰会即将召开,邀请圈内顶尖的布道师、技术专家和技术引领者,共话大数据平台构建、优化提升大数据平台的各项性能、Spark部署实践、企业流平台实践、以及实现应用大数据支持业务创新发展等核心话题,七位大牛与你相聚狂欢,详情查看所有嘉宾和议题,以及注册参会,分享还可优惠30元。

评论