I am building a small golang
webapp
, and I want
to push a container
up for it, which can eventually be used in
Google Cloud Run, or elsewhere.
In this post, I want to describe how I got it to push images build locally up to
googles artifact repository. It will include creating the artifac repository
using infrastructure as code
The project is in a
monorepo
that uses
bazel
.
The executable to be packaged
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # //products/example/cmd/server/BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
go_library(
name = "server_lib",
srcs = ["main.go"],
visibility = ["//visibility:private"],
)
go_binary(
name = "server",
embed = [":server_lib"],
visibility = ["//visibility:public"],
)
|
Build Container Image
add rules_oci
rules_oci
is a good place to start
to integrate container support into your bazel configuration. It’s also worth
reading up a bit on distroless
if you are not aware of it.
Adding rules_oci into bazel MODULES is straightforward:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| bazel_dep(name = "rules_oci", version = "2.2.6")
# For testing, we also recommend https://registry.bazel.build/modules/container_structure_test
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
# Declare external images you need to pull, for example:
oci.pull(
name = "distroless_base",
# 'latest' is not reproducible, but it's convenient.
# During the build we print a WARNING message that includes recommended 'digest' and 'platforms'
# values which you can use here in place of 'tag' to pin for reproducibility.
tag = "latest",
image = "gcr.io/distroless/base",
platforms = ["linux/amd64"],
)
# For each oci.pull call, repeat the "name" here to expose them as dependencies.
use_repo(oci, "distroless_base")
|
If you use the WORKSPACE, or if you want the latest version, you
can find details on their releases page
.
tar.bzl
oci_rules uses tar files to build the image. To build tar images, you will
want to use tar.bzl
.
Add the following to you MODULES
1
| bazel_dep(name = "tar.bzl", version = "0.3.0")
|
Latest version and instructions for WORKSPACE
can be found on their releases page
BUILD.bazel
There are
language specific sample build files
that you can start from.
Starting
from the example go BUILD.bazel
,
and simplifying it, I have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # //products/example/deploy/BUILD.bazel
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load", "oci_push")
load("@tar.bzl", "mutate", "tar")
# Put app go_binary into a tar layer.
tar(
name = "app_layer",
srcs = ["//products/muster/cmd/server:server"],
out = "app_layer.tar",
mutate = mutate(strip_prefix = package_name() + "/app_"),
)
oci_image(
name = "image",
# This is defined by an oci.pull() call in /MODULE.bazel
base = "@distroless_base",
entrypoint = ["/app"],
# Link the resulting image back to the repository where the build is defined.
labels = {
"org.opencontainers.image.source": "https://github.com/aspect-build/bazel-examples",
},
tars = [":app_layer"],
)
|
There is
more information at the go doc page
including details of migrating from
rules_docker
1
2
3
4
5
6
| products/example/deploy $ bazel build ...
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
INFO: Elapsed time: 0.444s, Critical Path: 0.07s
INFO: 2 processes: 1 internal, 1 linux-sandbox.
INFO: Build completed successfully, 2 total actions
|
Building of the images now completes
Push Image
Pushing the image can be pretty straightforward:
- Enable the container repository in Google Cloud
- Configure it in the
BUILD.bazel file bazel run to push
I would like to do the whole thing through IaC
so that it is
- Repeatable, and
- more importantly documented
terraform
has no bazel support, and
is a questionable choice from an ethics perspective. opentofu
has
rules_tf
. However, that
does not support apply
Google Cloud Infrastucture Manager
is vendor lock in.
That leaves us with pulumi
(If there is another
alternative, please let me know).
I like the stacks concept in pulumi, and while it doesn’t have bazel
integration, what it does have is an
automation api
. While not
he best documented, there is enough documentation out there to be able to piece
it together.
With this, we can build a runnable unit, and then run it too. I’ve experimented
with tagging specific runnables and then running them through the CI. I won’t
get that far in this post. My focus here is to get it working locally.
Enable Artifact Repository
In pulumi, you can enable the artifact repository with:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
func enableArtifactRepository(ctx *pulumi.Context) error {
// Set your GCP project and region
projectID := "<projectId>"
region := "europe-west1"
repoName := "<repo-name>"
// Create Artifact Registry repository
repo, err := artifactregistry.NewRepository(ctx, repoName, &artifactregistry.RepositoryArgs{
Format: pulumi.String("DOCKER"),
Location: pulumi.String(region),
RepositoryId: pulumi.String(repoName),
Description: pulumi.String("Repository for OCI container images"),
Project: pulumi.String(projectID),
})
if err != nil {
return err
}
// Export the repository URL (used for pushes)
ctx.Export("repositoryURL", pulumi.Sprintf("%s-docker.pkg.dev/%s/%s", region, projectID, repo.Name))
return nil
}
|
Authenticating against the repo
You need to authenticate against the repository so that the oci_rule can pick
it up
1
| gcloud auth configure-docker europe-west1-docker.pkg.dev
|
Define the push target
1
2
3
4
5
6
7
8
9
| oci_push(
name = "push",
image = ":image",
remote_tags = [
"latest",
"1h",
],
repository = "europe-west1-docker.pkg.dev/<projectId>/<repo-name>/<image-name>",
)
|
Pushing
Once all the above is set up, you can then push the image with one of the
following:
1
2
| bazel run :push # if you in the directory
bazel run //<target/path>:push # fron anywhere in the repo
|
Side note
Please do not take my heavy usage of google and its products to be a fan letter
or an endorsement. They might at best, be the lesser evil.