dagger で可搬性の高い CI/CD 環境を構築する

2023年3月28日掲載

キービジュアル

dagger とは Go や Javascript, Python 等のプログラミング言語で CI/CD パイプラインをコードとして開発する事ができるエンジンです。SDK が用意されており、少ない記述でどこでも実行可能な CI/CD を構築する事ができます。可搬性が高い為、例えば GitHub Actions のデバッグをローカルで実施したり、GitHub Actions から Azure Pipeline へ移行するといったような事を簡単に行えるようになります。本記事ではこの dagger についてご紹介します。

目次

  • dagger に入門することができます
  • GitHub Actions に dagger を組み込むことができます

環境

本記事では以下のような環境を利用し、dagger を用いて node.js プロジェクトのテスト、ビルド、コンテナ化と GitHub Container Registry への登録までを行います。

  • go 1.19
  • dagger 0.4.6
  • GitHub Actions

 

開発

Go プロジェクトの用意

まずは dagger を実行する golang のプロジェクトを用意しましょう。

# Shell
go mod init dagger-study
touch main.go

 

次に main.go へ以下ソースコードを貼り付けます。コードの解説は次項へ記載します。なお 11 行目の `registryPath` はご自身が利用する GitHub Container Registry のパスへ置き換えを行います。

// main.go
package main

import (
	"context"
	"fmt"
	"os"

	"dagger.io/dagger"
)

var (
	registryPath string = "*** container registry path ***"
	hostDir      string = "./app"
	workDir      string = "/app"
)

func main() {

	ctx := context.Background()
	client, err := dagger.Connect(context.Background(), dagger.WithLogOutput(os.Stdout))

	if err != nil {
		fmt.Println(err)
	}

	defer client.Close()
	source := client.Host().Directory(hostDir)

	fmt.Println("===== Testing with Dagger =====")
	testContainer := client.Container().From("node:16")
	testContainer = testContainer.WithMountedDirectory(hostDir, source).WithWorkdir(workDir)
	testContainer = testContainer.WithExec([]string{"npm", "install"}).WithExec([]string{"npm", "test"})
	errorMessage, err := testContainer.Stderr(ctx)

	if err != nil {
		fmt.Println("> Test failed")
		fmt.Println(errorMessage)
	}

	fmt.Println("===== Building with Dagger =====")
	buildContainer := client.Container().Build(source, dagger.ContainerBuildOpts{})
	published, err := buildContainer.Publish(ctx, registryPath, dagger.ContainerPublishOpts{
		PlatformVariants: []*dagger.Container{buildContainer},
	})

	if err != nil {
		fmt.Println("> Build failed")
		fmt.Println(err)
	}

	fmt.Printf("Published : docker pull %s\n", published)
}

 

コードの解説です。dagger.Connect により dagger Engine へ接続します。

// main.go
	ctx := context.Background()
	client, err := dagger.Connect(context.Background(), dagger.WithLogOutput(os.Stdout))

	if err != nil {
		fmt.Println(err)
	}

	defer client.Close()

 

次のコードでは今回コンテナ化する Node.js アプリケーションのパスを指定して、取得しています。

// main.go
	source := client.Host().Directory(hostDir)

 

以下のコードでは今回用意した Node.js アプリケーションのテストを行なっています。Dockerfile でも代替可能ですが、今回は dagger で行なっています。今回は簡単なテストのため、 dagger で記述するメリットはあまりありませんが、もっと複雑なテスト等を行う際には dagger で行うメリットを感じる事ができると思います。

なお、最後の行で `testContainer.Stderr(ctx)` にてエラーメッセージの取得を行なっています。 テストコマンドの実行は `WithExec` メソッドにて行われていますがこの処理上でエラーが発生しても後続の処理はそのまま進んでしまいます。その為、明示的にエラーの取得を行い、 err が nil でなければ処理を止める為に記述しています。

// main.go
	testContainer := client.Container().From("node:16")
	testContainer = testContainer.WithMountedDirectory(hostDir, source).WithWorkdir(workDir)
	testContainer = testContainer.WithExec([]string{"npm", "install"}).WithExec([]string{"npm", "test"})
	errorMessage, err := testContainer.Stderr(ctx)

 

最後に以下の処理ではテストの終わったアプリケーションのコンテナビルドを行なっています。ここで勘の良い方はお気付きかもしれませんが、Github Container Registry に対する認証関連のパラメーターや処理は記述されていません。認証の必要なレジストリへコンテナを push 、または pull する際の処理方法を次に記載致します。

// main.go
	buildContainer := client.Container().Build(source, dagger.ContainerBuildOpts{})
	published, err := buildContainer.Publish(ctx, registryPath, dagger.ContainerPublishOpts{
		PlatformVariants: []*dagger.Container{buildContainer},
	})

 

GitHub Actions を例にご紹介します。以下 YAML ファイルの通り、事前に docker login を dagger の外で実施しています。 dagger にはレジストリへの認証関連の処理は組み込まれていない為、GitHub Actions でもそれ以外の CI ツール上でも事前に docker login を実行しておく必要があります。

※ 以下ファイルは GitHub 上で GitHub Actions の設定ファイルに対して投入してください。

# .github/workflows/build.yml
name: build by dagger

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io --username ${{ github.actor }} --password-stdin
      - uses: actions/setup-go@v3
        with:
          go-version: 1.19
      - run: go run ./main.go

 

これで golang における dagger を使った一連のファイルを作成できました。コマンドラインに戻り、 以下を実行してプロジェクトを完成させましょう。

# Shell
go mod tidy

 

次に今回コンテナ化するサンプルアプリケーションを用意します。

 

node.js プロジェクトの用意

本検証で利用する node.js プロジェクトを用意します。dagger の検証とは直接関係はないので、読み飛ばして頂いても問題ありません。

# Shell
cat << EOF > .eslintrc.yml
env:
  browser: true
  commonjs: true
  es2021: true
extends: eslint:recommended
overrides: []
parserOptions:
  ecmaVersion: latest
rules: {}
EOF

cat << EOF > Dockerfile
FROM node:alpine
COPY . /app
WORKDIR /app
RUN npm install
CMD ["npm", "start"]
EOF

cat << EOF > package.json
{
  "name": "app",
  "scripts": {
    "test": "eslint ./server.js",
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "eslint": "^8.36.0"
  }
}
EOF

cat << EOF > server.js
const express = require("express");
const app = express();
app.get("/", (req, res) => {
  res.send("Hello World!");
});
app.listen(3000, () => {
  console.log("Example app listening on port 3000");
});
EOF

 

これにてアプリケーション側も用意ができました。

 

GitHub Actions での動作確認

完成したプロジェクト全体を GitHub へ格納しましょう。GitHub Actions の YAML ファイル上に "main" ブランチへ "push" があれば、と自動的に動作するように指定している為、"push" が完了すると今回作成した dagger が実行され、GitHub Container Registry へビルドされたコンテナが格納されます。

# Shell
git add .
git commit -m "initial commit"
git push

まとめ

これにて本サンプルは完了となります。 通常であれば GitHub Actions 上のフローは都度 GitHub Actions を実行しなければテストを行う事ができませんが、dagger を用いれば事前にローカルで大部分をテストしておくことが可能です。また、GitHub Actions から他の CI ツールへ移行する際にもほとんど書き直す必要はなく、それでいて golang 上で複雑なテストやビルドを表現する事が可能です。是非、活用ください。

また、本記事は dagger において主に CI 部分を自動化しておりますが、当社ではその後の CD の自動化・高度化として CNAP という Kubernetes のマネージドサービスを公開しております。もし宜しければ以下のリンク記事もご確認ください。

 

※ Dagger and the Dagger logo are trademarks or registered trademarks of Blocklayer, Inc. in the United States and/or other countries. Blocklayer, Inc. and other parties may also have trademark rights in other terms used herein.

関連サービス

クラウドネイティブ・アプリケーションプラットフォーム(CNAP)

クラウドネイティブ・アプリケーションプラットフォーム(CNAP)とは標準化されたアプリケーション実行環境を手軽に利用できるサービスです。リリースプロセスを自動化し、開発者自身がセルフサービスで実行環境を準備できるようになります。

MSPサービス

MSP(Managed Service Provider)サービスは、お客さまのパブリッククラウドの導入から運用までをトータルでご提供するマネージドサービスです。

おすすめの記事

条件に該当するページがございません