Gentoo distccd Docker

August 17, 2021

背景

Gentoo の Docker イメージが作りたい。が、

  1. オフィシャルの gentoo/stage3-amd64 の更新頻度が低い( 2021/08/08 時点で以前のアップデートが 1 年前だった )
  2. portage 用のイメージ gentoo/portageがあり、こちらは毎日更新されている。
  3. コマンドや glibc などが置かれているビルド済みの領域は gentoo/stage3-amd64 に置かれているため、実際に使うときには emerge —update —deep —newuse @world とリビルドが必要
  4. リビルドするとイメージのサイズがかなり増える

が、できればコンパクトなイメージにしたい。

結論(2021/08/17 時点)

docker-slimを使ってイメージをコンパクトにする方法が一番コンパクトになった。

手法 サイズ 備考
ダウンサイジング前 3.04GB
不要なディレクトリの削除 1.8GB emerge が一時的に使うファイル郡
(上記ディレクトリ削除に加えて)不要なパッケージの削除 983MB パッケージ一覧を見ながら不要そうなパッケージを削除
docker-slim 39MB

Dockerfile サンプル

ここに公開した

手法の比較

不要なディレクトリ削除は emerge が一時的に使うファイル郡を削除するだけなので安全に使える。

不要なパッケージの削除だが、これはパッケージ名の変更やパッケージ同士の依存関係の変更があると 必須ではないが、都度 Dockerfile の修正が必要になり長期間の運用を考えると、運用コストが上がる。

docker-slim を使った方法が一番コンパクトになった。ただ docker-slim は必要なファイルを動的に解析して必要なファイルのみを残すので、解析できるように準備する必要がある。

portage 用のイメージ gentoo/portageを使った方が良いのか?

RUN emerge —sync でも同様な事はできるので… と思ったが、毎回 emerge —sync に時間が取られるのは無駄。 もし「Docker のキャッシュで古い状態でビルドされるのが嫌だ」という場合でも、キャッシュを使わないオプションで docker build すれば最新の状態でビルドできる。

理想をいえば gentoo/stage3-amd64 の方の更新頻度を上げていただけると助かる( glibc, binutils あたり )と思ったが、 USE などのオプションの豊富さもあって最適な状態を保つのはかなり難しいのだろうか。

docker-slim を使う

docker-slim はスリムにしたいコンテナを実行、解析し、必要なファイルをリストアップして、必要なファイルだけでコンテナを作り直す(1FD Linux を思い出した) このため Dockerfine の CMD に記載する内容だけで解析すると、実行時に必要なファイルが足りずエラーになる場合がある。

例えば分散コンパイルを行う distccd の場合、実行には下記の両方が必要。

  1. ポートを開けて distcc クライアントからの接続を待つデーモン
  2. gcc や必要なヘッダなど

が Dockerfile の CMD には distccd の起動だけを記載すれば良いので、これだけだと上記の[2]が足りず、実行時にはエラーになる。

docker-slim は —exec オプションで、解析をするための bash スクリプトが指定可能。 この bash スクリプトに必要なコマンドを列挙しておくことで、解析に必要な情報を揃えることができる。

以下は実際にクロスコンパイルと distccd を呼び出している例。

#!/bin/bash

aarch64-unknown-linux-gnu-gcc -c -I. -Iproto -DHAVE_CONFIG_H     -mtune=cortex-a72 -march=armv8-a+crc -O2 -pipe -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 /hello.c
/usr/bin/distccd --help

下記のようにして docker-slim を実行して軽量にできる。

docker-slim --state-path ~/tmp/docker-slim build --target gentoo-distcc-simple-202108172320:latest --tag ghcr.io/tin-machine/genpi64-distcc-docker:latest --http-probe=false --exec "/need.bash"

以下は docker-slim を使う以前に検討した事項

私の今回のケースでは最終的なイメージのサイズ削減には docker-slim が最適だった。 ただ docker-slim が上手くいかないケースもあるかもしれない( 必要なファイルの解析が上手く行かないなど )ので、別の手法の記録も残しておく。

Docker のレイヤーを少なくする。

Docker のマルチステージ機能を用いてscratchに全部コピーする事で

1 点目 コンパクトになる。
FROM gentoo/portage AS portage
FROM gentoo/stage3-amd64 AS distcc-builder        # distcc-builder という名前のコンテナでビルド
COPY --from=portage /var/db/repos/gentoo/ /var/db/repos/gentoo/
RUN emerge いろんなパッケージ

RUN rm -rf /var/cache/distfiles/* /var/db/repos/gentoo/ # 不要なディレクトリ削除

FROM scratch AS distcc-builder-squashed
COPY --from=distcc-builder / /                  # 成果物を全部コピー
2 点目  RUN の結果を再利用できる

例えばマルチステージビルドを使わず、一つのイメージで作る場合、レイヤーを増やさないため バックスラッシュで長く続ける。

FROM gentoo/stage3-amd64
RUN emerge --update --deep --changed-use --with-bdeps=y @world &&\
  emerge hoge &&\
  そのたの処理 &&\

だが、 emerge @world は非常に長い。どのみち、下記のように COPY でまるごとコピーするのであれば、RUN も\で連結せつ個別に実行したほうが検証の時間が早くできる。

FROM scratch AS distcc-builder-squashed
COPY --from=distcc-builder / /                  # 成果物を全部コピー
emerge 後、消せるディレクトリがある

下記のディレクトリは消してもプログラムの動作には支障が無かった。 不要なディレクトリを消さない状態だと 3.1GB 程度のイメージが 1.8GB 程度までコンパクトになった。

ディレクトリ 用途
/var/cache/distfiles/* portage のアーカイブ
/var/db/repos/gentoo/ ebuild のレポジトリ
不要なパッケージは無いだろうか?

これは gentoo/stage3-amd64 が頑張っていただいているらしく、消せるものはあまり無かった。 このアプローチは不毛な努力かもしれない。

パッケージ一覧はこのような方法で取得できた

cd /var/db/pkg && ls -d1 */*

emerge -unmerge でパッケージを一斉に消してから、追加でパッケージをインストールしたところ、パッケージの依存関係によって、結局、再度インストールされていった。

逆に考えて distccをコンパイルした後にパッケージを消していく というアプローチでやってみる。 ただこの場合でも emerge --unmerge これ自体が実行できる最低限のコマンドは必要(でないと emerge —unmerge が止まる)

この検証は「emerge —unmerge が止まったら、そのパッケージは削除しない」といった地道な作業になるので、Dockerfile に

RUN emerge --unmerge \

という行を入れ、あえてレイヤーを分割しておくと、過去の Docker レイヤーの結果が再利用できることが分かった( ムダ知識っぽい )

以下のパッケージは残す

bash
acct-group のもの
app-portage/portage-utils
sys-apps/portage
coreutils( /bin/chgrp ),
app-misc/pax-utils( /usr/bin/scanelf )
sys-apps/sed (/bin/sed )
sys-apps/baselayout
glibc
sys-apps/findutils

更に distcc の動作に必要なパッケージ郡は必要。 このパッケージ郡は

equery depgraph distcc

で確認することができる。ビルド時依存のものや、USE 依存のも含まれているような気もするが残しておく。 大量のパッケージを削除することはできる。 「distcc が起動するコンテナ」ができた… とにかく削除はできる Gentoo すごい。

この段階のコンテナのサイズは 619MB だった。

ここまでやると最初から AlpineLinux で distcc サーバーを建てた方が楽だったのでは、という気もするが、 クロスコンパイル環境を構築する手間もかかるので、ラズパイと母艦のディストリビューションを揃える方向でやってみる。

ただ distcc で実際にコンパイルを行った際に下記のエラーが出た。

distccd[18] (dcc_job_summary) client: 10.10.254.21:56798 COMPILE_ERROR exit:110 sig:0 core:0 ret:0 time:203ms aarch64-unknown-linux-gnu-gcc version.c

このためコンパイルに必要なヘッダファイルなどもの残してみる。

cross-aarch64-unknown-linux-gnu/binutils-2.35.2 \
cross-aarch64-unknown-linux-gnu/gcc-10.3.0-r2 \
cross-aarch64-unknown-linux-gnu/linux-headers-5.10 \
sys-apps/util-linux-2.36.2-r1
sys-kernel/linux-headers-5.10

おぉぉぉ? COMPILE_OK の文字が!

distccd[13] (dcc_job_summary) client: 10.10.254.21:56858 COMPILE_OK exit:0 sig:0 core:0 ret:0 time:293ms aarch64-unknown-linux-gnu-gcc if_xcmdsrv.c

イメージのサイズは 983MB と大きくなった。 このくらいのサイズであれば、まだ dockerhub からのダウンロードもあり得るだろうか。

ただ、できるようになって分かったが、これを『維持する』のは茨の道だ。

  • パッケージの依存関係が変わると、docker build に失敗しそう。
  • glibc のバージョンアップにどう対応したものか、まだ分からない。

自動化して、何か起きたらエラー通知して原因を調べやすいようなテスト環境を作る必要がありそう。

2021/08/07 時点でのエラー

gentoo/stage3-amd64 の binutils, gcc, glibc のバージョンが古いので、このイメージをそのまま使えず、ビルドが必要。

!!! The following installed packages are masked:
- sys-libs/glibc-2.31-r6::gentoo (masked by: package.mask)
/var/db/repos/gentoo/profiles/package.mask:
# Andreas K. Hüttel <dilfridge@gentoo.org> (2017-05-21)
# (and others, updated later)
# These old versions of toolchain packages (binutils, gcc, glibc) are no
# longer officially supported and are not suitable for general use. Using
# these packages can result in build failures (and possible breakage) for
# many packages, and may leave your system vulnerable to known security
# exploits.
# If you still use one of these old toolchain packages, please upgrade (and
# switch the compiler / the binutils) ASAP. If you need them for a specific
# (isolated) use case, feel free to unmask them on your system.

- sys-devel/binutils-2.33.1-r1::gentoo (masked by: package.mask)
- sys-apps/opentmpfiles-0.2::gentoo (masked by: package.mask)
/var/db/repos/gentoo/profiles/base/package.mask:
# Andreas K. Hüttel <dilfridge@gentoo.org> (2021-07-06)
# Root privilege escalation, CVE-2017-18925, bug 751415

Profile picture

Written by tin-machine 技術関連のメモ Twitter