LoginSignup
8
6

More than 1 year has passed since last update.

Daggerを使用してCIパイプラインを作成する

Last updated at Posted at 2022-06-13

最近話題になっている?Daggerを使用して
typescriptをトランスパイルしS3へdeployするパイプラインを作成してみました。

Daggerとは

Daggerは、CICD向けのポータブルなdevkitです。

DaggerはDocker互換のランタイムでパイプラインが実行されるため、Docker互換のランタイムが動く環境であれば
Daggerをそのまま実行し、同一の結果を得ることができます。

Daggerではパイプラインの記述を一般的な yaml などではなくcuelangを使用して記述します。
module/packageや型定義などの強力な構文を使用することができます。

チュートリアルなども用意されています。

メリット

上記のことからDaggerを使用することで

  • 開発環境とCI環境の統一
  • CIロックインをなくすことができる

といったメリットを受けることができます。

Daggerのinstall

brew tapでinstall可能です

install.sh
brew install dagger/tap/dagger

Daggerのsampleを見る

sampleに関しては、リポジトリのpkg内にあるtestを見る形になります。

(以前はexampleがあったのですが、現在のバージョンのものはリポジトリ内になさそう?過去のversionにはあります)

下記はbashのtest.cueです。

bash.test.cue
package bash

import (
	"dagger.io/dagger"
	"dagger.io/dagger/core"

	"universe.dagger.io/docker"
	"universe.dagger.io/bash"
)

dagger.#Plan & {
	actions: test: {

		_pull: docker.#Pull & {
			source: "index.docker.io/debian"
		}
		_image: _pull.output

		// Run a script from source directory + filename
		runFile: {

			dir:   _load.output
			_load: core.#Source & {
				path: "./data"
				include: ["*.sh"]
			}

			run: bash.#Run & {
				input: _image
				export: files: "/out.txt": _
				script: {
					directory: dir
					filename:  "hello.sh"
				}
			}
			output: run.export.files."/out.txt" & "Hello, world\n"
		}

		// Run a script from string
		runString: {
			run: bash.#Run & {
				input: _image
				export: files: "/output.txt": _
				script: contents: "echo 'Hello, inlined world!' > /output.txt"
			}
			output: run.export.files."/output.txt" & "Hello, inlined world!\n"
		}

	}
}

cueではgolangと同様に package 名を指定します。

package bash

その後使用するmoduleをimportします。

import (
	"dagger.io/dagger"
	"dagger.io/dagger/core"

	"universe.dagger.io/docker"
	"universe.dagger.io/bash"
)

このあたりはgolangと同様です。
moduleはv0.2.17現在では下記で用意されているものが一覧のようです。

パイプラインの内容は dagger.#Plan & {} に記述していきます。

dagger.#Plan & {
	actions: test: {

		_pull: docker.#Pull & {
			source: "index.docker.io/debian"
		}
		_image: _pull.output

		// Run a script from source directory + filename
		runFile: {

			dir:   _load.output
			_load: core.#Source & {
				path: "./data"
				include: ["*.sh"]
			}

			run: bash.#Run & {
				input: _image
				export: files: "/out.txt": _
				script: {
					directory: dir
					filename:  "hello.sh"
				}
			}
			output: run.export.files."/out.txt" & "Hello, world\n"
		}

		// Run a script from string
		runString: {
			run: bash.#Run & {
				input: _image
				export: files: "/output.txt": _
				script: contents: "echo 'Hello, inlined world!' > /output.txt"
			}
			output: run.export.files."/output.txt" & "Hello, inlined world!\n"
		}

	}
}

このパイプラインは
debianのdocker imageを使用して

		_pull: docker.#Pull & {
			source: "index.docker.io/debian"
		}
		_image: _pull.output

hello.sh を実行し結果を出力するものと

		runFile: {

			dir:   _load.output
			_load: core.#Source & {
				path: "./data"
				include: ["*.sh"]
			}

			run: bash.#Run & {
				input: _image
				export: files: "/out.txt": _
				script: {
					directory: dir
					filename:  "hello.sh"
				}
			}
			output: run.export.files."/out.txt" & "Hello, world\n"
		}

fileではなく script: contents: で shellを実行するものが定義されています。

		runString: {
			run: bash.#Run & {
				input: _image
				export: files: "/output.txt": _
				script: contents: "echo 'Hello, inlined world!' > /output.txt"
			}
			output: run.export.files."/output.txt" & "Hello, inlined world!\n"
		}

実行方法

  • 準備
dagger project update
  • 実行
exec.sh
dagger do タスク名 optionあれば(--log-format=plainや--no-cacheなど) 

使用可能なoptionについては dagger do -hで確認してください。

詳細な構文説明などはドキュメントを参照してください

今回作成したパイプライン

typescriptアプリケーションをcircle ci上のDaggerでトランスパイルし、daggerでのaws cliを通してs3へアップロードするといったものになります。

  • 作成したdeployパイプライン
deploy.cue
package main

import (
  "dagger.io/dagger"
  "dagger.io/dagger/core"
  "universe.dagger.io/docker"
  "universe.dagger.io/bash"
  "universe.dagger.io/aws"
  "universe.dagger.io/aws/cli"  
)

dagger.#Plan & {
  client: {
    env: {
      AWS_ACCESS_KEY_ID: dagger.#Secret
      AWS_SECRET_ACCESS_KEY: dagger.#Secret
    }
    filesystem: {
      "./": read: {
        contents: dagger.#FS,
        exclude: [
		  "README.md",
          ".gitignore",
		  "deploy.cue",
		  "node_modules",
          "dist"
		]
	  }
      "output.txt": write: contents: actions.deploy.export.files["/output.txt"]
      "./dist": write: contents: actions.build.contents.output
    }
  }
  actions: {
    params: {
      npmCommand?: string
      bucket?: string
    }

    build: {
      buildDeps: docker.#Build & {
        steps: [
          docker.#Pull & { source: "node:16.15-buster" },
          docker.#Copy & {
            contents: client.filesystem."./".read.contents
            dest:     "/app"
          },
          bash.#Run & {
            workdir: "/app"
            script: contents: "npm ci"
          },
          docker.#Run & {
            workdir: "/app"
            command: {
              name: "/bin/sh"
              args: ["-c", """
              npm run \(params.npmCommand)
              """]
            }
          },
        ]
      }
      contents: core.#Subdir & {
        input: buildDeps.output.rootfs
		path:  "/app/dist"
      }
    }
    deploy: cli.#Command & {
      workdir: "/app"
      mounts: "/app": {
		dest:     "/app"
		contents: build.contents.output
	  }
      credentials: aws.#Credentials & {
        accessKeyId:     client.env.AWS_ACCESS_KEY_ID
        secretAccessKey: client.env.AWS_SECRET_ACCESS_KEY
      }
      options: {
        region: "ap-northeast-1"
      }
      unmarshal: false
      service: {
        name:    "s3"
        command: "cp ./hoge.js s3://\(params.bucket)/"
      }
      _build: _scripts: core.#Source & {
        path: "_scripts"
      }
    }
  }
}

内容

aws関連の処理を行うのでawsのpackageをimportしています。
合わせてbuild周りでnode環境が必要なためdockerを使用しています。

package main

import (
  "dagger.io/dagger"
  "dagger.io/dagger/core"
  "universe.dagger.io/docker"
  "universe.dagger.io/bash"
  "universe.dagger.io/aws"
  "universe.dagger.io/aws/cli"  
)

次にplanで使用する環境変数やoutput先、読み込むファイルなどの設定をしています。

dagger.#Plan & {
  client: {
    env: {
      AWS_ACCESS_KEY_ID: dagger.#Secret
      AWS_SECRET_ACCESS_KEY: dagger.#Secret
    }
    filesystem: {
      "./": read: {
        contents: dagger.#FS,
        exclude: [
		  "README.md",
          ".gitignore",
		  "deploy.cue",
		  "node_modules",
          "dist"
		]
	  }
      "output.txt": write: contents: actions.deploy.export.files["/output.txt"]
      "./dist": write: contents: actions.build.contents.output
    }
  }

action部分に関しては実行するbuildコマンドとデプロイ先のバケット名の2つのパラメータを受け取れるようにし
buildとdeployのタスクを作成しています。

  actions: {
    params: {
      npmCommand?: string
      bucket?: string
    }

    build: {
      buildDeps: docker.#Build & {
       .........
       ......
       ...
      }
    }
    deploy: cli.#Command & {
       .........
       ......
       ...

    }
  }

buildタスク

buildタスクではdockerのimageを指定(node:16.15-buster)し
必要となる contents (jsファイルやbuild周りの設定ファイルなど)を /app 下に Copyしています。
その後コマンドを実行しています。

    build: {
      buildDeps: docker.#Build & {
        steps: [
          docker.#Pull & { source: "node:16.15-buster" },
          docker.#Copy & {
            contents: client.filesystem."./".read.contents
            dest:     "/app"
          },
          bash.#Run & {
            workdir: "/app"
            script: contents: "npm ci"
          },
          docker.#Run & {
            workdir: "/app"
            command: {
              name: "/bin/sh"
              args: ["-c", """
              npm run \(params.npmCommand)
              """]
            }
          },
        ]
      }
      contents: core.#Subdir & {
        input: buildDeps.output.rootfs
		path:  "/app/dist"
      }
    }

build結果は dist に吐かれるようになっているため contents: core.#SubdirinputbuildDeps.output.rootfs を指定 path も合わせて記述します。

      contents: core.#Subdir & {
        input: buildDeps.output.rootfs
		path:  "/app/dist"
      }

またこの結果は localの ./dist に吐かれるように client にて設定されているので
local上にbuildした結果の .js が出力されています

  "./dist": write: contents: actions.build.contents.output

deployタスク

deployではaws cliパッケージを使用してawsコマンドを使用してs3へdeployしています。

    deploy: cli.#Command & {
      workdir: "/app"
      mounts: "/app": {
		dest:     "/app"
		contents: build.contents.output
	  }
      credentials: aws.#Credentials & {
        accessKeyId:     client.env.AWS_ACCESS_KEY_ID
        secretAccessKey: client.env.AWS_SECRET_ACCESS_KEY
      }
      options: {
        region: "ap-northeast-1"
      }
      unmarshal: false
      service: {
        name:    "s3"
        command: "cp ./hoge.js s3://\(params.bucket)/"
      }
      _build: _scripts: core.#Source & {
        path: "_scripts"
      }
    }

buildした結果を使うため、 /appbuild.contents の結果を mountします

      mounts: "/app": {
		dest:     "/app"
		contents: build.contents.output
	  }

v0.2.17時点ではawsパッケージを使用する際は _build の指定が必須?になっており
今回は _scriptsにcli install shellを設置しています。

      _build: _scripts: core.#Source & {
        path: "_scripts"
      }

実際に実行するcommandに関しては service にて name(aws service名)command(実行するcommand)を指定します

      service: {
        name:    "s3"
        command: "cp ./hoge.js s3://\(params.bucket)/"
      }

実行方法

  • project updateを行い cue.mod を取得します
dagger.sh
dagger project update
  • deploy taskの実行
    実行するタスクを指定します

今回作成したtaskは実行時パラメータを指定する形になっているため指定します
事前にタスク内で使用する環境変数(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)をexportしておきます

dagger do deploy --with 'actions: params: { npmCommand: "build", bucket: "deploy_bucket" }' --log-format=plain

deployを実行することでdeployタスク内で build.contents buildのoutput結果を呼んでいるためbuild taskも実行されます。

  • build taskのみの実行

buildを指定すれば実行されます

dagger do build --with 'actions: params: { npmCommand: "build" }' --log-format=plain

circle ci上での実行

以前はorbが用意されていたのですが、消えてしまっています...

そのため別途install.shを実行する必要があります

config.yml
version: 2.1
jobs:
  deploy:
    machine: true
    steps:
      - run:
          name: Install dagger.io
          command: |
            cd /usr/local
            curl -L https://dl.dagger.io/dagger/install.sh | sudo sh
      - checkout
      - run:
          name: dagger project update
          command: dagger project update
      - run:
          name: dagger deploy task
          command: |
           dagger do deploy --with 'actions: params: { npmCommand: "build", bucket: "deploy_bucket" }' --log-format=plain
workflows:
  version: 2
  main:
    jobs:
      - deploy:
          context: 定義したcontext

まとめ

daggerを使用することで、どの環境でも同一のパイプラインでCI/CDの実行が可能になります。
どのCIを使用しても動作が変わらないため、一度パイプラインを作成してしまえばlocalで実行しても
CI(github actionsやcircle ciなど)上で実行しても同一の結果得ることができます。
localでも簡単に実行できることから、複数人で開発を行う際にbuild環境の差異などを吸収することができます。
また、yamlでの定義とは違いpackageとして提供も可能なため異なるパイプラインでの再利用なども
可能となり、CI/CDパイプライン作成への時短へも繋がると思います。
まだ v0.2.x系ということもあり提供されている標準パッケージの数などは少ないですが、便利な点も多いので
パイプライン作成時の有力な選択肢としてぜひ一度使ってみてはいかがでしょうか。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6