はじめに
なんか、Dockerを作った人がCIの抽象レイヤのプロダクトを作ってパブリックベータになったというので、取りあえずは触ってみたいと思い、触ってみました。
目的としては、
- 現在、GithubActions上で動いているCI/CDパイプラインがベンダーロックインにならないようにしたいということ
- ローカル環境でも同じものが動くということは、開発上のガイドレールを設定しやすいこと
- GithubActionsのリソースを使わなくてもいい場面が増えるなら、コストが減るかな?
という感じです。
GoalSetting
- (Local)HalloWorldを出す
- (Local)ApplicationのBuild/テストを行う
- (Local)ContainerBuildまで
結果
ApplicationBuild/TestまではJavaでも行けました。ですが、コンテナビルドまではできず。多分、DockerFileを使ったりする形では行けますが、SpringBootのイメージビルドやJibPluginを使った場合はまだ厳しそう。
前提条件
- ローカル環境にDaggerがInstallされていること
を参照して、動かせるところまで確認しておくこと。
※いきなり依存関係とかがあって、GettingStartなのにどう動いているかわかんない。
HelloWorldまでの道のり
右往左往する
以下は右往左往した経緯です。興味のある方はどうぞ。
結果が見たい方は飛ばして下さい。
右往左往の歴史
定義ファイル(cue)を作成する
コアコンセプトを見て、まずは作成する。
package test
import (
"dagger.io/dagger"
"dagger.io/dagger/core"
)
daggar.#Plan & {
actions: {
hello: {
run: bash.#Run & {
script: contents: #"""
echo "HelloWorld"
"""#
}
}
}
}
作成したので、適当なフォルダに保存して見様見真似で実行してみました。
dagger do hello
そうすると、Projectがないから、初期化しろと怒られた。
failed to load plan: dagger project not found. Run `dagger project init`
初期化(init)
dagger project init
適当なフォルダを作成し、上記コマンドを実行するとフォルダが作られました。
先ほどのhelloworld.cueを含むと以下のような感じ。
.
├── cue.mod
│ ├── module.cue
│ ├── pkg
│ └── usr
└── helloworld.cue
で、実行しようとすると、(Golangを知ってる人は想定されそう)、Updateしろと怒られた。
failed to load plan: import failed: cannot find package "dagger.io/dagger":
: running `dagger project update` may resolve this
PkgのUpdateと整理
dagger project update
とすると、pkg内に色々なものが保存されました。BashのPkgが足りてないので、
"universe.dagger.io/bash"
をImportに追記して実行。CoreのPkgを使ってないということで、Coreも削除。ここらへん、使ってないものを残すなというGolang製らしい感じを受けます。
いざ、実行
実行すると
dagger do build
[✔] actions.hello.run.script 0.0s
[✗] actions.hello.run 0.0s
6:13PM FTL failed to execute plan: task failed: actions.build.run._exec: invalid FS at path "actions.build.run._exec.input": FS is not set
FSがないと怒られました。なんで?
なお、contens:を削除すると、引数が足りないというErrorになりました。
bash.cueを読んでみる
github上で公開されているので、確認してみる。
\#Run: {
script: {
directory:
filename: string
} | {
contents: string
_filename: "run.sh"
_write: core.#WriteFile & {
input: dagger.#Scratch
path: _filename
"contents": contents
}
}
}
とあるので、今回の形だと、ファイルを読み込ませるのではなくStringで読み込ませたものをshとして動かしているよう。その際に、一時的にファイルに書き出している。なので、inputとしてFSが必要の様子。仮でということで
dagger.#Plan & {
actions: {
hello: {
dir: dagger.#FS
run: bash.#Run & {
input: dir
workdir: "/tmp"
script: contents: #"""
echo "HelloWorld"
"""#
}
}
}
}
とすると
failed to load plan: actions.hello.run.input: field not allowed: rootfs:
となる。そりゃ、FSが定義されてないので、rootになるんですかね。
FSを確認する
// For example:
// - The root filesystem of a container
// - A source code repository
// - A directory containing binary artifacts
// Rule of thumb: if it fits in a tar archive, it fits in a #FS.
#FS: {
$dagger: fs: _id: string | null
}
となっているので、自動的にコンテナのRootFsになっている気がするけど、ダメだったので、ファイルに切り替える。
の時に気づく、そういえば、動かすコンテナベースの指定をしていなかった。DaggerはDocker上で動かすことで各CIツールの環境を吸収しているので、必要?DefaultではAlpineを自動で呼ぶようだが、Bashを使うとどうもFS周りのためか失敗する。
depsで指定する。
package helloworld
import (
"dagger.io/dagger"
"universe.dagger.io/docker"
"universe.dagger.io/bash"
)
dagger.#Plan & {
actions: {
deps: docker.#Pull & {
source: "ubuntu:latest"
}
hello: {
run: bash.#Run & {
input: deps.output
script: contents: #"""
echo "HelloWorld"
"""#
}
}
}
}
とすることで、実行まで行けたが、--log-format=plain
を付与してもConsoleに文言が出てこない。
https://github.com/dagger/dagger/blob/main/pkg/universe.dagger.io/examples/helloworld/helloworld.cue
だと、actionsにalpineを指定してExecでechoコマンドを走らせているが、今後を考えるとBashなりで動かしたい。depsではなくサンプルと同じようにubuntuを指定したが、以下のErrorが出てくる。
failed to load plan: actions.hello.run.input: field not allowed: rootfs:
出る場合と出ない場合があるので、daggerでCache次第と判断して実行時にOption(キャッシュ無し)を追加。
HelloWorldができた
package helloworld
import (
"dagger.io/dagger"
"universe.dagger.io/docker"
"universe.dagger.io/bash"
)
dagger.#Plan & {
actions: {
// 実行環境の作成。BuildせずBaseImageをPullするだけにする。
deps: docker.#Pull & {
source: "ubuntu:latest"
}
// HelloWorld
hello: {
run: bash.#Run & {
input: deps.output
script: contents: #"""
echo "HelloWorld"
"""#
}
}
}
}
で
dagger do hello --log-format=plain --no-cache
を実行すると
11:45AM INF actions.hello.run.script._write | computing
11:45AM INF actions.deps._op | computing
11:45AM INF actions.hello.run.script._write | completed duration=0s
11:45AM INF actions.deps._op | completed duration=1.1s
11:45AM INF actions.hello.run._exec | computing
11:45AM INF actions.hello.run._exec | completed duration=200ms
11:45AM INF actions.hello.run._exec | #4 0.142 HelloWorld
こうなりました。
ApplicationBuildを実装する
他の方の記事だと、Goだと特に実行環境を指定せずにgo build
も実行できていたよう。DefaultImageがAlpineで、コマンドを実行する際に必要なpackageを導入してからRunを動かしてくれる様子。PythonもPkgにありますので対応していそうです。
弊社だとJava(SpringBoot)が多いので、そちらを考えてみます。
SpringBootのHelloWorldのサンプルを準備して、さあ、行くぞ。
package springtest
import (
"dagger.io/dagger"
"dagger.io/dagger/core"
"universe.dagger.io/docker"
"universe.dagger.io/bash"
)
dagger.#Plan & {
client: {
filesystem:{
// ローカルの現在のディレクトリをコンテナから読み出すRead権限を付与
"./": read: {
contents: dagger.#FS
exclude: [
"build",
"springtest.cue",
]
}
// _buildにBuild結果を書き出すWrite権限を付与
"./_build" : write: contents: actions.build.contents.output
}
}
actions: {
// 実行環境の作成。build用なのでGradleをベースにする
deps: docker.#Build & {
steps: [
docker.#Pull & {
source: "gradle:7.4.2-jdk11"
},
// 現在のディレクトリを実行コンテナにコピーする
docker.#Copy & {
contents: client.filesystem."./".read.contents
dest: "/app"
}
]
}
// build
build: {
run: bash.#Run & {
input: deps.output
workdir: "/app"
script: contents: #"""
./gradlew build
"""#
}
contents: core.#Subdir & {
input: run.output.rootfs
path: "/app/build"
}
}
}
}
dagger do build --log-format=plain
を実行することにより、_buildフォルダがローカルに作成されて、ビルドされたjarファイルが保存されました。もちろん、
java -jar ./_build/libs/dagger_spring-0.0.1-SNAPSHOT.jar
でHelloWorldが動くことも確認済みです。
Container Buildを追加する
gradle bootBuildImage
はLocalにDockerがないと上手く動かない。jibはイメージビルドまでは通るが、LocalのDockerが認識されないとDockerHubにつなごうとする様子。
なので、ビルドしたjarをDockerfileを使用する形でコンテナ化すれば行けそう感じがします。けど、
GIVE UP!!
補足
Outputの形式がcore.#Pull
だとdagger.#FS、docker.#Build
だと#imageとなっていて、どうも実行環境が用意されているものから外れると、難易度が高くなります。
bash.#Run
に#FSの定義が必要なので、core.#Pull
を使用して標準のalpineImage以外を使用してみたところ、#FSが定義されてないとbash.#Runがエラーを吐いたので、??となりました。
まとめ
GoとかPythonなら現状でも使えそうです。(イケてるとこはJavaなんて使ってないだろ?ということか※偏見)
まだ0.2なので、今後に色々とPkgが増えることとドキュメント群が充実したら再挑戦したいところです。