こんにちは。Xイノベーション本部ソフトウェアデザインセンターの陳です。
CI推進活動の一環として、話題のCI/CDツールDaggerを使ってみました。
この記事では、DaggerでNext.jsプロジェクトのCIを構築して、ローカルとGitHub Actionsで実行する方法について紹介します。
Daggerについて
みなさんはどのCI/CDツールを使っていますか?私が所属する部署ではGitHub Actionsを使うことが多いです。
従来のCI/CDでは、GitHub ActionsやCircle CIなどのサービスごとにCI/CDスクリプトを作成する必要があります。
移行するときには移行先のサービスに合わせてスクリプトを書き換えないので、大変なことになります。
一方、Daggerを使えばCI/CDスクリプトをポータブルにできます。
Daggerとは、特定の基盤を依存せずに、CI/CDパイプラインを素早く構築しどこでも実行できるツールです。
Daggerを利用して作成したCI/CDスクリプトは、GitHub ActionsやCircle CIなど様々なサービスで共通に使えます。
Dagger はDockerの創始者のSolomon Hykesが立ちあげた会社です。DaggerはDockerコンテナ上で動作します。 CI/CDスクリプトの設定ファイルはCUE言語で記述します。
環境構築
今回はNext.js+TypeScriptのプロジェクトを使ってDaggerでCIを構築してみました。
ここからは環境構築の手順をまとめます。
まずは公式ドキュメントに従って、Daggerをインストールします。
私はmacOSを使っています。Homebrewを使って簡単にインストールできました。
$ brew install dagger/tap/dagger
Windows、Linuxのインストール方法は公式ドキュメントに記載されていますので、ここでは省略します。 こちらのコマンドでバージョンおよびインストールされた場所を確認できます。
$ dagger version $ type dagger
続いて、プロジェクトのルートディレクトリで以下のコマンドを実行し、Daggerを初期化します。
$ dagger project init
ルートディレクトリでcue.mod
のフォルダが作成されます。
以下のコマンドを実行してDaggerのパッケージをインストールします。
$ dagger project update
pkg
フォルダーに以下の2つのパッケージが作成されました。
- dagger.io
- Daggerエンジン本体を動かすためのアクション(core actions)が実装されたパッケージ
- universe.dagger.io
- bash、yarn、go、PythonなどDaggerから提供された複合アクションが実装されたパッケージ
また、Daggerの全ての動作はDockerコンテナ上で実行されるため、Docker DesktopなどのDockerを実行できる環境のセットアップも必要です。 上記の手順でDaggerを利用できるようになりました。
ローカルでDaggerを実行してみた
dagger.cueファイルの作成
DaggerでCIを動かすためには、以下のようにCUE言語でCIスクリプトを作成する必要があります。
package main import ( "dagger.io/dagger" "dagger.io/dagger/core" "universe.dagger.io/bash" "universe.dagger.io/docker" "universe.dagger.io/netlify" ) dagger.#Plan & { client: { filesystem: { // ... } env: { // ... } } actions: { deps: docker.#Build & { // ... } test: bash.#Run & { // ... } build: { run: bash.#Run & { // ... } contents: core.#Subdir & { // ... } } deploy: netlify.#Deploy & { // ... } } }
dagger.#Plan
の中でactions
を宣言し、actions
の中でビルドやテストなどのアクションを宣言できます。
actions
以外にもclient
をdagger.#Plan
の中で宣言し、ローカルファイルの読み取りや書き込み、環境変数の参照などができます。詳細はこちらを参照してください。
アクションの実行は以下のコマンドを使います。
$ dagger do {アクション名}
今回は公式のサンプルを参考に以下のCIスクリプトを作成しました。
ルートディレクトリでdagger.cue
ファイルを作成します。
//dagger.cue package main import ( "dagger.io/dagger" "dagger.io/dagger/core" "universe.dagger.io/yarn" ) dagger.#Plan & { actions: { checkoutCode: core.#Source & { path: "." exclude: [ "node_modules", ".next", ".swc", "*.cue", "*.md", ".git", ] } build: { install: yarn.#Install & { source: checkoutCode.output } build: yarn.#Script & { source: checkoutCode.output name: "build" } test: yarn.#Script & { source: checkoutCode.output name: "test" } } } }
Daggerのuniverseのyarnパッケージを使って、ビルドとテストのアクションをbuild
に定義しました。以下のコマンドを実行すると、インストール、ビルド、テストのアクションが順番に実行されます。
$ dagger do build
以下のログが表示され、ビルドおよびテストが完了しました。
Jestのエラー
test
をactions
の中で直接に宣言しテストを動かすと、jest
の以下のエラーが発生しました。
Field Value logs """\n yarn run v1.22.17\n $ jest --maxWorkers=1\n jest-haste-map: Haste module naming collision: test\n The following files share their name; please adjust your hasteImpl:\n * <rootDir>/cue.mod/pkg/universe.dagger.io/yarn/test/data/foo/package.json\n * <rootDir>/cue.mod/pkg/universe.dagger.io/yarn/test/data/bar/package.json\n\n Done in 6.81s.\n\n """
jest.config.js
に以下の設定を追加するとこのエラーを解消できました。
//jest.config.js const customJestConfig = { modulePathIgnorePatterns: ["<rootDir>/cue.mod"], };
ローカルでアプリケーションを動かしてみた
Next.jsではnext dev
のコマンドを実行することで、開発モードでアプリケーションをローカルで起動できます。daggerを利用する場合は、actions
に以下のコードを追加します。
//dagger.cue dagger.#Plan & { actions: { checkoutCode: core.#Source & { path: "." } dev: yarn.#Script & { source: checkoutCode.output name: "dev" } } }
dagger do dev
コマンドを実行すると、localhost:3000
でアプリケーションを確認できます。
ただし、daggerの全ての動作はDockerコンテナ上で実行されるため、コードを変更したらdagger do dev
を再実行(つまりコンテナを更新)しないと反映されません。
universeパッケージのyarnとbash
Daggerでyarn
のアクションを実行する際に、universeパッケージのuniverse.dagger.io/yarn
以外にも、universe.dagger.io/bash
を利用してシェルスクリプトで実行できます。
例えば、インストール、ビルド、テストを順番に実行させたい場合は以下のactions
を宣言します。
//dagger.cue package main import ( "dagger.io/dagger" "dagger.io/dagger/core" "universe.dagger.io/bash" ) dagger.#Plan & { actions: { checkoutCode: core.#Source & { path: "." } build: { //yarnとbashがインストールされたイメージをpullする pull: docker.#Pull & { source: "node:lts" } //コンテナのfilesystemにファイルをコピーする copy: docker.#Copy & { input: pull.output contents: checkoutCode.output } //コンテナでbashスクリプトを実行する build: bash.#Run & { input: copy.output script: contents: """ yarn install --frozen-lockfile yarn run build yarn run test """ } } } }
ただし、bash
はyarn
より実行時間が長いです。ローカルでくインストール、ビルド、テストのアクションを行してみた結果、yarn
の実行時間はbash
より1分以上も早くなります。
bash 1分35秒
yarn 13秒
bash
とyarn
両方が使えるようでしたら、より実行時間の短いyarn
を利用したほうがよさそうですね。
npm
を利用したい場合は、yarn
のような直接に使える公式パッケージが実装されていないので、bash
を使います。
詳細は公式のサンプルを参照してください。
既存のDockerfileの実行もできる
以下のように、unverse
のdocker
パッケージを利用すれば、外部のDockerfileを読み込んで実行できます。
またDockerfileの内容をdocker.#Dockerfile & {}
にも記述できます。詳細はこちらをご参照ください。
//dagger.cue package main import ( "dagger.io/dagger" "dagger.io/dagger/core" "universe.dagger.io/docker" ) dagger.#Plan & { actions: { checkoutCode: core.#Source & { path: "." } build: docker.#Dockerfile & { source: checkoutCode.output } } }
GitHub ActionsでDaggerを実行してみた
ルートディレクトリで.github/workflows
フォルダを作成し、以下のようにyamlファイルにGitHub Actionsの設定を記述します。pushをトリガーにGitHub Actionsを走らせ、アプリケーションのビルドおよびテストを実行させます。
//build-test-on-push.yml on: push: name: Build and Test on Push jobs: build-and-test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Build and Test uses: dagger/dagger-for-github@v3 with: cmds: | project update do build
dagger/dagger-for-githubを利用して、dagger.cue
に定義されたアクションをGitHub Actions上で簡単に動かせます。
従来であれば、GitHub Actions上にNode.jsをインストールして、yarn install
やビルド、テストそれぞれの設定をしないといけないです。Daggerを利用する場合は、既存のCUEファイルをGitHub Actions上に実行させればOKなので、GitHub Actionsのyamlファイルもだいぶスッキリします。
使ってみた感想
CIスクリプトをローカルとGitHub Actionsで同じように実行できるのは便利ですね。ローカルで通ったテストをGitHub Actionsで実行するとなぜか通らなくなった、みたいなことも回避できそうです。また、Daggerを使うと、yamlファイルで頑張ってCIスクリプトを書かなくていいので、GitHub Actionsでの設定もだいぶ簡単になります。
今回はGitHub ActionsでDaggerを試してみましたが、他にもCircle CI、GitLab、Jenkinsなど多数のCIサービスに対応しています。サービス間の移行をより簡単に実現できるので、他のCIサービスでもDaggerを使ってみたいですね。
ただし、使いづらいところもあります。GitHub Actionsなどの設定が簡単になりますが、CUE言語やDaggerのパッケージの使い方を理解しないとDaggerの利用が難しく感じます。また現時点で、Daggerの公式サイトにあるuniverseパッケージに関するドキュメントはあまり詳しくなく、複雑なCI/CDを構築したい場合はコードを読んで試行錯誤を重ねる必要があるかもしれません。
まとめ
この記事では、DaggerでNext.jsプロジェクトのCIを動かしてみたことについてまとめました。
Daggerを使って基盤に依存しないポータブルなCIを簡単に構築できます。みなさんも是非使ってみてください。
私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。
執筆:@chen.xinying、レビュー:@yamashita.tsuyoshi (Shodoで執筆されました)