2016-10-14: systemd-nspawn で portage の binary package (binpkg) ビルドをいい感じにする

この記事は KMC アドベントカレンダー 2016 の 12 日目です。前日は spi8823 の「Unityでパーティクルをドット絵風にするShader」 でした。
… 2 日遅れです。完全に登録してた事わすれとった。すいませんでした。休暇でシドニー放浪してました。

みなさんは Portage もとい Gentoo 使ってますか? 選択肢がたくさんあり、最新のパッケージがわりとすぐ降ってきて、その上何か足りないものがあっても ebuild 書いて転がしとくだけで気楽にインストールできるの便利ですよね。

わたしはサーバーで Gentoo を使うようになってからかれこれ 4〜5 年ほど経過しています。ただ、まあ、問題となるのは emerge にかかる時間と CPU リソースですよね。VPS の上での emerge -uDNav @world は割と億劫だしつらいものになる。

今年の初めくらいからゆっくりとご家庭インフラの式年遷宮を進めるなかで、運用を見直してちゃんとうどん回せるようにしよう、と Binary package 導入をしてみたのでそのはなしです。

binary package

Portage には binary package 機能 が存在します (以後 binpkg)。Gentoo Wiki にある通り、複数のシステムがあるなかで何度もビルドするのは無駄・ハードウェアのスペックの問題でビルドが困難、といった状況をサポートするためにあります。便利ですね。これを使わない手はない。

そして実際 AWS t2.micro インスタンスで手元の base cookbook を完走させるには swap が数 GB 必要…。さらに 2 時間くらいかかる。なので、何かテストするためにインスタンス立ち上げてセットアップしてもやってられるかって感じである (AMI を作れという話はあるけれど)。つらい。

(余談だけれど、 https://github.com/sorah/gentoo-build というのがあって、systemd based Gentoo box を作るためのシェルスクリプト集と、AMI 作成用の packer script があってめちゃくちゃ便利です。)

binpkg の作り方

いくつかあります: quickpkg コマンド、emerge --buildpkg オプション、または FEATURES=buildpkg

quickpkg はシステム上にインストールされたファイルをパッケージにしてくれます。この方法だと config を含めようともクリーンでない場合があるのでオススメできないとのこと (使ったことないから分からん)。

emergeFEATURES あるいは --buildpkg で渡した場合、ビルドディレクトリからパッケージ生成をするのである程度クリーンになります。

作例

これだけ。

sudo emerge --buildpkg dev-libs/boost

すると、 /usr/portage/packages/dev-libs/boost-1.56.0-r1.tbz2 とかが生成されます。メタデータである /usr/portage/packages/Packages には、

BUILD_TIME: 1480786879
CPV: dev-libs/boost-1.56.0-r1
DEFINED_PHASES: compile configure install preinst prepare setup test
DEPEND: virtual/libiconv[abi_x86_64(-)] app-arch/bzip2[abi_x86_64(-)] sys-libs/zlib[abi_x86_64(-)] !app-admin/eselect-boost =dev-util/boost-build-1.56*
EAPI: 5
IUSE: context debug doc icu +nls mpi python static-libs +threads tools python_targets_python2_7 python_targets_python3_4 abi_x86_32 abi_x86_64 abi_x86_x32 abi_mips_n32 abi_mips_n64 abi_mips_o32 abi_ppc_32 abi_ppc_64 abi_s390_32 abi_s390_64
KEYWORDS: alpha amd64 arm ~arm64 hppa ia64 ~mips ppc ppc64 ~s390 ~sh sparc x86 ~ppc-aix ~amd64-fbsd ~x86-fbsd ~amd64-linux ~x86-linux ~ppc-macos ~x64-macos ~x86-macos ~sparc-solaris ~sparc64-solaris ~x86-solaris ~x86-winnt
LICENSE: Boost-1.0
MD5: b3720e94e07a32c7c076cec4c1c0207c
PROVIDES: x86_64: libboost_atomic.so.1.56.0 libboost_chrono.so.1.56.0 libboost_container.so.1.56.0 libboost_date_time.so.1.56.0 libboost_filesystem.so.1.56.0 libboost_graph.so.1.56.0 libboost_iostreams.so.1.56.0 libboost_locale.so.1.56.0 libboost_log.so.1.56.0 libboost_log_setup.so.1.56.0 libboost_math_c99.so.1.56.0 libboost_math_c99f.so.1.56.0 libboost_math_c99l.so.1.56.0 libboost_math_tr1.so.1.56.0 libboost_math_tr1f.so.1.56.0 libboost_math_tr1l.so.1.56.0 libboost_prg_exec_monitor.so.1.56.0 libboost_program_options.so.1.56.0 libboost_random.so.1.56.0 libboost_regex.so.1.56.0 libboost_serialization.so.1.56.0 libboost_signals.so.1.56.0 libboost_system.so.1.56.0 libboost_thread.so.1.56.0 libboost_timer.so.1.56.0 libboost_unit_test_framework.so.1.56.0 libboost_wave.so.1.56.0 libboost_wserialization.so.1.56.0
RDEPEND: virtual/libiconv[abi_x86_64(-)] app-arch/bzip2[abi_x86_64(-)] sys-libs/zlib[abi_x86_64(-)] !app-admin/eselect-boost
REQUIRES: x86_64: ld-linux-x86-64.so.2 libbz2.so.1 libc.so.6 libgcc_s.so.1 libm.so.6 libpthread.so.0 librt.so.1 libstdc++.so.6 libz.so.1
RESTRICT: test
SHA1: bebdceba8188c82399a7d655d97315c79a2a0454
SIZE: 9514605
SLOT: 0/1.56.0
USE: abi_x86_64 amd64 elibc_glibc kernel_linux nls python_targets_python2_7 python_targets_python3_4 threads userland_GNU
MTIME: 1480786898
REPO: gentoo

のように記録される。

あとは /usr/portage/packages を http(s) で見えるところに置いて make.conf の PORTAGE_BINHOST に書いて FEATURES=getbinpkg を指定すれば、emerge 時に Packages ファイルをとりにいって binpkg が利用可能であればそれを利用するようになる。

These are the packages that would be merged, in order:

Calculating dependencies... done!
[binary  N     ] dev-libs/boost-1.56.0-r1:0/1.56.0::gentoo  USE="nls threads -context -debug -doc -icu -mpi -python -static-libs -tools" ABI_X86="(64) -32 (-x32)" PYTHON_TARGETS="python2_7 python3_4" 0 KiB

ちなみにわたしは S3 に置いてます。

binpkg の落とし穴

ビルド時の依存や USE フラグが異なると利用されません。

たとえば、 icu USE flag がオンになっていない binpkg がある状態で、USE=icu emerge -av dev-libs/boost とかするとこうなる。

Calculating dependencies... done!
[ebuild  N     ] dev-libs/icu-58.1-r1:0/58.1::gentoo  USE="-debug -doc -examples -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] dev-libs/boost-1.56.0-r1:0/1.56.0::gentoo  USE="icu nls threads -context -debug -doc -mpi -python -static-libs -tools" ABI_X86="(64) -32 (-x32)" PYTHON_TARGETS="python2_7 python3_4" 0 KiB

Total: 2 packages (2 new), Size of downloads: 0 KiB

!!! The following binary packages have been ignored due to non matching USE:

    =dev-libs/boost-1.56.0-r1 -icu

NOTE: The --binpkg-respect-use=n option will prevent emerge
      from ignoring these binary packages if possible.
      Using --binpkg-respect-use=y will silence this warning.

--binpkg-respect-use=n--binpkg-changed-deps=n を指定すれば存在する binpkg を常に利用しようとするけれど、USEフラグは反映されることはない。依存はABI差で苦しんだり可能性があるってくらいかな。オススメはしない。

なお、複数の USE フラグのコンビネーションが想定される場合は FEATURES=binpkg-multi-instance を有効にすると良い。 (make.conf(5) を参照)
最新 1 binpkg だけでなく、同じバージョンに対するビルドが複数保持されるようになります。そのかわりクリーニングは自分でやらなくちゃいけない、という感じ。

binpkg を便利に運用する

binpkg はうまく運用すれば便利ですが、実際の運用はどうするのが良いでしょう? というのがこのポストの本題です。

需要

  1. emerge -uDNav @world をばしばし実行したいので binpkg 最新のがいろいろ常にあってほしい
  2. 依存が変わった・更新されたら binpkg 更新されてほしい

前者に関しては binpkg 実行するホスト用意して定期的に叩けば良い。
後者が問題で、そもそもサーバーやマシンによっては不要なパッケージハあるし、システムにインストールするのは微妙。 --buildpkgonly でも結局 build dependencies は必要になってしまうのでこれもだめ。

そこで systemd-nspawn を使う

systemd-nspawn とは

systemd-nspawn は systemd が提供している、Linux の namespace を活用したコンテナを実行するためのツールです。シンプルでたいへん便利。init プロセスを実行させ、さらに個別の IP を持たせて実際の仮想マシンの用に使えるコンテナを作る事もできれば、単純に chroot では実現できないので namespace を使ってコマンドを実行したい、といういろんな需要に対応できます。

ArchWiki の systemd-nspawn 記事も参考にすると分かりやすいと思う。

これをいい感じに使うと binpkg 運用に便利ができそうです。自力で chroot するより楽なのもあるし。

やってみる (emerge -uDNv world)

./base に stage3 を展開した物、 ./etc-portage/etc/portage 相当の物がある事が前提にしています (/etc/portage と読み替えても良いが、コピーを推奨。理由は後述)

まずは emerge -uDNav @world の場合。

sudo systemd-nspawn \
  --register=no \
  --bind=/usr/portage:/usr/portage \
  --bind-ro=$(pwd)/etc-portage:/etc/portage \
  --directory=./base \
  emerge --buildpkg -uDNv @world

--bind, --bind-ro は Docker でいう -v で、ホストシステムのディレクトリをコンテナ内でマウントできる。
/usr/portage はバイナリパッケージが置かれるのと、そもそも repo もホストシステムのを使えば良いので書き込み可能で --bind している。 /etc/portage は書き込み不要なので --bind-ro 、という具合。

これで ./base の上にある Gentoo システムを更新しつつ binpkg のビルドもされます。まずはこれを定期実行する事で上記需要の (1) は達成できます。既存のシステムと隔離されていることから、cron (あるいは systemd.timer) で実行する事もできるはず。

やってみる (個別にインストール)

./etc-portage/make.conf に適切な PORTAGE_BINHOSTFEATURES=getbinpkg などを設定した上で、単純に下記のように実行すれば OK。

sudo systemd-nspawn \
  --register=no \
  --bind=/usr/portage:/usr/portage \
  --bind-ro=$(pwd)/etc-portage:/etc/portage \
  --directory=./base \
  --ephemeral \
  emerge --buildpkg -v dev-libs/boost

--directory に実際にファイルの変更を反映させない状態でコンテナを起動できる --ephemeral を指定するのが良いです。これでまずビルド用の rootfs は綺麗に保たれます (なお ./base の親ディレクトリは btrfs である必要があります)。

また、ビルド環境の make.confFEATURES=getbinpkg を指定しておく事で、既にある binpkg が利用可能であればそれが利用されるし、なければ更新が走るという挙動になります (依存含めて)。これで (2) もおおむね達成できる。

ビルド環境の /etc/portage の管理

正直ここはまだ固まっていません。少なくとも、

  • USE フラグ (package.use) は依存含めて揃えておく必要がある
  • package.mask, package.unmask, package.accept_keywords も揃えておいた方が良い、あるいはインストール時に個別に指定しておく

というのが必要です。

つまり、想定しうる全部の package.use 等に書かれる指定をビルド環境の /etc/portage には含めておく必要がある。
全システムで一緒ならそれを使い回せばいいですが、実際場合によって USE フラグが異なる事もしばしばあるでしょう。たとえば net-analyzer/zabbix では agent, server, proxy といった具合に、役割毎に必要となるフラグが違います。

わたしは /etc/portage 以下の変更を itamae と sorah/itamae-plugin-resource-portage で書き換えています。itamae レシピを解析をするのはややしんどそうなので、今のところは全サーバーから package.use などを集めてきて手で整理してます。

(
  for x in rin udzuki mio; do
    ssh -T "${x}" 'sh -c "cat /etc/portage/package.use/*"'
  done
) | sort --key 1 | uniq > /tmp/package.use
vim /tmp/package.use
cp /tmp/package.use ./etc-portage/package.use/hoge

なお先に例に上げた net-analyzer/zabbix を USE=server としているサーバーはまだ式年遷宮されてない…。やるとしたら --ephemeral で実行しつつ emerge 実行前に設定を変更するしかない。

こういうケースを含め、自動実行を踏まえて通知がいい感じに欲しいとか、いろいろ欲求がでてきているので、なんか便利なラッパーツールでも書くかぁ、という気持ちになっています。

まとめ

わたしの運用は現状こんな感じになってます。みなさんも Gentoo でたのしくインフラ運用しましょう。

ツールたぶんそのうち作ります (いや、リポジトリはもうあるんだけどまだいい感じになってません)。

UPDATE (Feb 27, 2017): つくった sorah/binpkgbot

なお、このアドベントカレンダーの翌日担当は walkure の「ゼネコン辞めた話」 だそうです。

Published at 2016-10-14 23:59:59 +0900