This repository contains rules for interacting with Kubernetes configurations / clusters.
Add the following to your WORKSPACE
file to add the external repositories:
git_repository(
name = "io_bazel_rules_docker",
remote = "https://github.com/bazelbuild/rules_docker.git",
commit = "{HEAD}",
)
load(
"@io_bazel_rules_docker//docker:docker.bzl",
"docker_repositories",
)
docker_repositories()
# This requires rules_docker to be fully instantiated before
# it is pulled in.
git_repository(
name = "io_bazel_rules_k8s",
remote = "https://github.com/mattmoor/rules_k8s.git",
commit = "{HEAD}",
)
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories")
k8s_repositories()
As is somewhat standard for Bazel, the expectation is that the
kubectl
toolchain is preconfigured to authenticate with any clusters
you might interact with.
For more information on how to configure kubectl
authentication, see the
Kubernetes documentation.
For Google Container Engine (GKE), the gcloud
CLI provides a simple
command
for setting up authentication:
gcloud container clusters get-credentials <CLUSTER NAME>
load("@io_bazel_rules_k8s//k8s:object.bzl", "k8s_object")
k8s_object(
name = "dev",
kind = "deployment",
cluster = "my-gke-cluster",
# A template of a Kubernetes Deployment object yaml.
template = ":deployment.yaml.tpl",
# Format strings within the above template of the form:
# {environment} and {replicas}
# These can be embedded in other strings, e.g.
# image: gcr.io/my-project/my-image:{environment}
# You can think of these as augmenting the stamp variables
# supported by docker_{push,bundle}, but they also support
# stamp variables in their own values, see below.
substitutions = {
"environment": "{BUILD_USER}",
"replicas": "1",
},
# When the `:dev`, `:dev.create` or `:dev.replace`
# targets are `bazel run` these images are published and
# their digest is substituted into the template in place of
# what is currently published.
images = {
"gcr.io/convoy-adapter/bazel-grpc:{environment}": "//server:image"
},
)
Here, deployment.yaml.tpl
is a templatized Kubernetes Deployment resource,
e.g.
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: hello-world-{environment}
spec:
replicas: {replicas}
template:
metadata:
labels:
app: hello-world-{environment}
spec:
containers:
- name: hello-world
image: gcr.io/my-project/hello-world:{environment}
# If you follow the best practice of deploying by digest,
# then this stops being necessary.
imagePullPolicy: Always
ports:
- containerPort: 1234
In your WORKSPACE
you can set up aliases for a more readable short-hand:
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_defaults")
k8s_defaults(
# This becomes the name of the @repository and the rule
# you will import in your BUILD files.
name = "k8s_deploy",
kind = "deployment",
cluster = "my-gke-cluster",
)
Then in place of the above, you can use the following in your BUILD
file:
load("@k8s_deploy//:defaults.bzl", "k8s_deploy")
k8s_deploy(
name = "dev",
template = ":deployment.yaml.tpl",
substitutions = {
"environment": "{BUILD_USER}",
"replicas": "1",
},
images = {
"gcr.io/convoy-adapter/bazel-grpc:{environment}": "//server:image"
},
)
Suppose my team uses different clusters for development and production,
instead of a simple k8s_deploy
, you might use:
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_defaults")
k8s_defaults(
name = "k8s_dev_deploy",
kind = "deployment",
cluster = "my-dev-cluster",
)
k8s_defaults(
name = "k8s_prod_deploy",
kind = "deployment",
cluster = "my-prod-cluster",
)
This single target exposes a rich set of actions that empower developers
to effectively deploy applications to Kubernetes. We will follow the :dev
target from the example above.
Users can instantiate their deployment.sh.tpl
by simply running:
bazel build :dev
Following the above example, this would result in a single replica deployment
namespaced to my username written to bazel-bin/dev.yaml
:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: hello-world-mattmoor
spec:
replicas: 1
template:
metadata:
labels:
app: hello-world-mattmoor
spec:
containers:
- name: hello-world
image: gcr.io/my-project/hello-world:mattmoor
imagePullPolicy: Always
ports:
- containerPort: 1234
Deploying with tags, especially in production, is a bad practice because they are mutable. If a tag changes, it can lead to inconsistent versions of your app running after auto-scaling or auto-healing events. Thankfully in v2 of the Docker Registry, digests were introduced. Deploying by digest provides cryptographic guarantees of consistency across the replicas of a deployment.
You can "resolve" the instantiated template by running:
bazel run :dev
This command will publish any images = {}
present in your rule, substituting
those exact digests into the yaml template, and for other images resolving the
tags to digests by fetching their manifests.
Continuing with our example, this would result in the following being printed
to STDOUT
:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: hello-world-mattmoor
spec:
replicas: 1
template:
metadata:
labels:
app: hello-world-mattmoor
spec:
containers:
- name: hello-world
image: gcr.io/my-project/hello-world@sha256:deadbeefbadf00d
imagePullPolicy: Always
ports:
- containerPort: 1234
Users can create an environment by running:
bazel run :dev.create
This deploys the resolved template, which includes publishing images.
Users can update (replace) their environment by running:
bazel run :dev.replace
Like .create
this deploys the resolved template, which includes
republishing images. This action is intended to be the workhorse
of fast-iteration development (rebuilding / republishing / redeploying).
Users can tear down their environment by running:
bazel run :dev.delete
It is notable that despite deleting the deployment, this will NOT delete
any services currently load balancing over the deployment (e.g. .expose
below). This is intentional as creating load balancers can be slow.
Users can "expose" their environment by running:
bazel run :dev.expose
This does not currently wait for the load balancer to get fully created, which can be quite slow. You can check the status by running:
kubectl --cluster="..." get service
... and waiting for the appropriately namespaced service to have an external IP.
Users can "describe" their environment by running:
bazel run :dev.describe
k8s_object(name, kind, cluster, template, substitutions, images)
A rule that instantiates templated Kubernetes yaml and enables a variety of interactions with that object.
Attributes | |
---|---|
name |
Unique name for this rule. |
kind |
The kind of the Kubernetes object in the yaml. |
cluster |
The name of the K8s cluster with which
|
template |
A templatized form of the Kubernetes yaml. The yaml is allowed to container |
substitutions |
A set of replacements to make across the template. Instances of each key surrounded by |
images |
Identical to the |
k8s_defaults(name, kind, cluster)
A repository rule that allows users to alias k8s_object
with default values
for kind
and/or cluster
.
Attributes | |
---|---|
name |
The name of the repository that this rule will create. Also the name of rule imported from
|
kind |
The kind of objects the alias of |
cluster |
The name of the K8s cluster with which
|