写点什么

微型容器挑战:构建一个 6kB 的容器化 HTTP 服务器

devopsdirective.com

  • 2021-06-15
  • 本文字数:2112 字

    阅读完需:约 7 分钟

微型容器挑战:构建一个6kB的容器化HTTP服务器

我着手构建一个我所能构建的最小且仍然有一些用途的容器镜像。通过利用多阶段构建、一个 scratch 基础镜像以及一个微型的基于汇编语言的 http 服务器,我将这个镜像减小到 6.32kB!


膨胀的容器


容器通常被吹捧为一颗银弹,能够解决与操作软件相关的每一个挑战。虽然我喜欢容器,但我经常遇到有各种各样问题的容器镜像。一个常见的问题是容器大小,容器镜像有时候会达到几个 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:14COPY . .CMD ["node", "index.js"]
复制代码


这就有943MB


更小的基础镜像


减小镜像大小的最简单最明显的策略之一就是使用一个更小的基础镜像。官方的 node 镜像有一个slim变体(仍然基于 debian,但是预装的依赖更少),以及一个基于 Alpine Linux 的alpine变体。


使用node:14-slimnode:14-alpine作为基础镜像可以分别将镜像大小降低到167MB116MB

由于 docker 镜像是添加型的,每一层都是构建在另一层的基础上,因此我们不能做太多别的事情来进一步减小 node.js 方案。


编译型语言


为了更进一步,我们可以转换到一个具有更少运行时依赖的编译型语言。有很多选项,但对于构建 Web 服务,golang 是一个比较流行的选择。


我创建了一个基础的文件服务器server.go:


package mainimport (    "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.14COPY . .RUN go build -o server .CMD ["./server"]
复制代码


这有818MB. 这里的问题是 golang 基础镜像有很多预安装的依赖,这些依赖在构建 go 软件时有用,但是在运行软件时并不是必需的。


多阶段构建


Docker 有一个叫做“多阶段构建(multi-stage builds)”的特性,它可以轻易在一个具有所有必需依赖的环境中构建代码,然后将可执行结果拷贝到另一个不同的镜像中。


这样做有很多好处,但最明显的是镜像大小!通过如下重构 dockerfile:


### build stage ###FROM golang:1.14-alpine AS builderCOPY . .RUN go build -o server .### run stage ###FROM alpine:3.12COPY --from=builder /go/server ./serverCOPY index.html index.htmlCMD ["./server"]
复制代码


结果镜像只有13.2MB! 🙂


静态编译 +scratch 镜像


13MB 还不错,但我们还可以利用一些手段将镜像变得更小。


有一个名为 scratch 的基础镜像,它是空的且大小为零。由于scratch内部没有任何内容,因此任何基于它构建的镜像必须包含所有必需的依赖。


为了使我们的 go 基础服务器能够运行,我们需要在编译步骤中添加几个标志,从而确保必要的库静态链接到可执行程序中:


### build stage ###FROM golang:1.14 as builderCOPY . .RUN go build \  -ldflags "-linkmode external -extldflags -static" \  -a server.go### run stage ###FROM scratchCOPY --from=builder /go/server ./serverCOPY index.html index.htmlCMD ["./server"]
复制代码


具体来说,我们将链接模式设置为external,并将-static标志传给外部链接器。这两个更改使得镜像大小减小到8.65MB😀


ASM 决定胜局!


一个小于 10MB、用 Go 这样的语言编写的镜像,对于任何情况来说都已经是很小了... 但是我们可以让它变得更小!Github 用户 nemasu 在名为 assmttpd 的 github 上用汇编语言编写了一个功能齐全的 http 服务器。


在运行提供的make release脚本之前,需要将一些构建依赖安装到 ubuntu 基础镜像中,从而进行容器化:


### build stage ###FROM ubuntu:18.04 as builderRUN apt updateRUN apt install -y make yasm as31 nasm binutils COPY . .RUN make release### run stage ###FROM scratchCOPY --from=builder /asmttpd /asmttpdCOPY /web_root/index.html /web_root/index.htmlCMD ["/asmttpd", "/web_root", "8080"]
复制代码

然后将生成的asmttpd可执行文件复制到 scratch 镜像中,并使用CMD调用。这样下来,镜像大小只有 6.34kB! 🥳


容器镜像大小的进展!


希望你能从我们这段从最初的 943MB 的 Node.js 镜像一直到微型的 6.34kB 的汇编镜像的过程中,学到一些技术,将来用于减小你的容器镜像大小。


作者介绍:


devopsdirective.com


原文链接:


https://devopsdirective.com/posts/2021/04/tiny-container-image/index.html?fileGuid=KxkC6jGXydCvRRrg


2021-06-15 15:572510
用户头像

发布了 74 篇内容, 共 28.5 次阅读, 收获喜欢 83 次。

关注

评论

发布
暂无评论
发现更多内容

三、创建、更新和删除文档

Kylin

读书笔记 七日更 分布式数据库mongodb 二月春节不断更

程序员心中的一道坎:主存的编址与计算和串并联系统!

冰河

程序员 操作系统 计算 编址 串并联系统

New转乾坤——云网融合真正的打开方式!

脑极体

28天瞎写的第二百四十一天:正念是不是迷信、玄学、神棍?

树上

冥想 28天写作 正念 迷信

第5周作业_贷款申请流程图

园子

互联网金融

遇见ZooKeeper:初识

Jackey

zookeeper

我的配置中心知识整理

老白鹿

微服务 技术选型 配置中心 配置管理

面试系列一:精选大数据面试真题10道(混合型)-附答案详细解析

五分钟学大数据

大数据 面试 28天写作

产品训练营第四章作业(二)

Arnold

Impala 3.4在网易的最新实践

DataFunTalk

Eclipse快捷键大全

lnngle

Java eclipse 快捷键

【管理笔记11】优秀人才的十二个特质

L3C老司机

28天写作

梦境交互:做个现代灵媒,考虑一下?

脑极体

week13作业

zbest

LeetCode 采坑两次后,我终于学会了 BFS

与你一起学算法

Python BFS 数据结构与算法

ConcurrentBag 听过没?好家伙高并发知识点十分密集!一种并发优化思路!

yes

Java 面试 并发

如何监控Nginx的upstream后端server

运维研习社

nginx 负载均衡 zabbi

聊聊如何做好计划

数列科技杨德华

28天写作

MYSQL 索引篇(上)

new life

MySQL性能优化 执行计划 MySQL使用 索引性能

Mybatis【19】-- Mybatis自关联多对多查询

秦怀杂货店

如何在2021金三银四拿到35K,我肝了这份10W字Java面试手册送给大家

程序员 架构 面试

压力太大的话,就放点气儿吧

道伟

28天写作

产品经理是吃青春饭的吗?

涛哥 数字产品和业务架构

产品经理

lua 对象编程解读

程序员与厨子

lua 学习 编程

Selenium 八大定位,滚雪球学 Python 番外系列

梦想橡皮擦

Python 28天写作 2月春节不断更

真正的勇士,敢于重新开始,敢于再次开始😂

Nydia

python爬虫-学习urllib和requests使用,模拟请求

大佬sam

二月春节不断更

MYSQL 索引篇(下)

new life

MySQL MySQL性能优化 多字段联合验证 索引性能

Mybatis【20】-- Mybatis延迟加载怎么处理?

秦怀杂货店

数据库 缓存 mybatis 加载

(28DW-S8-Day3) 比特币、 区块链是什么?

mtfelix

比特币 区块链 28天写作

Elasticsearch 组合查询

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

微型容器挑战:构建一个6kB的容器化HTTP服务器_语言 & 开发_InfoQ精选文章