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