Deploying an ASP.NET Core Application to Kubernetes

This article is a quick guide to deploying an ASP.NET Core application to Kubernetes, writing a Dockerfile to form an image (Docker image) and a minimal manifest to create a deployment and an object that provides access to it – the article will use ingress executed by nginx.

Tools used:

Application Description

The application you deploy will be a minimal ASP.NET Core web application. It is a simple API that contains the following commands:

  • ‘api/’ – returns the ‘online’ response, which is used to check the health of the application;

  • ‘api/authors’ – returns a list of article authors;

  • ‘api/info’ – Returns information about the environment: variables, NetBIOS server name, username under which the application is running, hostname and IP addresses of all host interfaces.

Source code can be found here.

Image building

The Dockerfile for an ASP.NET Core application can be created both manually and automatically using the built-in generator in VS. To do this, when creating an ASP.NET Core project, in the tab with additional configuration, you need to check the box “Enable Docker” and select the OS used by the docker (see figure).

Additional configuration window
Additional configuration window

It is very convenient to shift Dockerfile generation to VS. But you should not rely on it entirely, and it is better to understand exactly which instructions are generated, their arguments, and the general structure of the resulting Dockerfile. This can be useful, for example, when switching to a more recent version of .NET – given the speed of release of the latest versions (5.0, 6.0, 7.0), this situation is quite real.

The generated Dockerfile for the application looks like this:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["HabrWebApi/HabrWebApi.csproj", "HabrWebApi/"]
RUN dotnet restore "HabrWebApi/HabrWebApi.csproj"
COPY . .
WORKDIR /src/HabrWebApi
RUN dotnet build "HabrWebApi.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "HabrWebApi.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HabrWebApi.dll"]

For users new to Docker, such a structure with many ‘FROM’ statements can be overwhelming. This file is close to the “combat” Dockerfiles and is built using multi-stage build technology (see. here).

A multi-stage build helps build a container more efficiently, without leaving extra, unused files in it. From each intermediate stage, only those files are copied that will be useful in the next or final stage.

The first “base” stage in our Dockerfile pulls in an ASP.NET image from the Microsoft Container Registry (MCR) and opens up port 80. This image contains everything you need to run an ASP.NET Core application. The image tag points to the .NET version.

Next, the second “build” stage is based on the sdk image, which contains the necessary tools to build the application. It is at this stage that the application is compiled, its dependencies are restored, and the necessary executable files are generated.

The next “publish” stage extends the previous “build” stage by adding the “dotnet publish” command. The result is application files that are ready to run in the ASP.NET environment.

The final stage “final” builds on the first stage and uses the results of the “publish” stage, i.e. combines the ASP.NET deployment environment and the files of our application that will be executed in it. The final line of the Dockerfile will be executed at the start of the container created in our image – it starts our application.

After creating the Dockerfile, you can start creating an image of our future container. To do this, you need to go to the folder with the application solution file (.sln), open the terminal in this folder and run the command: “docker build -f [путь к Dockerfile] –force-rm –tag [тег] .”. This command builds a container image according to the specified Dockerfile, assigns the specified tag to the image, removes all container images created at intermediate stages (option ‘–force-rm’) from the specified context (current directory). The image building should complete without errors. Ready image can be taken here.

At this stage, it is necessary to check the performance of the container created in our image. To do this, run the command: “docker run -d -P –name [имя контейнера] [тег образа или его id]”. The output of the command should be like this:

As you can see, the container was successfully created and launched. Now you can proceed to check the health of the application running in it. To do this, in Postman we will send requests to each method of our application. The port that was bound to the container can be found using the “docker container ls” command:

Let’s send requests to our application:

Deploying an Application to Kubernetes

Deploying an application in Kubernetes begins with writing a manifest containing a description of the deployment (Deployment). The key advantage of deployment is the built-in support for the required number of application instances (pods). The deployment also tracks all changes in its configuration, for example, changing the image of containers in pods. This makes it possible to “travel” through the history of the deployment change, and in case of applying fatal changes, it is easy to return to the latest stable version.

The minimum deployment contains the number of replicas (pods / instances of the application) and the configuration of the pods: a description of the containers (image, ports, environment variables, used storages (volumes)). Our application uses a single container per pod with port 80 with the default ASP.NET environment variable “ASPNETCORE_ENVIRONMENT”. The application does not use storage. The minimum deployment manifest looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: habrwebapi-deployment # Имя деплоймента
spec:
  replicas: 2 # Количество реплик
  selector:
    matchLabels: # Определение меток и их значений, по которым будет определяться принадлежность пода деплойменту
      app: habrwebapi-pod
  template: # Описание конфигурации подов
    metadata:
      labels:
        app: habrwebapi-pod # Специальная метка для подов, по которой определяется их принадлежность данному деплойменту
    spec:
      containers: # Описание контейнеров, создаваемых в поде
      - name: habrwepapi-container # Имя контейнера внутри пода
        image: hrenaki/habrwebapi-image:1.0 # Название образа контейнера с указанием репозитория
        ports: # Описание портов контейнера
        - name: pod-port
          containerPort: 80
        env: # Перечисление переменных окружения и их значений
        - name: "ASPNETCORE_ENVIRONMENT"
          value: "Kubernetes"

You can check the status of the created deployment by calling the “kubectl get all” command. Execution result:

As you can see, a deployment was created, a replicaset and two pods with the “Running” status. This means that our application is deployed and ready to go. It remains to create an object that will provide access to our application. In this article, following best practice, ingress will be used for this purpose.

To carry out the work of the ingress, you need to create a service that will direct the traffic coming from the ingress to the deployment pods. Let’s create a manifest with the service:

apiVersion: v1
kind: Service
metadata:
  name: habrwebapi-service
spec:
  selector:
    app: habrwebapi-pod
  ports:
  - targetPort: pod-port
    port: 80

Now we can move on to writing the ingress manifest. Ingress controls the routing of traffic coming from outside to services inside the Kubernetes cluster. There is only one service in our cluster, so we will direct all traffic that comes along the “/” path to it. As a result, the manifest for ingress looks like this:

apiVersion: networking.k8s.io/v1
kind: Ingress 
metadata:
  name: webapp-ingress
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name:  habrwebapi-service
            port:
              number: 80

Before creating ingress objects, you need to install an nginx controller in the cluster by running the command:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml

After all objects have been created, ingress is available at localhost. Let’s make a request to our application using the address “http://localhost/api/info”:

Results

The article outlined a way to quickly deploy a primitive ASP.NET Core application. Minimal versions of k8s image and manifests are considered. The above material can serve as the basis for the deployment of more serious applications.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *