はじめに
こんにちは,今年の4月に新卒で入社しました,menu事業部で基盤開発ということをやっている窪田と申します.
突然ですが,皆さんは開発でGolangを使っていますか?
もし,Go言語を使われている場合,cloud上のdev/stg/prod環境にデプロイをする際,多くの場合Dockerでimageを作り,pushしていることと思います.(もしかしたら,サーバ上でgo run main.go
をしているかもしれませんが...)
今回はそんな,imageの作成を高速に,簡単に行ってくれる ko(ko-build) というGolang専用のimage builderについて紹介させていただきます.
注意
- 筆者はM1 Mac環境です.windowsやIntel Macでの動作は確認しておりません.
- Goのバージョンは1.18,koのバージョンはv0.12.0を用いています.
- Localでの挙動確認のためには,Dockerが起動している必要があります.(Localでのimageのpush先にDockerのimage repositoryが使用されます)
koについて
koとは,
ko is a simple, fast container image builder for Go applications.
と公式のREADMEにもあるとおり,Goのために作られた,非常に高速なimage builderです.
Googleが開発を行っており,CNCFのsandbox projectにも採択されています.(参考リンク)
ko はローカルマシン上で go build を効果的に実行することでイメージを構築するため、docker のインストールは必要なく*1,マルチプラットフォームビルドにも対応しています.
Readme上で
It's ideal for use cases where your image contains a single Go application without any/many dependencies on the OS base image (e.g., no cgo, no OS package dependencies).
とある通り,OS のベース イメージに多くの依存性を持たない単一の Go アプリケーションのbuildに対して特に最適なimage builderです.
koの利点
koの利点は大きく二つあります.
- Dockerfileが不要
docker build
よりも大抵の場合2倍以上高速
以下ではこれらの利点を簡単にkoを使いながら実感してみましょう.
Dockerfile不要
koをインストールして簡単に使ってみましょう.
home brewを使用する場合には
brew install ko
home brewが無い環境の場合はgo installを用いて
go install github.com/google/ko@latest
でインストール可能です.
では,早速ko buildの便利さを実感してみましょう.
以下のようなmain.goを用意します.
package main import "fmt" func main() { fmt.Printf("Hello Gopher") }
このコードのimageは
time ko build -L main.go
でbuildできます.-L
オプションをつけることで,imageの保存をLocalのdockerが用意しているimage repositoryに対して行ってくれます.(DockerがLocalで起動していないと失敗します)
実際に,imageを確認してみると,以下のようにlatestタグと,何らかのhash値を用いたtagをつけたimageが作成されているのが確認できます.
docker images | grep ko.local ~~~以下出力~~~ ko.local/hello_world.go-758db53ba470591ced76d85d86b5f3fc 1a72d55ea703816a000073fa3e4323f4da7e1734b54be1d7493efb417840362d 3b524693e26c 14 hours ago 3.15MB ko.local/hello_world.go-758db53ba470591ced76d85d86b5f3fc latest 3b524693e26c 14 hours ago 3.15MB
ここで,Dockerでbuildしていた時のことを思い出してみましょう.
まず,Dockerfileが必要でした.
koのbase imageはデフォルトでcgr.dev/chainguard/static
となっているので*2,Dockerfileを作るなら以下のようになるでしょうか
FROM golang:1.18 as build COPY main.go main.go RUN go build -o /app main.go FROM cgr.dev/chainguard/static COPY --from=build /app . ENTRYPOINT ["./app"]
こちらのDockerfileをmain.go
と同じディレクトリにおいて
docker build -t test .
を実行することでimageが作れます.
ここまで見ていただいた通り,koの実行においてはDockerfileが不要になっています.
単純にGoのコードのimageをbuildするだけであれば,koではDockerfileのような設定ファイルを用意する必要がありません.
docker build
よりも高速
上の例でbuildにかかる時間を計測してみます.
簡単に測りたかったのでtime
コマンドを使用しました.*3
一番最後のtotalの手間に出ているのが,実行時間の総計になります.
以下がtime ko build -L main.go
の実行結果です.
ko build -L main.go 0.31s user 0.51s system 16% cpu 4.934 total
以下がtime docker build -t test .
の実行結果です.
docker build -t test . 0.23s user 0.32s system 4% cpu 12.210 total
これだけ単純なimageであってもko buildの方が早いことがわかるかと思います.
さらに少し大きめのプロジェクトとして,guestbookという,cloud codeのsample projectを用いた計測を行ってみましょう.*4*5
レポジトリをクローンしてsrc/backend
ディレクトリに移動してください.
Dockerfileで使用されているimageとbase imageを揃えるためにkoコマンドとしては以下を実行しました.*6
KO_DEFAULTBASEIMAGE=gcr.io/distroless/base ko build -L main.go
timeコマンドを用いた結果が以下の通りです.
8.58s user 1.85s system 118% cpu 8.811 total
一方でdocker build
の結果が以下の通りです.
0.25s user 0.35s system 3% cpu 17.276 total
こちらも2倍ほどkoの方がbuildが早いことがわかるかと思います.
現在自分が開発中のプロジェクトにおいては,kubernetes上で動く複数のサービスに対して,それぞれサービス用のimageをbuildする必要があるのですが,docker buildを用いていたときには一イメージあたり30秒程度かかっていた各imageのbuild時間が,koの導入によりそれぞれ5~10秒以内で実行できるようになり,localでkubenertesを起動してR&Dをする際に待ち時間をかなり減らすことができました.
koでできること,できないこと
ここまでkoに関するいい点を話してきたので,koの導入にきっと興味を持ってくださっているかと思います.
ここからは,koのできること,できないことの中でも開発に関わってきそうな話に関して簡単に解説して行こうと思います
Goのみ
Go以外のプログラミング言語のbuildはもちろんできません.
Go以外のコードが共存する環境では通常通りdockerを用いる方が安全かと思います.
静的ファイルはimageに入れられる
htmlやcss,画像ファイルや変更が起こることのない実行ファイルなどは,imageに含めることができます.
main.go
が存在するディレクトリ内にkodata
というディレクトリを作成し,ko build
を実行すると,image内の/var/run/kodata
というディレクトリ内に,localのkodata
においたファイルが展開されます.なので,code内で/var/run/kodata
を見にいくことで静的ファイルを使用することができます.
また,実行ファイルとしておくことが可能なコマンドなどに関しては,base imageに含めてしまうという手もあります.
base imageをあらかじめ作成しておき,docker hubなどにpushし,KO_DEFAULTBASEIMAGE
で呼び出すことで,そのコマンドを持ったimageをbuildできます.
ただ,先に『OS のベース イメージに多くの依存性を持たない単一の Go アプリケーションのbuildに対して特に最適』と紹介した通り,こうした利用はあくまでも可能というだけで,公式としてもあまり推奨した使い方ではないように思われます.
base imageを自作するとそのbase imageを管理する手間も発生するため,可能な限り行わず,そうした必要がある場合には今まで通りDockerfileを使うようにした方が安全だろうと感じています.
imageのpush
作ったimageのpush先を指定できます.
-L
コマンドでlocalのdockerが起動しているimage repositoryへのpushが行われます.
一方で,KO_DOCKER_REPO
にimageレポジトリのURLを設定することで,Docker Hub,GCPのArtifact RegistryやAmazon ECRなどremote環境へのimage pushも可能になります.
また,例えばGCPのArtifact Registryを用いる際などには,予めアクセス権限をworkload identityなどを用いて別途設定する必要があるため注意してください.
GitHub Actions
こちらのようにGitHub Actionsも用意されています.Readmeを読んで設定することが可能です.
Docker buildを行っている部分をkoコマンドを使うように書き換えるだけだったので,実装のコストはかなり低く感じました.
その他
こちらのサイトを参考にすることで,その他機能を確認できます.
このブログで紹介した機能以外にも
- koで作ったimageをdocker runで使ったりcloud serviceにdeployしたりする方法(参考リンク)
- kubernetesに対してのintegration(参考リンク)
- go build時のオプションの設定方法(参考リンク)
.ko.yaml
での設定管理(参考リンク) などの機能が紹介されています.
最後に
koについての日本語の記事はまだまだ少なく,特にサービスコードに取り入れている例はほぼ公表されていないように見えましたので,今回koの紹介記事を書かせていただきました.
まだまだ,発展途中のプロジェクトでもあるため,公式ドキュメントにおいてわかりずらい点もあり,そういう際には,こちらのGoDocを参考にしたり,実際のコードを読みにいったりして,どういうdefault値が設定されているのかなどの確認などを行いました.
Goを用いた開発をしているのであれば,是非koを使って快適にimage buildを行ってみてください.
また,弊社では様々なポジションのエンジニアを募集しています.
まずはカジュアルにお話を,という形でも結構ですので是非気軽にご応募ください.
*1:imageのpush先として必要になる場合はあります.
*2:リンク先のOverriding Base Imagesを参照
*3:それぞれのコマンド実行前にdocker desktopのTroubleshoot機能内にあるClean / Purge dataを実行してimageの履歴を消し,cacheがない状態で計測しています.
*4:筆者環境では動作のためにgo.mod, Dockerfile内のgoのバージョンを1.18に下げています.
*5:cloud codeに関しては今後のtech blog内で紹介予定です.
*6:KO_DEFAULTBASEIMAGEという変数にimageのpathを設定することでbase imageを変更可能です