Suppose you are a .NET developer, you love the Function-as-a-Service (FaaS) model, but you want to run your serverless functions in Google Cloud. You want to keep using C# or F#, and still leverage all serverless benefits—ease of use, scalability, elasticity, pay-per-value cost model—running in GCP.
You look at Google Cloud Functions, the native FaaS service of Google Cloud, but it only supports JavaScript, Python, and Go. No C# or F#. Time to give up on the plans?
Not so quickly!
You are probably already familiar with Azure Functions—the .NET-based FaaS runtime in Azure. Azure Functions has two faces: it’s a managed service in the Azure cloud, and also it is a self-contained runtime that can run anywhere: on your local machine, in a VM, or in a container. As I’m going to demonstrate, it can run in Google Cloud too.
Now, if I simply run an Azure Functions host on a VM in Google Cloud, I don’t get all the serverless properties like scalability and pay-for-value. This is where Google Cloud Run comes into the mix.
Cloud Run is a fully managed cloud service that takes a container image and deploys it as an elastic HTTP application with scaling from zero to hero and applying per-request pricing. Cloud Run can host pretty much any container that listens to HTTP requests at a given port.
At the same time, Microsoft provides an official image of Azure Functions host. The host is a web application listening for HTTP requests… It sounds like we can stick it into Cloud Run!
My plan has three steps:
Use your favorite tool to create a new Function App. I employedfunc
CLI to create a new Function App project in C# and define an HTTP Function “HttpExample”.
[FunctionName("HttpExample")]publicstaticasync Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req){string name = (string)req.Query["name"] ??"World";var service = Environment.GetEnvironmentVariable("K_SERVICE") ??"<unknown>";returnnew OkObjectResult($"Hello from Azure Function in {service}, {name}");}
It’s a standard hello-world Function, except it also retrieves the value of theK_SERVICE
environment variable and appends it to the response. This variable should be present when hosted in Cloud Run.
Now, we can wrap the Function App into a Docker image. Here is myDockerfile
:
FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS installer-envCOPY . /src/dotnet-function-appRUN cd /src/dotnet-function-app &&\ mkdir -p /home/site/wwwroot &&\ dotnet publish *.csproj --output /home/site/wwwrootFROM mcr.microsoft.com/azure-functions/dotnet:3.0ENV AzureWebJobsScriptRoot=/home/site/wwwroot\ AzureFunctionsJobHost__Logging__Console__IsEnabled=true\ ASPNETCORE_URLS=http://+:8080COPY --from=installer-env ["/home/site/wwwroot","/home/site/wwwroot"]
It uses the .NET Core SDK to build the application and publish the binaries to thewwwroot
folder. Then I use the official Azure Functions host image to run the application fromwwwroot
.
The only statement that I have to customize for Cloud Run isASPNETCORE_URLS=http://+:8080
. It instructs my application to listen on the port 8080: the one defined by the Cloud Run’scontainer contract.
Finally, I can deploy the container definition to Google Cloud Run service. I use Pulumi to deploy all the infrastructure, seethis post for a detailed walkthrough.
You can find the complete code of Azure Function deployment to Cloud Runhere. After runningpulumi up
, I get a URL back
endpoint: "https://cloudrun-functions-4f40772-q5zdxwsb2a-ew.a.run.app/api/HttpExample?name="
I can append my name, query the endpoint, and get the response back:
curl $(pulumi stack output endpoint)MikhailHello from Azure Function in cloudrun-functions-4f40772, Mikhail
It works! My Function confirms it’s running in thecloudrun-functions-4f40772
Cloud Run service.
I showed that it’s possible to run an Azure Function App inside the managed Google Cloud Run service. Let’s spend a moment to discuss the benefits and limitations of this approach.
You take full advantage of a serverless application model:
You write your application in a familiar language. I used .NET, it can be C#, F#, or VB, but the same approach should also work for other runtimes supported by Azure Functions, for example, JVM or PowerShell.
You can take advantage of many (but not all, see below) features of Azure Functions: HTTP routes, including parameters, authorization modes, logging, input and output bindings.
There is one substantial limitation to my approach, however. Cloud Run can only run HTTP-based workloads, so the set of Azure Function triggers available to you is basically limited to HTTP, EventGrid, and custom triggers based on HTTP. You can’t deploy Functions listening to events like Service Bus, or Storage Queues. However, since your application runs in Google Cloud anyway, do you really need those? Google Pub/Sub has HTTP endpoint integration out of the box.
Azure Functions container is an ASP.NET Core application, and we ran it in Google Cloud Run. You may want to forgo the Azure Functions host and deploy your own custom ASP.NET Core application to Cloud Run. Both are possible, and the choice is yours.
Open source and open standards open (dfjokasj) new horizons of possibilities. The team behind Azure Functions provides a way to host Function App inside containers, and Google Cloud Run service enables running arbitrary containers in a serverless manner. Therefore, we can combine the two products to come up with the usage that nobody anticipated in advance.
Isn’t that cool? Happy hacking, my cloud friends!