注目の記事

【docker buildのマニアックすぎる狂宴】Container Build Meetup #1に参加してきた #container_build

Docker buildに命を捧げた男たちが、そのアツい想いをぶちまけ、それを全力で受け取った参加者がハッシュタグつけてつぶやきまくるような、そんな勉強会でした。
2018.10.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

「あぁ、この人たち、すっごい楽しそうにマニアックな話するなぁ」

このイベントに参加しながら、ハマコーずっとそんなことを考えてました。

Container Build Meetup #1 - connpass

Docker Buildだけがテーマという、すげぇフォーカスを絞りまくった勉強会だったんですが、絞り方が絶妙だったのか、参加者の熱もアツく質疑応答も盛況だったので、そのレポートをお届けいたします。技術的にも、旬のDocker界隈の話がてんこ盛りで参考になりました。

container buildきたか…!!

  ( ゚д゚) ガタッ
  /   ヾ
__L| / ̄ ̄ ̄/_
  \/   /

登壇者一覧

タイトル スピーカー
Better Docker Image @orisano
Base Image Journey 2018 @stormcat24
BuildKitによる高速でセキュアなイメージビルド @AkihiroSuda

Better Docker Image

登壇者はおりさの(@orisano)さん。

良いDockerイメージを作るには2つのアプローチがある。

  1. どのように速くするか
  2. どのように小さくするか

1.どのように速くするか

docker buildを速くするアプローチは複数ある

  • イメージを小さくする
  • コマンドそのものを速くする
  • cacheを効かせる
  • 依存のないステージを並列で実行する
  • 必要ないステージをbuildしない

コマンドそのものを速くする

ADDはリモートURLからファイルを追加できるが、基本的にダウンロードが前提となるので遅い。アクセス先のコンテンツが冪等であれば、wget + gzip + tarで十分。RUN wgetはcacheが効く。

また、GitHubからcurl or wgetでファイルを取得するのは遅い。GitHub Releaseを確認してみるとS3でホストされているため、並列ダウンロードができることがわかった。

そこで開発したのが、wgetライクにGitHub releaseを並列ダウンロードできるrget。

環境によるが、5分程度かかっていたものが2分になったりするので、ぜひ活用してほしい。

cacheを効かせる

  • docker buildするマシンが同一の場合

RUNは文字列が変わらない限り基本的にcacheされる。COPY,ADDするファイルの内容が変わった場合、以降のRUNのcacheが破棄されるため、lockfileなどを先にコピーしてinstallだけしてcacheさせることができる。

  • docker buildするマシンが同一ではない場合

CIでbuildする場合は、cacheが無いことが多い。そういう場合は、docker save & load、もしくはdocker pullを使う。もしくは、docker build --cache-fromあたりを使うことでcacheを効かすことを検討できる。

imageやlayerが多い場合は、buildのほうが早い場合もある。

依存のないステージを並列で実行する

buildkitを使うと良いよ!

2.どのように小さくするか

イメージサイズを小さくする方法もいくつかある。

  • multi stage buildを使う
  • RUNをまとめる(?)
  • なぜ大きいかを知る

multi stage buildを使う

最終的なイメージが小さくなると言えども、それまでのステージも最適化したほうが良い。CI上でのbuild時は--cache-fromを使うが、最終ステージだけcacheを利用しても、最終ステージのみpushしている場合は、前段のcacheは利用できない。

各ステージでcacheを効かせたい場合、前のステージも明示的にpushする必要がある。結局各ステージでpush/pullするため、コストがかかる。全てのステージを平等に小さくするべき。

RUNをまとめる(?)

Dockerfileは、RUNを1つにまとめると、レイヤーが減ってイメージが小さくなるらしい(?)そこで、コマンドを一つにまとめるためのツールを作ってみた。

なぜ、イメージが小さくなるのか。レイヤーのオーバーヘッドが減るから小さくなるのか?そこは違う。まず、どういう形で保存されているか知るところから始まる。

Dockerで利用されているストレージドライバのAUFSを知る必要がある。

コンテナからのファイルの削除はホワイトアウト・ファイル(whiteout file)の設置。移動はopaqueファイルの追加+移動先のファイル差分として処理される。なので、一度でもRUN,COPY,ADDを跨ぐとimageに残ってしまう。そのため、機械的にでも一度のRUNにまとめることで余計なものを削減することができる。

1つのレイヤーに全てをまとめることがよいかどうかは考えどころ。レイヤーを分けることにより、並列ダウンロードが可能になったり、cacheが有効活用できたり、可読性が向上する。

そのため、最終的には計測して決めることが重要。とはいえ、指針としては、複数レイヤーに跨がらない程度に分割するのが良さそうではある。

なぜ大きいかを知る

docker historyコマンドはあるが、どのレイヤーが大きいかはわかるが、なぜ大きいかは分かりづらい。そこでこちらのツールをご利用。

これを利用して、実際にgolang公式alpineイメージのサイズを減らしてみた。

出力イメージ例

$ dlayer -f wordpres.tar

====================================================================================================
  55 MB 	 $ #(nop) ADD file:e6ca98733431f75e97eb09758ba64065d213d51bd2070a95cf15f2ff5adccfc4 in /
====================================================================================================
 2.0 MB 	 usr/bin/perl
 1.8 MB 	 usr/lib/x86_64-linux-gnu/libdb-5.3.so
 1.8 MB 	 usr/lib/x86_64-linux-gnu/perl-base/unicore
 1.8 MB 	 usr/lib/x86_64-linux-gnu/libapt-pkg.so.5.0.1
 1.7 MB 	 lib/x86_64-linux-gnu/libc-2.24.so
 1.6 MB 	 usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.22
 1.5 MB 	 usr/lib/locale/C.UTF-8/LC_COLLATE
 1.1 MB 	 lib/x86_64-linux-gnu/libgcrypt.so.20.1.6
 1.1 MB 	 bin/bash
 1.1 MB 	 lib/x86_64-linux-gnu/libm-2.24.so

====================================================================================================
   46 B 	 $ # multiple line script
set -eux
{
    echo 'Package: php*'
    echo 'Pin: release *'
    echo 'Pin-Priority: -1'
} >/etc/apt/preferences.d/no-debian-php
====================================================================================================
   46 B 	 etc/apt/preferences.d/no-debian-php

====================================================================================================
 209 MB 	 $ apt-get update && apt-get install -y $PHPIZE_DEPS ca-certificates curl xz-utils --no-install-recommends && rm -r /var/lib/apt/lists/*
====================================================================================================
  87 MB 	 usr/lib/gcc/x86_64-linux-gnu/6
  19 MB 	 usr/lib/x86_64-linux-gnu/perl/5.24.1
 5.5 MB 	 usr/lib/x86_64-linux-gnu/libpthread.a
 4.9 MB 	 usr/lib/file/magic.mgc
 4.6 MB 	 usr/lib/x86_64-linux-gnu/libc.a
 3.3 MB 	 usr/share/perl/5.24.1/Unicode
 3.1 MB 	 usr/bin/x86_64-linux-gnu-ld.gold
 3.1 MB 	 usr/include/c++/6/bits
 3.0 MB 	 usr/share/perl/5.24.1/unicore
 2.9 MB 	 usr/bin/x86_64-linux-gnu-dwp

rgetこぼれ話

(ハマコー:ここの話はマニアックすぎるので、是非資料を参考に…)

QAタイム

  • QA1
    • Q:「イメージのリポジトリっていろいろあると思うんですが、どれが使い勝手良いでしょうか?」
    • A:「各クラウドプロバイダによって、速度はだいぶ異なります。ぶっちゃけ体感で、すっげぇ遅いリポジトリもあります」
  • QA2
    • Q:「Dockerビルド前に関連ファイルをあらかじめダウンロードしておくのはどうか?
    • A:「リポジトリにあらかじめファイルを用意しておく形式だと、結局ルートから全て用意してAddすれのと変わらなくなる。Dockerfileも基本は全ての環境で同じように動くべきだと考えているので、全てDockerfile内で完結させたい。」

ハマコー感想

いやーやばかったですね。docker buildのサイズの最適化とイメージの最適化の両面から、考慮すべき事項がまとめられていて参考になりまくります。一部理解が追いついていないところがかなりあったので、あれこれググりながら関連コマンドながしてみて、ようやく話していた内容がわかってきた感じ。

関連ツールを使ってみたんですが、どのツールも使いやすく強力で面白かったです。dlayerは、いろんな公式イメージのDockerfileとdlayer出力結果を比較するだけで面白く参考になる。

AUFSによるファイルの削除処理とか、全く興味なかった観点なので、目からウロコでした。orisanoさんツールのソース、読むだけでもわかりみが深いんじゃないでしょうか。Golang勉強したくなった。

Base Image Journey 2018

登壇者は?????????????(@stormcat24)さん。

CyberAgentで、FRESH LIVEのテックリードやってます。

技術評論社からDocker/Kubernetes実践コンテナ開発入門出しました!単著は無茶だった。疲れたよ。

こんな年表作ってみました。イメージの変遷がよくわかるかと。これ、むっちゃ頑張って作ったから、みなさんも味わいながら眺めてね。

scratch〜buxybox〜Alpine〜ubuntuその他ときて、今新しいイメージの時代としてdistrolessがきている。

distrolessとは

実行用イメージとして最低限必要な、これらが含まれているので使いやすい。

  • ca-certificates
  • glibc
  • libssl/openssl
  • /etc/passwd(root)
  • /tmp

基本的にアプリケーションの実行用に特化し最低限必要なもののみを備えているため、成果物をビルドするには向いていない。multi-stage buildなどで、ビルド結果のバイナリだけを実行用イメージに突っ込むなどの工夫が必要。

また、distrolessにはshが無いため、コンテナに入りたければdebugイメージの利用が必要。

何故distrolessか?

実行に必要最小限のものだけが含まれるため、セキュリティリスクの低減が見込まれる。CVE記載の脆弱性に対応した最新のイメージが常に提供される。

タグにはlatestdebugしかない。

latestだけの提供のため、distrolessベースのイメージをビルドする際、常に最新のベースイメージが利用できるため、脆弱性対応のコストがDOWNする。distrolesssを使うこと自体が、セキュリティを高めることにつながる。

QAタイム

  • QA1
    • Q:「distrolessは、latestを利用する前提だと、node.jsなどどこかのタイミングで実行バージョンが変わってしまうのでは?」
    • A:「最初のうちは、goとかcとかjavaでの利用が筋が良さそう」

その他、ここには書けないようなQAがあれこれ。

ハマコー所感

scratchから始まるイメージの歴史解説が面白かったです。自分も愛読しているDocker/Kubernetes 実践コンテナ開発入門 の著者だけあって、ストーリー仕立ての話が心に沁みる。正直、各イメージがでてきた年表とか歴史とか全然把握してなかったので、そこらへんの全体感を知ることができたのは収穫。distrolessも、面白そうですね。

wordpressのlatestイメージをbuildkit利用したのがこちら。見た目も違うし、あきらかに実行速度が早くなってて楽しい。

今後のDockerイメージの進化の方向性に期待。

BuildKitによる高速でセキュアなイメージビルド

登壇者はAkihiro Suda(@AkihiroSuda)さん

コンテナ関連OSSのメンテナをやってます。

従来のdocker buikdには不満があった。

  • Dockerfileのキャッシュが効きにくい
  • コンパイラやパッケージマネージャのキャッシュが保存されない
  • ビルドコンテキストの転送に時間がかかる
  • 並列実行できるはずの命令を、並列実行してくれない
  • プライベートなGitやS3などへのアクセスが困難
  • COPY命令で 鍵を置くのは危ない
  • 環境変数を使うのも危ない
  • root権限が必要であるため、HPC環境や、Kubernetesクラスタ上などでは、利用を許可されないことがある
  • 個人のラップトップで実行する場合でも、セキュリティ上のリスクが大きい

BuildKit

ここで、次世代docker buildである、BuildKitの登場。

使い方はかんたん。Docker v18.06以降には、BuildKitが組み込まれているため、クライアント側でexport DOCKER_BUILDKIT=1するだけで有効に成る。コマンドラインは、従来のdocker buildと同じ。

RUN --mount-type=cache

コンパイラやパッケージマネージャのキャッシュディレクトリを保持できる。

RUN --mount-type=secret

S3やSSHの鍵を、RUNコンテナ内に安全にマウント可能。マウントされるだけなので、出力イメージ内には残らない。

RUN --mount-type=ssh

クライアントのssh-agentソケットにRUNコンテナからアクセスできる

BuildKitの将来

  • Kubernetes、Knative上での分散ビルドの改善
  • キャッシュの自動ガーベジコレクション
  • OCI v1に変わるイメージフォーマット・配布プロトコルへの対応

ハマコー感想

mobyプロジェクトのメンテナの方だけあって、最新のBuildkit事情と今後のDockerの進化の方向性まで聞けて、面白かったです。現状のDockerfileの痛みからのBuildKitの説明がむっちゃわかりやすかった。

Buildkit、自分もちょっと手元のmacで試してみましたが、build時の見た目がいきなり変わってるのも驚きですが、今まで使ってたDockefileのビルドが明らかに早くなったりして感動。試すのも簡単なので、皆さん是非やってみませう。

「テーマが一貫していて参加者の熱気も濃くマニアックな勉強会」

当日の熱狂の様子はtogetter参照。ハッシュタグも盛況やで。

Container Build Meetup #1 - MajiでRunする5秒前 - - Togetter

まじで情報量大杉の勉強会でした。自分、正直Dockerfileそこまで考えて作ったことなかったので、全部が目新しく面白いものばっかりでしたね。参加者の期待感と登壇者の濃いネタが相まって独特の雰囲気があって、なかなかこんな密度の濃い勉強会も無いなぁと思いました。

Container Build Meetup - connpass

今後も、マニアックな情報が飛び交う現場となりそうな予感がプンプンするので、Docker興味あるかたはこちら登録しておくと楽しいかと思います。

それでは、今日はこのへんで。濱田(@hamako9999)でした。