A while back I stumbled upon an issue on the faas-cli repo for OpenFaaS from one user Anders about faas-cli failing to use downloaded templates and deploy an OpenFaaS function to GKE.

You can read about the whole issue in detail here

Upon some investigative git blame and with the help of Lucas and Alex I figured out that we had migrated to a non-root user earlier this year following the best practices of app packaging using Docker

git blame

Using root user for a Docker container can be dangerous at best be it making an intruder of your app into the host machine and letting them run malicious commands on the host as root, in simple words you can take it the same way of how putting all unchecked powers of a state into the hands of one can bring havoc to its people!

Puns aside, there are certain situations where a root user is needed, for example Cloud Build where the CI/CD pipeline runs with apps and build tools running inside of containers, running these tools with such requirements means there are times when you need root permissions to perform specific tasks, like creating a directory or creating a file.

Apart from the introducing a root variant of the faas-cli Docker image, we also introduced a breaking change by moving from CMD to ENTRYPOINT which would break the CI/CD systems of existing users, but it was particularly necessary because of the subtle differences between the two commands, making it impossible for us to securely login into OpenFaaS with overridden commands.

So now we have two variants of images for faas-cli, one as a non-root user and the other as a root for CI/CD environments.

I've previously raised an issue and a PR to address the same

We would be trying to deploy a simple OpenFaaS function to a GKE Cluster with the root variant of the faas-cli image with a CI/CD pipeline on Google Cloud Build.

Let's go from scratch to substance!

But we need a cluster first!

gcloud container clusters create playground \
--num-nodes 3 \
--machine-type e2-small \
--disk-size 15G \
--zone us-central1-a \

While our cluster is getting provisioned let's get through the steps of creating the credentials to authenticate the deployment of the OpenFaaS functions with faas-cli.

Lets create a 🔑  and a 🔑💍  with Google Cloud KMS

Create a keyring

# Create a keyring
gcloud kms keyrings create openfaas --location global

# To verify that your keyring was created, list your keyring with
gcloud kms keyrings list --location global

Now that a keyring is created, we'll be creating a symmetric key for it

gcloud kms keys create openfaas-gateway-passwd \
--keyring openfaas \
--location global \
--purpose "encryption"

Create a random password for authenticating to OpenFaaS with faas-cli

PASSWORD=$(head -c 12 /dev/urandom | shasum | cut -d' ' -f1)

Encrypt the PASSWORD with the KMS symmetric key and encode with base64

# This will save the base64 encoded and encrypted password in the passwd.txt file

echo -n $PASSWORD | gcloud kms encrypt \
--key openfaas-gateway-passwd \
--keyring openfaas \
--location global \
--plaintext-file=- \
--ciphertext-file=- | base64 > passwd.txt

Meanwhile our cluster is ready roll!

Lets's fetch the configuration of our GKE cluster so that we can cuddle with it!

gcloud container clusters get-credentials playground

To verify that kubectl can reach our cluster

kubectl get nodes


Since we're on GKE we need an extra step of creating a clusterrolebinding, so let's create one

kubectl create clusterrolebinding "cluster-admin-$(whoami)" \
--clusterrole cluster-admin \
--user="$(gcloud config get-value core/account)"

Install OpenFaaS on the GKE Cluster

There are two ways to install OpenFaaS on GKE you can either use arkade which is easier or use helm which is more configurable, it's up to you what you might want to use. I'm going to use arkade today!

The fastest and easiest way is to use arkade

# Download arkade for MacOS / Linux
curl -sLSf https://dl.get-arkade.dev | sudo sh

# Download arkade for Windows (using Git Bash)
curl -sLSf https://dl.get-arkade.dev | sh

# Install OpenFaaS with
arkade install openfaas -l --basic-auth-password $PASSWORD

# Download faas-cli with arkade
arkade get faas-cli
# Follow the output commands to install it

# Use kubectl to get the external IP address of the OpenFaaS Gateway
kubectl get svc gateway-external -n openfaas

# Login with faas-cli to your OpenFaas deployment
echo -n $PASSWORD | faas-cli login -g <EXTERNAL-IP:PORT> --password-stdin

You should get something like this after running the last two commands

Let's shift our attention to the code and repository now!

Create a directory to hold our hello world demo app

mkdir gcb-openfaas-golang
cd gcp-openfaas-golang

You can choose from a lot of function templates from the store or even use one of your own if you have specific needs.


# Pull the golang-http template
faas-cli template store pull golang-http

# Create your new hello-world function
faas new hello-world --lang golang-http -g <EXTERNAL_IP:PORT> -p gcr.io/<GCP_PROJECT_ID>

For my configuration with my specific IP and projectID, I got this output upon successful execution

Create a git repo in the current dir

# Create a git repo
git init
git add .
git commit -m "Initial Commit"

# Create a Github repo, I'll be using hub since I already have it configured, but you can use Github's web UI to create one
hub create

Now comes the configuration of our build steps in cloudbuild.yaml

Every 'step' in Cloud Build runs inside of a Docker container, making Cloud Build versatile enough to build, test, and deploy for anything that can be packaged into a container. Today we're just concerned with building and deploying our OpenFaaS function.

You can refer to the sample repo here

The cloudbuild.yaml looks something like this:

- id: "Pull Template"
  name: 'openfaas/faas-cli:latest-root'
  args: ['template', 'store', 'pull', golang-http]

- id: "Do a shrinkwrap build"
  name: 'openfaas/faas-cli:latest-root'
  args: ['build', '-f', './hello-world.yml', '--shrinkwrap']

# $PROJECT_ID and $SHORT_SHA are env vars internal to Cloud Build and are set automatically, so no need to replace them with your own values here!
- id: "Build the docker image"
  name: "gcr.io/cloud-builders/docker"
  args: ['build', '-t=gcr.io/$PROJECT_ID/hello-world:$SHORT_SHA', '-t=gcr.io/$PROJECT_ID/hello-world:latest', '-f', './build/hello-world/Dockerfile', './build/hello-world/']

- id: "Push docker image"
  name: "gcr.io/cloud-builders/docker"
  args: ['push', 'gcr.io/$PROJECT_ID/hello-world:latest']

- id: "Login with faas-cli login"
  name: "openfaas/faas-cli:latest-root"
  entrypoint: "sh"
  args: ['-c', 'echo -n $${OPENFAAS_GATEWAY_PASSWD} | faas-cli login --password-stdin -g ${_GATEWAY}']

- id: "Deploy to Openfaas"
  name: "openfaas/faas-cli:latest-root"
  args: ['deploy', '-f', './hello-world.yml', '-g', '${_GATEWAY}']

# A temporary volume attached to all the build steps, necessary to store the creds and shrinkwrap build of the function
    - name: 'buildvol'
      path: '/openfaas'

# Substitute the `_GATEWAY` env var with the IP addr and port from the output you got earlier with `kubectl get svc gateway-external -n openfaas`

# Reference to the encrypted OpenFaas Gateway Password
# Make sure to replace the references to the project ID and the names of keyrings and keys where necessary
# The value of `OPENFAAS_GATEWAY_PASSWD` is the base64 encoded and encrypted password we got and the contents of which we stored in passwd.txt file earlier.
- kmsKeyName: "projects/utsav-talks/locations/global/keyRings/openfaas/cryptoKeys/openfaas-gateway-passwd"

Add the cloudbuild.yaml to git

git add cloudbuild.yaml
git commit -m "Add cloudbuild.yaml"

Let's get to Google Cloud Console and setup our build trigger




On the next screen, select the source, I'm selecting Github as my remote source


Since the current project repo is not showing up for me to select, I selected "Edit repositories on Github"


Punch in your Github Password


Search and select our current repository and click "Save"


Switch to Cloud Build's Connect Repository page and select the repo and click "Connect Repository"


Now that the trigger is setup, let's push our last commit to Github

# Push the latest changes
git push

I got an unpleasant surprise when I switched to Build History page on Cloud Build

Hmm 🤔 

Looks like the Cloud Builder's service account doesn't have decrpt permissions to our symmetric key in Google Cloud Key Management Service (KMS), let's change that

Grant the required permissions with gcloud

gcloud kms keys add-iam-policy-binding <KEY> \
--keyring <KEYRING> \
--location global \
--member serviceAccount:<SERVICEACCOUNTEMAIL> \
--role roles/cloudkms.cryptoKeyEncrypterDecrypter

I got something like this as the output

And we're done! Let's test it by pushing a silly minor change to our response in the function handler for the sake of a git push and...

Perform a git commit and push with minor changes to the response

git add .
git commit -m "Update response"
git push -f

And out build passes this time!


The result?

Our function is deployed successfully to OpenFaaS running on GKE!


You can imagine me running some tests in one of the steps in the cloudbuild.yaml and that would make this setup in a production ready state!

If you have any doubts, suggestions, comment below or reach out to me on Twitter!
I would love to know your thoughts! Happy Hacking!