1. Overview
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 Kubernetes 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 Google Kubernetes 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 Kubernetes 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
- How to package a simple ASP.NET Core app as a Docker container.
- How to create your Kubernetes cluster on Google Kubernetes Engine (GKE).
- How to deploy your ASP.NET Core app to a pod.
- How to allow external traffic to your pod.
- How to scale up your service and roll out an upgrade.
- How to run Kubernetes Graphical dashboard.
What you'll need
How will you use this tutorial?
How would rate your experience with Google Cloud Platform?
2. Setup and Requirements
Self-paced environment setup
- Sign-in to the Google Cloud Console and create a new project or reuse an existing one. If you don't already have a Gmail or Google Workspace account, you must create one.
- The Project name is the display name for this project's participants. It is a character string not used by Google APIs. You can update it at any time.
- The Project ID must be unique across all Google Cloud projects and is immutable (cannot be changed after it has been set). The Cloud Console auto-generates a unique string; usually you don't care what it is. In most codelabs, you'll need to reference the Project ID (it is typically identified as
PROJECT_ID
). If you don't like the generated ID, you may generate another random one. Alternatively, you can try your own and see if it's available. It cannot be changed after this step and will remain for the duration of the project. - For your information, there is a third value, a Project Number which some APIs use. Learn more about all three of these values in the documentation.
- Next, you'll need to enable billing in the Cloud Console to use Cloud resources/APIs. Running through this codelab shouldn't cost much, if anything at all. To shut down resources so you don't incur billing beyond this tutorial, you can delete the resources you created or delete the whole project. New users of Google Cloud are eligible for the $300 USD Free Trial program.
Start Cloud Shell
While Google Cloud can be operated remotely from your laptop, in this codelab you will be using Google Cloud Shell, a command line environment running in the Cloud.
Activate Cloud Shell
- From the Cloud Console, click Activate Cloud Shell .
If you've never started Cloud Shell before, you're presented with an intermediate screen (below the fold) describing what it is. If that's the case, click Continue (and you won't ever see it again). Here's what that one-time screen looks like:
It should only take a few moments to provision and connect to Cloud Shell.
This virtual machine is loaded with all the development tools you need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. Much, if not all, of your work in this codelab can be done with simply a browser or your Chromebook.
Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your project ID.
- Run the following command in Cloud Shell to confirm that you are authenticated:
gcloud auth list
Command output
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Run the following command in Cloud Shell to confirm that the gcloud command knows about your project:
gcloud config list project
Command output
[core] project = <PROJECT_ID>
If it is not, you can set it with this command:
gcloud config set project <PROJECT_ID>
Command output
Updated property [core/project].
3. Create an ASP.NET Core app in Cloud Shell
In Cloud Shell prompt, you can verify that the dotnet command line tool is already installed by checking its version. This should print the version of the installed dotnet command line tool:
dotnet --version
Next, create a new skeleton ASP.NET Core web app.
dotnet new mvc -o HelloWorldAspNetCore
This should create a project and restore its dependencies. You should see a message similar to below.
Restore completed in 11.44 sec for HelloWorldAspNetCore.csproj.
Restore succeeded.
4. Run the ASP.NET Core app
We're almost ready to run our app. Navigate to the app folder.
cd HelloWorldAspNetCore
Finally, run the app.
dotnet run --urls=http://localhost:8080
Application starts listening on port 8080.
Hosting environment: Production
Content root path: /home/atameldev/HelloWorldAspNetCore
Now listening on: http://[::]:8080
Application started. Press Ctrl+C to shut down.
To verify that the app is running, click on the web preview button on the top right and select ‘Preview on port 8080'.
You'll see the default ASP.NET Core webpage:
Once you verify that the app is running, press Ctrl+C to shut down the app.
5. Package the ASP.NET Core app as a Docker container
Next, prepare your app to run as a container. The first step is to define the container and its contents.
In the base directory of the app, 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).
# Use Microsoft's official build .NET image. # https://hub.docker.com/_/microsoft-dotnet-core-sdk/ FROM mcr.microsoft.com/dotnet/sdk:6.0-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/aspnet:6.0-alpine-amd64 AS runtime WORKDIR /app COPY --from=build /app/out ./ # Make sure the app binds to port 8080 ENV ASPNETCORE_URLS http://*:8080 # Run the web service on container startup. ENTRYPOINT ["dotnet", "HelloWorldAspNetCore.dll"]
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
. Now, let's build the image:
docker build -t gcr.io/${GOOGLE_CLOUD_PROJECT}/hello-dotnet:v1 .
Once this completes (it'll take some time to download and extract everything), you can see the image is built and saved locally:
docker images REPOSITORY TAG gcr.io/yourproject-XXXX/hello-dotnet v1
Test the image locally with the following command which will run a Docker container locally on port 8080 from your newly-created container image:
docker run -p 8080:8080 gcr.io/${GOOGLE_CLOUD_PROJECT}/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 by Ctrl-> C
.
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) :
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/hello-dotnet:v1
If all goes well and after a little while, you should be able to see the container image listed in the Container Registry section. 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/).
6. Create Kubernetes cluster
Ok, you are now ready to create your GKE cluster but before that, navigate to the Google Kubernetes 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. Adjust your zone to somewhere close to you ( the list of zones). This will take a few minutes to complete:
gcloud container clusters create hello-dotnet-cluster --cluster-version=latest --num-nodes 4 --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.10.7-gke.6
You should now have a fully-functioning Kubernetes cluster powered by Google Kubernetes 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.
7. Create deployment
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.
Create an hello-dotnet.yaml
file using your favorite editor (vim, nano,emacs
or Cloud Shell's code editor) and define the Kubernetes Deployment for the pod:
apiVersion: apps/v1 kind: Deployment metadata: labels: run: hello-dotnet name: hello-dotnet namespace: default spec: replicas: 1 selector: matchLabels: run: hello-dotnet template: metadata: labels: run: hello-dotnet spec: containers: - name: hello-dotnet image: gcr.io/YOUR-PROJECT-ID/hello-dotnet:v1 imagePullPolicy: IfNotPresent ports: - containerPort: 8080
Deploy to the default namespace with kubectl
:
kubectl apply -f hello-dotnet.yaml
deployment.apps/hello-dotnet created
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.
8. Allow external traffic
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.
9. Scale your service
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:
You can also scale down your service very easily. Here's how you would scale down from 4 pods to 2 pods.
kubectl scale deployment hello-dotnet --replicas=2
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
10. Test Resiliency
Kubernetes (or more specifically ReplicaSet) watches your pods and if something is wrong with the pod and it goes down, it creates a new one right away. Let's test this out and see how it works.
First get the list of pods:
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
Delete one of the pods by passing in the pod name:
kubectl delete pod hello-dotnet-714049816-g4azy
If you look at the list of pods again, you'll see a new pod being created and running again right away:
kubectl get pods
NAME READY STATUS RESTARTS AGE hello-dotnet-714049816-abczy 1/1 ContainerCreating 0 1m hello-dotnet-714049816-rk0u6 1/1 Running 0 1m
11. Roll out an upgrade to your service
At some point, the application that you've deployed to production will require bug fixes or additional features. Let's see how that process looks like.
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 about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core
And change it to this:
Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core on Google Cloud
Save the changes and then go back to Cloud Shell. Inside HelloWorldAspNetCore,
build the docker image:
docker build -t gcr.io/${GOOGLE_CLOUD_PROJECT}/hello-dotnet:v2 .
And push to the Container Registry:
docker push gcr.io/${GOOGLE_CLOUD_PROJECT}/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/${GOOGLE_CLOUD_PROJECT}/hello-dotnet:v1
to gcr.io/${GOOGLE_CLOUD_PROJECT}/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: apps/v1
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 Kubernetes 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.
12. Cloud Build
So far, we've been building containers with regular Docker commands (docker build ...), and then manually pushed the image into Google Cloud Platform's Container Registry. It's also possible to defer both steps to the server side Cloud Build, which can build and push the container image without having local installation of Docker.
First, enable Cloud Build API in API Manager > Library. Search for Cloud Build, click into Cloud Build API:
Click Enable API, if it's not already enabled. In the end, you should see the API enabled as follows:
Once the Cloud Build API is enabled, you can run the following command to build and push your image all from the Container Builder service:
$ gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/hello-dotnet:v3
The image is automatically stored on Container Registry.
13. Run the Kubernetes Graphical dashboard
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 ${GOOGLE_CLOUD_PROJECT}
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.
14. Logging
You can use kubectl logs
command to retrieve the logs of a container running inside of Kubernetes. When you use Google Kubernetes Engine to run managed Kubernetes clusters, all of the logs are automatically forwarded and stored in Google Cloud Logging. You can see all the log output from the pods by navigating to Stackdriver → Logging → Logs in the Google Cloud console:
Once in the logging console, you can navigate to GKE Container to see all of the logs collected from STDOUT:
From here, you can optionally export the logs into Google BigQuery for further log analysis, or setup log-based alerting. We won't get to do this during the lab today.
15. Congratulations!
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.
Clean up
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.${GOOGLE_CLOUD_PROJECT}.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.
What we've covered
- How to package a simple ASP.NET Core app as a Docker container.
- How to create your Kubernetes cluster on Google Kubernetes Engine.
- How to deploy your ASP.NET Core app to a pod.
- How to allow external traffic to your pod.
- How to scale up your service and roll out an upgrade.
- How to run Kubernetes Graphical dashboard.
Next Steps
- Learn more about Kubernetes ( http://kubernetes.io/).
- Learn more about Windows on Google Cloud Platform.
- Learn more about .NET on Google Cloud Platform.
- Learn more about SQL Server on Google Cloud Platform.
- Learn more about Cloud Tools for Visual Studio.
- Learn more about Cloud Tools for PowerShell.
License
This work is licensed under a Creative Commons Attribution 2.0 Generic License.