Skip to main content

Kubernetes: Deploying Services

Kubernetes - This article is part of a series.
Part 2: This Article

Deploying Helm Charts
#

To install a Helm chart on NixOS we can use the services.k3s.autoDeployCharts.<chart> config value to define the chart to be imported, the namespace it is to be deployed to as well as the values to be passed to the chart. Images can also be preloaded using services.k3s.images config value.

Adding the charts
#

First, we add a module to import the chart and set the namespace:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.netbird-operator-charts =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      netbirdOperatorChart = {
        name = "netbird-operator";
        repo = "oci://ghcr.io/netbirdio/helm-charts/netbird-operator";
        version = "0.4.1";
        hash = "sha256-gRdZViio1QZYNlaEEKk/36F0wc3MnMgtTZE2HTIx4qo=";
      };
    in
    {
      config = lib.mkIf config.netbird-operator.enable {
        services.k3s.autoDeployCharts = {
          netbird-operator = netbirdOperatorChart // {
            targetNamespace = "netbird";
            createNamespace = true;
          };
        };
      };
    };
}

Adding the images
#

Images used by the Helm chart can be loaded into k3s and set in the chart values as such:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.netbird-operator-images =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      operatorImage = pkgs.dockerTools.pullImage {
        imageName = "ghcr.io/netbirdio/netbird-operator";
        imageDigest = "sha256:0f89a7385eadfde8a47adbfa0ee7913e8d0b938293e08615ca0aa1deab91fcb4";
        hash = "sha256-DYq4Gb7aGoH0L915fCf9f+gP17CZHfjbq8kR21/kuc4=";
        finalImageTag = "v0.4.1";
        arch = "amd64";
      };
      routerImage = pkgs.dockerTools.pullImage {
        imageName = "ghcr.io/netbirdio/netbird";
        imageDigest = "sha256:4c37fb63f33531fc0c5eec272839b0e4d290c595a0b15bf5fd9a288df1890392";
        hash = "sha256-TDBO4bjEhbcj01z1ZdoSjfPS226LmC3bxOyyusFwh2M=";
        finalImageTag = "0.71.0-rootless";
        arch = "amd64";
      };
    in
    {
      config = lib.mkIf config.netbird-operator.enable {
        services.k3s = {
          images = [
            operatorImage
            routerImage
          ];
          autoDeployCharts.netbird-operator.values = {
            operator.image = {
              repository = operatorImage.imageName;
              tag = operatorImage.imageTag;
            };
            routingClientImage = "${routerImage.imageName}:${routerImage.imageTag}";
          };
        };
      };
    };
}

Adding extra configuration settings using chart values
#

Extra chart values can be defined in their own module also:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.netbird-operator-settings =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      config = lib.mkIf config.netbird-operator.enable {
        services.k3s.autoDeployCharts.netbird-operator.values = {
          resources = {
            requests.cpu = "100m";
            requests.memory = "128Mi";
            limits.cpu = "250m";
            limits.memory = "256Mi";
          };
        };
      };
    };
}

Deploying Raw Manifests
#

To install a service using raw manifests on NixOS we can use the services.k3s.manifests.<service-name> config value to define the manifests to be deployed. Images can also be preloaded using services.k3s.images config value.

Creating the namespace
#

First, a module should be added to create the necessary namespace in k3s:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.blog-namespace =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      config = lib.mkIf config.blog.enable {
        services.k3s.manifests.blog.content = [
          {
            apiVersion = "v1";
            kind = "Namespace";
            metadata = {
              name = "website";
            };
          }
        ];
      };
    };
}

Loading images
#

Second, a module importing the images into k3s should be added. In this case, we import the image generated by my blog flake:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.blog-images =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      blogImage = inputs.blog.packages."x86_64-linux".dockerImage;
    in
    {
      config = lib.mkIf config.blog.enable {
        services.k3s = {
          images = [ blogImage ];
        };
      };
    };
}

Adding deployment manifests
#

Finally, a deployment module is required where replica count, security context, containers and probes are defined:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.blog-deployment =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      config = lib.mkIf config.blog.enable {
        services.k3s.manifests.blog.content = [
          {
            apiVersion = "apps/v1";
            kind = "Deployment";
            metadata = {
              name = "blog";
              namespace = "website";
            };
            spec = {
              replicas = 1;
              selector.matchLabels.app = "blog";
              template = {
                metadata.labels.app = "blog";
                spec = {
                  securityContext = {
                    runAsUser = 1000;
                    runAsGroup = 1000;
                    fsGroup = 1000;
                  };
                  containers = [
                    {
                      name = "blog";
                      image = "${
                        (lib.lists.findSingle (
                          x: x ? imageName && x.imageName == "blog"
                        ) null null config.services.k3s.images).imageName
                      }:${
                        (lib.lists.findSingle (
                          x: x ? imageName && x.imageName == "blog"
                        ) null null config.services.k3s.images).imageTag
                      }";
                      imagePullPolicy = "Never";
                      ports = [ { containerPort = 8080; } ];
                      startupProbe = {
                        httpGet = {
                          path = "/";
                          port = 8080;
                        };
                        failureThreshold = 30;
                        periodSeconds = 5;
                      };
                      readinessProbe = {
                        httpGet = {
                          path = "/";
                          port = 8080;
                        };
                        initialDelaySeconds = 15;
                        periodSeconds = 10;
                        timeoutSeconds = 2;
                        failureThreshold = 3;
                      };
                      livenessProbe = {
                        httpGet = {
                          path = "/";
                          port = 8080;
                        };
                        initialDelaySeconds = 30;
                        periodSeconds = 20;
                        timeoutSeconds = 2;
                        failureThreshold = 3;
                      };
                      resources = {
                        requests.cpu = "20m";
                        requests.memory = "32Mi";
                        limits.cpu = "100m";
                        limits.memory = "128Mi";
                      };
                    }
                  ];
                };
              };
            };
          }
        ];
      };
    };
}

Kubernetes - This article is part of a series.
Part 2: This Article