Shhh… Golang (!!) Cloud Functions

Daz Wilkin
Feb 2, 2018 · 7 min read

2018–09–21 Update: Google is running an early access for “Go on Cloud Functions” (link). I recommend you sign-up for the early access rather than pursue the approach outlined in this story.

My colleague (link) has been exploring .NET-based Cloud Functions (here). His work builds upon a clever project by some Googlers (link) that provides a native Go runtime for Google Cloud Functions. This post takes that solution and mashes it w/ Google Container Builder to simplify the process.

https://github.com/GoogleCloudPlatform/cloud-functions-go

NB the disclaimer: “This is not an official Google product. It is not and will not be maintained by Google, and is not part of Google Cloud Functions project. There is no guarantee of any kind, including that it will work or continue to work, or that it will supported in any way.”

Fork the GitHub repo

Fork

We will use (changes to) the forked repo as the trigger for deployments. If we’re clever, our first push to the forked repo will successfully trigger our first Cloud Functions deployment of a Go binary. Grab the link from the “Clone or download” button as you’ll need that soon.

Setup Google Cloud Platform project

You may use Google Cloud Platform (GCP) for free (link). We’ll use Google Cloud Functions, Google Cloud Container Builder and Google Cloud Source Repositories and all of them is covered with “always free” usage limits.

If you don’t have a project to use:

PROJECT=[[YOUR-PROJECT]]
gcloud projects create ${PROJECT}

You must reference your billing account even if you’re getting free usage:

BILLING=[[YOUR-BILLING]]
gcloud beta billing projects link $PROJECT \
--billing-account=$BILLING

Enable Cloud Functions, Container Builder and Source Repositories:

for SERVICE in cloudbuild cloudfunctions sourcerepo
do
gcloud services enable ${SERVICE}.googleapis.com \
--project=${PROJECT}
done

Container Builder needs permission to deploy Cloud Functions on our behalf. The Container Builder (!) service account’s email id includes our project’s number, so we must identify the project number and then use that to add the “cloudfunctions.developer” role (including the appropriate permissions) to the service account:

NUM=$(gcloud projects describe $PROJECT \
--format='value(projectNumber)')
gcloud projects add-iam-policy-binding ${PROJECT} \
--member=serviceAccount:${NUM}@cloudbuild.gserviceaccount.com \
--role=roles/cloudfunctions.developer

OK, that’s sufficient for now. The next step is to link the forked GitHub repo as a Container Builder Build Trigger. This requires us to use the Console:

https://console.cloud.google.com/gcr/triggers?project=[[YOUR-PROJECT]]

Container Registry: Build Trigger

Click “Create trigger” and choose “GitHub”:

Container Registry: GitHub

You may be prompted to authenticate to GitHub and should then be presented with a list of your GitHub repos. Choose your “cloud-functions-go” fork and, if you agree, click “I consent”, to proceed.

The Trigger’s “Name” is optional and I leave it empty. I’ve left the “Trigger type” as “Branch” and “.*”. For “Build configuration” please choose “cloudbuild.yaml” (we’ve not yet created this). Under “Substitution variables”, please add “_NAME” with a value of “helloworld” and “_ENTRY_POINT” with a value of “helloworld”:

Container Registry: Create Trigger

Click “Create trigger”:

Done

Any changes pushed to the GitHub fork will trigger this (to be defined) Container Builder process.

Clone GitHub Repo

Create a local clone of your GitHub repo. We’ll be adding configuration files for Container Builder to this. We’ll push the change caused by the addition of this file to trigger our first Cloud Functions deployment. You’ll need the link from the GitHub “Clone or download” button:

DIR=[[YOUR-DIR]]mkdir -p ${HOME}/tmp/${DIR} && cd ${HOME}/tmp/${DIR}git clone https://github.com/[[YOUR-GITHUB]]/cloud-functions-go.gitcd cloud-functions-go

Container Builder

Let’s now define the Container Builder process. It will:

  • Build a static Golang binary
  • Perform the npm install of the Node.js code
  • Deploy the result to Cloud Functions

Here’s the script please create this in the “cloud-functions-go” directory:

We’re using Google-provided Builders (gcr.io/cloud-builders) for each of the 3 steps. In the 3rd which deploys the function, we provide the parameters that you created in the Build trigger and replace ${_NAME} with “helloworld” and use “helloworld” for the entry-point.

I recommend you also use the following for .gitignore. This file (although hidden) should already exist in “cloud-functions-go”:

build
/examples
/nodego
*.go
*.md
*.sh
function.zip
Makefile
Vagrantfile

Once you’ve added “cloudbuild.yaml” and changed “.gitignore”, a “git status” should:

git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: .gitignore
modified: cloudbuild.yaml
no changes added to commit (use "git add" and/or "git commit -a")

And you may:

git add cloudbuild.yaml .gitignore
git commit --message "Added Container Builder config"
git push

You’ll be prompted to — and should — authenticate to GitHub for the push.

Very shortly after the push completes, the build should trigger:

https://console.cloud.google.com/gcr/builds?project=[[YOUR-PROJECT]]

Container Registry: Build Details

All being well, the “Build details” logs should show:

Step #2: Deploying function (may take a while - up to 2 minutes)...
...
DONE

If you receive errors, it’s possible you forgot to permit Container Builder to deploy to Cloud Functions:

Step #2: ERROR: (gcloud.beta.functions.deploy) ResponseError: status=[403], code=[Forbidden], message=[The caller does not have permission]

Run the following and then “retry” the build:

NUM=$(gcloud projects describe $PROJECT \
--format='value(projectNumber)')
gcloud projects add-iam-policy-binding ${PROJECT} \
--member=serviceAccount:${NUM}@cloudbuild.gserviceaccount.com \
--role=roles/cloudfunctions.developer

Cloud Functions

Once Container Builder logs “DONE”, the Cloud Function is deployed:

https://console.cloud.google.com/functions/list?project=[[YOUR-PROJECT]]

Cloud Functions: Golang “helloworld”

And, you may “test” the function from Console:

Cloud Functions: Test

We’re looking for “Hello, I’m native Go!

Cloud Functions: Native Go

Or, from the command-line, click “Trigger” on the Console to identify the URL for your Cloud Function and:

curl --request GET https://us-central1-[[YOUR-PROJECT]].cloudfunctions.net/helloworld  
Hello, I'm native Go!

The solution works by replacing the included Node.js solution with the Golang binary. So, if it didn’t work, we’ll interact with the original Node.js solution instead. The Node.js solution responds:

it didn't work :(

If you receive this self-explanatory message, you have your answer. If so, I encourage you to review the steps above to ensure you didn’t miss any of them.

Conclusion

Cloud Functions is fast but with Golang it’s “fasterest”. Cloud Functions is limited today in only supporting Node.js but, as the (*unsupported* == use at your own discretion) cloud-functions-go project shows, it’s possible to swap out the Node.js runtime and swap in a Go binary.

This post explains how to simplify (and automate) the deployment with Container Builder. February is Golang month for me. If I can choose, I’m going to use Golang. I think it’s probably time for me to return my attention to Kubernetes too as I’ve blogged many times on Cloud Functions and Container Builder recently…. because... they’re both pretty awesome!

Feedback *always* sought and valued.

Teardown

Use with care but the easiest is:

gcloud projects delete ${PROJECT}

If you prefer more finesse:

gcloud beta functions delete helloworld --project=$PROJECT

Build Triggers are Console-only so you must delete these through the Console:

Container Registry: Build Triggers

Well, unless you’d like to build your own solution thanks to Google’s APIs

https://medium.com/@DazWilkin/scripted-build-triggers-b53a234ab65

I guess I could add a “delete” option ;-)

And, if you’d like, you may delete the Source Repository too. This was created as a mirror of your GitHub fork. Delete the Source Repository will *not* delete your GitHub fork:

gcloud source repos list \
--project=$PROJECT \
--format="value(name)"
github-[[YOUR-GITHUB]]-cloud-functions-go

and:

gcloud source repos delete github-[[YOUR-GITHUB]]-cloud-functions-go --project=${PROJECT}

NB Even though we used Container Builder, we did not build any containers. Container Builder is more than a container builder. It’s better considered to be a pipeline tool that uses (anything that can be containerized; anything can be containerized) to perform work for us.

That’s it!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade