🎄

apt2ostreeを使ってみる

2020/12/20に公開

長い前置き。調べるのが楽しかったので許してほしい

これは Linux Advent Calendar 2020 の19日目の記事にあたる。

本番環境でやらかしちゃった人によるアドベントカレンダーで今年もまた sudo yum update で環境を壊してしまったという過ちを悔いる人を見かけた。

覆水盆に返らず。パッケージの依存関係は修復しても、パッケージのバージョンは気軽に更新できても、アプリケーションが安定して動くことまでは保証しないのがパッケージマネージャーのハマりがちな罠だ。

その問題に対して、パッケージがインストールされたファイルシステムそのものをGitみたいにバージョン管理しようという考え方が出てきた。OSTreeだ。詳細はOSTree: OSイメージとパッケージシステムの間にGitのアプローチをに詳しい。

このOSTreeを発展させる形で誕生したのがrpm-ostree である。名前の通り、RPM Package ManagerをOSTreeの仕組みを使ってバージョン管理できるようにしたツールだ。OSTreeの生みの親の一人でもある、元CoreOS社のColin Waltersさんによって、OSTreeをWrapしたRPM用のツールとして開発された。[1][2][3]

rpm-ostreeはベースとなるRPM全体のimageがあり、そこにユーザーが独自に定義したパッケージを rpm-ostree installrpm-ostree override replace などを使って"重ね着"することで環境を構築することができる。

このrpm-ostreeは現在、Atomic Host Projectの後継であるFedora CoreOSを始めとするRHEL関連のOSに導入されている。RHEL本家にも、バージョン8.3でrpm-ostree imageを作るための仕組みが入ったそうだ。

前置きが長くなったが、Debian/Ubuntu系のディストリビューションでも rpm-ostree likeにパッケージ管理ができるようにしたい!
その要望に答えたツールが apt2ostree である。

apt2ostree とは

apt2ostreeはDebian/Ubuntuベースのostreeイメージを構築するために、ビルドツール ninja用の設定ファイルを自動で作成してくれる Pythonライブラリである。

ninja の作成者であるDavid Röthlisberger氏によって作成された。以下の説明はMerkle trees and build systems による。

ざっくりまとめれば、 apt2ostree を含むエコシステムは次の通りに動く。

ステージ1: 現行のパッケージに関する情報をostreeで保存したい場合

  1. ninja設定ファイル用コマンド configure.py を実行して build.ninja ファイルを出力する
    ここで自動生成されたファイルの中に、後述するロックファイルを出力するビルドタスクやdebファイルをイメージとしてパッケージするビルドタスクが入っている。

  2. 改造したaptly (Debianのリポジトリマネジメントツール) を使って、ostreeで管理したいパッケージとその依存関係を"ロックファイル"として出力する

  3. "ロックファイル" に基づいて ninja は 管理対象のパッケージ(要はdebファイル)を一つのイメージとしてビルドする

  4. ビルドしたイメージをそのままostreeコマンドを使って保存する

ステージ2: ostreeに保存したパッケージ情報を復元したい場合

  1. ステージ1で出力したロックファイルを用意する

  2. ostreeリポジトリを参照しながら、展開コマンド multistrap.py を実行する
    2.1. ostreeリポジトリからdebファイルを取得し、unpackする
    2.2. dpkg --configure -a コマンドでunpackしたデビアンファイルを適用する

実際に使ってみた

例にあったnginx-coreパッケージを保存する例を紹介する。

:::message error
ステージ2の解決が時間切れになってしまったので、ステージ1のみ実施した。
つよつよエンジニアが興味を持ってくれることを望む。
:::

ソースコード

https://github.com/stb-tester/apt2ostree

READMEのコマンドに間違いが複数あったので修正が必要。後述で修正箇所を説明する。

事前準備及び環境説明

WSL2+Ubuntu 20.04 LTS環境下で実施した。
以下のツールを事前にインストールする必要がある。

  • ostreeコマンド
$ ostree --version
libostree:
 Version: '2020.3'
 Features:
  - libsoup
  - gpgme
  - ex-fsverity
  - libarchive
  - selinux
  - avahi
  - libmount
  - systemd
  - release
  - p2p
  • python2
$ python2 --version
Python 2.7.18
  • ninja
    ビルドツール。
$ ninja --version
1.10.0
  • golang
    Aptlyをビルドするために使用する
$ go version
go version go1.15.5 linux/amd64
  • Aptly(改造版)

Debian リポジトリのマネジメントツール。lockfileを作成できるように下記の改造版のソースコードをビルドしてバイナリ配置する必要がある。

  $ mkdir -p $GOPATH/src/github.com/aptly-dev/aptly
  $ git clone https://github.com/stb-tester/aptly $GOPATH/src/github.com/aptly-dev/aptly
  $ cd $GOPATH/src/github.com/aptly-dev/aptly
  $ git checkout lockfile # このブランチ切り替えが大事
  $ make install

上記コマンドで作成した後、 GOPATH/binをPATHに追加する。

$ cat ~/.bashrc
# 略
export PATH="$GOPATH/bin:$PATH"

:::message error
READMEに、ブランチの切り替えコマンドがなかったので死ぬほどハマった。
これを切り替えないままaptlyを作成すると、後述するPackage.lockファイル作成に失敗する。
:::

手順

# ソースコードをcloneして、nginxディレクトリまで移動する
$ git clone https://github.com/soharaki/apt2ostree.git
$ cd apt2ostree/examples/nginx

# ビルドイメージを出力するためにostreeリポジトリを作成する
$mkdir -p _build/ostree
$ ostree init --mode=bare-user --repo=_build/ostree

# build.ninjaファイルを作成する
$ ./configure.py

# Package.lockファイルはそのままだと自分の環境と差異があり、使用できないので
# 先にupdate-apt-lockfilesコマンドで更新する
$ ninja update-apt-lockfiles

# Package.lockファイルを更新後、ninjaコマンドでビルドする
# この際にrefs `deb/images/Packages.lock/configured` をツリーとするOSTreeリポジトリ内のメタデータファイルが作成される
$ ninja

:::message error
Package.lockファイルを更新しないまま、READMEの通りにninjaコマンドを実行すると
ninja: build stopped: subcommand failed. という警告が出て終了します
:::

# deb/images/Packages.lock/configured をツリーとして被せる
$ ostree commit --tree=ref=deb/images/Packages.lock/configured \
    -b mybranch -s "My message"

所感

apt2ostreeを使ってUbuntu環境におけるパッケージ管理のベースイメージを作成してみた。

この記事を書いたモチベーションとして、apt2ostreeを触ってみることで、OSTreeの勘所みたいなのが分かればと期待していた。しかし、正直今もまったくわかった気がしない。

Flatpak等、他にもOSTreeをwrapしたツールが出てきているのでそれらに触れながら、いやそれ以前にハッシュ木やgitの仕組みについて復習しながら、造詣を深めることを冬休みの宿題としたいと思う。

脚注
  1. rpm-ostreeを最初期に採用したAtomic Host Projectはコンテナを動かすためだけの専用OSを作りたいというモチベーションから始まった。結果としてできたFedora Atomic Hostはrpmもyumコマンドも使えず、OSのバージョンアップデートはOSTreeで行うという硬派OSであった。確かにコンテナを動かすための静的な環境があれば他に何もいらないけど、尖りすぎたと思う。後続のFedora CoreOSをGitHub Actionsのself-hosted runnerとして活用できないか検討したことがあったが、こっちは使いやすかったし、GitHub Actionsの標準OSイメージであるUbuntuに入っている初期パッケージにビルド時に邪魔されるイライラも消えたので非常に良かった。 ↩︎

  2. Atomic Host ProjectはRed Hatがlaunchしたプロジェクトであり、当時CoreOS社は競合関係(ソース)にもあったはずだが、なぜ関わっているのか調べきれなかった。 ↩︎

  3. Atomic Host Projectと競合関係にあったCoreOS社が出していたCoreOS Container Linuxもまた、コンテナの安定した動作環境を作ろうとしたモチベーションのもと、環境依存問題を可能な限り減らすユニークな取り組みで知られており、その一部はFedora CoreOSに引き継がれている。例えば、Ignition という仕組みがある。これはOSのプロビジョニングツールであり、ディスクのパーティション分割やsystemdへの設定を起動前に全て行うことができる。よく似たツールとしてはcloud-initがあるが、Ignitionは最初からbootstrap時のイミュータブルな環境構築を念頭に設計されているため、やれることの範囲が広い。 ↩︎

Discussion