🍓

Daggerでビルド、プッシュ、デプロイのパイプラインを作成する

2023/04/06に公開

はじめに

CICDを学び始めたいなと思いました。しかし、色々なツールがありどれがよいか迷ってしまいます。今後、どれを使うにしても役に立つものを学びたいです。
そんなとき、ローカルで実行できて、他ツール上で動かしやすいDaggerを知ったので使ってみることにしました。

Daggerとは

コンテナを使ってCICDパイプラインを作り、実行することができます。Daggerでは、コンテナ内部にソースファイルを配置してイメージのビルド、プッシュなど行うことができます。Dagger SDKとコンテナを動かせる場所であれば、パイプラインを実行できます。

引用:https://docs.dagger.io/

パイプライン作成に使える言語には、Go、CUE、Pythonなどがあります。また、クラウドやCICDツールのモジュールが用意されていたり、連携方法が紹介されています。

前提とする構成

以降は以下の構成で作業します。

CUE SDKのインストール

EC2に入っている依存モジュールのバージョンがあっているか考えた結果、CUEを使うことにしました。

$ curl -L https://dl.dagger.io/dagger-cue/install.sh | sudo VERSION=0.2.232 BIN_DIR=/usr/local/bin sh

コンテナイメージをビルド、プッシュするパイプラインを作る

パイプラインの定義

パイプラインの流れは以下の通りです。

パイプラインを定義するCUEファイルは以下のようにしました。
「docker login」済みで、作成されたconfig.jsonを使うこととします。

dagger.cue
package main

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

dagger.#Plan & {
  client: {
    env: { 
      //クライアントの環境変数です。「|*」で初期値を設定できます。
      //exportで上書きすることができます。
      REPOSITORY: string | *"test-ecr-repo"
      VERSION: string | *"latest"
    }
    commands: {
      config: {
        name: "cat"
        args: ["/home/ec2-user/.docker/config.json"]
      }
      dest: {
        name: "jq"
        args: ["-r","-j",".auths|keys[0]"]
        stdin: config.stdout
	//以下のshellを実行しています。
	//$ cat /home/ec2-user/.docker/config.json | jq -r -j ".auths|keys[0]"
      }
      username: {
        name: "jq"
        args: ["-r","-j",".auths[].auth|@base64d|split(\":\")[0]"]
        stdin: config.stdout
	//以下のshellを実行しています。
	//$ cat /path/to/.docker/config.json | jq -r -j ".auths[].auth|@base64d|split(\":\")[0]"
      }
      secret: {
        name: "jq"
        args: ["-r","-j",".auths[].auth|@base64d|split(\":\")[1]"]
        stdin: config.stdout
        stdout: dagger.#Secret
	//以下のshellを実行しています。
	//$ cat /path/to/.docker/config.json | jq -r -j ".auths[].auth|@base64d|split(\":\")[1]"
      }
    }
  }
  actions: {
    build: { //Dockerイメージをビルドするステージ
      _src: core.#Source & {
        path: "."
      }
      _build: core.#Dockerfile & {
        source: _src.output
        dockerfile: {
          path: "Dockerfile"
        }
      }
      config: _build.config
      output: _build.output
    }
    push: core.#Push & { //Dockerイメージをプッシュするステージ
      dest: "\(client.commands.dest.stdout)/\(client.env.REPOSITORY):\(client.env.VERSION)"
      input: build.output
      config: build.config
      auth: {
        username: client.commands.username.stdout
        secret: client.commands.secret.stdout
      }
    }
  }
}

※ECRにイメージをプッシュする方法は以下のページを参考にしました。

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/getting-started-cli.html#cli-authenticate-registry

パイプラインの実行

以下のフォルダ構成になっているとします。

$ tree .
.
├── Dockerfile
└── dagger.cue

0 directories, 2 files

プロジェクトを初期化します。

$ dagger-cue project init
Project initialized! To install dagger packages, run `dagger-cue project update`
$ dagger-cue project update
1:57PM INFO  system | installing all packages...
1:57PM INFO  system | installed/updated package dagger.io@0.2.232
1:57PM INFO  system | installed/updated package universe.dagger.io@0.2.232

dagger-cue doコマンドでパイプラインに定義されているアクションを見ることができます。

$ dagger-cue do -h
Usage:
  dagger-cue do <action> [subaction...] [flags]

Options


Available Actions:
 build
 push

...

「Available Actions」は実行できるアクションであり、buildとpushがあります。
dry-runで実行前にpushの実行順を見てみます。

$ dagger-cue do push --experimental --dry-run
[-] actions.build
[-] client.commands.config
[-] client.env
[-] client.commands.secret
[-] client.commands.dest
[-] client.commands.username
[-] actions.push

actionsはbuild→pushの順で実行されていることがわかります。
また、関連しているclientとactionsは依存関係の順番で実行されています。例えば、コンテナレジストリを定義するclient.commands,envが実行された後で、actions.pushが実行されています。

プッシュしてみます。

$ dagger-cue do push -l error
Field   Value
dest    "..."
result  "..."

成功するとプッシュのアクション内の要素が表示されます。

クラスタにデプロイするパイプラインを作る

パイプラインの定義

パイプラインの流れは以下の通りです。

CUEファイルは以下の通りです。applyだけでなくlintやdiffを使えるようにしています。

dagger.cue
package main

import (
  "dagger.io/dagger"
  "dagger.io/dagger/core"
  "universe.dagger.io/alpha/kubernetes/kustomize"
)

dagger.#Plan & {
  client: {
    commands: {
      lint: {
        name: "sh" //エラーで止まらずログを確認できるようにしています。
        args: ["-c", "kube-linter lint -;:"]
        stdin: actions.lint.kustomize.output
      }
      diff: {
        name: "sh"
        args: ["-c", "kubectl diff -f -;:"]
        stdin: actions.diff.kustomize.output
      }
      deploy: {
        name: "sh"
        args: ["-c", "kubectl apply -f -;:"]
        stdin: actions.deploy.kustomize.output
      }
    }
  }
  actions: {
    build: {
      _src: core.#Source & {
        path: "."
      }
      _build: kustomize.#Kustomize & {
        source: _src.output
        type: "directory"
        kustomization: _src.output
      }
      output: _build.output
    }
    lint: {
      kustomize: core.#Nop & {
        input: build.output
      }
      stdout: client.commands.lint.stdout
      stderr: client.commands.lint.stderr
    }
    diff: {
      kustomize: core.#Nop & {
        input: build.output
      }
      stdout: client.commands.diff.stdout
      stderr: client.commands.diff.stderr
    }
    deploy: {
      kustomize: core.#Nop & {
        input: build.output
      }
      stdout: client.commands.deploy.stdout
      stderr: client.commands.deploy.stderr
    }
  }
}

パイプラインの実行

ディレクトリ構成は以下のようになっているとします。

$ tree .
.
├── dagger.cue
└── kustomization.yaml

0 directories, 2 files

実行できるアクションは以下の通りです。

$ dagger-cue do -h
Usage:
  dagger-cue do <action> [subaction...] [flags]

Options


Available Actions:
 build #Kustomizeでマニフェストをyaml形式で出力するステージ
 lint #kube-linterをかけるステージ
 diff #diffをかけるステージ
 deploy #applyするステージ
...

前項と同様にプロジェクトを初期化します。
その後、dry-runでdeployを見てみます。

$ dagger-cue do deploy --experimental --dry-run
[-] actions.build
[-] actions.deploy.kustomize
[-] client.commands.deploy

actionsとclientの関係を見てみると、actions.deploy.kustomizeの出力を使ってclient.commands.deployを実行しています。actions.deploy.kustomizeにてマニフェストをyaml形式で出力したものをclient.commands.deployでデプロイしているためです。

実行方法も同じですが、オプションを使うことでログをきれいに出力できます。

$ dagger-cue do build -l error --output-format yaml
output: |
  apiVersion: apps/v1
  kind: Deployment
  metadata:
...
#ログを改行させたかったのですが、オプションがなかったのでリダイレクトで表示する方法を調べました。
$ dagger-cue do lint -l error | echo -e $(cat)
{ "stderr": "Error: found 13 lint errors
", "stdout": "KubeLinter v0.6.1-0-gc6177366a3

<standard input>: (object: <no namespace>/nginx-deployment apps/v1, Kind=Deployment) object has 3 replicas but does not specify inter pod anti-affinity (check: no-anti-affinity, remediation: Specify anti-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes. Using podAntiAffinity, specify a labelSelector that matches pods for the deployment, and set the topologyKey to kubernetes.io/hostname. Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for details.)
...

おわりに

ローカルで実行するパイプラインを作成しました。ローカルで実行できると方々に気を遣わずに作業できてよいと思いました。
将来はGitOpsで楽にリソース管理できるようになりたいです。
これからも勉強します。

Discussion