Configure a .NET app with Kubernetes

August 8, 2024 Ā· View on GitHub

This .NET app sample demonstrates multiple Kubernetes settings. It uses/requires a non-root user, has container limits set, uses multiple replicas, and registers a liveness probe.

The following instructions demonstrate how to apply the ClusterIP and LoadBalancer variants of the hello-dotnet sample. ClusterIP is the default service type and works well for local clusters and private cloud deployments. LoadBalancer is intended for exposing an app to the internet. They are other service types, such as Ingress.

Run on your local cluster

Apply to your local cluster, using a ClusterIP service.

$ kubectl apply -f https://raw.githubusercontent.com/dotnet/dotnet-docker/main/samples/kubernetes/hello-dotnet/hello-dotnet.yaml
$ kubectl port-forward service/hello-dotnet 8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

View the sample app at http://localhost:8080/ and call curl http://localhost:8080/Environment.

$ curl http://localhost:8080/Environment 
{"runtimeVersion":".NET 8.0.4","osVersion":"Ubuntu 22.04.4 LTS","osArchitecture":"Arm64","user":"app","processorCount":1,"totalAvailableMemoryBytes":78643200,"memoryLimit":104857600,"memoryUsage":31797248,"hostName":"hello-dotnet-5b887d9fbd-b6bmh"}

The LoadBalancer variant can also be applied locally. It will use port 80.

The following example uses Docker Desktop.

$ kubectl apply -f hello-dotnet-loadbalancer.yaml 
deployment.apps/hello-dotnet created
service/hello-dotnet created
$ kubectl get po
NAME                           READY   STATUS    RESTARTS   AGE
hello-dotnet-bd5fdfcfb-f5vjq   1/1     Running   0          4s
hello-dotnet-bd5fdfcfb-nmshh   1/1     Running   0          4s
hello-dotnet-bd5fdfcfb-twp9d   1/1     Running   0          4s
$ kubectl get service
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
hello-dotnet   LoadBalancer   10.101.23.131   localhost     80:31466/TCP   12s
kubernetes     ClusterIP      10.96.0.1       <none>        443/TCP        29h

And then call the Environment endpoint with curl.

$ curl http://localhost/Environment 
{"runtimeVersion":".NET 8.0.4","osVersion":"Ubuntu 22.04.4 LTS","osArchitecture":"Arm64","user":"app","processorCount":1,"totalAvailableMemoryBytes":78643200,"memoryLimit":104857600,"memoryUsage":54845440,"hostName":"hello-dotnet-bd5fdfcfb-f5vjq"}

The following example uses minikube.

$ kubectl apply -f hello-dotnet-loadbalancer.yaml 
deployment.apps/hello-dotnet created
service/hello-dotnet created
$ kubectl get svc -w
NAME           TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
hello-dotnet   LoadBalancer   10.96.208.21   <pending>     80:32597/TCP   29s
kubernetes     ClusterIP      10.96.0.1      <none>        443/TCP        2m28s

The EXTERNAL-IP will stay pending forever.

In another terminal, run minikube tunnel.

$ minikube tunnel  
āœ…  Tunnel successfully started

As soon as that is run, the EXTERNAL-IP will be provided, back in the original terminal.

$ kubectl get svc -w
NAME           TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
hello-dotnet   LoadBalancer   10.96.208.21   <pending>     80:32597/TCP   29s
kubernetes     ClusterIP      10.96.0.1      <none>        443/TCP        2m28s
hello-dotnet   LoadBalancer   10.96.208.21   127.0.0.1     80:32597/TCP   2m9s

And then a call with curl.

$ curl http://localhost/Environment
{"runtimeVersion":".NET 8.0.4","osVersion":"Ubuntu 22.04.4 LTS","osArchitecture":"Arm64","user":"app","processorCount":1,"totalAvailableMemoryBytes":78643200,"memoryLimit":104857600,"memoryUsage":42151936,"hostName":"hello-dotnet-86f4cffb9d-blcnb"}

This is the same as with Docker Desktop, just with the additional requirement of using minikube tunnel.

Run on your cloud cluster

Apply to your local cluster, using a LoadBalancer service.

The example uses Azure Kubernetes Service (AKS) as the Kubernetes environment

$ az aks get-credentials --resource-group myResourceGroup --name myAKSCluster
$ kubectl get nodes                  
NAME                                STATUS   ROLES    AGE   VERSION
aks-agentpool-17841206-vmss000000   Ready    <none>   31m   v1.29.2
aks-agentpool-17841206-vmss000001   Ready    <none>   32m   v1.29.2
aks-userpool-17841206-vmss000000    Ready    <none>   32m   v1.29.2
aks-userpool-17841206-vmss000001    Ready    <none>   32m   v1.29.2

Apply the LoadBalancer deployment to that cluster.

$ kubectl apply -f https://raw.githubusercontent.com/dotnet/dotnet-docker/main/samples/kubernetes/hello-dotnet/hello-dotnet-loadbalancer.yaml
$ kubectl get service -w
NAME           TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
hello-dotnet   LoadBalancer   10.0.120.232   <pending>     80:31567/TCP   5s
kubernetes     ClusterIP      10.0.0.1       <none>        443/TCP        42h
hello-dotnet   LoadBalancer   10.0.120.232   20.51.80.224   80:31567/TCP   9s

The EXTERNAL-IP will resolve (after a brief wait) and you will be able to access it in the same way, via curl or the browser, on port 80.

$ curl http://20.51.80.224/Environment
{"runtimeVersion":".NET 8.0.4","osVersion":"Ubuntu 22.04.4 LTS","osArchitecture":"X64","user":"app","processorCount":1,"totalAvailableMemoryBytes":78643200,"memoryLimit":104857600,"memoryUsage":42323968,"hostName":"hello-dotnet-bd5fdfcfb-twgqs"}

You may notice that this approach is using raw HTTP on an internet-facing endpoint. That's only acceptable for brief testing like this. Enforce HTTPS in ASP.NET Core and What is Application Gateway Ingress Controller? describe options for exposing secure HTTPS endpoints.