a Git or Github repository containing your project
your project can have a Dockerfile (but not mandatory)
With Cloud Run, you can easily deploy a container image and let it scale up and down as needed, in a serverless fashion:
No need to focus on infrastructure (provisioning servers, clusters, upgrading OS, etc.)
Your application can scale transparently from 0 to 1, and from 1 to n (no need for a pager when your app is featured on Hackernews)
You pay as you go, proportionally to the usage
If your project is hosted on Github, for example, how can you help users get started with your project? You usually explain all the steps needed to build a container image, or where to fetch a pre-made image from a hub, and then steps to actually deploy that image on the platform. But thanks to the Cloud Run button, you can add a button image on your README.md page for instance, and then users can click on it and get started with building and deploying to a GCP project automagically.
If the Git repository contains a Dockerfile, it will be built using the docker build command. Otherwise, the CNCF Buildpacks (with the pack build command) will be used to build the repository.
The Cloud Run button github project gives extra information on the parameterization of the deploy URL of the button, for example if you want to specify a particular branch or directory.
an existing Google Cloud Platform account and project
a Java or alternative language web application
a build that creates a standalone executable JAR file
Usually App Engine applications in Java are deployed with the gcloud command-line interface, or via a Maven or Gradle build plugin. Either way, an app.yaml file to describe your application is required to let the cloud SDK know that the project at hand is an App Engine project.
With the Java 11 runtime, however, it's possible to deploy a standalone executable JAR without app.yaml. The gcloud app deploy command now takes also a path to a standalone JAR file:
gcloud app deploy path/to/app.jar
App Engine will automatically assume that you are deploying to the Java 11 runtime, using an F1 instance (256MB of RAM and 600MHz of CPU). So this deployment would be equivalent to having a simple app.yaml file as follows:
You've certainly interacted with webhooks at some point: with a Github commit webhook, for Slack or Dialogflow chatbots, for being notified of Stripe payments, or when you receive an SMS via Twilio. The concept is fairly well known, but there are some roadblocks along the way, whether you implement a webhook handler (the URL being called) or a webhook backend (the service notifying URLs). It's not necessarily as trivial as it may first seem. As I've been interested in Web APIs for a long time, I decided to look into this topic a bit more, by working on a new talk.
I've had the chance of giving this talk at GeeCON Prague:
As well as (in French) at BDX.IO:
You can also watch the slide deck here:
Initially, I was focusing on the backend webhook implementation aspects, but both the sending and receiving ends of webhooks have their own challenges.
Let me name the ones I encountered.
On the handler / client / receiving side, your webhook handlers should:
Reply with a 200 HTTP status code, to let the service provider know that you successfully received the event notification.
Reply fast, so that the service provider doesn't have to keep as many open connections to the handlers, to let it scale more gracefully to more customers. So a good approach is to acknowledge the reception of the event, but treat that event asynchronously afterwards.
Ack reception and defer event handling, as mentioned above when replying fast, it's important to "ack" quickly the reception, and then you're free to do long event handling afterwards, potentially with some worker queue, with workers that can treat those events at their own pace. You can then scale your workers pool when you need to deal with more notifications.
Calls should be idempotent. Sometimes, for various reasons, it's possible you get event notifications twice for the same event.
Use IP whitelisting, when possible, to ensure that only some IP addresses can ping your handler. Since you're opening an URL to the public, better be sure that it's only the service provider that calls you. But it's not always possible to define such a whitelist, as IP addresses are not necessarily fixed for the service provider.
Check request signature, this time not to avoid a DDoS, but more to ensure the integrity of the event payload that you receive. More on signatures in the server part below.
Take advantage of serverless solutions, as sometimes, you don't get a frequent or regular flow of event notifications, why have a server running all the time? Instead, you can take advantage of serverless solutions, like App Engine or Cloud Run, as you're only billed for the time used.
On the service provider / server / notifier side, your webhook backend should:
Send small data payloads, instead of the whole resource that triggered the event. For instance, your service might notified handlers that there's a new video available. But you don't want to send tons of gigabytes of videos to each and every handler subscribed to that event. So just send a reference to that resource, and keep the event payload small.
Timeout if client is too slow, as you can't wait forever for a faulty handler to reply. Cut the connection if the client handler doesn't reply under a set time interval, and treat this as if the message hasn't been successfully delivered. Which means you'll have to retry sending that event later on.
Retry sending events with exponential backoff, to not overload a handler which is struggling to keep pace, in order to avoid DDoS-ing it with your retries. Instead, use exponential backoff to try to redeliver, for example, after 1s, 2s, 4s, 8s, 16s, etc.
Keep track of non-responding handlers, after too many failed delivery attempts, mark those handlers as non-responding, and perhaps somehow notify the creator of the handler that it's not responding correctly.
Deliver messages from a work queue, as you have potentially tons of subscribers interested in your events, you don't want your event loop to be taking longer and longer to operate as the number of handlers grow, and instead, offload the work to some worker queue that you can scale independently from the work of keeping pace with ongoing events flow.
Batch events when too frequent, when there are too many event notifications to send. It might be more costly to send each and every event as they come, in real-time. If there are too many events, you can group them in one batch, so as to deliver them at an increased interval of time to your handlers.
Use a dead letter queue, for auditing purpose in particular. For non-responding handlers, or in case of handlers sometimes miss some events, you can push those never-received events in a dead letter queue, so that later on handler developers can check it to see if they actually missed something at some point in the flow.
Use HTTPS for secured connections, well, everyone should use HTTPS all the time these days anyone, but it's better for avoiding man-in-the-middle attacks, to avoid events replay, etc.
Sign requests with a secret, when handlers and service providers share a common secret, the provider can signe the request it sends to handlers, so that handlers can check the message is really coming from the service provider. For example, the Github API is using an HMAC signature, with a SHA-1 digest.
Use proper authentication/authorization mechanisms. This one is a bit vague, but the usual authentication/authorization best practices still apply to webhooks!
Going further, I'd like to expand this presentation with more hands-on concrete demos, that put all those best practices into action, and perhaps create some animations to show what happens when handlers are flooded with notifications, when handlers don't respond rapidly enough, etc, as that would probably help visualise more concretely each of those problems or practices. Let's see how I can continue iterating and improving this presentation and topic!
Last but not least, there are some great resources available on the topic, that I've added at the end of my slide deck. Be sure to check them out as well:
Last week, I was in Tokyo for the first time, to speak at the Google Cloud Next conference. During the DevDay, I spoke about Google App Engine and its 2nd generation runtimes, and I also presented Cloud Run on how to deploy and run containers in a serverless fashion. It's been awesome to visit Japan for the first time and get a chance to meet developers there. Here are the slides I presented:
Guillaume Laforge is blogging here about whatever comes to his mind,
but mostly about technology, and especially topics related to
the Groovy dynamic language,
the Gaelyk lightweight toolkit
for developing applications for Google App Engine,
and all the other cool things in the Java and Groovy ecosystem.