ASP.NET Core is a new open-source and cross-platform framework for building modern cloud-based and internet-connected applications using the C# programming language.

Kubernetes is an open source project which can run in many different environments, from laptops to high-availability multi-node clusters, from public clouds to on-premise deployments, from virtual machines to bare metal.

In this lab, you deploy a simple ASP.NET Core app to Kubernetes running on Container Engine. This codelab builds on the Build and launch ASP.NET Core app from Google Cloud Shell codelab. You might want to do that lab first before attempting this lab.

The goal of this codelab is for you to turn your code (a simple Hello World ASP.NET Core app here) into a replicated application running on Kubernetes. You take code that you have developed on your machine, turn it into a Docker container image, and then run that image on Container Engine.

Here's a diagram of the various parts in play in this codelab to help you understand how pieces fit together. Use this as a reference as you progress through the codelab; it should all make sense by the time you get to the end (but feel free to ignore this for now).

For the purpose of this codelab, using a managed environment such as Container Engine (a Google-hosted version of Kubernetes running on Compute Engine) allows you to focus more on experiencing Kubernetes rather than setting up the underlying infrastructure.

If you are interested in running Kubernetes on your local machine, such as a development laptop, you should probably look into Minikube. This offers a simple setup of a single node kubernetes cluster for development and testing purposes. You can use Minikube to go through this codelab if you wish.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with Google Cloud Platform?

Novice Intermediate Proficient

Self-paced environment setup

If you don't already have a Google account (Gmail or G Suite), you must create one. Then, sign-in to Google Cloud Platform console (console.cloud.google.com) and create a new project:

Remember the project ID (which is different from the project name), a unique identifier across all Google Cloud Platform projects. It will be referred to later in this codelab as PROJECT_ID.

Next, you need to enable billing in Google Cloud Console in order to use Google Cloud Platform resources.

Start Cloud Shell

From Google Cloud Platform Console, click on the "Activate Google Cloud Shell" icon in the top right hand corner of the header bar.


A Cloud Shell session opens inside a new frame at the bottom of the console and displays a command-line prompt. This might take a few seconds as Cloud Shell is spinning up a VM.



Wait until the $ prompt appears.

In Cloud Shell prompt, you can run the dotnet command line tool is installed in Cloud Shell.

dotnet

You should see the .NET Core version installed.

Microsoft .NET Core Shared Framework Host
  Version  : 1.0.1
  Build    : cee57bf6c981237d80aa1631cfe83cb9ba329f12
...

Next, create a new project folder for our first ASP.NET Core app.

mkdir HelloWorldAspNetCore

Navigate to that folder.

cd HelloWorldAspNetCore

Create a skeleton ASP.NET Core web app using the dotnet command.

dotnet new -t web

Since this is the first time you use dotnet, you will see some initialization messages, followed by a message about project creation.

Welcome to .NET Core!
---------------------
Learn more about .NET Core @ https://aka.ms/dotnet-docs

..
Decompressing 100% 2568 ms
Expanding 100% 11018 ms
Created new C# project in /home/atameldev/HelloWorldAspNetCore.

This creates a number of files in your project folder. By default, ASP.NET Core apps use port 5000. Let's change that to port 8080.

Find Program.cs. Using your favorite editor (emacs, vim, nano etc.), change the main method, and add the UseUrls method to make the host bind to port 8080. The main method should look like this:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseUrls("http://*:8080")
                .Build();
    host.Run();
}

We're almost ready to run our app but we need to restore dependencies first.

dotnet restore

This will download all the NuGet dependencies for our app and you should see restore completed message at the end.

...
log  : Restore completed in 16298ms.

Finally, run the app. You might see some warnings about dependencies that you can safely ignore.

dotnet run

Application starts listening on port 8080.

...
Now listening on: http://*:8080
Application started. Press Ctrl+C to shut down.

To verify that the app is running, visit the web preview and select ‘Preview on port 8080'.

You'll see the default ASP.NET Core webpage in a new tab.

Now, publish the app to get a self-contained DLL using the dotnet publish command.

dotnet publish -c Release

Running publish displays a number of messages with a successfully published message at the end of the process.

...
Publishing HelloWorldAspNetCore for .NETCoreApp,Version=v1.0
[16:47:53] Using gulpfile ~/HelloWorldAspNetCore/gulpfile.js
...
Published 1/1 projects successfully

Next, prepare your app to run on Kubernetes. The first step is to define the container and its contents.

In the publish directory and create a Dockerfile to define the Docker image.

touch Dockerfile

Add the following to Dockerfile using your favorite editor (vim, nano,emacs or Cloud Shell's code editor).

FROM gcr.io/google-appengine/aspnetcore:1.0
ADD ./ /app                                                                                                                                                                                           
ENV ASPNETCORE_URLS=http://*:${PORT}                                                                                                                                                                 
WORKDIR /app                                                                                                                                                                                           
ENTRYPOINT [ "dotnet", "HelloWorldAspNetCore.dll" ]

Dockerfile builds on the official Google App Engine image for ASP.NET Core 1.0 apps, which is already configured to run .NET Core apps and adds the app files and the tools necessary to run the app from the directory.

One important configuration included in your Dockerfile is the port on which the app listens for incoming traffic (8080). This is accomplished by setting the ASPNETCORE_URLS environment variable, which ASP.NET Core apps use to determine which port to listen to.

Save this Dockerfile and build this image by running this command (make sure to replace PROJECT_ID with yours) :

docker build -t gcr.io/PROJECT_ID/hello-dotnet:v1 .

Once this completes (it'll take some time to download and extract everything) you can test the image locally with the following command which will run a Docker container as a daemon on port 8080 from your newly-created container image:

docker run -d -p 8080:8080 gcr.io/PROJECT_ID/hello-dotnet:v1

And again take advantage of the Web preview feature of CloudShell :

You should see the default ASP.NET Core webpage in a new tab. Once you verify that the app is running fine locally in a Docker container, you can stop the running container. First, get the container id.

docker ps

In this example, your app was running as Docker process ced2872b26fc :

CONTAINER ID        IMAGE                              COMMAND
ced2872b26fc        gcr.io/PROJECT_ID/hello-dotnet:v1    "dotnet HelloWorl    
$ docker stop ced2872b26fc
ced2872b26fc

Stop the container.

docker stop ced2872b26fc

Now that the image works as intended you can push it to the Google Container Registry, a private repository for your Docker images accessible from every Google Cloud project (but also from outside Google Cloud Platform) :

gcloud docker -- push gcr.io/PROJECT_ID/hello-dotnet:v1

If all goes well and after a little while you should be able to see the container image listed in the console: Compute > Container Engine > Container Registry. At this point you now have a project-wide Docker image available which Kubernetes can access and orchestrate as you'll see in a few minutes.

If you're curious, you can navigate through the container images as they are stored in Google Cloud Storage by following this link: https://console.cloud.google.com/storage/browser/ (the full resulting link should be of this form: https://console.cloud.google.com/project/PROJECT_ID/storage/browser/).

Ok, you are now ready to create your Container Engine cluster but before that, navigate to the Google Container Engine section of the web console and wait for the system to initialize (it should only take a few seconds).

A cluster consists of a Kubernetes master API server managed by Google and a set of worker nodes. The worker nodes are Compute Engine virtual machines. Let's use the gcloud CLI from your CloudShell session to create a cluster with two n1-standard-1 nodes (this will take a few minutes to complete):

gcloud container clusters create hello-dotnet-cluster \
                --num-nodes 2 \
                --machine-type n1-standard-1 \
                --zone europe-west1-b

In the end, you should see the cluster created.

Creating cluster hello-dotnet-cluster...done.
Created [https://container.googleapis.com/v1/projects/dotnet-atamel/zones/europe-west1-b/clusters/hello-dotnet-cluster].
kubeconfig entry generated for hello-dotnet-cluster.
NAME                  ZONE            MASTER_VERSION  
hello-dotnet-cluster  europe-west1-b  1.4.7

You should now have a fully-functioning Kubernetes cluster powered by Google Container Engine:

It's now time to deploy your own containerized application to the Kubernetes cluster! From now on you'll use the kubectl command line (already set up in your Cloud Shell environment). The rest of this codelab requires both the kubernetes client and server version to be 1.2 or above. kubectl version will show you the current version of the command.

A kubernetes pod is a group of containers, tied together for the purposes of administration and networking. It can contain a single container or multiple. Here you'll simply use one container built with your ASP.NET Core image stored in your private container registry. It will serve content on port 8080.

Let's now create a pod with the kubectl run command (replace PROJECT_ID with your own project name) :

kubectl run hello-dotnet \
    --image=gcr.io/PROJECT_ID/hello-dotnet:v1 \
    --port=8080

As you can see, you've created a deployment object. Deployments are the recommended way to create and scale pods. Here, a new deployment manages a single pod replica running the hello-dotnet:v1 image.

To view the deployment you just created, simply run:

kubectl get deployments
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-dotnet   1         1         1            1           37s

To view the pod created by the deployment, run this command:

kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
hello-dotnet-714049816-ztzrb   1/1       Running   0          57s

Now is a good time to run through some interesting kubectl commands (none of these will change the state of the cluster, full documentation is available here):

kubectl get pods
kubectl cluster-info
kubectl config view
kubectl get events
kubectl logs <pod-name>

At this point you should have your container running under the control of Kubernetes but you still have to make it accessible to the outside world.

By default, the pod is only accessible by its internal IP within the cluster. In order to make the hello-dotnet container accessible from outside the kubernetes virtual network, you have to expose the pod as a kubernetes service.

From Cloud Shell you can expose the pod to the public internet with the kubectl expose command combined with the --type="LoadBalancer" flag. This flag is required for the creation of an externally accessible IP :

kubectl expose deployment hello-dotnet --type="LoadBalancer" --port=8080

The flag used in this command specifies that you'll be using the load-balancer provided by the underlying infrastructure (in this case the Compute Engine load balancer). Note that you expose the deployment, and not the pod directly. This will cause the resulting service to load balance traffic across all pods managed by the deployment (in this case only 1 pod, but you will add more replicas later).

The Kubernetes master creates the load balancer and related Compute Engine forwarding rules, target pools, and firewall rules to make the service fully accessible from outside of Google Cloud Platform.

To find the publicly-accessible IP address of the service, simply request kubectl to list all the cluster services:

kubectl get services
NAME         CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE
hello-dotnet 10.3.253.62   104.155.20.69   8080/TCP    1m
kubernetes   10.3.240.1     <none>           443/TCP    5m

Note there are 2 IP addresses listed for your service, both serving port 8080. One is the internal IP that is only visible inside your cloud virtual network; the other is the external load-balanced IP. In this example, the external IP address is 104.155.20.69.

You should now be able to reach the service by pointing your browser to this address: http://<EXTERNAL_IP>:8080

At this point you've gained at least several features from moving to containers and Kubernetes - you do not need to specify which host to run your workload on, and you also benefit from service monitoring and restart. Let's see what else you can gain from your new Kubernetes infrastructure.

One of the powerful features offered by Kubernetes is how easy it is to scale your application. Suppose you suddenly need more capacity for your application; you can simply tell the replication controller to manage a new number of replicas for your pod:

kubectl scale deployment hello-dotnet --replicas=4
kubectl get deployment
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-dotnet   4         4         4            3           16m
kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
hello-dotnet-714049816-g4azy   1/1       Running   0          1m
hello-dotnet-714049816-rk0u6   1/1       Running   0          1m
hello-dotnet-714049816-sh812   1/1       Running   0          1m
hello-dotnet-714049816-ztzrb   1/1       Running   0          16m

Note the declarative approach here - rather than starting or stopping new instances you declare how many instances should be running at all time. Kubernetes reconciliation loops simply make sure the reality matches what you requested and takes action if needed.

Here's a diagram summarizing the state of your Kubernetes cluster:

At some point the application that you've deployed to production will require bug fixes or additional features. Kubernetes is here to help you deploy a new version to production without impacting your users.

First, let's modify the application. Open the code editor from Cloud Shell.

Navigate to Index.cshtml under HelloWorldAspNetCore > Views > Home and update one of the carousel messages.

Find the following line.

  Learn how to build ASP.NET apps that can run anywhere

And change it to this.

  Learn how to build ASP.NET apps that can run on Google Cloud Platform!

Save the changes and then go back to Cloud Shell. Inside HelloWorldAspNetCore,publish the app to get a self-contained DLL.

dotnet publish -c Release

You can now build and publish a new container image to the registry with an incremented tag (v2 in this case).

cd bin/Release/netcoreapp1.0/publish/
docker build -t gcr.io/PROJECT_ID/hello-dotnet:v2 . 
gcloud docker -- push gcr.io/PROJECT_ID/hello-dotnet:v2

You're now ready for Kubernetes to smoothly update your replication controller to the new version of the application. In order to change the image label for your running container, you need to edit the existing hello-dotnet deployment and change the image from gcr.io/PROJECT_ID/hello-dotnet:v1 to gcr.io/PROJECT_ID/hello-dotnet:v2.

To do this, you will use the kubectl edit command. This will open up a text editor displaying the full deployment yaml configuration. It isn't necessary to understand the full yaml config right now, instead just understand that by updating the spec.template.spec.containers.image field in the config you are telling the deployment to update the pods to use the new image.

kubectl edit deployment hello-dotnet
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: 2017-01-06T10:05:28Z
  generation: 3
  labels:
    run: hello-dotnet
  name: hello-dotnet
  namespace: default
  resourceVersion: "151017"
  selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/hello-dotnet
  uid: 981fe302-f1e9-11e5-9a78-42010af00005
spec:
  replicas: 4
  selector:
    matchLabels:
      run: hello-dotnet
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: hello-dotnet
    spec:
      containers:
      - image: gcr.io/PROJECT_ID/hello-dotnet:v1 # Update this line
        imagePullPolicy: IfNotPresent
        name: hello-dotnet
        ports:
        - containerPort: 8080
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      securityContext: {}
      terminationGracePeriodSeconds: 30

After making the change, save and close the file (this uses vi, so press "Esc" then type :wq and press the "Enter" key).

deployment "hello-dotnet" edited

This updates the deployment with the new image, causing new pods to be created with the new image and old pods to be deleted.

kubectl get deployments
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-dotnet   4         5         4            3           1h

While this is happening, the users of the services should not see any interruption. After a little while they will start accessing the new version of your application.

You can find more details on rolling updates in the Container Engine documentation.

Hopefully with these deployment, scaling and update features you'll agree that once you've setup your environment (your GKE/Kubernetes cluster here), Kubernetes can help you focus on your application rather than managing the infrastructure.

With recent versions of Kubernetes, a graphical web user interface (dashboard) has been introduced. This user interface allows you to get started quickly and enables some of the functionality found in the CLI as a more approachable and discoverable way of interacting with the system.

To configure access to the Kubernetes cluster dashboard, from the Cloud Shell window, type these commands :

gcloud container clusters get-credentials hello-dotnet-cluster \
    --zone europe-west1-b --project <PROJECT_ID>
kubectl proxy --port 8081

And then use the Cloud Shell preview feature once again to head over to port 8081:

This should send you to the API endpoint. You might get an "Unauthorized" page but don't worry about it. To get to the dashboard, remove "?authuser=3" and replace it with "/ui".

Enjoy the Kubernetes graphical dashboard and use it for deploying containerized applications, as well as for monitoring and managing your clusters!

Alternatively you can access the dashboard from a development or local machine using similar instructions provided when, from the Web console, you press the "Connect" button for the cluster you wish to monitor.

Once you're done with the dashboard, you can Control + C to stop the proxy. Learn more about the Kubernetes dashboard by taking the Dashboard tour.

That's it! Time for some cleaning of the resources used (to save on cost and to be a good cloud citizen).

Delete the Deployment (which also deletes the running pods) and Service (which also deletes your external load balancer):

First, delete the service and the deployment, which also deletes your external load balancer:

kubectl delete service,deployment hello-dotnet
service "hello-dotnet" deleted
deployment "hello-dotnet" deleted

Next, delete your cluster :

gcloud container clusters delete hello-dotnet-cluster --zone=europe-west1-b
The following clusters will be deleted.
 - [hello-dotnet-cluster] in [europe-west1-b]
Do you want to continue (Y/n)?  Y
Deleting cluster hello-dotnet-cluster...done.                                                                                                                                                                                            
Deleted [https://container.googleapis.com/v1/projects/<PROJECT_ID>/zones/europe-west1-b/clusters/hello-dotnet-cluster].

This deletes all the Google Compute Engine instances that are running the cluster.

Finally delete the Docker registry storage bucket hosting your image(s) :

gsutil ls
gs://artifacts.<PROJECT_ID>.appspot.com/
gsutil rm -r gs://artifacts.<PROJECT_ID>.appspot.com/
Removing gs://artifacts.<PROJECT_ID>.appspot.com/...
Removing gs://artifacts.<PROJECT_ID>.appspot.com/...

Of course, you can also delete the entire project but you would lose any billing setup you have done (disabling project billing first is required). Additionally, deleting a project will only stop all billing after the current billing cycle ends.

This concludes this simple getting started codelab with ASP.NET Core and Kubernetes. We've only scratched the surface of this technology and we encourage you to explore further with your own pods, replication controllers, and services but also to check out liveness probes (health checks) and consider using the Kubernetes API directly.

What we've covered

Next Steps

License

This work is licensed under a Creative Commons Attribution 2.0 Generic License.

/