概要
Bazel解説第3弾です。
Bazelを使ってみる その1(Goのビルド) - Carpe Diem
Bazelを使ってみる その2(protobufのビルド) - Carpe Diem
今回はDocker imageをビルドしてみます。
環境
- Bazel v4.2.2
前提知識
通常docker imageを作成する際はDockerfileを使いますが、BazelではDockerfileを使わずにビルドします。
Dockerfile内で複雑にプロビジョニングする場合はDockerfileの方が直感的に書けてオススメですが、単純にバイナリを置くだけであればBazelも十分選択肢になります。
またBazelでバイナリを生成した場合はSandbox内に置かれるため、そこの連携という意味でもBazelに閉じる方がシンプルに用意できます。
container_imageとgo_image
Bazelにはcontainer_imageと*_image(go_image, java_imageなど)といった各言語のルールが用意されています。
container_imageの場合
まず先にcontainer_imageのやり方を説明します。
WORKSPACE
GitHub - bazelbuild/rules_docker: Rules for building and handling Docker images with Bazel の通りにWORKSPACE
に以下を追記します。
# Docker http_archive( name = "io_bazel_rules_docker", sha256 = "59536e6ae64359b716ba9c46c39183403b01eabfbd57578e84398b4829ca499a", strip_prefix = "rules_docker-0.22.0", urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.22.0/rules_docker-v0.22.0.tar.gz"], ) load( "@io_bazel_rules_docker//repositories:repositories.bzl", container_repositories = "repositories", ) container_repositories() load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") container_deps() load( "@io_bazel_rules_docker//container:container.bzl", "container_pull", ) container_pull( name = "alpine_linux_amd64", registry = "index.docker.io", repository = "library/alpine", tag = "3.15", )
io_bazel_rules_docker
はrelease noteを見て最新版にしてください。
ベースイメージとして今回はalpineを取得しています。
container_pull( name = "alpine_linux_amd64", registry = "index.docker.io", repository = "library/alpine", tag = "3.15", ) container_pull( name = "distroless_linux_amd64", registry = "gcr.io", repository = "distroless/base", tag = "latest", )
のように複数用意することも可能です。
go_binaryのあるBUILD.bazel
次にgo_binary()
のあるBUILD.bazel
に以下を追記します。
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_push") go_library( name = "cmd_lib", srcs = ["main.go"], importpath = "github.com/jun06t/bazel-sample/docker/cmd", visibility = ["//visibility:private"], ) go_binary( name = "cmd", embed = [":cmd_lib"], pure = "on", visibility = ["//visibility:public"], ) +container_image( + name = "image", + base = "@alpine_linux_amd64//image", + entrypoint = ["/cmd"], + files = [":cmd"], + repository = "jun06t", +) +container_push( + name = "image-push", + format = "Docker", + image = ":image", + registry = "index.docker.io", + repository = "jun06t/bazel-sample-cmd", + tag = "latest", +)
container_imageでdocker image用のtarを生成し、[container_push]で指定したDocker Registryにプッシュします。 (https://github.com/bazelbuild/rules_docker/blob/master/docs/container.md#container_push)
ポイント
ポイントは以下です。
container_image()
のentrypoint
にはgo_binary()
のname
を指定container_image()
のfiles
にはgo_binary()
のname
を指定container_push()
のimage
にはcontainer_image()
のname
を指定
.bazelrc
alpine(=linux)環境なので、goもクロスコンパイルするようにします。
.bazelrc
ファイルを用意して以下を追記します。
build --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64
動作確認
$ bazel build //cmd:image INFO: Analyzed target //cmd:image (1 packages loaded, 130 targets configured). INFO: Found 1 target... Target //cmd:image up-to-date: bazel-bin/cmd/image-layer.tar INFO: Elapsed time: 75.432s, Critical Path: 44.93s INFO: 6 processes: 4 internal, 2 darwin-sandbox. INFO: Build completed successfully, 6 total actions
すると.tar
が生成されます。
$ bazel run //cmd:image-push
するとdocker imageをpushします。
認証は?
pushする環境で~/.docker/config.json
といったDocker Registryへの認証情報があればbazel側で特に意識する必要はありません。
CircleCIでGCPのContainer Registryにpushしたい、といった場合はcircleci/gcp-gcrといったOrbを使えば認証されてpushできるようになります。
go_imageの場合
次にgo_imageでのやり方を説明します。
WORKSPACE
WORKSPACE
に以下を追記します。先程よりはやや少ないです。
go_imageはデフォルトだとdistroless/baseをベースイメージとして使います。
# Docker http_archive( name = "io_bazel_rules_docker", sha256 = "59536e6ae64359b716ba9c46c39183403b01eabfbd57578e84398b4829ca499a", strip_prefix = "rules_docker-0.22.0", urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.22.0/rules_docker-v0.22.0.tar.gz"], ) load( "@io_bazel_rules_docker//repositories:repositories.bzl", container_repositories = "repositories", ) container_repositories() load( "@io_bazel_rules_docker//go:image.bzl", _go_image_repos = "repositories", ) _go_image_repos()
go_binaryのあるBUILD.bazel
次にgo_binary(go_libraryがあればそちらメイン)
をgo_image
にリネームするような形で以下のように修正します。
load("@io_bazel_rules_go//go:def.bzl", "go_binary") load("@io_bazel_rules_docker//go:image.bzl", "go_image") load("@io_bazel_rules_docker//container:container.bzl", "container_push") go_image( name = "image", srcs = ["main.go"], importpath = "github.com/jun06t/bazel-sample/docker-go-image/cmd", pure = "on", visibility = ["//visibility:private"], ) container_push( name = "image-push", format = "Docker", image = ":image", registry = "index.docker.io", repository = "jun06t/bazel-sample-cmd", tag = "go_image", )
細かい設定がしたい場合はgo_image-custom-baseを見てください。
.bazelrc
distroless(=linux)環境なので、goもクロスコンパイルするようにします。
.bazelrc
ファイルを用意して以下を追記します。
build --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64
動作確認
$ bazel build //cmd:image Starting local Bazel server and connecting to it... INFO: Analyzed target //cmd:image (77 packages loaded, 8213 targets configured). INFO: Found 1 target... Target //cmd:image up-to-date: bazel-bin/cmd/image-layer.tar INFO: Elapsed time: 62.680s, Critical Path: 44.92s INFO: 1 process: 1 internal. INFO: Build completed successfully, 1 total action
すると.tar
が生成されます。
$ bazel run //cmd:image-push
するとdocker imageをpushします。
その他
Tipsや検証する上で遭遇したエラーなど
docker imageをホスト環境に用意したい
bazel buildするとdocker imageの.tar
が生成されますが、それをローカルでdocker imageとして扱いたい場合です。
docker loadで.tar
をインポートする方法とbazelコマンドの2通りありますが、簡単なのはbazel runです。
$ bazel run //cmd:image INFO: Analyzed target //cmd:image (0 packages loaded, 0 targets configured). INFO: Found 1 target... Target //cmd:image up-to-date: bazel-bin/cmd/image-layer.tar INFO: Elapsed time: 0.462s, Critical Path: 0.01s INFO: 1 process: 1 internal. INFO: Build completed successfully, 1 total action INFO: Build completed successfully, 1 total action Loaded image ID: sha256:9c18e5c241760317a4deecde7e63a7f2dcdc2d6b43b16241130a02f2aea533b3 Tagging 9c18e5c241760317a4deecde7e63a7f2dcdc2d6b43b16241130a02f2aea533b3 as jun06t/cmd:image
すると以下のようにホスト環境に用意されます。
$ docker images ... jun06t/cmd image 9c18e5c24176 52 years ago 26.5MB
欠点として作成日が大昔になる点と、container_pushと違ってrepositoryやtagが柔軟にいじれません。
image tagを外から注入したい
tag
の箇所をこのように変数にして、
container_push( name = "image-push", format = "Docker", image = ":image", registry = "index.docker.io", repository = "jun06t/bazel-sample-cmd", tag = "$(IMAGE_TAG)", )
bazelコマンド時に--define=
オプションを指定するようにします。
$ bazel run --define=IMAGE_TAG=v1.0.0 //cmd:image-push
fail: go_register_toolchains: version must be a string
WORKSPACE
でcontainer_deps()などを以下の箇所に挿入したところ、
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository") go_rules_dependencies() // ここに入れるとエラー go_register_toolchains(version = "1.17.2") gazelle_dependencies()
次のようなエラーが発生しました。
ERROR: Traceback (most recent call last): File "/Users/jun06t/.go/src/github.com/jun06t/bazel-sample/docker/WORKSPACE", line 45, column 15, in <toplevel> container_deps() File "/private/var/tmp/_bazel_a13156/ee39864ec5fa85be79e557bb2ef72cc8/external/io_bazel_rules_docker/repositories/deps.bzl", line 32, column 12, in deps go_deps() File "/private/var/tmp/_bazel_a13156/ee39864ec5fa85be79e557bb2ef72cc8/external/io_bazel_rules_docker/repositories/go_repositories.bzl", line 34, column 27, in go_deps go_register_toolchains() File "/private/var/tmp/_bazel_a13156/ee39864ec5fa85be79e557bb2ef72cc8/external/io_bazel_rules_go/go/private/sdk.bzl", line 460, column 17, in go_register_toolchains fail('go_register_toolchains: version must be a string like "1.15.5" or "host"') Error in fail: go_register_toolchains: version must be a string like "1.15.5" or "host"
go_register_toolchains(version = "1.17.2")
とあるのになんでだろ、と思いつつも先にgo_register_toolchains()
を呼んでからcontainer_deps()
等を呼ぶようにしたところ直りました。
CI上でエラー
CircleCI上で実行したところ以下のエラーが出ました。
Error occurred while attempting to use the default Python toolchain (@rules_python//python:autodetecting_toolchain). According to '/usr/bin/python -V', version is 'Python 2.7.16', but we need version 3. PATH is: /root/.cache/bazelisk/downloads/bazelbuild/bazel-4.2.2-linux-x86_64/bin:/root/google-cloud-sdk/bin:/home/circleci/.local/bin:/home/circleci/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
これはエラーの通りpython3をインストールしたところ解消しました。
apt-get update
apt-get -y install python3 python3-setuptools python3-pip
exec format error
go_binaryをクロスコンパイルしてdocker imageと環境を合わせておかないと以下のエラーがでます。
$ docker run --rm jun06t/cmd:image standard_init_linux.go:228: exec user process caused: exec format error
.bazelrc
でビルド時のパラメータを設定しておきましょう。
サンプルコード
今回のコードは以下で全体を見ることができます。
container_image
go_image
まとめ
Bazelを使ったdocker buildとpush方法について説明しました。
container_image()
かgo_image()
かでいうと、
- 成果物を意識しないで済む
- 覚えるパラメータが最低限で済む
といった点からcontainer_image()
の方が使いやすいかなと思います。