摘要:本文介紹了如何使用匯編程序編寫(xiě)一個(gè)極小的docker容器。以下是譯文。
不,這不是打錯(cuò)字,也不是玩笑。我創(chuàng)建了一個(gè)Docker容器,該容器包含一個(gè)Unix可執(zhí)行文件,沒(méi)有其他依賴關(guān)系,磁盤(pán)空間占用不足1KB。容器中沒(méi)有其他文件,甚至沒(méi)有l(wèi)ibc。
這就是證明。
在解釋如何實(shí)現(xiàn)這個(gè)容器之前,應(yīng)該先解釋下為什么要這么做。 caddy-docker(這是我寫(xiě)的另外一個(gè)工具,在這里有詳細(xì)的說(shuō)明)將傳入的請(qǐng)求根據(jù)其標(biāo)簽路由到其他運(yùn)行的容器中去。
我需要用caddy-docker作為特定主機(jī)的反向代理,要實(shí)現(xiàn)這個(gè)簡(jiǎn)單的方法就是啟動(dòng)一個(gè)容器,這個(gè)容器的目的就是包含兩個(gè)特殊的標(biāo)簽,并且容器在停止之前不應(yīng)該做任何事情。
就在那個(gè)時(shí)候我想到了這個(gè)絕妙透頂?shù)闹饕狻?/span>
我立即開(kāi)始研究這個(gè)應(yīng)用程序,并因?yàn)槠洳粚こ5哪康亩麨椤癶ang”。 Go可以輕松生成沒(méi)有依賴關(guān)系的可執(zhí)行文件,允許Docker容器從scratch繼承。的缺點(diǎn)是,Go可執(zhí)行文件相當(dāng)?shù)拇?/span>,大小通常會(huì)超過(guò)8MB。
這絕對(duì)不行。
我認(rèn)為,編寫(xiě)一個(gè)這樣的C程序很容易:為SIGTERM注冊(cè)一個(gè)信號(hào)處理程序,并在收到這個(gè)信號(hào)的時(shí)候退出。不幸的是,這意味著我需要使用libc,這樣,容器很快會(huì)變得與Go可執(zhí)行文件差不多大小。這根本就沒(méi)有任何優(yōu)勢(shì)。
是的,生成一個(gè)沒(méi)有依賴關(guān)系的小型可執(zhí)行文件的快方法是用匯編編寫(xiě)。我更喜歡Intel風(fēng)格的語(yǔ)法,所以,NASM是不二選擇。
曾幾何時(shí),在x86架構(gòu)出現(xiàn)的早些年間,系統(tǒng)調(diào)用看起來(lái)是這樣的:
mov eax, 0x01 mov ebx, 0x00 int 0x80
行指定要執(zhí)行哪個(gè)系統(tǒng)調(diào)用 - sys_exit。第二行指定返回值(0)。第三行產(chǎn)生一個(gè)內(nèi)核后續(xù)會(huì)處理的中斷。
x86操作系統(tǒng)后來(lái)轉(zhuǎn)為使用sysenter/sysret,而x86_64則引入了一個(gè)新的操作碼:syscall。與上面的例子類似,rax寄存器用于指定要調(diào)用的特定系統(tǒng)調(diào)用。上面的示例可以在x86_64程序集中進(jìn)行重寫(xiě),如下所示:
mov rax, 0x3c mov rdi, 0x00 syscall
請(qǐng)注意,sys_exit的系統(tǒng)調(diào)用號(hào)在x86_64上是不同的。
在C中注冊(cè)信號(hào)處理程序很普通:
#include <signal.h> void handler(int param) {} int main() { struct sigaction sa;
sa.sa_handler = handler;
sigaction(SIGTERM, &sa, 0); return 0;
}
不幸的是,C標(biāo)準(zhǔn)庫(kù)隱瞞了以下這幾件事情:
SA_RESTORER添加到了sa.sa_flags上
sa.sa_restorer成員上設(shè)置了一個(gè)特殊的函數(shù)
我們不能直接將C代碼轉(zhuǎn)換成匯編,因?yàn)?/span>sigaction的結(jié)構(gòu)與sys_rt_sigaction所期望的不一致。以下是NASM中內(nèi)核結(jié)構(gòu)的樣子:
struc sigaction
.sa_handler resq 1 .sa_flags resq 1 .sa_restorer resq 1 .sa_mask resq 1 endstruc
每個(gè)成員的大小為8字節(jié)。
首先,我們必須在.bss段中為該結(jié)構(gòu)體分配空間:
section .bss act resb sigaction_size
請(qǐng)注意,sigaction_size是匯編程序?yàn)槲覀儎?chuàng)建的特殊值 - 它等于sigaction的大小(以字節(jié)為單位)。然后可以在.text段中初始化該結(jié)構(gòu)體,如下所示:
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
handler和restorer這兩個(gè)標(biāo)簽我們稍后會(huì)提到。現(xiàn)在我們可以調(diào)用sys_rt_sigaction這個(gè)系統(tǒng)調(diào)用了:
mov rax, 0x0d ; sys_rt_sigaction mov rdi, 0x0f ; SIGTERM lea rsi, [act] mov rdx, 0x00 mov r10, 0x08 syscall
下一步是等待SIGTERM信號(hào)的到來(lái)。 sys_pause這個(gè)系統(tǒng)調(diào)用可以用下面這種方式輕松地實(shí)現(xiàn):
mov rax, 0x22 ; sys_pause syscall
處理程序本身很普通,它沒(méi)有做任何事情:
handler: ret
恢復(fù)器(restorer)也很簡(jiǎn)單,雖然它需要調(diào)用sys_rt_sigreturn系統(tǒng)調(diào)用:
restorer: mov rax, 0x0f ; sys_rt_sigreturn syscall
需要兩個(gè)命令來(lái)構(gòu)建應(yīng)用程序。假定源文件名為hang.asm,則命令是:
nasm -f elf64 hang.asm ld -s -o hang hang.o
這將產(chǎn)生一個(gè)名為hang的可執(zhí)行文件,它很小:
$ stat hang File: hang Size: 736
是的,它只有736字節(jié)。
Dockerfile相當(dāng)簡(jiǎn)單,只需要兩個(gè)命令:
FROM scratch
ADD hang /usr/bin/hang
ENTRYPOINT ["/usr/bin/hang"]
我們來(lái)看看容器是否能工作:
$ docker build -t nathanosman/hang . $ docker run -d --name hang nathanosman/hang
此時(shí),容器應(yīng)該保持運(yùn)行狀態(tài):
$ docker ps -a CONTAINER ID IMAGE COMMAND STATUS
f1861f628ea8 nathanosman/hang "/usr/bin/hang" Up 3 seconds
當(dāng)執(zhí)行docker stop時(shí),應(yīng)該立即停止:
$ docker stop hang
hang
有用!我們來(lái)確認(rèn)以下容器的大小是否和可執(zhí)行文件的大小一致:
$ docker images REPOSITORY TAG CREATED SIZE nathanosman/hang latest 2 minutes ago 736B
是的!一個(gè)非常小的容器!
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來(lái)源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。