Nuke: Deploy Helm package locally (special guest, GitVersion)

Raul Naupari - Sep 29 - - Dev Community

Today, we will continue our journey with Nuke by adding new targets to the solution presented in Nuke: Deploy ASP.NET Web App to Azure. This time, we will deploy our application to a local Kubernetes cluster using Helm.

But first, we will need the following:

Generating the build number

Our first target will get the build number using GitVersion (we discussed it in Semantic Versioning with GitVersion). Let's start by adding the following NuGet package:

Next, add the following namespaces to access all gitversion commands:

using static Nuke.Common.Tools.GitVersion.GitVersionTasks;
using Nuke.Common.Tools.GitVersion;
Enter fullscreen mode Exit fullscreen mode

Add a variable to store the generated build number:

private string BuildNumber;
Enter fullscreen mode Exit fullscreen mode

And now, the target itself:

Target GetBuildNumber => _ => _
    .Executes(() =>
    {
        var (result, _) = GitVersion();
        BuildNumber = result.SemVer;
    });
Enter fullscreen mode Exit fullscreen mode

Building the Docker image

The first step is to create a Dockerfile in our project folder as follows:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore "./nuke-sandbox-app/nuke-sandbox-app.csproj"
# Build and publish a release
RUN dotnet publish "./nuke-sandbox-app/nuke-sandbox-app.csproj" -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "nuke-sandbox-app.dll"]
Enter fullscreen mode Exit fullscreen mode

Add the following namespaces to access all docker commands:

using static Nuke.Common.Tools.Docker.DockerTasks;
using Nuke.Common.Tools.Docker;
Enter fullscreen mode Exit fullscreen mode

Add a variable to store the Docker image repository used in the project:

private readonly string Repository = "raulnq/nuke-sandbox-app";
Enter fullscreen mode Exit fullscreen mode

And for the final step, add the target:

Target BuildImage => _ => _
    .DependsOn(GetBuildNumber)
    .Executes(() =>
    {
        var dockerFile = RootDirectory / "nuke-sandbox-app" / "Dockerfile";
        var image = $"{Repository}:{BuildNumber}";

        DockerBuild(s => s
            .SetPath(RootDirectory)
            .SetFile(dockerFile)
            .SetTag(image)
            );
    });
Enter fullscreen mode Exit fullscreen mode

Installing the Helm package

And finally, here is the step to install the Helm package in the Kubernetes cluster. You can first check Useful commands for Helm. Go to your solution folder and run the following commands to create the Helm package:

mkdir helm
cd helm
helm create nuke-sandbox-app
Enter fullscreen mode Exit fullscreen mode

Keep only these files in your Helm package:

helm\nuke-sandbox-app
|-- templates
|   |-- _helpers.tpl
|   |-- deployment.yaml
|   `-- service.yaml
|-- .helmignore
|-- Chart.yaml
`-- values.yaml
Enter fullscreen mode Exit fullscreen mode

Modify the deployment.yaml file as follows:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "nuke-sandbox-app.fullname" . }}
  labels:
    {{- include "nuke-sandbox-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "nuke-sandbox-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "nuke-sandbox-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
Enter fullscreen mode Exit fullscreen mode

The service.yaml file:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "nuke-sandbox-app.fullname" . }}
  labels:
    {{- include "nuke-sandbox-app.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "nuke-sandbox-app.selectorLabels" . | nindent 4 }}
Enter fullscreen mode Exit fullscreen mode

And the values.yaml file:

replicaCount: 1

image:
  repository: raulnq/nuke-sandbox-app
  pullPolicy: IfNotPresent
  tag: "1.0"

nameOverride: ""
fullnameOverride: ""

service:
  type: ClusterIP
  port: 80
Enter fullscreen mode Exit fullscreen mode

Go back to the build.cs file and add:

using static Nuke.Common.Tools.Helm.HelmTasks;
using Nuke.Common.Tools.Helm;
using System.Collections.Generic;
Enter fullscreen mode Exit fullscreen mode

Add a variable to store the Helm release name used in the project:

private readonly string ReleaseName = "release-nuke-sandbox-app";
Enter fullscreen mode Exit fullscreen mode

To wrap up, we will add two targets: one to install and the other to uninstall the Helm package:

Target HelmInstall => _ => _
    .DependsOn(BuildImage)
    .Executes(() =>
    {
        var chart = HelmDirectory / "nuke-sandbox-app";

        HelmUpgrade(s => s
            .SetRelease(ReleaseName)
            .SetSet(new Dictionary<string, object>() { { "image.tag", BuildNumber }, { "image.repository", Repository } })
            .SetChart(chart)
            .EnableInstall()
        );
    });

Target HelmUninstall => _ => _
    .Executes(() =>
    {
        HelmDelete(s => s
        .SetReleaseNames(ReleaseName)
        );
    });
Enter fullscreen mode Exit fullscreen mode

That's it! Now it's time to run our Nuke command:

nuke HelmInstall
Enter fullscreen mode Exit fullscreen mode

We can check our deployment with kubectl get deployments:

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
release-nuke-sandbox-app   1/1     1            1           18s
Enter fullscreen mode Exit fullscreen mode

And uninstall when needed:

nuke HelmUninstall
Enter fullscreen mode Exit fullscreen mode

You can find the updated solution here.

. . . . . . . . .