Laminar CI  - Jenkins代替

# はじめに
CI/CDツールのうち, オンプレミスで動作して, Dockerコンテナを起動するタイプではないものは限られています. 巨大なフレームワークやSDKを扱う場合や, Windows上のSDKしか提供されていない場合, Dockerコンテナ越しでCI/CDを行うことは大変です.

## Jenkins
オンプレミスでDockerコンテナに依存しないCIツールとして, Jenkinsが有名ですが, 私が欠点と思っているのは次です.

- ジョブをコードとして管理することが難しい
  - シェルスクリプトにするか, GroovyのDSLにすればできる
  - GroovyのDSLはローカル実行できないので, 開発・保守が非常に手間
- 不安定
  - 多く要求される機能でもプラグインで対応する体制なので, 更新停止, 脆弱性・不具合の放置がよくある
  - うっかりJenkinsを更新しようものなら, プラグインが対応していなくて環境が崩壊する
  - "何もしていないのに動かなくなりました"が本当に起きる

OSSなんだから不満は自分から改善するように働きかければ？というのは, 私が自動化・CI/CDに興味がないのでモチベーションが湧かないのです.

## Laminar
[Laminar](https://laminar.ohwg.net/docs.html)([GitHub](https://github.com/ohwgiles/laminar))を試してみます.
LaminarはLinux専用のCIツールです. Laminarの役割は待ち行列に積まれたジョブを実行し, ジョブの出力とステータスの集計・保存, それらをHTMLに整形することです.  
ジョブ管理, VCSとの連携や定期実行などの機能はありません. ジョブの記述はシェルスクリプト, 定期実行は`cron`などを使います.

# インストール
Windowsで使いたいので, WSL上にインストールします.

aptやrpmのパッケージもありますが, 依存するバージョンの`libcapnp`がDebian Bookwormになかったため, ソースからビルドします.

依存パッケージとビルドツールをインストールします.

```shell
sudo apt install git capnproto cmake g++ libboost-dev libcapnp-dev libsqlite3-dev make rapidjson-dev zlib1g-dev pkg-config
```

インストール時に, systemdのサービス設定もコピーされます.
```shell
git clone https://github.com/ohwgiles/laminar.git
cd laminar
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
make -j "$(nproc)"
# Warning: the following will overwrite an existing /etc/laminar.conf
sudo make install
sudo mkdir -p /var/lib/laminar
```

## WSLのsystemd
systemdで動かしたいので, `/etc/wsl.conf`にsystemdサポートを追加しておきます.

```conf:/etc/wsl.conf
[boot]
systemd=true
```

## Laminarサービス
`/lib/systemd/system/laminar.service`の実行ユーザを変更します. ローカルで動かすことにするので, ユーザは`root`とします. 

```conf
[Service]
User=root
```

```shell
sudo systemctl start laminar
sudo systemctl enable laminar
```

# 設定
バインドアドレス等を設定する場合は, `/etc/laminar.conf`を編集します.

## ジョブ
ジョブは`/var/lib/laminar/cfg/jobs`以下に, 拡張子`.run`で作成します. 中身はシェルスクリプトです.

`echo`と`sleep`するだけの簡単なジョブ`hello.run`を作成してみます.
バイナリファイルでも起動できるようにということか, シェル経由で実行ではなくプロセス起動だと思われます. 実行権限を与えないとエラーになります.

```shell
sudo mkdir -p /var/lib/laminar/cfg/jobs
sudo touch /var/lib/laminar/cfg/jobs/hello.run
sudo chmod a+x /var/lib/laminar/cfg/jobs/hello.run
```

次のような, ジョブを作成します.
```shell:hello.run
#!/usr/bin/bash -ex
echo 'start hello'
sleep 10
echo 'finish hello'
```

CLIツール`laminarc`でキューにジョブを入れます.
```shell
laminarc queue hello
```

## Web UI
デフォルトでは, `http://localhost:8080`でWeb UIを確認できます.

トップ画面では各種統計が表示されます.
![laminar00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27065/a429405d-0841-b5eb-152c-6b8c5dd686f8.jpeg)

ジョブのコンソールログも確認できます.
![laminar01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27065/4714081d-1df3-38df-baed-cc233aa15172.jpeg)

## CLIツール
`laminarc <command>`の形式で, 操作・情報表示ができます.

| コマンド | |
| :--- | :--- |
| queue [JOB [PARAMS...]]... | ひとつ以上のジョブをキューに入れる. --nextをJOBの前に付けると, キューの先頭に入れる. |
| start [JOB [PARAMS...]]... | ひとつ以上のジョブを開始する. ジョブが開始されるまで待つ. |
| run [JOB [PARAMS...]]... | ひとつ以上のジョブを実行する. 全てのジョブが終了するまで待つ. |
| set [VARIABLE=VALUE]... | ひとつ以上の環境変数を設定する. $JOBと$RUNが同一な以降のスクリプトにエクスポートされる |
| show-jobs | ジョブ一覧を表示する ($LAMINAR_HOME/cfg/jobs/*.run).|
| show-running | 実行中のジョブを表示する |
| show-queued | キューで待機中のジョブを表示する |
| abort JOB NUMBER | 実行中のジョブを強制終了する |

# ジョブの書き方
公式ドキュメントから, 必要そうなシナリオを実現する方法です.

## 定期実行
`cron`に登録します.

```conf
0 3 * * * LAMINAR_REASON="Nightly build" laminarc queue hello
```

## ジョブチェイン
ジョブを連鎖させたい場合は, `laminarc queue`をスクリプト内に書けばよいです.

```shell
#!/usr/bin/bash -xe
if [ ... ]; then
  laminarc run example-downstream-special
else
  laminarc run example-downstream-regular
fi
```

## ジョブのパラメータ
ジョブ名の後に, `パラメータ名=値`を付けるとスクリプトに渡される.
```shell
laminarc queue example foo=bar
```

```shell
#!/usr/bin/bash
if [ "$foo" == "bar" ]; then
   ...
else
   ...
fi
```

## 共通処理
`/var/lib/laminar/cfg/scripts`がPATHに追加されるためヘルパースクリプトを置いておくと便利です.

## Windowsのバッチや実行ファイルを起動
WSLから直接起動もできますが, パスや環境変数の扱いに詰まることがあるので, SSH経由の方が安定している印象です.

TODO:SSH経由の詳細

# まとめ
今のところ安定性はJenkinsより良いと思います. すでに枯れたLinuxのツールを組み合わせるので, 将来環境が壊れるという心配も少ないです.
環境移行も設定やスクリプト群とSQLiteのファイルひとつをコピーするだけなので簡便です.