Goの開発で Bazel を使っているので簡単にまとめてみました。
サンプル用のプロジェクトを作ったのでこれをもとに進めていこうと思います。
righ/bazel-sample-project - Github
利用する Bazel のバージョンは以下です。
3.0.0
備考
Bazelisk というツールを使うとプロジェクトで利用する Bazel のバージョンを固定できます。
今回使う開発環境は Mac Catalina 64bit です。
目次
Bazel
Bazelは Google が開発したOSSのビルドツールです。
キャッシュが効くため高速にビルドできたり、バイナリを作るだけでなくコンテナに乗せるところもやってくれるので 私達は開発に専念することができます。
設定ファイル
Bazel で重要なのは WORKSPACE 、 BUILD.bazel (BUILD) という2種類のテキストファイルです。
フォーマットは Python に似た Starlark と呼ばれる 言語で記述します。 Python の構文がすべて使えるわけではないので注意してください。例えば import 文は使えません。
WORKSPACE
WORKSPACE が配置されたディレクトリ配下は Bazel ではワークスペースとして認識されます。
このファイルにはプロジェクト全体に関する設定を書いていきますが、必要がなければ空でも構いません。
WORKSPACEでは主に以下のことを行います。
-
load と呼ばれる関数を使って必要なルールを読み込み
Python でいうなら from import のようなものと考えるとわかりやすい
使用するルールを外部から読み込む http_archive, http_file の実行とそれらの設定を有効にする命令の実行
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
http_archive(
name = "io_bazel_rules_go",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.22.2/rules_go-v0.22.2.tar.gz",
"https://github.com/bazelbuild/rules_go/releases/download/v0.22.2/rules_go-v0.22.2.tar.gz",
],
sha256 = "142dd33e38b563605f0d20e89d9ef9eda0fc3cb539a14be1bdb1350de2eda659",
)
load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies()
go_register_toolchains()
http_archive(
name = "bazel_gazelle",
urls = [
"https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/bazel-gazelle/releases/download/v0.20.0/bazel-gazelle-v0.20.0.tar.gz",
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.20.0/bazel-gazelle-v0.20.0.tar.gz",
],
sha256 = "d8c45ee70ec39a57e7a05e5027c32b1576cc7f16d9dd37135b0eddde45cf1b10",
)
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
gazelle_dependencies()
http_archive(
name = "io_bazel_rules_docker",
sha256 = "dc97fccceacd4c6be14e800b2a00693d5e8d07f69ee187babfd04a80a9f8e250",
strip_prefix = "rules_docker-0.14.1",
urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.14.1/rules_docker-v0.14.1.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//go:image.bzl",
go_image_repos = "repositories",
)
go_image_repos()
http_archive(
name = "com_google_protobuf",
strip_prefix = "protobuf-master",
urls = ["https://github.com/google/protobuf/archive/master.zip"],
)
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
protobuf_deps()
http_file(
name = "grpc_health_probe",
downloaded_file_path = "grpc_health_probe",
urls = ["https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.3.2/grpc_health_probe-linux-amd64"],
)
go_repository(
name = "co_honnef_go_tools",
importpath = "honnef.co/go/tools",
sum = "h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=",
version = "v0.0.0-20190523083050-ea95bdfd59fc",
)
go_repository(
name = "com_github_burntsushi_toml",
importpath = "github.com/BurntSushi/toml",
sum = "h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=",
version = "v0.3.1",
)
go_repository(
name = "com_github_census_instrumentation_opencensus_proto",
importpath = "github.com/census-instrumentation/opencensus-proto",
sum = "h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=",
version = "v0.2.1",
)
go_repository(
name = "com_github_client9_misspell",
importpath = "github.com/client9/misspell",
sum = "h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=",
version = "v0.3.4",
)
go_repository(
name = "com_github_cncf_udpa_go",
importpath = "github.com/cncf/udpa/go",
sum = "h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=",
version = "v0.0.0-20191209042840-269d4d468f6f",
)
go_repository(
name = "com_github_envoyproxy_go_control_plane",
importpath = "github.com/envoyproxy/go-control-plane",
sum = "h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=",
version = "v0.9.4",
)
go_repository(
name = "com_github_envoyproxy_protoc_gen_validate",
importpath = "github.com/envoyproxy/protoc-gen-validate",
sum = "h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=",
version = "v0.1.0",
)
go_repository(
name = "com_github_golang_glog",
importpath = "github.com/golang/glog",
sum = "h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=",
version = "v0.0.0-20160126235308-23def4e6c14b",
)
go_repository(
name = "com_github_golang_mock",
importpath = "github.com/golang/mock",
sum = "h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=",
version = "v1.1.1",
)
go_repository(
name = "com_github_golang_protobuf",
importpath = "github.com/golang/protobuf",
sum = "h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=",
version = "v1.3.5",
)
go_repository(
name = "com_github_google_go_cmp",
importpath = "github.com/google/go-cmp",
sum = "h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=",
version = "v0.2.0",
)
go_repository(
name = "com_github_prometheus_client_model",
importpath = "github.com/prometheus/client_model",
sum = "h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=",
version = "v0.0.0-20190812154241-14fe0d1b01d4",
)
go_repository(
name = "com_google_cloud_go",
importpath = "cloud.google.com/go",
sum = "h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=",
version = "v0.26.0",
)
go_repository(
name = "org_golang_google_appengine",
importpath = "google.golang.org/appengine",
sum = "h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=",
version = "v1.4.0",
)
go_repository(
name = "org_golang_google_genproto",
importpath = "google.golang.org/genproto",
sum = "h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=",
version = "v0.0.0-20190819201941-24fa4b261c55",
)
go_repository(
name = "org_golang_google_grpc",
importpath = "google.golang.org/grpc",
sum = "h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k=",
version = "v1.28.1",
)
go_repository(
name = "org_golang_x_crypto",
importpath = "golang.org/x/crypto",
sum = "h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=",
version = "v0.0.0-20190308221718-c2843e01d9a2",
)
go_repository(
name = "org_golang_x_exp",
importpath = "golang.org/x/exp",
sum = "h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=",
version = "v0.0.0-20190121172915-509febef88a4",
)
go_repository(
name = "org_golang_x_lint",
importpath = "golang.org/x/lint",
sum = "h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=",
version = "v0.0.0-20190313153728-d0100b6bd8b3",
)
go_repository(
name = "org_golang_x_net",
importpath = "golang.org/x/net",
sum = "h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=",
version = "v0.0.0-20190311183353-d8887717615a",
)
go_repository(
name = "org_golang_x_oauth2",
importpath = "golang.org/x/oauth2",
sum = "h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=",
version = "v0.0.0-20180821212333-d2e6202438be",
)
go_repository(
name = "org_golang_x_sync",
importpath = "golang.org/x/sync",
sum = "h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=",
version = "v0.0.0-20190423024810-112230192c58",
)
go_repository(
name = "org_golang_x_sys",
importpath = "golang.org/x/sys",
sum = "h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=",
version = "v0.0.0-20200413165638-669c56c373c4",
)
go_repository(
name = "org_golang_x_text",
importpath = "golang.org/x/text",
sum = "h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=",
version = "v0.3.0",
)
go_repository(
name = "org_golang_x_tools",
importpath = "golang.org/x/tools",
sum = "h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=",
version = "v0.0.0-20190524140312-2c0ae7006135",
)
備考
Bazel で指定するターゲットは @ルール//ワークスペース直下からBUILD.bazelまでのパス:名前
のようなフォーマットになっています。
先頭が // から始まる場合はローカルを指します。
load に指定する場合、 名前 の部分はマクロファイル名になります。
WORKSPACE に書くことはほかにもありますが、詳しくは gazelle セクションで話します.
BUILD.bazel
BUILD.bazel (あるいは BUILD) はビルド対象のソースコードが配置されたディレクトリごとに配置し、 このファイルが置かれているディレクトリを Bazel では パッケージ と呼びます。
備考
BUILD.bazel と BUILD はいずれもパッケージを定義するための設定ファイルで同じ意味を持つので片方だけあれば問題ありません。
どちらにすべきというのは公式には書かれていないようですが、両方置いた場合には BUILD.bazel が優先されるっぽいです(未検証)
BUILD.bazel にはビルドに必要なソースコードや依存パッケージの指定などを行います。 以下のようなものがあります。
- go_library
-
パッケージ内のソースをビルドして GoLibrary を作成します。 これは直接実行することはできず、後述する go_binary などから参照される形で利用されます。
オプションは以下のURLを参照してもらうとして、重要なのは 依存パッケージを指定する deps です。 必要なパッケージがここに指定されていないとビルドが通らないので注意です。
https://github.com/bazelbuild/rules_go/blob/master/go/core.rst#go-library
- go_binary
-
mainパッケージに属するファイル群をビルドして実行ファイルを作ります。 実行するには
bazel run
かbazel build
を実行します。https://github.com/bazelbuild/rules_go/blob/master/go/core.rst#go_binary
- go_test
-
$ bazel test
とすることでテストをビルドします。ワークスペース内のすべてのテストを行う場合は
bazel test --test_output=errors //...
とします。 これはgo test ./...
と同じです。https://github.com/bazelbuild/rules_go/blob/master/go/core.rst#go-test
gazelle
ビルドするのにルールなんていちいち覚えてられないし、 何よりファイルを作るたびにビルドの設定ファイルを手動で書き換えるのはめんどくさいですね。
この設定を自動生成するためのツールが gazelle です。
以下のようにセットアップします。
-
WORKSPACE で bazel_gazelle を取得
詳しくは WORKSPACEセクション に戻るか ドキュメント を参照
-
BUILD.bazel で gazelle を load して登録する
bazel-sample-project/BUILD.bazelload("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/righ/go-sample-bazel-project gazelle(name = "gazelle")
これで bazel run //:gazelle
のように呼び出せるようになります。
$ bazel run //:gazelle INFO: Analyzed target //:gazelle (3 packages loaded, 139 targets configured). INFO: Found 1 target... Target //:gazelle up-to-date: bazel-bin/gazelle-runner.bash bazel-bin/gazelle INFO: Elapsed time: 4.819s, Critical Path: 0.01s INFO: 0 processes. INFO: Build completed successfully, 1 total action INFO: Build completed successfully, 1 total action
このコマンドは ワークスペース配下の goのソースコードが含まれるすべてのディレクトリに BUILD.bazel を配置します。 単純な上書きではないので、もともと書いてある内容を勝手に消し去ったりすることはありません。
実際に作成された BUILD.bazel は 後のセクション で見ていくことにします。
備考
# gazelle:prefix github.com/example/project
のようなコメントを BUILD.bazel に追加するか -go_prefix のようなオプションを渡してあげないと以下のようなエラーが発生します。
gazelle: /bazel-sample-project/echo: go prefix is not set, so importpath can't be determined for rules. Set a prefix with a '# gazelle:prefix' comment or with -go_prefix on the command line
今回は bazel-sample-project/BUILD.bazel にコメントを記述しました。
update-repos
このままでは 外部パッケージのバージョンが固定されていないため ビルドに失敗することがあります。
そこで、 update-repos というサブコマンドを使います。
--
の後に指定した引数が gazelle に渡ります。(ターゲットに渡す引数は --
以降に記述します)
$ bazel run //:gazelle -- update-repos -from_file ./go.mod INFO: Analyzed target //:gazelle (59 packages loaded, 6979 targets configured). INFO: Found 1 target... Target //:gazelle up-to-date: bazel-bin/gazelle-runner.bash bazel-bin/gazelle INFO: Elapsed time: 5.798s, Critical Path: 0.01s INFO: 0 processes. INFO: Build completed successfully, 1 total action INFO: Build completed successfully, 1 total action
このコマンドは go.mod や Gopkg.lock のようなパッケージのバージョンを管理するファイルを指定することで 依存パッケージのリポジトリ情報を WORKSPACE に書き出してくれます。
先程の WORKSPACE の下の方に書かれていた大量の go_repository はこれによるものだったのですね。
実際に使ってみる try
これまでの概要だけでは何が嬉しいのかイマイチ分かりづらいと思います。
以降のセクションではサンプルプロジェクトを動かして解説していきます。 最終的に k8s で動かすのを目標にします。
gateway サーバと echo サーバがあって、 gateway にきたリクエストを echo に gRPC で受け流してリクエストをそのままgatewayに返却するという簡単な構成です。
それぞれのプログラム自体は簡易ですが 別々のサーバなのでビルドはそれぞれで行わなければなりません。
bazel を使わずにビルドして実行してみます。
localhost:8000 にアクセスしてから、以下のように /echo に aaa を渡します。

aaa が返ってきたので期待通りです。
bazel run
続いて bazel を使って動かしてみます。
bazel でビルドしたものをそのまま動かすには bazel run ターゲット
のようにします。
ターゲットの名前部分(:
の右側)を省略するとパッケージ名で補完されます。
以下は 実行対象の BUILD.bazel と登録された go_binary を呼び出す例です。
(service_image というのは次のセクションで説明しますが、今回の実行には関係ありません)
先ほどと同じように localhost:8000 にアクセスしてから、以下のように /echo に aaa を渡します。

期待通りですがなんか普通のビルドよりも複雑になっている気がします。
--platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64
ってなんなの?って思いましたよね。
実は最終的に Dockerのコンテナとして動かすことを想定しているため、Linuxのバイナリとして出力されるように .bazelrc を設定しているのです。
build --platforms=@io_bazel_rules_go//go/toolchain:linux_amd64
ビルドだけなら動作しますがそのバイナリをMacの環境で動かそうとすると当然動かないのです。 (cannot execute binary file って言われる)
そこで私が使っている環境で動くように --platforms オプションでMac用バイナリを吐くように制御しています。
通常(Dockerで動かさない場合)は bazel run //xxxx
だけでうまく動くはずです。
Container で動かす
コンテナを作るには (container_image と) go_image を使います。
が、今回は作成した BUILD.bazel には直接これらを書かず、service.bzl というマクロファイルに独自定義した service_image を介して利用しています。 わざわざ関数化した理由は単に複数回利用するからです。今回の例ではサービスは2つですが多くなると同じような処理を何回も書きたくありません。
マクロ関数は以下のようになっています。
load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_push")
load("@io_bazel_rules_docker//go:image.bzl", "go_image")
def service_image(name, **kwargs):
pkg_tar(
name = "tar",
srcs = ["@grpc_health_probe//file"],
mode = "0o755",
package_dir = "/bin",
visibility = ["//visibility:public"],
)
container_image(
name = "base_image",
base = "@go_image_base//image",
tars = [":tar"],
visibility = ["//visibility:public"],
)
go_image(
name = "image",
base = ":base_image",
embed = ["//" + name + ":go_default_library"],
goarch = "amd64",
goos = "linux",
visibility = ["//visibility:public"],
**kwargs
)
container_push(
name = "push_to_dockerhub",
format = "Docker",
image = ":image",
registry = "docker.io",
repository = "righm9/" + name,
tag = "{BUILD_TIMESTAMP}",
)
container_push(
name = "push_to_gcr",
format = "Docker",
image = ":image",
registry = "gcr.io",
repository = "righm9/" + name,
tag = "latest",
)
このマクロ関数は サービス名を受け取り、そのサービス名を元にビルドしたバイナリが指定されます。
実はただコンテナを作るだけであれば go_image だけでよいのですが、コンテナを tar 形式のファイルとして出力するために container_image で作ったイメージをベースに go_image を呼び出しています。 そして container_image の tars 引数に pkg_tar の tar ファイルを指定すれば tar にコンテナイメージを埋め込めるようです。 これは ドキュメントにも書かれている custom base と呼ばれる手法だそうです。
一番最後に書いた container_push はその名の通り作成したコンテナをレジストリに登録するためのものです。 Push のセクションで解説しますが、ここでは利用しません。
前置きが長くなりましたが実際に実行してみます。
一見動いているように見えますが、 http://localhost:8000/ にアクセスすることができません。 理由は gateway が echo サーバ (localhost:8001) にアクセスしてもDocker的に別ホストのため localhost で接続できず処理がブロックされHTTPサーバが起動しないためです (ブロックしないようにもできるけどechoサーバに接続できないのは同じこと)
このために gateway のプログラムは echo のホストを環境変数(ECHO_HOST)で変えられるようにしています。 つまり ECHO_HOST に実際の echo コンテナのIPアドレスを指定すればうまくいきそうです。
ただ、今回は以下の理由で bazel run
で通信させることはできませんでした。
コンテナ起動時に特定のDockerネットワークに所属させることができなかった
-
BUILD.bazel でシェルからわたした環境変数を読み出すことができなかった
container_image には env 引数で辞書形式で受け取れるが全環境で固定になってしまうのでホストから渡した環境変数を読みたい
というわけで、ビルドしたコンテナを docker コマンドで呼び出すようにしてみます。
docker network create test-network
で test-network を作り、以下を実行します。
# gateway $ docker run --rm --name="gateway" --net=test-network --env ECHO_HOST=echo -p 8000:8000 07908b17146 |
# echo $ docker run --rm --name="echo" --net=test-network 82ccbd677fb |
今度は http://localhost:8000/ にアクセスできました。

なんとか動いたようです。
bazel から直接呼び出せているわけではないのでなんか気持ち悪いですが、今回のゴールはここじゃないので目を瞑ってください。
備考
go_image の name, base, deps, binary, layers 以外のキーワード引数はすべて go_binary 関数に引き継がれます。
https://github.com/bazelbuild/rules_docker/blob/master/go/image.bzl
Push する
Bazelを使って任意のコンテナレジストリに Push することができます。
先程 container_push を書いたのはこれが理由です。
備考
container_push には go_image の名前を指定します。
container_image の名前を指定して Push しても イメージの CMDがなく No command specified と言われます。
今回のアップロード先は Docker Hub です。 予めログインを済ませて、リポジトリを作成しておく必要があります。
ここはレジストリによって操作が異なるのであんまり詳しくやりませんが、 私の場合は以下のようなリポジトリがある状態です。

今回 DockerHub に Push するための、 push_to_dockerhub と push_to_gcr の2つを用意しました。
以下は DockerHub に Push する例です。
GCR へ Push するのは GKE に kustomize を使ってデプロイしてみる を御覧ください。
Skaffold で動かす
ローカルで Kubernetes を動かすために Skaffold というツールを使います。
Bazel と同様に Google が開発したツールで、 Bazel でビルドしたコンテナを指定することができます。
以下のような設定ファイルを用意しました。
apiVersion: skaffold/v2beta1
kind: Config
deploy:
kustomize:
paths:
- ./kube/skaffold
profiles:
- name: dev
build:
artifacts:
- image: righm9/echo
bazel:
target: //echo:image.tar
- image: righm9/gateway
bazel:
target: //gateway:image.tar
local:
push: false
useBuildkit: true
activation:
- command: dev
artifacts の bazel.target にコンテナを指定できますが、 tar 形式である必要があります。 先程のマクロ関数で pkg_tar を使っていたのはこれが理由だったのです。
Kubernetes の解説になってしまいますが、 deploy.kustomize には Kustomization の定義を指定できるので、 kube/skaffold を指定しています。 これにより kube/skaffold/kustomization.yaml が読み込まれます。
-
📁 kube
-
📁 services
-
📁 echo
-
📁 base
- 🗒 deployment.yaml
- 🗒 kustomization.yaml
- 🗒 service.yaml
-
📁 overlays
-
📁 local
- 🗒 kustomization.yaml
-
📁 production
- 🗒 kustomization.yaml
- 🗒 patch.yaml
-
📁 local
-
📁 base
-
📁 gateway
-
📁 base
- 🗒 deployment.yaml
- 🗒 kustomization.yaml
- 🗒 service.yaml
-
📁 overlays
-
📁 local
- 🗒 kustomization.yaml
- 🗒 service.yaml
-
📁 production
- 🗒 kustomization.yaml
- 🗒 patch.yaml
- 🗒 service.yaml
-
📁 local
-
📁 base
-
📁 echo
-
📁 skaffold
- 🗒 kustomization.yaml
-
📁 services
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
spec:
revisionHistoryLimit: 3
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
name: echo
spec:
containers:
- image: righm9/echo
name: echo
ports:
- containerPort: 8001
name: grpc
protocol: TCP
resources:
limits:
cpu: 50m
memory: 200Mi
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
apiVersion: v1
kind: Service
metadata:
name: echo
spec:
ports:
- name: grpc
port: 8001
protocol: TCP
targetPort: 8001
selector:
app: echo
type: ClusterIP
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
patchesJson6902:
- path: patch.yaml
target:
kind: Deployment
name: echo
group: apps
version: v1
- op: replace
path: /spec/template/spec/containers/0/image
value: gcr.io/righm9/echo:latest
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
spec:
revisionHistoryLimit: 3
selector:
matchLabels:
app: gateway
template:
metadata:
labels:
app: gateway
name: gateway
spec:
containers:
- image: righm9/gateway
name: gateway
env:
- name: ECHO_HOST
value: echo.default.svc.cluster.local
ports:
- containerPort: 8000
name: grpc
protocol: TCP
resources:
limits:
cpu: 50m
memory: 200Mi
- image: nginx:1.17
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
resources:
limits:
cpu: 50m
memory: 200Mi
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
nginx.conf: |
user nginx;
worker_processes 3;
error_log /var/log/nginx/error.log;
events {
worker_connections 10240;
}
http {
log_format main
'remote_addr:$remote_addr\t'
'time_local:$time_local\t'
'method:$request_method\t'
'uri:$request_uri\t'
'host:$host\t'
'status:$status\t'
'bytes_sent:$body_bytes_sent\t'
'referer:$http_referer\t'
'useragent:$http_user_agent\t'
'forwardedfor:$http_x_forwarded_for\t'
'request_time:$request_time';
access_log /var/log/nginx/access.log main;
server {
listen 80;
server_name _;
location / {
root html;
proxy_pass http://localhost:8000;
}
}
}
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
apiVersion: v1
kind: Service
metadata:
name: gateway
spec:
ports:
- name: app
port: 8000
protocol: TCP
targetPort: 8000
- name: server
port: 80
protocol: TCP
targetPort: 80
selector:
app: gateway
type: ClusterIP
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
patchesStrategicMerge:
- service.yaml
apiVersion: v1
kind: Service
metadata:
name: gateway
spec:
type: LoadBalancer
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
patchesStrategicMerge:
- service.yaml
patchesJson6902:
- path: patch.yaml
target:
kind: Deployment
name: gateway
group: apps
version: v1
- op: replace
path: /spec/template/spec/containers/0/image
value: gcr.io/righm9/gateway:latest
apiVersion: v1
kind: Service
metadata:
name: gateway
spec:
type: LoadBalancer
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../services/echo/overlays/local
- ../services/gateway/overlays/local
skaffold/kustomization.yaml では echo と gateway のローカル用設定を 読み込み、 ローカル設定はそれぞれの base 設定を読み込んでいるという具合です。 (この記事では production は使いません)
構造化しているのでファイルは多いですがやっていることは割と単純です。
また、 gateway の ECHO_HOST 環境変数は echo.default.svc.cluster.local にしています。 これは Kubernetes 内で Pod のアドレスを解決するための FQDN です。 (詳しくは こちら)
Skaffold でサーバを起動するために skaffold dev
を実行します。
これはファイルの変更検知をして Bazel のリビルドが実行されるというすぐれものです。
(Realize とか使わなくていいですね
$ skaffold dev Listing files to watch... - righm9/echo - righm9/gateway Generating tags... - righm9/echo -> righm9/echo:0907753 - righm9/gateway -> righm9/gateway:0907753 Checking cache... - righm9/echo: Found Locally - righm9/gateway: Found Locally Tags used in deployment: - righm9/echo -> righm9/echo:82ccbd677fb859dd731e61c4d4e2cf5f80a50636eb56e85f8263b320263cf080 - righm9/gateway -> righm9/gateway:07908b17146c811733912239c242080108fcf917580cbfe0cd97e2d613cd10a2 local images can't be referenced by digest. They are tagged and referenced by a unique ID instead Starting deploy... - configmap/nginx-conf created - service/echo created - service/gateway created - deployment.apps/echo created - deployment.apps/gateway created Waiting for deployments to stabilize... - deployment/echo: waiting for rollout to finish: 0 of 1 updated replicas are available... - deployment/gateway: waiting for rollout to finish: 0 of 1 updated replicas are available... - deployment/echo is ready. [1/2 deployment(s) still pending] - deployment/gateway is ready. Deployments stabilized in 2.752441541s Watching for changes...
今回はせっかくの k8s なので gateway の前に Nginx を噛ませてみました。 gateway と同じ Pod で稼働させています。
Nginxが待ち受けている ポート 80番 にアクセスしてみます。

うまく動いているようなので完了ですね
Errors
bazel run でいくつかエラーがでてハマったのでメモしておきます。
- エラー
-
no such package '@zlib//': The repository '@zlib' could not be resolved and referenced by '@com_google_protobuf//:protobuf'
- 対応
-
WORKSPACE で以下を実行する。
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") protobuf_deps()
--
- エラー
-
com.google.devtools.build.lib.packages.BuildFileContainsErrorsException: error loading package '@io_bazel_rules_docker//toolchains/docker': Unable to load file '@bazel_skylib//:bzl_library.bzl': file doesn't exist
- 対応
-
WORKSPACE で以下を実行する。
load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies") go_rules_dependencies()
--
- エラー
-
no such package '@org_golang_google_grpc//': no such package '@bazel_gazelle_go_repository_cache//': gazelle could not find a Go SDK. Specify which one to use with gazelle_dependencies(go_sdk = "go_sdk").
- 対応
-
WORKSPACE で以下を実行する。
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains") go_register_toolchains()
--
- エラー
-
Failed to load Starlark extension '@rules_python//python:pip.bzl'. Cycle in the workspace file detected. This indicates that a repository is used prior to being defined.
- 対応
-
WORKSPACE で以下を実行する。
load( "@io_bazel_rules_docker//repositories:repositories.bzl", container_repositories = "repositories", ) container_repositories()
--
- エラー
-
no such package '@go_image_base//image': The repository '@go_image_base' could not be resolved
- 対応
-
WORKSPACE で以下を実行する。
load( "@io_bazel_rules_docker//go:image.bzl", go_image_repos = "repositories", ) go_image_repos()
デバッグのやり方まで書きたかったんですが skaffold を通していい感じに設定する方法がわからなかったので今回はここまでにしときます。
bazel でデバッグするだけなら -c dbg
でビルドしたものを delve で解析する感じかな?
Missing external sources when debugging with dlv · Issue #1708 · bazelbuild/rules_go
Breakpoint on absolute path not working · Issue #1730 · go-delve/delve
Expose test cases in test XML · Issue #236 · bazelbuild/rules_go
(資料だけおいて撤退
わかったら別の記事に上げます。もし知ってる方がいたら教えてもらえるとうれしいです。