Posted at

GoのプロジェクトのDocker imageをDockerを使わずにBazelだけで作成する


はじめに

この記事ではGoのプロジェクトに対してBazelを用いることでDockerのimageを作成する方法をハンズオン形式で紹介する.

前回の記事で、Bazelによってbuildできるようにしたプロジェクトを、ここでは再利用する。

このプロジェクトにはprotobufが含まれているが、それは今回の話では本質ではなく、protobufを使用しないプロジェクトにおいても以下の解説は同じである。


BazelによるDocker imageの作成


WORKSPACE

Bazelを用いるプロジェクトではトップディレクトリーにWORKSPACEというファイルがある。前回、このファイルにおいてGoのための設定を行ったが、さらに、Docker imageを作成するためのruleを読み込むため、以下の記述を追加する。


WORKSPACE

# download Docker rules

http_archive(
name = "io_bazel_rules_docker",
sha256 = "29d109605e0d6f9c892584f07275b8c9260803bf0c6fcb7de2623b2bedc910bd",
strip_prefix = "rules_docker-0.5.1",
urls = ["https://github.com/bazelbuild/rules_docker/archive/v0.5.1.tar.gz"],
)

# load Docker
load(
"@io_bazel_rules_docker//container:container.bzl",
"container_pull",
container_repositories = "repositories",
)

container_repositories()

# load Go image
load(
"@io_bazel_rules_docker//go:image.bzl",
_go_image_repos = "repositories",
)

_go_image_repos()



BUILD

次に greeter_server/BUILD.bazel の最後に、以下の設定を加える。これがDocker imageを作成するための記述となる。


greeter_server/BUILD.bazel

# Docker

load("@io_bazel_rules_docker//go:image.bzl", "go_image")

go_image(
name = "greeter_server_image",
embed = [":go_default_library"],
importpath = "google.golang.org/grpc/examples/helloworld/greeter_server",
goarch = "amd64",
goos = "linux",
pure = "on",
)



BazelによるDocker imageの作成

bazelを実行することでDocker imageが作成される。

$ bazel build //greeter_server:greeter_server_image


Mac / Windowsでのcross-compile

Linuxでは、問題ないが、MacやWindowsでは次のようなエラーが出る。Docker imageはLinuxなので、buildではcross-compileを指定する必要がある。

ERROR: /private/var/tmp/_bazel_stn/56853cb50709ed59d05b544de7cfcee8/external/org_golang_google_grpc/internal/channelz/BUILD.bazel:3:1: GoCompile external/org_golang_google_grpc/internal/channelz/linux_amd64_pure_stripped/go_default_library%/google.golang.org/grpc/internal/channelz.a failed (Exit 1)

GoCompile: missing strict dependencies:
/private/var/tmp/_bazel_stn/56853cb50709ed59d05b544de7cfcee8/sandbox/darwin-sandbox/50/execroot/__main__/external/org_golang_google_grpc/internal/channelz/types_linux.go: import of "golang.org/x/sys/unix"
Known dependencies are:
google.golang.org/grpc/connectivity
google.golang.org/grpc/credentials
google.golang.org/grpc/grpclog
Check that imports in Go sources match importpath attributes in deps.
Target //greeter_server:greeter_server_image failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 37.508s, Critical Path: 16.71s
INFO: 48 processes: 48 darwin-sandbox.
FAILED: Build did NOT complete successfully

そこで、 --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64 を指定して再度buildする。

$ bazel build --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64 //greeter_server:greeter_server_image

今度は、無事、imageが作成された。

INFO: Build options have changed, discarding analysis cache.

INFO: Analysed target //greeter_server:greeter_server_image (1 packages loaded).
INFO: Found 1 target...
Target //greeter_server:greeter_server_image up-to-date:
bazel-bin/greeter_server/greeter_server_image-layer.tar
INFO: Elapsed time: 27.162s, Critical Path: 20.21s
INFO: 74 processes: 74 darwin-sandbox.
INFO: Build completed successfully, 79 total actions

ここまでのステップで、Dockerを使っていないことに気づいただろうか。この時点では、Docker daemonを起動しておく必要はなく、Dockerの入っていないシステムでもimageの作成はBazelだけでできる。


Dockerでの実行

作成されたimageは bazel-bin/greeter_server/greeter_server_image-layer.tar とtar形式になっている。このimageを実際に実行して動作を確認してみる。

imageを読み込むには、

$ docker import bazel-bin/greeter_server/greeter_server_image-layer.tar イメージ名:tag

でもできるが、先程のbazel buildbazel runに変えて、

$ bazel run --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64 //greeter_server:greeter_server_image

とすればよい。 docker psによってbazel/greeter_server:greeter_server_imageというimageが読み込まれgreeter_serverが実行されていることが分かる。

$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4dfd4b02596b bazel/greeter_server:greeter_server_image "/app/greeter_server…" 5 seconds ago Up 4 seconds tender_turing

また、走らせる必要はなく、imageを登録するだけなら、コマンドの最後に -- --norunをつければよい。


おまけ: portの話

Dockerのimageを作成して、実行するという点ではここまでだが、greeter_serverbazel run //greeter_clientで接続を試みると失敗する。

$ bazel run //greeter_client

...
2018/10/14 08:37:14 could not greet: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp [::1]:50051: connect: connection refused"

そう、ポートが開いていない。そこで、

$ docker run -p 50051:50051 bazel/greeter_server:greeter_server_image

とすることで、接続ができるようになる。

$ bazel run //greeter_client

...
2018/10/14 08:38:53 Greeting: Hello world


まとめ

Bazelを用いて、GoのプロジェクトのDocker imageを作成することができた。

ここに書かれていないことなども調べたいときは、こちらを参考にしてください。

https://github.com/bazelbuild/rules_docker#go_image

また、ここまでの作業を行った結果をGithubにあげておきました。

https://github.com/stn/grpc-helloworld-bazel/tree/docker