Hello Cloud Run with C#

6a5cf23c8e20491f.png

Cloud Run is a managed compute platform that enables you to run stateless containers that are invocable via HTTP requests. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most — building great applications.

It is built from Knative, letting you choose to run your containers either fully managed with Cloud Run, or in your Google Kubernetes Engine cluster with Cloud Run on GKE.

The goal of this codelab is for you to build a container image and deploying it to Cloud Run.

Self-paced environment setup

  1. Sign in to Cloud Console and create a new project or reuse an existing one. (If you don't already have a Gmail or G Suite account, you must create one.)

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

Remember the project ID, a unique name across all Google Cloud projects (the name above has already been taken and will not work for you, sorry!). It will be referred to later in this codelab as PROJECT_ID.

  1. Next, you'll need to enable billing in Cloud Console in order to use Google Cloud resources.

Running through this codelab shouldn't cost much, if anything at all. Be sure to to follow any instructions in the "Cleaning up" section which advises you how to shut down resources so you don't incur billing beyond this tutorial. New users of Google Cloud are eligible for the $300USD Free Trial program.

Google Cloud Shell

While Google Cloud can be operated remotely from your laptop, in this codelab we will be using Google Cloud Shell, a command line environment running in the Cloud.

This Debian-based virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. This means that all you will need for this codelab is a browser (yes, it works on a Chromebook).

  1. To activate Cloud Shell from the Cloud Console, simply click Activate Cloud Shell fEbHefbRynwXpq1vj2wJw6Dr17O0np8l-WOekxAZYlZQIORsWQE_xJl-cNhogjATLn-YxLVz8CgLvIW1Ncc0yXKJsfzJGMYgUeLsVB7zSwz7p6ItNgx4tXqQjag7BfWPcZN5kP-X3Q (it should only take a few moments to provision and connect to the environment).

I5aEsuNurCxHoDFjZRZrKBdarPPKPoKuExYpdagmdaOLKe7eig3DAKJitIKyuOpuwmrMAyZhp5AXpmD_k66cBuc1aUnWlJeSfo_aTKPY9aNMurhfegg1CYaE11jdpSTYNNIYARe01A

Screen Shot 2017-06-14 at 10.13.43 PM.png

Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your PROJECT_ID.

gcloud auth list

Command output

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If, for some reason, the project is not set, simply issue the following command:

gcloud config set project <PROJECT_ID>

Looking for your PROJECT_ID? Check out what ID you used in the setup steps or look it up in the Cloud Console dashboard:

R7chO4PKQfLC3bvFBNZJALLTUiCgyLEq_67ECX7ohs_0ZnSjC7GxDNxWrJJUaoM53LnqABYamrBJhCuXF-J9XBzuUgaz7VvaxNrkP2TAn93Drxccyj2-5zz4AxL-G3hzxZ4PsM5HHQ

Cloud Shell also sets some environment variables by default, which may be useful as you run future commands.

echo $GOOGLE_CLOUD_PROJECT

Command output

<PROJECT_ID>
  1. Finally, set the default zone and project configuration.
gcloud config set compute/zone us-central1-f

You can choose a variety of different zones. For more information, see Regions & Zones.

Enable the Cloud Run API

From Cloud Shell, enable the Cloud Run API :

gcloud services enable run.googleapis.com

This should produce a successful message similar to this one :

$ gcloud services enable run.googleapis.com
Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.

We'll build a simple ASP.NET C# application responding to HTTP requests.

To create your application, use dotnet command line tool in Cloud Shell:

dotnet new web -o helloworld-csharp

Change to helloworld-csharp directory:

cd helloworld-csharp

Next, update the CreateHostBuilder definition in Program.cs by specifying the port URL for .UseUrls() to define the serving port. The sample shows port 8080, but you can use other ports. Your server must listen to whatever port you specify here:

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace helloworld_csharp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args)
        {
            string port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
            string url = String.Concat("http://0.0.0.0:", port);

            return Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>().UseUrls(url);
                });
        }
    }
}

Update the app.Run(...) statement in Startup.cs to read and return the TARGET environment variable:

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace helloworld_csharp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    var target = Environment.GetEnvironmentVariable("TARGET") ?? "World";
                    await context.Response.WriteAsync($"Hello {target}!\n");
                });
            });
        }
    }
}

This code creates a basic web server that listens on the port defined by the PORT environment variable and replies back with Hello and the value of the TARGET environment variable. Your app is now ready to be containerized, tested, and uploaded to Google Container Registry.

You can test the app by running it locally in Cloud Shell. You should see it listening on port 8080:

$ dotnet run
Using launch settings from /home/atameldev/helloworld-csharp/Properties/launchSettings.json...
Hosting environment: Development
Content root path: /home/atameldev/helloworld-csharp
Now listening on: http://0.0.0.0:8080
Application started. Press Ctrl+C to shut down.

To containerize the sample app, create a new file named Dockerfile in the same directory as the source files, and copy the following content :

# Use Microsoft's official build .NET image.
# https://hub.docker.com/_/microsoft-dotnet-core-sdk/
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build
WORKDIR /app

# Install production dependencies.
# Copy csproj and restore as distinct layers.
COPY *.csproj ./
RUN dotnet restore

# Copy local code to the container image.
COPY . ./
WORKDIR /app

# Build a release artifact.
RUN dotnet publish -c Release -o out


# Use Microsoft's official runtime .NET image.
# https://hub.docker.com/_/microsoft-dotnet-core-aspnet/
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime
WORKDIR /app
COPY --from=build /app/out ./

# Run the web service on container startup.
ENTRYPOINT ["dotnet", "helloworld-csharp.dll"]

Now, build your container image using Cloud Build, by running the following command from the directory containing the Dockerfile:

gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/helloworld

Once pushed to the registry, you will see a SUCCESS message containing the image name (gcr.io/$GOOGLE_CLOUD_PROJECT/helloworld). The image is stored in the Container Registry and can be re-used if desired.

You can list all the container images associated with your current project using this command :

gcloud container images list

If you would like to run and test the application locally from Cloud Shell, you can start it using this standard docker command :

docker run -p 8080:8080 gcr.io/${GOOGLE_CLOUD_PROJECT}/helloworld

... and use the Web preview feature to point to port 8080. In the Cloud Shell window, click on Web preview and select "Preview on port 8080". This should open a browser window showing the "Hello World!" message. You could also simply use curl localhost:8080.

Deploying your containerized application to Cloud Run is done using the following command (make sure to adjust this to the correct image name for the app you've built or to use the gcr.io/cloudrun/hello prebuilt image):

gcloud run deploy --image gcr.io/${GOOGLE_CLOUD_PROJECT}/helloworld --platform managed

When prompted:

  • select a region (for example us-central1)
  • confirm the service name
  • respond y to allow unauthenticated invocations (that last step is important and can also be avoided by using the --allow-unauthenticated deploy option).

Then wait a few moments until the deployment is complete. On success, the command line displays the service URL :

Service [helloworld] revision [helloworld-00001] has been deployed
and is serving traffic at https://helloworld-wdl7fdwaaa-uc.a.run.app

You can now visit your deployed container by opening the service URL in a web browser :

63260b4d3aee42b8.png

Congratulations! You have just deployed an application packaged in a container image to Cloud Run. Cloud Run automatically and horizontally scales your container image to handle the received requests, then scales down when demand decreases. You only pay for the CPU, memory, and networking consumed during request handling.

While Cloud Run does not charge when the service is not in use, you might still be charged for storing the built container image.

You can either decide to delete your GCP project to avoid incurring charges, which will stop billing for all the resources used within that project, or simply delete your helloworld image using this command :

gcloud container images delete gcr.io/${GOOGLE_CLOUD_PROJECT}/helloworld

To delete the Cloud Run service, use this command :

gcloud run services delete helloworld

A good next step would be to Deploy to Cloud Run on GKE.

For more information on building a stateless HTTP container suitable for Cloud Run from code source and pushing it to Container Registry, see: