Kekeの日記

エンジニア、読書なんでも

Small Imageと堅牢性のためのDistroless ImageとビルドのためのBuildKitの検証

本記事

本記事ではDockerでコンテナイメージを作るときに使えるBuildKitの話とSmall Containerを作ることができるDistrolessの話をしようと思います。

以前、Building Small Containerのために使えるBuilderパターンを紹介しました。

www.1915keke.com

アプリケーションをビルドしてlinuxのaplineイメージへマウントして、小さなアプリケーションを作ることができました。 この記事ではなぜSmall Imageにすることが重要かも同時に解説しましたので今回は解説しません。

目次

前提

今回のソースコードはgolangを使って作っています。

package main

import "fmt"

func main() {
    fmt.Println("Hoge")
}

また、ビルド時間計測はlinuxコマンドのtimeを使っています。

Distroless

概要

Distroless Imageとは、

言語のためのアプリケーションのコードとランタイムに必要な最低限のイメージ

です。Shellやpackageマネージャーなど他のイメージは一切含まないです。

github.com

使い方

一般的にはBazelを使うようですが、Dockerでも使えるみたいです。

Dockerに詳しい方は知っていると思いますが、Dockerでは

ENTRYPOINT ['myapp']
ENTRYPOINT 'myapp'

には大きな違いがあり、後者はShellを通して実行されます。Distroless ImageはShellがないので前者で行う必要があることに注意です。

Dockerを使っていやろうと思います。

以下の一覧からImageを選択します。

  • gcr.io/distroless/base
  • gcr.io/distroless/java
  • gcr.io/distroless/cc

再度リンクを載せますが、ビルダーパターンで行うとSmallImageを作ることができます。

検証

Builderパターンを使わず

以下のようなDockerfileを作成しました。

FROM golang:1.11.1

WORKDIR /go/src/test-distroless
COPY . .

RUN go get -d -v ./...
RUN go install -v ./...

CMD ["/go/bin/test-distroless"]

容量をチェックすると以下のようになっていました。

single              latest              3fb95f25a970        9 seconds ago       780MB

ビルド時間は4.40秒でした。

Golang aplineを使う

先ほどのDockerfileのFROMを書き換えます。

FROM golang:1.11.1-alpine

容量をチェックすると以下のようになっていました。

single-apline       latest              73aad510dc00        41 seconds ago      314MB

ビルド時間は4.27秒でした。

linuxのalpineに載せるBuilderパターン

FROM golang:1.8 as build

WORKDIR /go/src/test-distroless
COPY . .

RUN go get -d -v ./...
RUN go install -v ./...

FROM alpine
COPY --from=build /go/bin/test-distroless /
CMD ["/test-distroless"]

イメージ容量とビルド時間です。

linux-apline        latest              69f315e4765c        49 seconds ago      5.96MB

4.56秒。

Distrolessイメージを使う

以下のようなDockerfileを作りました。

FROM golang:1.8 as build

WORKDIR /go/src/test-distroless
COPY . .

RUN go get -d -v ./...
RUN go install -v ./...

FROM gcr.io/distroless/base
COPY --from=build /go/bin/test-distroless /
CMD ["/test-distroless"]

容量を確認します。

test-distroless          latest              a906e611f5cb        3 minutes ago       18.1MB

ビルド時間は4.95秒でした。

考察

なんとなくつけましたが、ビルド時間は計測しても意味はないです。

Builderパターンは二つ目のコンテナイメージは何が来ても実際は起動時間しか差異はないので、あまり差がでなくて当然で、またBuilderパターンについても使わない場合の方が処理コストは低いのでパターンを使っても時間が短縮できないのは当然でした。

しかし、なぜalpineの方が軽量なのにdistrolessの時代って言われているのでしょう。

以下の記事に答えがありました。

speakerdeck.com

alpineは軽量なのだが、安定・堅牢性のある実行用コンテナイメージとしてdistrolessが注目されているとのことでした。

本節にもある「Small Imageと堅牢性のためのDistroless」を作ることができました。

付録 Distroless ImageのDebug方法

どのDistroless Imageも:debugタグが用意されてあり、shellが付いてきます。

付録 Distrolessのdocker tagからみたtag運用

Distrolessにはlatestdebugしかなく、常に最新のイメージを使うことになるので脆弱性対策を心がけなくても済む様になる。

チームでも0.3などをつけてわからなくなるので、このような運用方法は良いと思った。

BulidKit

概要

以下のような特徴を持つコンテナビルドツールです。

  • 高い並列処理能力
  • 賢いキャッシュ機能

また既存のbuildでは、少しでもN行を変えるとN+1行目からはキャッシュが使われない設定になっていました。

セットアップ

環境変数をセットアップします。

export DOCKER_BUILDKIT=1

そしてdocker daemonの設定がこのようになっているのを確認します。

f:id:bobchan1915:20181010020929p:plain

そしてdockerを再起動します。

sudo service docker restart

実行してみる

普通にビルドしていたときは5秒近くかかりましたがいまでは2秒くらいです。

他の言語

Nodeなどのスクリプト言語

スクリプト言語は対してDistroless Imageの恩恵をうけないのですが、強いて言うならセキュリティなどのメリットがあるくらいです。

例えばNodeならば

FROM node:alpine
WORKDIR /app
COPY package.json /app/package.json
RUN npm install --production
COPY server.js /app/server.js
EXPOSE 8080
CMD ["node", "/app/server"]

FROM node:8.9.1 AS build-env
ADD . /app
WORKDIR /app

FROM gcr.io/distroless/nodejs
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.js"]

にできるみたいなのですが、いまいちメリットがわかりません。同様にPythonなどもです。

参考文献

今回は以下の記事を参考にしました。

speakerdeck.com