そのような小さな容器を持ち上げるのは弱いですか?6kBのコンテナ化されたHTTPサーバーの構築

TL; DR  私はあなたがまだ何か役に立つことができる最小のコンテナイメージを作成することに決めました。マルチステージビルド、ベースイメージ、  scratch



 およびこのビルドに基づく小さなhttpサーバーを利用して、結果を6.32kBに絞ることができました。











あなたがビデオを好むなら、ここに記事のためのYouTubeビデオがあります!



膨満した容器



コンテナは、ソフトウェアメンテナンスの課題に対処するための万能薬として宣伝されることがよくあります。また、コンテナが好きなので、実際にはコンテナの画像に出くわし、色々な問題を抱えています。一般的な問題は、コンテナのサイズです。一部の画像では、数ギガバイトに達します。  



そこで、自分自身と他のみんなに挑戦し、できるだけコンパクトな画像を作成することにしました。



仕事



ルールは非常に単純です。



  • コンテナは、ファイルの内容をhttp経由で選択したポートに提供する必要があります 
  • ボリュームのマウントは許可されていません(いわゆる「マレック病の法則」)


簡略化されたソリューション



ベースイメージのサイズを確認するには、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/`);
});

      
      





そして、ノードの公式ベースイメージを実行して、それからイメージを作成します。



FROM node:14
COPY . .
CMD ["node", "index.js"]

      
      





これがかかった  943MB







縮小されたベースイメージ



皮膚のサイズを小さくするための最も単純で最も明白な戦術的アプローチの1つは、より細いベース皮膚を選ぶことです。ノードの公式ベース画像は、変異体に存在する slim



 (まだはDebianに基づいているが、より少ないプレインストール依存関係)と変異体  alpine



 に基づいて  アルパインのLinux



使用  node:14-slim



 および  node:14-alpine



 塩基としては、画像のサイズを小さくすることが可能となる  167MB



 と  116MB



 それに応じ。



Dockerイメージは付加的であり、各レイヤーが次のレイヤーの上に構築されるため、node.jsソリューションをさらに削減するためにここで行うことはほとんどありません。



コンパイルされた言語



物事を次のレベルに引き上げるために、実行時の依存関係がはるかに少ないコンパイル言語に移行できます。いくつかのオプションがありますが、golangはWebサービスの作成によく使用されます



私は最も単純なファイルサーバーを作成しました  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には、マルチステージビルドと呼ばれる機能があります。この機能を使用すると  、必要なすべての依存関係を含む環境でコードを簡単にビルドし、結果の実行可能ファイルを別のイメージにコピーできます。



これはいくつかの理由で役立ちますが、最も明白なものの1つは画像のサイズです!次のようにdockerfileをリファクタリングします。



###   ###
FROM golang:1.14-alpine AS builder
COPY . .
RUN go build -o server .
 
###   ###
FROM alpine:3.12
COPY --from=builder /go/server ./server
COPY index.html index.html
CMD ["./server"]

      
      





結果の画像のサイズはすべて  13.2MB



です!



静的コンパイル+スクラッチ画像



13 MBはまったく悪くはありませんが、これをさらにタイトに見せるために、まだいくつかのトリックが残っています。  スクラッチ



と呼ばれるベースイメージがありますが  、これは明確に空であり、サイズはゼロです。内部  には何もないため、それに基づいて作成されたイメージには、必要なすべての依存関係が含まれている必要があります。 goサーバーに基づいてこれを可能にするには、コンパイル時にいくつかのフラグを追加して、必要なすべてのライブラリが実行可能ファイルに静的にリンクされるようにする必要があります。 scratch











###   ###
FROM golang:1.14 as builder
COPY . .
RUN go build \
  -ldflags "-linkmode external -extldflags -static" \
  -a server.go
 
###   ###
FROM scratch
COPY --from=builder /go/server ./server
COPY index.html index.html
CMD ["./server"]

      
      





特に、external



 リンクモードを設定 し、フラグを-static



 外部リンカに渡し  ます。



これらの2つの変更のおかげで、画像サイズを最大にすることが可能です 8.65MB



 



勝利の保証としてのASM!



Goのような言語で書かれた10MB未満のサイズの画像は、ほとんどすべての状況で明らかに小型化されています...しかし、さらに小さくすることもできます。ユーザー  nemasuが アセンブラーで記述した本格的なhttpサーバーをGithubに投稿しました。それはassmttpdと呼ばれ ます



コンテナ化するのに必要なのは、提供されたレシピを実行する前に、ベースのUbuntuイメージにいくつかのビルド依存関係をインストールすることだけでした make release







###   ###
FROM ubuntu:18.04 as builder
RUN apt update
RUN apt install -y make yasm as31 nasm binutils 
COPY . .
RUN make release
 
###   ###
FROM scratch
COPY --from=builder /asmttpd /asmttpd
COPY /web_root/index.html /web_root/index.html
CMD ["/asmttpd", "/web_root", "8080"] 

      
      





結果の実行可能ファイルは  asmttpd



 、スクラッチイメージにコピーされ、コマンドラインから呼び出されます。結果の画像のサイズはわずか6.34kBです!



All Articles