はじめに
こんにちはACS事業部の谷合です。
みなさんDaggerなるツールをご存じでしょうか?
Daggerとは、数か月前にコードでCI/CDを実行するツールとしてCloudNative界隈を騒がせたツールです。
dagger.io
このDagger、私が知ったのはTwitterでした。
その時は単にコードでCI/CDを実行するためだけのツールとの認識でしたが、最近思い立って調査してみるとコードでBuildができる機能が生えていましたので、今回は私の主言語であるGo言語でBuildをする方法をご紹介したいと思います。
Dockerfileを用いてBuild
今回は以下のDokcerfileを用いて、Buildしてみました。
FROM golang:1.19 WORKDIR /workspace COPY go.mod go.mod RUN go mod download # Copy the go source COPY main.go main.go # Build RUN CGO_ENABLED=0 go build -a -o myapp main.go ENTRYPOINT ["/myapp"]
コードとしては以下のようになり、/home/taniai/dagger-testディレクトリにあるDockerfileを読み取り、BuildしてAzure Container Registry(以降ACR)にPushしているだけの単純なコードとなります。
package main import ( "context" "fmt" "os" "dagger.io/dagger" ) func main() { ctx := context.Background() client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr)) if err != nil { panic(err) } defer client.Close() addr, err := client.Host().Directory("/home/taniai/dagger-test"). DockerBuild(). Publish(ctx, "taniaitest.azurecr.io/myexample:with-dockerfile") if err != nil { panic(err) } fmt.Printf("Published to %s", addr) }
これをBuildすることで以下のような出力が得られます。
$ go run main.go 4: upload /home/taniai/dagger-test DONE 4: > in host.directory /home/taniai/dagger-test 4: upload /home/taniai/dagger-test DONE 4: upload /home/taniai/dagger-test 4: > in host.directory /home/taniai/dagger-test 4: transferring /home/taniai/dagger-test: 4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s] 4: upload /home/taniai/dagger-test DONE 3: copy /home/taniai/dagger-test CACHED 3: > in host.directory /home/taniai/dagger-test 3: copy /home/taniai/dagger-test CACHED 3: copy /home/taniai/dagger-test CACHED 3: > in host.directory /home/taniai/dagger-test 3: > in docker build 3: copy /home/taniai/dagger-test CACHED 4: upload /home/taniai/dagger-test DONE 4: > in host.directory /home/taniai/dagger-test 4: > in docker build 4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s] 4: upload /home/taniai/dagger-test DONE 12: [builder 1/6] FROM docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 12: > in docker build 12: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 12: ... 13: [stage-1 1/3] FROM gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f DONE 13: > in docker build 13: resolve gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f [0.07s] 13: [stage-1 1/3] FROM gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f DONE 12: [builder 1/6] FROM docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 DONE 12: > in docker build 12: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.08s] 12: [builder 1/6] FROM docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 DONE 9: [builder 3/6] COPY go.mod go.mod CACHED 9: > in docker build 9: [builder 3/6] COPY go.mod go.mod CACHED 10: copy / / CACHED 10: > in docker build 10: copy / / CACHED 11: [builder 2/6] WORKDIR /workspace CACHED 11: > in docker build 11: [builder 2/6] WORKDIR /workspace CACHED 14: [stage-1 2/3] COPY --from=builder /workspace/myapp . CACHED 14: > in docker build 14: [stage-1 2/3] COPY --from=builder /workspace/myapp . CACHED 15: [builder 6/6] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o myapp main.go CACHED 15: > in docker build 15: [builder 6/6] RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o myapp main.go CACHED 16: [builder 5/6] COPY main.go main.go CACHED 16: > in docker build 16: [builder 5/6] COPY main.go main.go CACHED 17: [builder 4/6] RUN go mod download CACHED 17: > in docker build 17: [builder 4/6] RUN go mod download CACHED 18: exporting to image 18: exporting layers 18: exporting layers [0.00s] 18: exporting manifest sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e 18: exporting manifest sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e [0.02s] 18: exporting config sha256:369d542cb26dfdf5b711a00bc3dda04c65d580a6916d177e2bbaaee2f9f79f2c 18: exporting config sha256:369d542cb26dfdf5b711a00bc3dda04c65d580a6916d177e2bbaaee2f9f79f2c [0.03s] 18: pushing layers 18: pushing layers [0.35s] 18: pushing manifest for taniaitest.azurecr.io/myexample:with-dockerfile@sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e 18: pushing manifest for taniaitest.azurecr.io/myexample:with-dockerfile@sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e [0.47s] 18: exporting to image DONE Published to taniaitest.azurecr.io/myexample:with-dockerfile@sha256:889a6d9431189999abbe39495a8e1526324ef5bbd899d2acfbfab24277a77d0e
Dockerfileを使用せずにコードでBuild
DaggerはDockerfileを使用しない、コードでのBuildも可能です。
モデルとなるDockerfileは前セクションDockerfileを用いてBuild
で記載したDockerfileで、こちらをコードで表現してみます。
WithDirectory関数で/home/taniai/dagger-testにあるファイルをすべて/workspaceディレクトリにコピーして、go buildしてACRにPushしています。
package main import ( "context" "fmt" "os" "dagger.io/dagger" ) func main() { ctx := context.Background() client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr)) if err != nil { panic(err) } defer client.Close() project := client.Host().Directory("/home/taniai/dagger-test") builder := client.Container(). From("golang:1.19"). WithDirectory("/workspace", project). WithWorkdir("/workspace"). WithEnvVariable("CGO_ENABLED", "0"). WithExec([]string{"go", "build", "-o", "myapp"}) // publish the container image addr, err := builder.Publish(ctx, "taniaitest.azurecr.io/myapp:whithout-dockerfile") if err != nil { panic(err) } fmt.Printf("Published to %s", addr) }
これをBuildすることで以下のような出力が得られます。
ちゃんとBuild & Pushできてますね。
$ go run main.go 4: upload /home/taniai/dagger-test DONE 4: > in host.directory /home/taniai/dagger-test 4: upload /home/taniai/dagger-test DONE 4: upload /home/taniai/dagger-test 4: > in host.directory /home/taniai/dagger-test 4: transferring /home/taniai/dagger-test: 4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s] 4: upload /home/taniai/dagger-test DONE 3: copy /home/taniai/dagger-test CACHED 3: > in host.directory /home/taniai/dagger-test 3: copy /home/taniai/dagger-test CACHED 8: resolve image config for docker.io/library/golang:1.19 8: > in from golang:1.19 8: resolve image config for docker.io/library/golang:1.19 DONE 4: upload /home/taniai/dagger-test DONE 4: > in host.directory /home/taniai/dagger-test 4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s] 4: upload /home/taniai/dagger-test DONE 3: copy /home/taniai/dagger-test CACHED 3: > in host.directory /home/taniai/dagger-test 3: copy /home/taniai/dagger-test CACHED 17: pull docker.io/library/golang:1.19 17: > in from golang:1.19 17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.03s] 17: pull docker.io/library/golang:1.19 DONE 16: copy / /workspace CACHED 16: copy / /workspace CACHED 14: exec go build -o myapp 14: exec go build -o myapp DONE 18: exporting to image 18: exporting layers 18: exporting layers [0.86s] 18: exporting manifest sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 18: exporting manifest sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 [0.04s] 18: exporting config sha256:414bb776d51659fe945dd0186e0e61e77a19b78541a765ff5997eb1af99168fa 18: exporting config sha256:414bb776d51659fe945dd0186e0e61e77a19b78541a765ff5997eb1af99168fa [0.03s] 18: pushing layers 18: pushing layers [0.89s] 18: pushing manifest for taniaitest.azurecr.io/myapp:whithout-dockerfile@sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 18: pushing manifest for taniaitest.azurecr.io/myapp:whithout-dockerfile@sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453 [0.49s] 18: exporting to image DONE Published to taniaitest.azurecr.io/myapp:whithout-dockerfile@sha256:5b8b72ca28a376a48628094699bc4d76a5f8278380acc4167761fbd54302e453
Dockerfileを用いないMulti-Stage Build
通常のBuildだけなく、Multi-Stage Buildも可能です。
以下のようなMulti-Stage Buildを行うDockerfileをコードで表現してみます。
# Build the myapp binary FROM golang:1.19 as builder WORKDIR /workspace COPY go.mod go.mod RUN go mod download # Copy the go source COPY main.go main.go # Build RUN CGO_ENABLED=0 go build -a -o myapp main.go # Use distroless as minimal base image to package the myapp binary # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/myapp . USER 65532:65532 ENTRYPOINT ["/myapp"]
以下のようなコードとなり、最初にbuilder変数に初回のBuildした結果を入れます。
その後、prodImage変数に次回のBuild結果を入れ、最後にACRにPushしています。
package main import ( "context" "fmt" "os" "dagger.io/dagger" ) func main() { ctx := context.Background() client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr)) if err != nil { panic(err) } defer client.Close() project := client.Host().Directory("/home/taniai/dagger-test") builder := client.Container(). From("golang:1.19"). WithDirectory("/workspace", project). WithWorkdir("/workspace"). WithEnvVariable("CGO_ENABLED", "0"). WithExec([]string{"go", "build", "-o", "myapp"}) prodImage := client.Container(). From("gcr.io/distroless/static:nonroot"). WithFile("/workspace/myapp", builder.File("/workspace/myapp")). WithEntrypoint([]string{"/workspace/myapp"}) // publish the container image addr, err := prodImage.Publish(ctx, "taniaitest.azurecr.io/myapp:multistage") if err != nil { panic(err) } fmt.Printf("Published to %s", addr) }
結果は以下の通りとなり、Multi-Stage Buildができていることが確認できるはずです。
$ go run main.go 4: upload /home/taniai/dagger-test DONE 4: > in host.directory /home/taniai/dagger-test 4: upload /home/taniai/dagger-test DONE 4: upload /home/taniai/dagger-test 4: > in host.directory /home/taniai/dagger-test 4: transferring /home/taniai/dagger-test: 4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s] 4: upload /home/taniai/dagger-test DONE 3: copy /home/taniai/dagger-test CACHED 3: > in host.directory /home/taniai/dagger-test 3: copy /home/taniai/dagger-test CACHED 8: resolve image config for docker.io/library/golang:1.19 8: > in from golang:1.19 8: resolve image config for docker.io/library/golang:1.19 DONE 17: pull docker.io/library/golang:1.19 17: > in from golang:1.19 17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.04s] 17: pull docker.io/library/golang:1.19 DONE 16: copy / /workspace CACHED 16: copy / /workspace CACHED 14: exec go build -o myapp 14: exec go build -o myapp DONE 20: resolve image config for gcr.io/distroless/static:nonroot 20: > in from gcr.io/distroless/static:nonroot 20: resolve image config for gcr.io/distroless/static:nonroot DONE 4: upload /home/taniai/dagger-test DONE 4: > in host.directory /home/taniai/dagger-test 4: transferring /home/taniai/dagger-test: 2.828KiB [0.01s] 4: upload /home/taniai/dagger-test DONE 3: copy /home/taniai/dagger-test CACHED 3: > in host.directory /home/taniai/dagger-test 3: copy /home/taniai/dagger-test CACHED 14: exec go build -o myapp DONE 14: exec go build -o myapp DONE 17: pull docker.io/library/golang:1.19 DONE 17: > in from golang:1.19 17: resolve docker.io/library/golang:1.19@sha256:405b708c4612817e9966bff1469570eaed1ecf02a98055fb43bd6ca7b6401b37 [0.04s] 17: pull docker.io/library/golang:1.19 DONE 16: copy / /workspace CACHED 16: copy / /workspace CACHED 24: pull gcr.io/distroless/static:nonroot 24: > in from gcr.io/distroless/static:nonroot 24: resolve gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f 24: resolve gcr.io/distroless/static:nonroot@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f [0.04s] 24: pull gcr.io/distroless/static:nonroot DONE 25: copy /workspace/myapp /workspace/myapp CACHED 25: copy /workspace/myapp /workspace/myapp CACHED 26: exporting to image 26: exporting layers 26: exporting layers [0.00s] 26: exporting manifest sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d 26: exporting manifest sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d [0.01s] 26: exporting config sha256:ab0284c5c2e4a2560df1e44e23af70d29903758f47dfc5b12d3ad39fa34c0e07 26: exporting config sha256:ab0284c5c2e4a2560df1e44e23af70d29903758f47dfc5b12d3ad39fa34c0e07 [0.01s] 26: pushing layers 26: pushing layers [0.13s] 26: pushing manifest for taniaitest.azurecr.io/myapp:multistagebuild@sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d 26: pushing manifest for taniaitest.azurecr.io/myapp:multistagebuild@sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d [0.44s] 26: exporting to image DONE Published to taniaitest.azurecr.io/myapp:multistagebuild@sha256:9d7a9a45835b05c1bbb1527a22ea882c5bc453783762ba507f2a63bd3315219d
さいごに
いかがでしたでしょうか?
DockerイメージをBuildするときはDockerfile必要でしょ!っと固定概念がありましたが、まさかコードで表現できるとは...
勉強になったと同時に、なかなか尖った検証ができ、楽しい時間を過ごせました。
普段は案件中心に業務を行っていますが、こういったOSSの検証はやはりエンジニアとして心躍るものがありますね。
今回ご紹介したコードでのBuildは、Daggerの本懐であるコードでのCI/CDでこそ活きてくるものだと思います。
GitHub Actionsなど優秀なツールが充実している分、なかなかCI/CDをコード化することはないと思いますが、CI/CD自体を内製化したいといった場合には活きてくる機能なのかなと感じました。
以下にDockerfileコマンドとそれに相当するDagger関数の対応についての記事とMulti-Stage Buildの方法のドキュメントも貼っておきますので、もしご興味あれば是非挑戦してみてください! dagger.io docs.dagger.io
ACS事業部のご紹介
私達ACS事業部はAzure・AKSなどのクラウドネイティブ技術を活用した内製化のご支援をしております。
www.ap-com.co.jp
また、一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。
www.ap-com.co.jp