2022年9月13日火曜日

Dagger 使ってみた

Dagger とは?
Dagger は CI/CD パイプライン用のポータブルな開発キットです。
Dagger は他の一般的な CI/CD が YAML でパイプラインを記述するのと異なり、CUE で記述します。CUE については後述しますが、YAML よりプログラミング言語に近くて柔軟性が高いって感じですかね。

CUE によって YAML ではやりづらかったパイプライン再利用が容易になっています。
また、Dagger はローカルでも CI でも、どこでもパイプラインが実行できるのが特徴です。

Dagger はすべてコンテナ上で実行されます。コンテナが動く環境上であればどこでも「同じ」パイプラインを実行できるのが Dagger であり、CI/CD ツールではなく、パイプラインを共通化するレイヤーとして機能するツールです。

CUE
CUE は Configure, Unify, Execute の頭文字のようです。
CUE は JSON のスーパーセットで、CUE から JSON/YAML にエクスポートも可能です。

CUE については Dagger のページにも説明が詳しくあるのでそちらを参照してください。
https://docs.dagger.io/1215/what-is-cue/

CUE は YAML のマージ機能も備えているので↓みたいな使い方もできるみたいです。これはこれで便利そうですね。
CircleCI の設定ファイルを分割して CUE で合成してみたら割と簡単で便利そう - Mitsuyuki.Shiiba

今回は Dagger の話なので CUE そのものもそのうち触ってみたいです。

使ってみる
ものは試しで、使ってみました。
Dagger のサンプルとして todoapp があるのでそれでまずは動作を試してみるのがオススメです。このへんはありきたりな内容になってしまうので、省略します。
iutest に適用
iutest は筆者が書いてる C++ テスティングフレームワークです。
(まぁ最近はテスティングフレームワークの機能開発はほとんどしてないんですが・・いろんな CI サービスを使っている・使っていたのでそういう意味では参考になることもあるかもしれないです。)

今回 alpine イメージ上でのテストを今までしてなかったので、それを Dagger でやるようにしました。

ベースとなる cue ファイルはサンプルの todoapp を使いました。
1から書くよりもサンプルなどをベースに書いていくと楽だと思います。

なんですが、todoapp が参考にしたときと大分違う設定になっていました。
ちょうどそのころの Dagger 使った系の記事があったのでそちらを参考にしてください。
ベンダーロックインしないCI/CDパイプライン、Daggerを使ってCodeCheckを自動化してみた。

どういうふうに書いたかは百聞は一見に如かずということで、レポジトリを見てください。
この記事向けにコメントも追加してます。



とはいえ、見てくださいだけではこの記事の意味がないのでこの設定に至るまでの紆余曲折を記述しておきます。
変数展開は () でする
同じ設定はまとめたいので変数にしてたのですが、使うときに単純に変数を書くだけじゃだめでハマりました。
       // 必要な環境変数をセット
        env: {
            // string は () で囲んで展開する
            IUTEST_ROOT_DIR: (iutest_root_dir)
            // 文字列の中で変数展開する場合は ( をエスケープする
            IUTEST_OUTPUT_DIR: "\(test_result_dir)"
        }
exit: 0 は今の所意味がない
CTest が失敗しても後続の処理を継続したかったので終了コードを 0 に上書きしたいなーと思ったのですが、今はできないっぽい。
なので、しかたがないので script: contents の処理で失敗を無視してます。
always は condition の always ではない
↑と同じ理由で常に実行される処理を書きたかったので、always を使ってみたのですが、どうやらこれは実行条件の always とはちょっと違って、cache 利用するかしないかの always だったようです。
always を true にするとキャッシュを無視して常に実行されるようになりますが、依存処理が失敗していても実行するようなことはできませんでした。

https://docs.dagger.io/1231/always-execute/

常に実行したいってのは cache invalidate のことではないんだよなぁ・・

GitHub Actions で動かすには
dagger の cue ファイルを置いておいても、そのままでは CI サービスで実行されないので、各サービスでワークフローを書く必要があります。
GitHub Actions の場合は、dagger 用の action があるのでそれを使えばすぐに動かせます。
  dagger:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: dagger/dagger-for-github@v3
        with:
          cmds: |
            project init
            project update
            do ctest
    
CUE/Dagger を新たに覚えるのはつらい
ここから使ってみた感想なのですが、CUE が使いやすいかというとまだわからないのですが、少なくとも覚えることは多い印象。
YAML でのパイプライン記述における問題の1つである再利用性を CUE は解決していますが、最初に紹介した CUE を YAML のマージツールとして利用することで十分なような気もしますし、各 CI ツールで再利用性できる機能があったりしますし、Dagger である必要性があまり感じられませんでした。

より理解をするには CUE と Dagger の境界を認識して、それぞれで調べる力、検索力が必要になるかなと思いました。
ポータビリティについて
環境のポータビリティ
Dagger は「環境」のポータビリティを実現しています。
CI でもローカルでも同じ「環境」で処理を実行できます。

ローカルと CI で同じ「処理」をすることは Makefile でもなんでもいいですが、可能です。
なので CI と同じ「処理」をすることは Dagger を使わなくても可能です。
ただ、それだと実行環境は CI やローカルで異なるため、実行結果は変わってきます。
Dagger は実行環境もセットになっているので、同じ「環境」で同じ「処理」ができ、CI とローカルで同じ実行結果が期待できます。

これは大きなメリットです。仕事や個人開発でも CI でしか起きない問題の調査はめんどうです。ローカルで同じことができればこれも楽になります。
また、問題調査としてではなく開発者がローカルで動作確認をしたい場合に、ローカルで手軽に実行できるのも利点ですね。
私はゲーム開発に携わっているので、ターゲットプラットフォームでのビルドをローカルでしたいという需要はすごくわかります。(Dagger でゲーム開発のターゲットプラットフォームビルドができるかというと難しい気がしますが・・)

ただ、Dagger の実行環境はコンテナに制限されるため、コンテナでできないことはできません。
また、iutest はテスティングフレームワークなので、1つの同じ環境でテストするよりも、多様な環境でテストできたほうが嬉しいです。(※Dagger で複数環境用意するのもありですが、管理下の想定された環境よりも、管理外の環境のほうが意味があると思っている)
今回は alpine コンテナでのテストが今までなかったので Dagger を採用して GitHub Actions で動かしています(Dagger も環境の1つなので何かしらのテストを回しておきたいという意思)。
ワークフローのポータビリティ
ワークフロー?パイプライン?
言葉の定義があいまいなんですが、ここでは PR トリガーから結果の通知など CI の最初から最後までという意味合いでワークフローを使ってます。

「A PORTABLE DEVKIT FOR CI/CD PIPELINES」
Dagger のトップページにはこう書かれています。
パイプラインという言葉を使ってるので Dagger がカバーしている範囲はパイプラインと呼びます。

図にするとこんな感じかな。

Jenkins でジョブに直接処理を書かずに .sh ファイルとかにしようね。とか。ビルドツール使おうねとか。そららはパイプラインの話。
それのもう1つ上のレイヤーで環境とセットでまとめてくれるのが Dagger 。
さらに上のレイヤーであるワークフローはそれぞれ対応する必要がある。

Dagger はベンダー依存しない CI ツールという立ち位置ですが GitHub Actions での実行例を見ていただければ分かる通り、特定の CI サービス上で Dagger を実行するには Dagger だけでは完結しません。というか、GitHub Actions でいうとトリガー部分(on: pull_request)は Dagger では設定できませんし、動かす CI サービス固有の機能を使いたい場合は Dagger 外でやる必要があります。(Dagger はそういう CI の差を埋めてくれるものではないので)
そして CI サービスを引っ越しする場合には、トリガー周りなどそういった設定は書き直しになります。

CI/CD ワークフローのポータビリティという観点では、Dagger は直接的に貢献するわけではないです。(パイプライン部分だけでもポータブルになるので楽にはなる)
ただ、どのサービスを使うのか決まっているのであれば、そのサービスの書き方をしたほうが良いとも思えます。
逆に社内で使ってる CI サービスが多種多様であるとか、OSS だから手軽に乗り換えできると嬉しいとかであれば、Dagger を使う利点はあると思います。
ローカル実行について
実はローカル実行できる CI サービスもあります。
なので目的や事情によってはそのサービスのローカル実行機能を利用したほうがいい場合もあるかもしれません。
例えば CircleCI では cli からローカル実行できます。(制約はあります
GitHub Actions の場合、nektos/act というツールを使えばエミュレートできます。(act は大分できないことや、Hosted runner との差異がありますが・・)

これらで十分なのであれば Dagger に置き換える必要はないと思います。
例えば、act は GitHub Actions のイベントをエミュレートしてくれるのでワークフローのローカル実行が可能ですが、Dagger は step レベルのローカル実行しかできないです。
このへんは使い分けでしょうね。

まとめ
個人的には最近使ってみた Earthly の方が Dagger より使いやすいなと思いました。
Dagger がちょっと話題になりましたが、類似のツールと比較したり、今置かれている状況を踏まえて選択するのが良いと思いました。
以上、久しぶりに長めに書いた。ではでは。


0 件のコメント:

コメントを投稿