Host a dotnet app on SSL in Kubernetes (AKS)
By: Sahil Malik
Tags: Kubernetes, Dotnet, Ssl, Docker, Dotnet Core

AKS or Azure Kubernetes Services is a pretty awesome way of deploying your apps. And if you are doing anything even mildly serious, you want to do SSL. For instance, any OIDC authenticated service, such as Azure AD, won’t let you even register redirect URLs that are not SSL.

So this brings us to an interesting dilemma, how do I create a SSL protected web app in dotnet core, that I can host in AKS over kubernetes. Here is how.

Step 1 — Have a dotnet core app. Yup!

Step 2 — dockerize it, here is my docker file.

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env WORKDIR /app # Copy csproj and restore as distinct layers COPY *.csproj ./ RUN dotnet restore EXPOSE 443 # Copy everything else and build COPY . ./ COPY docker-compose/cert/cert.ofx . RUN dotnet publish AspnetCoreWebApp-calls-Microsoft-Graph.sln -c Release -o out # Build runtime image FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 WORKDIR /app COPY --from=build-env /app/out . COPY --from=build-env /app/cert.pfx . ENTRYPOINT ["dotnet", "WebApp-OpenIDConnect-DotNet.dll"]

There is something peculiar going on here. This is just a simple dockerize web app, but I am also copying a PFX cert on to the final runtime image.

Step 3 — Verify this runs in your local docker instance as expected

docker build --tag aadwebsite:latest . docker run -it --name aadwebsite -p 44321:443 aadwebsite

Good! Now we deal with the Kubernetes part.

Create a resource group

az group create -n sahilaadwebsiterg -l eastus

Create an ACR instance

az acr create --name sahilacrregistry --resource-group sahilaadwebsiterg --sku basic --admin-enabled

Tag your local docker image

az acr list --resource-group sahilaadwebsiterg --query "[].{acrLoginServer:loginServer}" --output table

This should give you the name of your login server, like this `sahilacrregistry.azurecr.io`

docker tag aadwebsite:latest sahilacrregistry.azurecr.io/aadwebsite:v1

Push the image into ACR

az acr login --name sahilacrregistry docker push sahilacrregistry.azurecr.io/aadwebsite:v1

We are ready to deploy using `kubectl` but first get the credentails from the AKS cluster

az aks get-credentials --resource-group sahilaadwebsiterg --name sahilakscluster

Allow AKS to pull images from ACR, this will take a few minutes to complete, and you need to be owner of the subscription to run this.

az aks update --name sahilakscluster --resource-group sahilaadwebsiterg --attach-acr sahilacrregistry

Now use kubectl to apply your yaml

kubectl apply -f ./docker-compose/deploy-webapp.yml

And that’s it! Except, there is a LOT of magic in side that YAML, so lets talk about that next :)

The YAML to deploy all this

The YAML has 3 main parts,

a) Deployment

b) Service

c) Ingress controller

NOTE: This is demo code, you want to use kubernetes secrets, so you don’t embed secrets directly in YAML.

First, the deployment

apiVersion: apps/v1 kind: Deployment metadata: name: webapp labels: app: aadwebsite spec: replicas: 1 selector: matchLabels: service: webapp template: metadata: labels: app: aadwebsite service: webapp spec: containers: - name: webapp image: sahilacrregistry.azurecr.io/aadwebsite:v1 imagePullPolicy: Always ports: - name: http containerPort: 80 protocol: TCP - name: https containerPort: 443 protocol: TCP env: - name: ASPNETCORE_URLS value: https://+:443;http://+:80 - name: ASPNETCORE_Kestrel__Certificates__Default__Password value: "removed" - name: ASPNETCORE_Kestrel__Certificates__Default__Path value: "/app/cert.pfx"

What is going on here is, we are creating a pod, we are pulling it from our docker image in the cloud, and we are exposing port 80 and 443. 80 is not necessary, but u may wanna do auto redirect to 443 etc.

Also, look at the environment variables — that is how dotnet knows to serve this on SSL.

Next, the service,

apiVersion: v1 kind: Service metadata: name: webapp labels: app: aadwebsite service: webapp spec: type: LoadBalancer ports: - name: http protocol: TCP port: 80 targetPort: 80 - name: https protocol: TCP port: 443 targetPort: 443 selector: service: webapp

Well this is easy, we are exposing port 80 and 443 from the web app.

And finally, the ingress controller,

apiVersion: extensions/v1beta1 kind: Ingress metadata: name: aadwebsitessl annotations: kubernetes.io/ingress.class: azure/application-gateway spec: tls: - hosts: - <yourdns> secretName: aadwebsitesecret rules: - host: <yourdns> http: paths: - backend: serviceName: webapp servicePort: 443

This will setup an ingress controller to expose your site on the aforementioned SSL + DNS.

That’s it! Now setup the DNS, and deploy, and you are good to go.

Few tips

It is quite possible that your app crashes and your browser times out. View the pod logs as

kubectl logs <podname>

This will give u the full .NET exception trace.

Note that the imagePullPolicy: Always — is not the default value. This is useful for dev, or even otherwise. If I make a code change, I can simply push the docker container, delete the pod, and it will pull the container image and reflect my changes. Just makes life much easier.

It is highly recommended that you use certs and passwords as K8s secrets. And externalize them for your docker image as well. The above is demo code :)