Cloud Pub/Sub + Cloud Run
Introduction
If you’ve heard of Cloud Run, you already know that it’s great for spinning public endpoints inside stateless containers to handle HTTP request/reply type of workloads. And the best part is that you only pay for the duration of the request!
However, HTTP request/reply handling is not the only use-case for Cloud Run. Combined with Cloud Pub/Sub, Cloud Run is very well suited for internal async worker type use-cases because:
- In Cloud Run, you can have an internal/private endpoint only accessible from services with Cloud Run Invoke IAM role.
- In Cloud Pub/Sub, you can have push subscriptions, i.e. Pub/Sub can push messages to a predefined endpoint.
- You can give a Pub/Sub push subscription an identity and grant this identity the Cloud Run Invoker IAM role.
All of this means that you can have Pub/Sub push messages to an internal Cloud Run endpoint. This is perfect for an internal async worker! You can see how to set this up in detail Using Cloud Pub/Sub with Cloud Run Tutorial.
In this blog post, we will follow that tutorial but I also want to see what it takes to have one of my existing Knative Eventing samples and deploy as an internal async worker in Cloud Run.
Knative Eventing to Cloud Run
In my Knative Tutorial, I show how to connect Cloud Storage to Cloud Vision API using Knative Eventing:
To summarize, when a user drops an image in a Cloud Storage bucket, it triggers a message to a Pub/Sub topic which in turn gets read by Knative Eventing. Once the event is read by Knative Eventing, it’s turned into a CloudEvent and passed to a Knative Service to make a Vision API call and extract labels out of the image.
What does it take to take this sample and run it on Cloud Run? Cloud Run implements Knative Serving, so it’s very easy to move a service from Knative Serving to Cloud Run. However, it does not implement Knative Eventing directly, so it’s a little more work to go from Knative Eventing to Cloud Run.
Let’s go through the steps needed to understand what’s needed. I’m assuming that you have a project ready for Cloud Run already.
Create a Cloud Pub/Sub topic
First, we need to create a Pub/Sub topic for Cloud Storage notifications to go to:
gcloud pubsub topics create **cloudrun-atamel-topic**
Create a bucket and enable notifications
Next, create a Cloud Storage bucket to store images. You can do this from Google Cloud console or use gsutil
command line tool as follows:
gsutil mb gs://**cloudrun-atamel-bucket**/
Enable Pub/Sub notifications on the bucket and link to the previously created topic:
gsutil notification create -t **cloudrun-atamel-topic** -f json gs://**cloudrun-atamel-bucket**/
Deploy Cloud Run Service
We’re now ready to deploy our Cloud Run Service. We need to push our image to Google Container Registry (GCR). Inside vision-csharp
folder, there is already a Dockerfile
. Run the following to submit it to Cloud Build:
gcloud builds submit --tag gcr.io/**cloudrun-atamel**/`vision-csharp:v1`
This will build the image using Cloud Build and store to GCR:
Run the following to deploy to Cloud Run as vision-csharp
service:
gcloud beta run deploy vision-csharp --image gcr.io/**cloudrun-atamel**/`vision-csharp:v1`
During deployment, respond “No”, to the “allow unauthenticated” prompt to keep the service private. In a minute or so, you should see the service in Cloud Run section of Google Cloud Console:
Integrate with Cloud Pub/Sub
At this point, we have the bucket and the service ready but we need the Pub/Sub in the middle to forward messages. We need to configure Pub/Sub to push messages to our internal Cloud Run service. I’m following the Using Cloud Pub/Sub with Cloud Run Tutorial.
- Enable your project to create Cloud Pub/Sub authentication tokens (replace project id and number with yours):
gcloud projects add-iam-policy-binding **cloudrun-atamel** \--member=serviceAccount:service-**165810103461**@gcp-sa-pubsub.iam.gserviceaccount.com --role=roles/iam.serviceAccountTokenCreator
- Create a service account to represent the Cloud Pub/Sub subscription identity:
gcloud iam service-accounts create **cloud-run-pubsub-invoker** \--display-name "**Cloud Run Pub/Sub Invoker**"
- Give this service account permission to invoke your CloudRun service:
gcloud beta run services add-iam-policy-binding vision-csharp \--member=serviceAccount:cloud-run-pubsub-invoker@**cloudrun-atamel**.iam.gserviceaccount.com --role=roles/run.invoker
- Create a Cloud Pub/Sub subscription with the service account:
gcloud beta pubsub subscriptions create **cloudrun-atamel-subscription** --topic **cloudrun-atamel-topic** --push-endpoint=[**https://vision-csharp-u6v7imyeip-uc.a.run.app**](https://vision-csharp-u6v7imyeiq-uc.a.run.app/)**/** --push-auth-service-account=cloud-run-pubsub-invoker@**cloudrun-atamel**.iam.gserviceaccount.com
Admittedly, this is a lot of manual steps to setup Pub/Sub and I hope it gets easier going forward but once you go through it once, it kind of makes sense.
The Moment of Truth!
We’re finally ready to test our internal Cloud Run service! I go to the Cloud Storage bucket and drop an image:
I go to Cloud Run console and look at the logs of the vision-csharp service:
My service throws a NullPointerException
! Turns out this is because the event types in Knative Eventing and Cloud Run is different. In Knative Eventing, you end up receiving CloudEvents and that’s what my code tries to parse. In Cloud Run, you receive a Pub/Sub push message which has a slightly different format. Once I adjusted my code to parse Pub/Sub push messages, I was able to make calls to Vision API.
Summary
In this blog post, we took a look at Cloud Run and how it can be used as an internal async worker. There are more manual steps than I expected but once setup, it works great. Combining Cloud Run with Cloud Pub/Sub opens up a lot of opportunities for Cloud Run to act like a background worker for pretty much any service.