<p>I have a monorepo with some backend (<a href="https://nodejs.org/" rel="noreferrer">Node.js</a>) and frontend (<a href="https://angular.io/" rel="noreferrer">Angular</a>) services. Currently my deployment process looks like this:</p>

<ol>
<li>Check if tests pass</li>
<li>Build docker images for my services</li>
<li>Push docker images to container registry</li>
<li>Apply changes to Kubernetes cluster (<a href="https://cloud.google.com/kubernetes-engine/" rel="noreferrer">GKE</a>) with <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" rel="noreferrer">kubectl</a></li>
</ol>

<p>I'm aiming to automate all those steps with the help of <a href="https://bazel.build/" rel="noreferrer">Bazel</a> and <a href="https://cloud.google.com/cloud-build/" rel="noreferrer">Cloud Build</a>. But I am really struggling to get started with Bazel:</p>

<p>To make it work I'll probably need to add a <code>WORKSPACE</code> file with my external dependencies and multiple <code>BUILD</code> files for my own packages/services? <em>I need help with the actual implementation:</em></p>

<ol>
<li>How to build my Dockerfiles with Bazel?</li>
<li>How push those images into a registry (preferably <a href="https://cloud.google.com/container-registry/" rel="noreferrer">GCR</a>)?</li>
<li>How to apply changes to Google Kubernetes Engine automatically?</li>
<li>How to integrate this toolchain with Google Cloud Build?</li>
</ol>

<h1>More information about the project</h1>

<blockquote>
  <p>I've put together a tiny <a href="https://github.com/flolude/learning-bazel-monorepo" rel="noreferrer">sample monorepo</a> to showcase my use-case</p>
</blockquote>

<h3>Structure</h3>

<pre><code>├── kubernetes
├── packages
│   ├── enums
│   ├── utils
└── services
    ├── gateway
</code></pre>

<h3>General</h3>

<ul>
<li><code>Gateway</code> service depends on <code>enums</code> and <code>utils</code></li>
<li>Everything is written in Typescript</li>
<li>Every service/package is a Node module</li>
<li>There is a <code>Dockerfile</code> inside the <code>gateway</code> folder, which I want to be built</li>
<li>The Kubernetes configuration are located in the <code>kubernetes</code> folder.</li>
<li>Note, that I don't want to publish any <code>npm</code> packages!</li>
</ul>

<p>What we want is a portable Docker container that holds our Angular app along with its server and whatever machine image it requires, that we can bring up on any Cloud provider, We are going to create an entire pipeline to be incremental. &quot;Docker Rules&quot; are fast. Essentially, it provides instrumentality by adding new Docker layers, so that the changes you make to the app are the only things sent over the wire to the cloud host. In addition, since Docker images are tagged with a SHA, we only re-deploy images that changed. To manage our production deployment, we will use Kubernetes, for which Bazel rules also exist. Building a docker image from Dockerfile using Bazel is not possible to my knowledge because
it's by design not allowed due to non-hermetic nature of Dockerfile. (Source:
<a href="https://blog.bazel.build/2015/07/28/docker_build.html" rel="nofollow noreferrer">Building deterministic Docker images with Bazel</a>)</p>
<p>The changes done as part of the source code are going to get deployed in the Kubernetes Cluster, This is one way to achieve the following using Bazel.</p>
<ol>
<li><p>We have to put Bazel in watch mode, Deploy replace tells the Kubernetes cluster to update the deployed version of the app.
a.</p>
<p>Command : ibazel run :deploy.replace</p>
</li>
<li><p>In case there are any source code changes do it in the angular.</p>
</li>
<li><p>Bazel incrementally re-builds just the parts of the build graph that depend on the changed file, In this case, that includes the ng_module that was changed, the Angular app that includes that module, and the Docker nodejs_image that holds the server. As we have asked to update the deployment, after the build is complete it pushes the new Docker container to Google Container Registry and the Kubernetes Engine instance starts serving it. Bazel understands the build graph, it only re-builds what is changed.</p>
</li>
</ol>
<p>Here are few Snippet level tips, which can actually help.</p>
<p><strong>WORKSPACE FILE:</strong></p>
<p>Create a Bazel Workspace File, The WORKSPACE file tells Bazel that this directory is a &quot;workspace&quot;, which is like a project root. Things that are to be done inside the Bazel Workspace are listed below.
•   The name of the workspace should match the npm package where we publish, so that these imports also make sense when referencing the published package.
•   Mention all the rules in the Bazel Workspace using &quot;http_archive&quot; , As we are using the angular and node the rules should be mentioned for rxjs, angular,angular_material,io_bazel_rules_sass,angular-version,build_bazel_rules_typescript, build_bazel_rules_nodejs.
•   -Next we have to load the dependencies using &quot;load&quot;.    sass_repositories, ts_setup_workspace,angular_material_setup_workspace,ng_setup_workspace,
•   Load the docker base images also , in our case its &quot;@io_bazel_rules_docker//nodejs:image.bzl&quot;,
•   Dont forget to mention the browser and web test repositaries
web_test_repositories()
browser_repositories(
chromium = True,
firefox = True,
)</p>
<p><strong>&quot;BUILD.bazel&quot; file.</strong></p>
<p>•   Load the Modules which was downloaded ng_module, the project module etc.
•   Set the Default visiblity using the &quot;default_visibility&quot;
•   if you have any Jasmine tests use the ts_config and mention the depndencies inside it.
•   ng_module (Assets,Sources and Depndeencies should be mentioned here )
•   If you have Any Lazy Loading scripts mention it as part of the bundle
•   Mention the root directories in the web_package.
•   Finally Mention the data and the welcome page / default page.</p>
<p>Sample Snippet:</p>
<pre><code>load(&quot;@angular//:index.bzl&quot;, &quot;ng_module&quot;)
ng_module(
    name = &quot;src&quot;,
    srcs = glob([&quot;*.ts&quot;]),
    tsconfig = &quot;:tsconfig.json&quot;,
    deps = [&quot;//src/hello-world&quot;],
)
load(&quot;@build_bazel_rules_nodejs//:future.bzl&quot;, &quot;rollup_bundle&quot;)
rollup_bundle(
  name = &quot;bundle&quot;,
  deps = [&quot;:src&quot;]
  entry_point = &quot;angular_bazel_example/src/main.js&quot;
)
</code></pre>
<p>Build the Bundle using the Below command.</p>
<pre><code>bazel build :bundle
</code></pre>
<p><strong>Pipeline : through Jenkins</strong></p>
<p>Creating the pipeline through Jenkins and to run the pipeline there are stages. Each Stage does separate tasks, But in our case we use the stage to publish the image using the BaZel Run.</p>
<pre><code>pipeline {
  agent any
  stages {
    stage('Publish image') {
      steps {
        sh 'bazel run //src/server:push'
      }
    }
  }
}
</code></pre>
<p>Note :</p>
<pre><code>bazel run :dev.apply
</code></pre>
<ol>
<li><p>Dev Apply maps to kubectl apply, which will create or replace an existing configuration.(For more information see the kubectl documentation.) This applies the resolved template, which includes republishing images. This action is intended to be the workhorse of fast-iteration development (rebuilding / republishing / redeploying).</p>
</li>
<li><p>If you want to pull containers using the workpsace file use the below tag</p>
<p>container_pull(
name = &quot;debian_base&quot;,
digest = &quot;sha256:**&quot;,
registry = &quot;gcr.io&quot;,
repository = &quot;google-appengine/debian9&quot;,
)</p>
</li>
</ol>
<p>If GKE is used, the gcloud sdk needs to be installed and as we are using GKE(Google Contianer Enginer), It can be authenticated using the below method.</p>
<pre><code>gcloud container clusters get-credentials &lt;CLUSTER NAME&gt;
</code></pre>
<p>The Deploymnet Object should be mentioned in the below format:</p>
<p>load(&quot;@io_bazel_rules_k8s//k8s:object.bzl&quot;, &quot;k8s_object&quot;)</p>
<pre><code>k8s_object(
  name = &quot;dev&quot;,
  kind = &quot;deployment&quot;,
  template = &quot;:deployment.yaml&quot;,
  images = {
    &quot;gcr.io/rules_k8s/server:dev&quot;: &quot;//server:image&quot;
  },
)
</code></pre>
<p>Sources :</p>
<ul>
<li><a href="https://docs.bazel.build/versions/0.19.1/be/workspace.html" rel="nofollow noreferrer">https://docs.bazel.build/versions/0.19.1/be/workspace.html</a></li>
<li><a href="https://github.com/thelgevold/angular-bazel-example" rel="nofollow noreferrer">https://github.com/thelgevold/angular-bazel-example</a></li>
<li><a href="https://medium.com/@Jakeherringbone/deploying-an-angular-app-to-kubernetes-using-bazel-preview-91432b8690b5" rel="nofollow noreferrer">https://medium.com/@Jakeherringbone/deploying-an-angular-app-to-kubernetes-using-bazel-preview-91432b8690b5</a></li>
<li><a href="https://github.com/bazelbuild/rules_docker" rel="nofollow noreferrer">https://github.com/bazelbuild/rules_docker</a></li>
<li><a href="https://github.com/GoogleCloudPlatform/gke-bazel-demo" rel="nofollow noreferrer">https://github.com/GoogleCloudPlatform/gke-bazel-demo</a></li>
<li><a href="https://github.com/bazelbuild/rules_k8s#update" rel="nofollow noreferrer">https://github.com/bazelbuild/rules_k8s#update</a></li>
<li><a href="https://codefresh.io/howtos/local-k8s-draft-skaffold-garden/" rel="nofollow noreferrer">https://codefresh.io/howtos/local-k8s-draft-skaffold-garden/</a></li>
<li><a href="https://github.com/bazelbuild/rules_k8s" rel="nofollow noreferrer">https://github.com/bazelbuild/rules_k8s</a></li>
</ul>
