概要
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()の方が使いやすいかなと思います。