Skip to main content

Kubernetes: Adding CloudnativePG Databases

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

Deploying postgres databases using CloudnativePG
#

CloudnativePG provides an easy to use API to deploy postgres databases on our cluster drawing from a single image pool with easy backups and configuration.

Deploying the cloudnativePG Helm chart and image catalog
#

Before using the cloudnativePG API we must deploy the official helm chart. We must also define a postgres image catalog for database deployments to use:

{
  inputs,
  ...
}:
{
  flake.modules.nixos.postgres =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      cnpgChart = {
        name = "cloudnative-pg";
        repo = "https://cloudnative-pg.github.io/charts";
        version = "0.28.3";
        hash = "sha256-oiDxdLcmN/UFVTucD92wuf7QM4DVi+Fxk1zZnYgId9Y=";
      };
      barmanPluginChart = {
        name = "plugin-barman-cloud";
        repo = "https://cloudnative-pg.github.io/charts";
        version = "0.7.0";
        hash = "sha256-aDSUwEzJT30zxKxfPY1kwgljS0i9DoTaMdfR+tIs3Ns=";
      };
      operatorImage = pkgs.dockerTools.pullImage {
        imageName = "ghcr.io/cloudnative-pg/cloudnative-pg";
        imageDigest = "sha256:0dfff19ba7b52ca25851a1010028b6940fff2e233290465af1cfb08a5f3f4661";
        hash = "sha256-zt741Ql1ILjDLNQn8XzmTQmds4407P8h9xtlArL+fmA=";
        finalImageTag = "1.29.1";
        arch = "amd64";
      };
      barmanPluginImage = pkgs.dockerTools.pullImage {
        imageName = "ghcr.io/cloudnative-pg/plugin-barman-cloud";
        imageDigest = "sha256:71589dbac582333442812b07b31f7ea4d00324a8358aac7ca507dabf9f4b6c96";
        hash = "sha256-nyUky+FGRG2eVeDVeMputmWCpEeXs8cAB0TTFTScBUA=";
        finalImageTag = "v0.13.0";
        arch = "amd64";
      };
      postgresImage = pkgs.dockerTools.pullImage {
        imageName = "ghcr.io/cloudnative-pg/postgresql";
        imageDigest = "sha256:9dd9fda84a67a3f351885885fec02ec6346fd941965d8fd94226531fb329624a";
        hash = "sha256-nKDBrn5eq0pxsqbQOO3pBcP1lseDmayt3WN/0fF5k0s=";
        finalImageTag = "18.4-standard-trixie";
        arch = "amd64";
      };
    in
    {
      options = {
        postgres.enable = lib.mkEnableOption "Cloudnative-pg helm chart on k3s";
      };

      config = lib.mkIf config.postgres.enable {
        services.k3s = {
          images = [
            operatorImage
            postgresImage
            barmanPluginImage
          ];
          autoDeployCharts = {
            cloudnative-pg = cnpgChart // {
              targetNamespace = "cnpg-system";
              createNamespace = true;
              values = {
                image = {
                  repository = operatorImage.imageName;
                  tag = operatorImage.imageTag;
                };
                resources = config.server.resources.profiles.infraLarge;
              };
              extraDeploy = [
                {
                  apiVersion = "postgresql.cnpg.io/v1";
                  kind = "ClusterImageCatalog";
                  metadata = {
                    name = "postgresql-global";
                  };
                  spec = {
                    images = [
                      {
                        major = 18;
                        image = "${postgresImage.imageName}:${postgresImage.imageTag}";
                      }
                    ];
                  };
                }
              ];
            };
            barman-plugin = barmanPluginChart // {
              targetNamespace = "cnpg-system";
              createNamespace = true;
              values = {
                image = {
                  repository = barmanPluginImage.imageName;
                  tag = barmanPluginImage.imageTag;
                };
                resources = config.server.resources.profiles.infraLarge;
              };
            };
          };
        };
      };
    };
}

Deploying postgres databases
#

Once installed, we can use CloudnativePG to define postgres databases for use in complex service deployments:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.immich-postgres =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      options = {
        immich.initdb.enable = lib.mkEnableOption "initialize fresh postgres database for Immich";
      };

      config = lib.mkIf config.immich.enable {
        services.k3s.autoDeployCharts.immich.extraDeploy = [
          {
            apiVersion = "postgresql.cnpg.io/v1";
            kind = "Cluster";
            metadata = {
              namespace = "immich";
              name = "immich-postgres";
            };
            spec = {
              instances = 1;
              imageCatalogRef = {
                apiGroup = "postgresql.cnpg.io";
                kind = "ClusterImageCatalog";
                name = "postgresql-global";
                major = 18;
              };
              storage = {
                pvcTemplate = {
                  resources.requests.storage = "8Gi";
                  accessModes = [ "ReadWriteOnce" ];
                  volumeName = "immich-pg-pv";
                };
              };
              managed.roles = [
                {
                  name = "immich";
                  passwordSecret.name = "immich-secrets";
                  superuser = true;
                  login = true;
                }
              ];
              bootstrap =
                if config.immich.initdb.enable then
                  {
                    initdb = {
                      database = "immich";
                      owner = "immich";
                      secret.name = "immich-secrets";
                    };
                  }
                else
                  {
                    recovery.source = "source";
                  };
              plugins = [
                {
                  name = "barman-cloud.cloudnative-pg.io";
                  isWALArchiver = true;
                  parameters = {
                    barmanObjectName = "garage-store";
                    serverName = "immich-postgres-backup-3"; # Must change before each restore
                  };
                }
              ];
              externalClusters = [
                {
                  name = "source";
                  plugin = {
                    name = "barman-cloud.cloudnative-pg.io";
                    parameters = {
                      barmanObjectName = "garage-store";
                      serverName = "immich-postgres-backup-2"; # Must match previous plugin server name
                    };
                  };
                }
              ];
              resources = config.server.resources.profiles.dbMedium;
            };
          }
          {
            apiVersion = "barmancloud.cnpg.io/v1";
            kind = "ObjectStore";
            metadata = {
              name = "garage-store";
              namespace = "immich";
            };
            spec = {
              configuration = {
                destinationPath = "s3://immich-postgres-bucket/";
                endpointURL = "http://garage.garage.svc.cluster.local:3900";
                s3Credentials = {
                  accessKeyId = {
                    name = "immich-secrets";
                    key = "AWS_ACCESS_KEY_ID";
                  };
                  secretAccessKey = {
                    name = "immich-secrets";
                    key = "AWS_SECRET_ACCESS_KEY";
                  };
                  region = {
                    name = "immich-secrets";
                    key = "AWS_REGION";
                  };
                };
                wal.compression = "gzip";
              };
              retentionPolicy = "30d";
              instanceSidecarConfiguration.resources = config.server.resources.profiles.infraLarge;
            };
          }
          {
            apiVersion = "postgresql.cnpg.io/v1";
            kind = "ScheduledBackup";
            metadata = {
              name = "immich-postgres-backup";
              namespace = "immich";
            };
            spec = {
              cluster.name = "immich-postgres";
              method = "plugin";
              pluginConfiguration.name = "barman-cloud.cloudnative-pg.io";
              backupOwnerReference = "self";
              schedule = "0 0 0 * * *";
            };
          }
          {
            apiVersion = "monitoring.coreos.com/v1";
            kind = "PodMonitor";
            metadata = {
              name = "immich-postgres-prometheus-podmonitor";
              namespace = "monitoring";
              labels.release = "prometheus";
            };
            spec = {
              selector.matchLabels = {
                "cnpg.io/cluster" = "immich-postgres";
              };
              namespaceSelector.matchNames = [ "immich" ];
              podMetricsEndpoints = [
                { port = "metrics"; }
              ];
            };
          }
        ];
      };
    };
}

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