Laminar CI の紹介と Guix System への導入

Laminar という軽量 CI を使って ci.tojo.tokyo という CI サーバーを構築したので、Laminar と Guix System への導入方法について紹介する。

動機

自前の git サーバーを構築したが、しばらく運用をしていると CI (Continuous Integration) 環境が欲しくなった。趣味では基本的に一人で開発をしているので他の開発者の変更と統合したテストをしたいなどという気持ちは全くないが、git リポジトリに push したら勝手にテストをしてくれたり、デプロイをしてくれるような良い感じの環境が欲しくなったのだ。

それとは別に、現在 ACL2 で定理証明をするのにもはまっていて、証明した結果を共有するときにコードだけではなくて ACL2 の出力結果を気軽に共有できる環境が欲しいと思っていたのも大きな理由の一つである。

きっかけ

CI が欲しいなーと思いながら、Guix のマニュアルの Continuous Integration (GNU Guix Reference Manual) という節を読んでいたところ、Cuirass と Laminar というソフトウェアが載っていたのに気づいた。Cuirass は GNU Guix に対して継続的インテグレーションをするやつなので私が求めているものとは違うものだが、laminar はちょうど欲しいと思っていた軽量の CI を実現するものだった。

Laminar とは

Laminar はジョブの自動化やトラッキング機能を提供し、良い感じの Web インターフェースでジョブの実行結果等を表示してくれるツールだ。

これだけであれば一般的な CI ツールと同じだが、Laminar の使用方法は他の CI ツールとは全く異なる。

Laminar には:

  • Web UI の管理画面による設定がない
  • YAML 等による CI の設定用ファイルがない
  • 無駄な車輪の再発明がない

Web UI による設定画面が存在するとそれだけで設定のバージョン管理が難しくなるのでない方がよい。また管理画面が存在すると、公開時のセキュリティ上のリスクについて悩まないといけなくなってしまう。

CI を設定するために YAML 等で表現された謎の CI 設定用言語を学べとかいわれるとなんでそのツールを使うためだけに表現力の貧弱な設定用言語を使わなくてはならんのだと感じる(GNU Guix を見倣って欲しいものである)ので YAML の設定ファイルはない方がよい。

そして、一般的に CI について勉強していると一番よく思うのが「なんでそんな簡単なことをそんな複雑にしてるんだ……」という問題で、既存のツールを使えば簡単にできることを中途半端に再発明することで無駄な学習を強要し、さらに無駄に表現力が狭められていることで融通の効かない不便な仕組みの中に閉じ込められてしまう。

Laminar にはそういった無駄な車輪の再発明が一切なく、既存のツールをフルに生かすことができる Hackable な CI ツールなのだ。

では、Laminar ではどうやって他の CI に必要な機能を実現しているのだろうか。この章では Laminar のアプローチについて紹介する。

Laminar でのジョブの設定方法

では Laminar ではどうやって CI にジョブを設定するかというと、基本的には LAMINAR_HOME ディレクトリ(デフォルトは /var/lib/laminar) の cfg ディレクトリに、 <ジョブの名前>.run というファイル名の実行可能なスクリプトを置くだけである。 そしてコマンド laminarc queue <ジョブの名前> を実行するとジョブがキューに入り laminar ユーザーで実行され、Web UI に結果が良い感じに表示される。

git への push を hooks するにはどうする?

git サーバーと同居する場合は Git Hooks を使う。 私は git サーバーと同居させる選択をしたのでこれで終わりだが、そうでない場合についても対応可能なようなのでその場合は公式ドキュメントを参照すると良いだろう。

ジョブの定期実行はどうする?

cron を使う。GNU Guix System を利用している場合は GNU mcron を使うのがいいだろう(参照: Scheduled Job Execution (GNU Guix Reference Manual))。

Docker でジョブを実行するにはどうする?

Laminar は別に Docker をサポートしていない。しかし、ジョブのスクリプト中に docker run をすればよいとある。 たしかにこれで駄目な理由は特に考えられないので、CI 側で Docker をサポートするのは無駄である。

また、Guix ユーザーであれば Docker よりも guix environment を使いたいと思うだろう(例: deploy-www-tojo-tokyo ジョブ)。にもかかわらず公式で Docker をサポートされたり、Docker を使用することを過剰に推奨されるとうっとうしいだけなので、Laminar が無駄に Docker サポートをしないという方針は良いものである。

他のジョブから同期的に別のジョブを呼ぶにはどうするの?

ジョブのスクリプト中に laminarc run <別のジョブ> と書けばよい。それだけで別のジョブを呼んでくれるし、Web UI からは元のジョブの実行結果から別のジョブの実行結果にジャンプできるし、逆に別のジョブの実行結果から呼び出し元のジョブの実行結果にジャンプすることもできる。

パラメータ付きのジョブを作るにはどうしたらいい?

環境変数を使う。lamiarc queue <ジョブの名前> key=value とすればパラメータを付けてジョブを呼びだせる。

ci.tojo.tokyo の構築

ci.tojo.tokyo での構築の方法について紹介する。

Laminar サーバーの構築

Laminar は Guix System の Service として登録されているので、単に Service の設定をシステムの設定に追加するだけである(guix-config のコミット, cgit サーバー側の設定の変更も混じっているがこれは気にしないでいただきたい……)。

ジョブの設定

CI の設定は ci.git リポジトリに記載している通りである。ただ、Guix System で利用する際に一点注意が必要な点があったのでそこだけ説明する。

Guix System の環境変数の設定

ジョブの設定だが Guix Environment を使うユーザーには注意が必要な点がある。 ジョブは laminar ユーザーで実行されるのだが、PATH 環境変数がリセットされた環境でジョブが実行される。 guix では shebang に /usr/bin/sh/usr/bin/env しか使えないが、/usr/bin/env を使っても PATH 環境変数がリセットされているためなんのジョブも呼び出すこともできない。 Laminar には必要な環境変数を静的に設定する方法($LAMINAR_HOME/cfg/env に記載)が提供されているが、Guix System のユーザーは下記のようにして環境変数を設定したいと思うだろう。

GUIX_PROFILE="/var/lib/laminar/.config/guix/current"
. "$GUIX_PROFILE/etc/profile"

Laminar では、$LAMINAR_HOME/cfg/before に全てのジョブの前に実行される前処理を記述することができるので私は下記のようにスクリプトを配置することで解決した。

$LAMINAR_HOME/cfg/before

#!/bin/sh
set -eu

export PATH="${PATH}:/run/current-system/profile/bin:/run/current-system/profile/sbin"

GUIX_PROFILE="/var/lib/laminar/.config/guix/current"
. "$GUIX_PROFILE/etc/profile"

env | while read kv; do
  laminarc set "$kv"
done

おわりに

Laminar の概要と、Guix System に Laminar の構築をしてジョブの設定する方法について紹介した。必要な機能が十分揃っている上に、既存のツールを利用できるように設計されているため CI ツールそのものの学習コストは最小である。Guix System の設定に laminar を追加するだけでを導入できるし、CI の実行時に guix environment を使うこともできるから Guix System との相性も抜群である。

他の CI システムと比較してシンプルでかつ利用が簡単なため、既存の CI ツールを使うのが難しいと感じていた方は一度試してみてはいかがだろうか。