Daggerでビルド、プッシュ、デプロイのパイプラインを作成する
はじめに
CICDを学び始めたいなと思いました。しかし、色々なツールがありどれがよいか迷ってしまいます。今後、どれを使うにしても役に立つものを学びたいです。
そんなとき、ローカルで実行できて、他ツール上で動かしやすいDaggerを知ったので使ってみることにしました。
Daggerとは
コンテナを使ってCICDパイプラインを作り、実行することができます。Daggerでは、コンテナ内部にソースファイルを配置してイメージのビルド、プッシュなど行うことができます。Dagger SDKとコンテナを動かせる場所であれば、パイプラインを実行できます。
パイプライン作成に使える言語には、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を使うこととします。
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にイメージをプッシュする方法は以下のページを参考にしました。
パイプラインの実行
以下のフォルダ構成になっているとします。
$ 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を使えるようにしています。
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