微型容器挑战:构建一个6kB的容器化HTTP服务器
作者 | devopsdirective.com译者 | 张健欣策划 | 施尧我着手构建一个我所能构建的最小且仍然有一些用途的容器镜像。通过利用多阶段构建、一个 scratch 基础镜像以及一个微型的基于汇编语言的 http 服务器,我将这个镜像减小到 6.32kB!如果你更喜欢视频格式,请访问这里的 YouTube 链接!
膨胀的容器容器通常被吹捧为一颗银弹,能够解决与操作软件相关的每一个挑战。虽然我喜欢容器,但我经常遇到有各种各样问题的容器镜像。一个常见的问题是容器大小,容器镜像有时候会达到几个 GB!
正因为如此,我决定进行挑战,来构建尽可能最小的镜像。
挑战规则很简单:
这个容器应该在你指定的端口上通过 http 提供一个文件的内容
不允许挂载卷(也即“Marek 规则”)
初始方案为了获得符合基准的镜像大小,我们可以使用 node.js 创建一个简单的服务器index.js:
const fs = require("fs");
const http = require('http');
const server = http.createServer((req, res) = {
res.writeHead(200, { 'content-type': 'text/html' })
fs.createReadStream('index.html').pipe(res)
})
server.listen(port, hostname, () = {
console.log(`Server: http://0.0.0.0:8080/`);
});
然后将它构建到一个启用官方的 node 基础镜像的镜像中:
FROM node:14
COPY . .
CMD ["node", "index.js"]
这就有943MB!
更小的基础镜像减小镜像大小的最简单最明显的策略之一就是使用一个更小的基础镜像。官方的 node 镜像有一个slim变体(仍然基于 debian,但是预装的依赖更少),以及一个基于 Alpine Linux 的alpine变体。
使用node:14-slim和node:14-alpine作为基础镜像可以分别将镜像大小降低到167MB和116MB。
由于 docker 镜像是添加型的,每一层都是构建在另一层的基础上,因此我们不能做太多别的事情来进一步减小 node.js 方案。
编译型语言为了更进一步,我们可以转换到一个具有更少运行时依赖的编译型语言。有很多选项,但对于构建 Web 服务,golang 是一个比较流行的选择。
我创建了一个基础的文件服务器server.go:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
fileServer := http.FileServer(http.Dir("./"))
http.Handle("/", fileServer)
fmt.Printf("Starting server at port 8080\n")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
然后将它构建到一个使用官方的 golang 基础镜像的容器镜像:
FROM golang:1.14
COPY . .
RUN go build -o server .
CMD ["./server"]
这有818MB. 这里的问题是 golang 基础镜像有很多预安装的依赖,这些依赖在构建 go 软件时有用,但是在运行软件时并不是必需的。
多阶段构建Docker 有一个叫做“多阶段构建(multi-stage builds)”的特性,它可以轻易在一个具有所有必需依赖的环境中构建代码,然后将可执行结果拷贝到另一个不同的镜像中。
这样做有很多好处,但最明显的是镜像大小!通过如下重构 dockerfile:
### build stage ###
FROM golang:1.14-alpine AS builder
COPY . .
RUN go build -o server .
### run stage ###
FROM alpine:3.12
COPY --from=builder /go/server ./server
COPY index.html index.html
CMD ["./server"]
结果镜像只有13.2MB! ??
静态编译 +scratch 镜像13MB 还不错,但我们还可以利用一些手段将镜像变得更小。
有一个名为 scratch 的基础镜像,它是空的且大小为零。由于scratch内部没有任何内容,因此任何基于它构建的镜像必须包含所有必需的依赖。
为了使我们的 go 基础服务器能够运行,我们需要在编译步骤中添加几个标志,从而确保必要的库静态链接到可执行程序中:
### build stage ###
FROM golang:1.14 as builder
COPY . .
RUN go build \
-ldflags "-linkmode external -extldflags -static" \
-a server.go
### run stage ###
FROM scratch
COPY --from=builder /go/server ./server
COPY index.html index.html
CMD ["./server"]
具体来说,我们将链接模式设置为external,并将-static标志传给外部链接器。这两个更改使得镜像大小减小到8.65MB??
ASM 决定胜局!一个小于 10MB、用 Go 这样的语言编写的镜像,对于任何情况来说都已经是很小了... 但是我们可以让它变得更小!Github 用户 nemasu 在名为 assmttpd 的 github 上用汇编语言编写了一个功能齐全的 http 服务器。
在运行提供的make release脚本之前,需要将一些构建依赖安装到 ubuntu 基础镜像中,从而进行容器化:
### build stage ###
FROM ubuntu:18.04 as builder
RUN apt update
RUN apt install -y make yasm as31 nasm binutils
COPY . .
RUN make release
### run stage ###
FROM scratch
COPY --from=builder /asmttpd /asmttpd
COPY /web_root/index.html /web_root/index.html
CMD ["/asmttpd", "/web_root", "8080"]
然后将生成的asmttpd可执行文件复制到 scratch 镜像中,并使用CMD调用。这样下来,镜像大小只有 6.34kB! ??
容器镜像大小的进展!
希望你能从我们这段从最初的 943MB 的 Node.js 镜像一直到微型的 6.34kB 的汇编镜像的过程中,学到一些技术,将来用于减小你的容器镜像大小。
作者介绍:
devopsdirective.com
原文链接:
Tiny Container Challenge: Building a 6kB Containerized HTTP Server!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线