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

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

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 app 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;

Add a variable to store the generated build number:

private string BuildNumber;

And now, the target itself:

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

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"]

Add the following namespaces to access all docker commands:

using static Nuke.Common.Tools.Docker.DockerTasks;
using Nuke.Common.Tools.Docker;

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

private readonly string Repository = "raulnq/nuke-sandbox-app";

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)
            );
    });

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

Keep only these files in your Helm package:

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

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

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 }}

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

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;

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

private readonly string ReleaseName = "release-nuke-sandbox-app";

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)
        );
    });

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

nuke HelmInstall

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

And uninstall when needed:

nuke HelmUninstall

You can find the updated solution here.