はじめに
こんにちは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 main.go main.go
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" })
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をコードで表現してみます。
FROM golang:1.19 as builder
WORKDIR /workspace
COPY go.mod go.mod
RUN go mod download
COPY main.go main.go
RUN CGO_ENABLED=0 go build -a -o myapp main.go
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" })
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
本記事の投稿者: 谷合純也
AKS/ACAをメインにインフラ系のご支援を担当しています。
junya0530さんの記事一覧 | Zenn