Friday, February 17, 2017

Bazel + Go + Google App Engine

In my previous post, I shared some of my Bazel rules for external dependencies with bazel for go. While I got my code to compile, Deployment to the Google App Engine Flex Environment failed due to missing dependencies.

I primarily want to use Bazel because I want to have deterministic dependencies. Vendoring in Go by default pull from head, which isn't the behavior I want - especially when working with a remote team. I initially used govendor to pull deps at a specific commit into a vendor directory in my source tree - that commit was defined in the Bazel file. This turned out to be clunky - I was pulling deps twice.

Since the App Engine Flex Environment allows you to deploy any Docker container in Google Container Registry, I decided instead to use Bazel's docker_build rule to just build my container.

This is mostly straight forward. The only catch is that I wanted to have the base layer of y container be the GAE Go base layer. There isn't currently any way to define a remote base layer in Bazel's rules. The solution here is to build a tarball using the base layer, and then use a new_http_archive rule to pull down the tarball. Here are the steps to do that:

$ gcloud docker pull gcr.io/google-appengine/golang:1.6
$ docker save gcr.io/google-appengine/golang -o aegolang1.6
$ gzip aegolang1.6.tar

I then uploaded the tarball to a Google Cloud Storage bucket. And added the following modifications to my Bazel files.

in WORKSPACE:

new_http_archive(
    name = "appengine_go",
    url = "https://storage.googleapis.com/path_to_tarball/base_image.tar.gz",
    type = "tar.gz",
    build_file = "appenginego.BUILD",
)

Notice I've defined a build_file above. So now we have to fill that in as well - so I created a new file called appenginego.BUILD with the following contents:

load("@bazel_tools//tools/build_defs/docker:docker.bzl", "docker_build")

# Extract .xz files
genrule(
    name = "aego_tar",
    srcs = ["aegolang1.6.tar.gz"],
    outs = ["aego_tar.tar"],
    cmd = "cat $< | gunzip >$@",
)

docker_build(
    name = "aego",
    tars = [":aego_tar"],
    visibility = ["//visibility:public"],
)

And then I use that in my application build file:

load("@bazel_tools//tools/build_defs/docker:docker.bzl", "docker_build")

docker_build(
    name = "aego",
    base = "@appengine_go//:aego",
)

docker_build(
    name = "image",
    files = [":api"],
    entrypoint = ["/api"],
    base = ":aego",
    ports = ["8080"],
    repository = "gcr.io/your_repo"
)

The final steps for deployment here are to upload the new container to GCR and then deploy. 

First you'll want to load your image into docker. You can do this by running the following command:

$ bazel run src/path/to/your/build/rule:image

Now issuing a "docker images" call should list your newly built image as gcr.io/your_repo/path_to_your_build. 

Next, do the actual push to gcr:

$ gcloud docker push gcr.io/your_repo/path_to_your_build

Finally to deploy your awesome new container you'll run:

$ gcloud app deploy --image-url=gcr.io/your_repo/path_to_your_build:image

And that's it.  This allows you to completely use Bazel for building and handling external deps without worrying about manual vendoring work.  ᕕ( ᐛ )ᕗ







Saturday, February 4, 2017

Bazel with Go

I've spent a bit of time this afternoon trying to get bazel working with Go.

The bazel documentation isn't entirely clear at this point, so I'm doing a short writeup of my experience here.

I essentially want to bazel-ise the Google Cloud Enpoints with Go Tutorial. I'm working in a private repo, so I won't be sharing links to my repo.

The first issue I came across was how to handle external dependencies. In the sample app.go has a dependency on "github.com/gorilla/mux".

Its not too difficult to handle external dependencies. In your WORKSPACE file at the root of your repo, you'll want to include:

load("@io_bazel_rules_go//go:def.bzl", "new_go_repository")
new_go_repository(
  name = "com_github_gorilla_mux",
  importpath = "github.com/gorilla/mux",
  commit = "392c28fe23e1c45ddba891b0320b3b5df220beea",
)


I chose the commit hash of the most recent tagged version. You can specify tag here instead of commit if you prefer.

Next up, in your BUILD file that sits next to your code, you'll need to actually add a dependency to mux. Below is the entirety of my BUILD file, as it currently stands.

load("@io_bazel_rules_go//go:def.bzl", "go_binary", "new_go_repository")

go_binary(
    name = "api",
    srcs = ["app.go"],
    deps = ["@com_github_gorilla_mux//:go_default_library"]
)

It compiles! Hooray! That's it for now, I'll add more posts as I progress with this.