feat: organize all services into separate stacks by dependency
This commit is contained in:
47
utility-services/authentik/index.ts
Normal file
47
utility-services/authentik/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Construct } from "constructs";
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
import { Providers } from "../../types";
|
||||
|
||||
type AuthentikServerOptions = {
|
||||
providers: Providers;
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
|
||||
export class AuthentikServer extends Construct {
|
||||
constructor(scope: Construct, id: string, options: AuthentikServerOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { kubernetes, helm } = options.providers;
|
||||
|
||||
new OnePasswordSecret(this, "secret-key", {
|
||||
provider: kubernetes,
|
||||
name: "authentik-secret-key",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/authentik-secret-key",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "smtp", {
|
||||
provider: kubernetes,
|
||||
name: "authentik-smtp-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/smtp-token",
|
||||
});
|
||||
|
||||
new Release(this, id, {
|
||||
...options,
|
||||
provider: helm,
|
||||
repository: "https://charts.goauthentik.io",
|
||||
chart: "authentik",
|
||||
createNamespace: true,
|
||||
values: [
|
||||
fs.readFileSync(path.join(__dirname, "values.yaml"), {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
],
|
||||
}).importFrom("homelab/authentik");
|
||||
}
|
||||
}
|
||||
110
utility-services/authentik/values.yaml
Normal file
110
utility-services/authentik/values.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
global:
|
||||
addPrometheusAnnotations: true
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
podLabels:
|
||||
app: authentik
|
||||
nodeSelector:
|
||||
nodepool: worker
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: DoNotSchedule
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app: authentik
|
||||
env:
|
||||
- name: AUTHENTIK_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-secret-key
|
||||
key: password
|
||||
- name: AUTHENTIK_EMAIL__USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-smtp-token
|
||||
key: authentik-username
|
||||
- name: AUTHENTIK_EMAIL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-smtp-token
|
||||
key: authentik-password
|
||||
- name: AUTHENTIK_EMAIL__FROM
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-smtp-token
|
||||
key: authentik-username
|
||||
- name: AUTHENTIK_EMAIL__USE_TLS
|
||||
value: "true"
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLMODE
|
||||
value: verify-full
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLROOTCERT
|
||||
value: "/opt/authentik/certs/ca.crt"
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLCERT
|
||||
value: "/opt/authentik/certs/tls.crt"
|
||||
- name: AUTHENTIK_POSTGRESQL__SSLKEY
|
||||
value: "/opt/authentik/certs/tls.key"
|
||||
- name: AUTHENTIK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: valkey
|
||||
key: password
|
||||
volumes:
|
||||
- name: ssl-bundle
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: authentik-client-cert
|
||||
items:
|
||||
- key: tls.crt
|
||||
path: tls.crt
|
||||
- key: tls.key
|
||||
path: tls.key
|
||||
mode: 0600
|
||||
- secret:
|
||||
name: postgres-server-cert
|
||||
items:
|
||||
- key: ca.crt
|
||||
path: ca.crt
|
||||
volumeMounts:
|
||||
- name: ssl-bundle
|
||||
mountPath: /opt/authentik/certs
|
||||
readOnly: true
|
||||
|
||||
authentik:
|
||||
error_reporting:
|
||||
enabled: false
|
||||
email:
|
||||
host: "smtp.protonmail.ch"
|
||||
port: 587
|
||||
postgresql:
|
||||
host: postgres-cluster-rw
|
||||
user: authentik
|
||||
name: authentik
|
||||
redis:
|
||||
host: valkey
|
||||
|
||||
server:
|
||||
replicas: 3
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
||||
cert-manager.io/acme-challenge-type: dns01
|
||||
cert-manager.io/private-key-size: "4096"
|
||||
ingressClassName: traefik
|
||||
hosts:
|
||||
- auth.dogar.dev
|
||||
tls:
|
||||
- secretName: authentik-tls
|
||||
hosts:
|
||||
- auth.dogar.dev
|
||||
|
||||
worker:
|
||||
replicas: 3
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
redis:
|
||||
enabled: false
|
||||
1
utility-services/gitea/index.ts
Normal file
1
utility-services/gitea/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { GiteaServer } from "./server";
|
||||
82
utility-services/gitea/server.ts
Normal file
82
utility-services/gitea/server.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { Release } from "@cdktf/provider-helm/lib/release";
|
||||
import { Construct } from "constructs";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
import { IngressRouteTcp } from "../../utils/traefik";
|
||||
|
||||
type GiteaServerOptions = {
|
||||
providers: {
|
||||
helm: HelmProvider;
|
||||
kubernetes: KubernetesProvider;
|
||||
};
|
||||
name: string;
|
||||
namespace: string;
|
||||
r2Endpoint: string;
|
||||
};
|
||||
|
||||
export class GiteaServer extends Construct {
|
||||
constructor(scope: Construct, id: string, options: GiteaServerOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { kubernetes, helm } = options.providers;
|
||||
|
||||
new OnePasswordSecret(this, "admin", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-admin",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/gitea-admin",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "oauth", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-oauth",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/gitea-oauth",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "smtp", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-smtp-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/smtp-token",
|
||||
});
|
||||
|
||||
new OnePasswordSecret(this, "r2", {
|
||||
provider: kubernetes,
|
||||
name: "gitea-cloudflare-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/cloudflare",
|
||||
});
|
||||
|
||||
new Release(this, id, {
|
||||
...options,
|
||||
provider: helm,
|
||||
repository: "https://dl.gitea.com/charts",
|
||||
chart: "gitea",
|
||||
createNamespace: true,
|
||||
set: [
|
||||
{
|
||||
name: "gitea.config.storage.MINIO_ENDPOINT",
|
||||
value: options.r2Endpoint,
|
||||
},
|
||||
],
|
||||
values: [
|
||||
fs.readFileSync(path.join(__dirname, "values.yaml"), {
|
||||
encoding: "utf8",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
new IngressRouteTcp(this, "ssh-ingress", {
|
||||
provider: kubernetes,
|
||||
namespace: options.namespace,
|
||||
entryPoint: "ssh",
|
||||
serviceName: `${options.name}-ssh`,
|
||||
servicePort: 22,
|
||||
});
|
||||
}
|
||||
}
|
||||
161
utility-services/gitea/values.yaml
Normal file
161
utility-services/gitea/values.yaml
Normal file
@@ -0,0 +1,161 @@
|
||||
global:
|
||||
storageClass: longhorn
|
||||
image:
|
||||
rootless: false
|
||||
service:
|
||||
http:
|
||||
annotations:
|
||||
metallb.universe.tf/allow-shared-ip: gitea
|
||||
ssh:
|
||||
annotations:
|
||||
metallb.universe.tf/allow-shared-ip: gitea
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-issuer
|
||||
cert-manager.io/acme-challenge-type: dns01
|
||||
cert-manager.io/private-key-size: 4096
|
||||
className: traefik
|
||||
hosts:
|
||||
- host: git.dogar.dev
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: gitea-tls
|
||||
hosts:
|
||||
- git.dogar.dev
|
||||
gitea:
|
||||
podAnnotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "6060"
|
||||
admin:
|
||||
existingSecret: gitea-admin
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
config:
|
||||
server:
|
||||
ENABLE_PPROF: true
|
||||
ENABLE_GZIP: true
|
||||
LFS_START_SERVER: true
|
||||
SSH_DOMAIN: git.dogar.dev
|
||||
database:
|
||||
DB_TYPE: postgres
|
||||
HOST: postgres-cluster-rw
|
||||
NAME: gitea
|
||||
USER: gitea
|
||||
SSL_MODE: verify-full
|
||||
metrics:
|
||||
ENABLED: true
|
||||
cache:
|
||||
ADAPTER: memory
|
||||
session:
|
||||
PROVIDER: db
|
||||
PROVIDER_CONFIG: ""
|
||||
queue:
|
||||
TYPE: channel
|
||||
storage:
|
||||
STORAGE_TYPE: minio
|
||||
MINIO_USE_SSL: true
|
||||
MINIO_BUCKET_LOOKUP_STYLE: path
|
||||
MINIO_LOCATION: auto
|
||||
service:
|
||||
DISABLE_REGISTRATION: true
|
||||
oauth2_client:
|
||||
ENABLE_AUTO_REGISTRATION: true
|
||||
mailer:
|
||||
ENABLED: true
|
||||
PROTOCOL: smtp+starttls
|
||||
SMTP_ADDR: smtp.protonmail.ch
|
||||
SMTP_PORT: 587
|
||||
FROM: git@dogar.dev
|
||||
picture:
|
||||
GRAVATAR_SOURCE: gravatar
|
||||
oauth:
|
||||
- name: "authentik"
|
||||
provider: "openidConnect"
|
||||
existingSecret: gitea-oauth
|
||||
autoDiscoverUrl: "https://auth.dogar.dev/application/o/gitea/.well-known/openid-configuration"
|
||||
iconUrl: "https://goauthentik.io/img/icon.png"
|
||||
scopes: "email profile"
|
||||
additionalConfigFromEnvs:
|
||||
- name: GITEA__MAILER__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-smtp-token
|
||||
key: gitea-password
|
||||
- name: GITEA__PACKAGES__CHUNKED_UPLOAD_PATH
|
||||
value: "/tmp/gitea-uploads"
|
||||
- name: GITEA__PACKAGES__CHUNKED_UPLOAD_CONCURRENCY
|
||||
value: "4"
|
||||
- name: GITEA__STORAGE__MINIO_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-cloudflare-token
|
||||
key: access_key_id
|
||||
- name: GITEA__STORAGE__MINIO_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-cloudflare-token
|
||||
key: secret_access_key
|
||||
persistence:
|
||||
labels:
|
||||
recurring-job.longhorn.io/source: "enabled"
|
||||
recurring-job.longhorn.io/daily-backup: "enabled"
|
||||
enabled: true
|
||||
size: 50Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
deployment:
|
||||
env:
|
||||
- name: PGSSLMODE
|
||||
value: verify-full
|
||||
- name: PGSSLROOTCERT
|
||||
value: /opt/gitea/.postgresql/root.crt
|
||||
- name: PGSSLCERT
|
||||
value: /opt/gitea/.postgresql/postgresql.crt
|
||||
- name: PGSSLKEY
|
||||
value: /opt/gitea/.postgresql/postgresql.key
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 6
|
||||
memory: 6Gi
|
||||
extraVolumes:
|
||||
- name: ssl-bundle
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: gitea-client-cert
|
||||
items:
|
||||
- key: tls.crt
|
||||
path: postgresql.crt
|
||||
- key: tls.key
|
||||
path: postgresql.key
|
||||
mode: 0600
|
||||
- secret:
|
||||
name: postgres-server-cert
|
||||
items:
|
||||
- key: ca.crt
|
||||
path: root.crt
|
||||
- name: gitea-temp
|
||||
emptyDir: {}
|
||||
extraInitVolumeMounts:
|
||||
- name: ssl-bundle
|
||||
mountPath: /opt/gitea/.postgresql
|
||||
readOnly: true
|
||||
extraContainerVolumeMounts:
|
||||
- name: ssl-bundle
|
||||
mountPath: /opt/gitea/.postgresql
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
- name: gitea-temp
|
||||
mountPath: /tmp/gitea-uploads
|
||||
postgresql-ha:
|
||||
enabled: false
|
||||
valkey-cluster:
|
||||
enabled: false
|
||||
94
utility-services/index.ts
Normal file
94
utility-services/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import * as path from "path";
|
||||
import { DataKubernetesNamespaceV1 } from "@cdktf/provider-kubernetes/lib/data-kubernetes-namespace-v1";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { HelmProvider } from "@cdktf/provider-helm/lib/provider";
|
||||
import { DataTerraformRemoteStateLocal, TerraformStack } from "cdktf";
|
||||
import { Construct } from "constructs";
|
||||
|
||||
import { ValkeyCluster } from "./valkey";
|
||||
import { GiteaServer } from "./gitea";
|
||||
import { AuthentikServer } from "./authentik";
|
||||
import { PostgresCluster } from "./postgres";
|
||||
|
||||
export class UtilityServices extends TerraformStack {
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
const kubernetes = new KubernetesProvider(this, "kubernetes", {
|
||||
configPath: "~/.kube/config",
|
||||
});
|
||||
|
||||
const helm = new HelmProvider(this, "helm", {
|
||||
kubernetes: {
|
||||
configPath: "~/.kube/config",
|
||||
},
|
||||
});
|
||||
|
||||
const homelabState = new DataTerraformRemoteStateLocal(
|
||||
this,
|
||||
"homelab-state",
|
||||
{
|
||||
path: path.join(
|
||||
__dirname,
|
||||
"../cdktf.out/stacks/homelab/terraform.tfstate",
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const namespaceName = homelabState.getString("namespace-output");
|
||||
const namespaceResource = new DataKubernetesNamespaceV1(
|
||||
this,
|
||||
"homelab-namespace",
|
||||
{
|
||||
provider: kubernetes,
|
||||
metadata: {
|
||||
name: namespaceName,
|
||||
},
|
||||
},
|
||||
);
|
||||
const namespace = namespaceResource.metadata.name;
|
||||
|
||||
const r2Endpoint = `${process.env.ACCOUNT_ID!}.r2.cloudflarestorage.com`;
|
||||
|
||||
const valkeyCluster = new ValkeyCluster(this, "valkey-cluster", {
|
||||
namespace,
|
||||
provider: kubernetes,
|
||||
name: "valkey",
|
||||
});
|
||||
|
||||
const postgres = new PostgresCluster(this, "postgres-cluster", {
|
||||
certManagerApiVersion: "cert-manager.io/v1",
|
||||
name: "postgres-cluster",
|
||||
namespace,
|
||||
provider: kubernetes,
|
||||
users: ["shahab", "budget-tracker", "authentik", "gitea"],
|
||||
primaryUser: "shahab",
|
||||
initSecretName: "postgres-password",
|
||||
backupR2EndpointURL: `https://${r2Endpoint}`,
|
||||
});
|
||||
|
||||
const authentik = new AuthentikServer(this, "authentik-server", {
|
||||
providers: {
|
||||
helm,
|
||||
kubernetes,
|
||||
},
|
||||
name: "authentik",
|
||||
namespace,
|
||||
});
|
||||
|
||||
authentik.node.addDependency(valkeyCluster);
|
||||
authentik.node.addDependency(postgres);
|
||||
|
||||
const gitea = new GiteaServer(this, "gitea-server", {
|
||||
providers: {
|
||||
helm,
|
||||
kubernetes,
|
||||
},
|
||||
name: "gitea",
|
||||
namespace,
|
||||
r2Endpoint: r2Endpoint,
|
||||
});
|
||||
|
||||
gitea.node.addDependency(authentik);
|
||||
}
|
||||
}
|
||||
456
utility-services/postgres/index.ts
Normal file
456
utility-services/postgres/index.ts
Normal file
@@ -0,0 +1,456 @@
|
||||
import { Manifest } from "@cdktf/provider-kubernetes/lib/manifest";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { Construct } from "constructs";
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
|
||||
type PostgresClusterOptions = {
|
||||
provider: KubernetesProvider;
|
||||
name: string;
|
||||
namespace: string;
|
||||
users: string[];
|
||||
primaryUser: string;
|
||||
initSecretName: string;
|
||||
certManagerApiVersion: string;
|
||||
backupR2EndpointURL: string;
|
||||
};
|
||||
|
||||
export class PostgresCluster extends Construct {
|
||||
constructor(scope: Construct, id: string, options: PostgresClusterOptions) {
|
||||
super(scope, id);
|
||||
|
||||
const { provider } = options;
|
||||
|
||||
const destinationPath = "s3://postgres-backups/";
|
||||
const endpointURL = options.backupR2EndpointURL;
|
||||
const barmanStoreName = "r2-postgres-backup-store";
|
||||
const backupServerName = `${options.name}-backup`;
|
||||
|
||||
const barmanConfiguration = {
|
||||
destinationPath,
|
||||
endpointURL,
|
||||
s3Credentials: {
|
||||
accessKeyId: {
|
||||
name: "barman-cloudflare-token",
|
||||
key: "access_key_id",
|
||||
},
|
||||
secretAccessKey: {
|
||||
name: "barman-cloudflare-token",
|
||||
key: "secret_access_key",
|
||||
},
|
||||
region: {
|
||||
name: "barman-cloudflare-token",
|
||||
key: "AWS_REGION",
|
||||
},
|
||||
},
|
||||
wal: {
|
||||
compression: "gzip",
|
||||
},
|
||||
data: {
|
||||
compression: "gzip",
|
||||
},
|
||||
};
|
||||
|
||||
new OnePasswordSecret(this, "barman-cloudflare-token", {
|
||||
provider: options.provider,
|
||||
name: "barman-cloudflare-token",
|
||||
namespace: options.namespace,
|
||||
itemPath: "vaults/Lab/items/cloudflare",
|
||||
});
|
||||
|
||||
new Manifest(this, "r2-backup-store", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "barmancloud.cnpg.io/v1",
|
||||
kind: "ObjectStore",
|
||||
metadata: {
|
||||
namespace: options.namespace,
|
||||
name: barmanStoreName,
|
||||
},
|
||||
spec: {
|
||||
retentionPolicy: "15d",
|
||||
configuration: {
|
||||
...barmanConfiguration,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { certManagerApiVersion } = options;
|
||||
|
||||
const certNames = {
|
||||
server: "postgres-server-cert",
|
||||
client: "postgres-client-cert",
|
||||
};
|
||||
|
||||
const caNames = {
|
||||
server: "postgres-server-ca",
|
||||
client: "postgres-client-ca",
|
||||
};
|
||||
|
||||
// Self-signed issuer for creating CA certificates
|
||||
new Manifest(this, "selfsigned-issuer", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
metadata: {
|
||||
name: "selfsigned-issuer",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
selfSigned: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Server CA certificate
|
||||
new Manifest(this, "server-ca-cert", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: "server-ca",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
isCA: true,
|
||||
commonName: caNames.server,
|
||||
secretName: caNames.server,
|
||||
privateKey: {
|
||||
algorithm: "ECDSA",
|
||||
size: 384,
|
||||
},
|
||||
duration: "52560h", // 6 years
|
||||
renewBefore: "8760h", // 1 year before expiration
|
||||
issuerRef: {
|
||||
name: "selfsigned-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Issuer using the server CA
|
||||
new Manifest(this, "server-ca-issuer", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
metadata: {
|
||||
name: `${caNames.server}-issuer`,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
ca: {
|
||||
secretName: caNames.server,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Server certificate
|
||||
new Manifest(this, "server-cert", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: certNames.server,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: certNames.server,
|
||||
usages: ["server auth"],
|
||||
dnsNames: [
|
||||
"postgres-cluster-rw",
|
||||
"postgres-cluster-rw.homelab.svc.cluster.local",
|
||||
"postgres.dogar.dev",
|
||||
],
|
||||
duration: "4380h", // 6 months
|
||||
renewBefore: "720h", // 30 days before expiration
|
||||
issuerRef: {
|
||||
name: `${caNames.server}-issuer`,
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Client CA certificate
|
||||
new Manifest(this, "client-ca", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: "client-ca",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
isCA: true,
|
||||
commonName: caNames.client,
|
||||
secretName: caNames.client,
|
||||
privateKey: {
|
||||
algorithm: "ECDSA",
|
||||
size: 256,
|
||||
},
|
||||
duration: "52560h", // 6 years
|
||||
renewBefore: "8760h", // 1 year before expiration
|
||||
issuerRef: {
|
||||
name: "selfsigned-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Issuer using the client CA
|
||||
new Manifest(this, "client-ca-issuer", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Issuer",
|
||||
metadata: {
|
||||
name: `${caNames.client}-issuer`,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
ca: {
|
||||
secretName: caNames.client,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Secret for client certificate
|
||||
new Manifest(this, `${certNames.client}-secret`, {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "v1",
|
||||
kind: "Secret",
|
||||
metadata: {
|
||||
name: certNames.client,
|
||||
namespace: options.namespace,
|
||||
labels: {
|
||||
"cnpg.io/reload": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Client certificate for streaming replica
|
||||
new Manifest(this, "streaming-replica-cert", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: certNames.client,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: certNames.client,
|
||||
usages: ["client auth"],
|
||||
commonName: "streaming_replica",
|
||||
duration: "4380h", // 6 months
|
||||
renewBefore: "720h", // 30 days before expiration
|
||||
issuerRef: {
|
||||
name: "postgres-client-ca-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Client certificates for users
|
||||
options.users.forEach(
|
||||
(user) =>
|
||||
new Manifest(this, `${user}-client-cert`, {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: certManagerApiVersion,
|
||||
kind: "Certificate",
|
||||
metadata: {
|
||||
name: `${user}-client-cert`,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
secretName: `${user}-client-cert`,
|
||||
usages: ["client auth"],
|
||||
commonName: user,
|
||||
duration: "4380h", // 6 months
|
||||
renewBefore: "720h", // 30 days before expiration
|
||||
issuerRef: {
|
||||
name: "postgres-client-ca-issuer",
|
||||
kind: "Issuer",
|
||||
group: "cert-manager.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
new Manifest(this, "postgres-cluster", {
|
||||
provider,
|
||||
fieldManager: { forceConflicts: true },
|
||||
manifest: {
|
||||
apiVersion: "postgresql.cnpg.io/v1",
|
||||
kind: "Cluster",
|
||||
metadata: {
|
||||
name: options.name,
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
instances: 3,
|
||||
minSyncReplicas: 1,
|
||||
maxSyncReplicas: 2,
|
||||
primaryUpdateStrategy: "unsupervised",
|
||||
certificates: {
|
||||
serverCASecret: certNames.server,
|
||||
serverTLSSecret: certNames.server,
|
||||
clientCASecret: certNames.client,
|
||||
replicationTLSSecret: certNames.client,
|
||||
},
|
||||
postgresql: {
|
||||
parameters: {
|
||||
archive_mode: "on",
|
||||
archive_timeout: "60min",
|
||||
checkpoint_timeout: "10min",
|
||||
checkpoint_completion_target: "0.7",
|
||||
dynamic_shared_memory_type: "posix",
|
||||
full_page_writes: "on",
|
||||
log_destination: "csvlog",
|
||||
log_directory: "/controller/log",
|
||||
log_filename: "postgres",
|
||||
log_rotation_age: "0",
|
||||
log_rotation_size: "0",
|
||||
log_truncate_on_rotation: "false",
|
||||
logging_collector: "on",
|
||||
max_parallel_workers: "32",
|
||||
max_replication_slots: "32",
|
||||
max_worker_processes: "32",
|
||||
max_slot_wal_keep_size: "256MB",
|
||||
max_wal_size: "512MB",
|
||||
min_wal_size: "128MB",
|
||||
shared_memory_type: "mmap",
|
||||
shared_preload_libraries: "",
|
||||
ssl_max_protocol_version: "TLSv1.3",
|
||||
ssl_min_protocol_version: "TLSv1.3",
|
||||
wal_compression: "on",
|
||||
wal_keep_size: "128MB",
|
||||
wal_level: "replica",
|
||||
wal_log_hints: "on",
|
||||
wal_receiver_timeout: "5s",
|
||||
wal_sender_timeout: "5s",
|
||||
},
|
||||
pg_hba: [
|
||||
`hostssl all ${options.primaryUser} all cert`,
|
||||
"hostssl sameuser all all cert",
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: "barman-cloud.cloudnative-pg.io",
|
||||
isWALArchiver: true,
|
||||
parameters: {
|
||||
barmanObjectName: barmanStoreName,
|
||||
serverName: backupServerName,
|
||||
},
|
||||
},
|
||||
],
|
||||
bootstrap: {
|
||||
recovery: {
|
||||
source: "clusterBackup",
|
||||
},
|
||||
},
|
||||
externalClusters: [
|
||||
{
|
||||
name: "clusterBackup",
|
||||
plugin: {
|
||||
name: "barman-cloud.cloudnative-pg.io",
|
||||
parameters: {
|
||||
barmanObjectName: barmanStoreName,
|
||||
serverName: backupServerName,
|
||||
skipWalArchiveCheck: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
managed: {
|
||||
services: {
|
||||
disabledDefaultServices: ["ro", "r"],
|
||||
additional: [
|
||||
{
|
||||
selectorType: "rw",
|
||||
serviceTemplate: {
|
||||
metadata: {
|
||||
name: "postgres-cluster",
|
||||
superuser: true,
|
||||
},
|
||||
spec: {
|
||||
type: "LoadBalancer",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
name: options.primaryUser,
|
||||
inRoles: ["postgres"],
|
||||
inherit: true,
|
||||
disablePassword: true,
|
||||
createdb: true,
|
||||
createrole: true,
|
||||
login: true,
|
||||
ensure: "present",
|
||||
},
|
||||
],
|
||||
},
|
||||
storage: {
|
||||
size: "10Gi",
|
||||
storageClass: "longhorn",
|
||||
},
|
||||
walStorage: {
|
||||
size: "2Gi",
|
||||
storageClass: "longhorn",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
new Manifest(this, "postgres-backup-job", {
|
||||
provider,
|
||||
manifest: {
|
||||
apiVersion: "postgresql.cnpg.io/v1",
|
||||
kind: "ScheduledBackup",
|
||||
metadata: {
|
||||
name: "postgres-cluster",
|
||||
namespace: options.namespace,
|
||||
},
|
||||
spec: {
|
||||
immediate: true,
|
||||
// weekly midnight on Sunday
|
||||
schedule: "* 0 0 * * 0",
|
||||
backupOwnerReference: "self",
|
||||
method: "plugin",
|
||||
pluginConfiguration: {
|
||||
name: "barman-cloud.cloudnative-pg.io",
|
||||
parameters: {
|
||||
barmanObjectName: barmanStoreName,
|
||||
serverName: backupServerName,
|
||||
},
|
||||
},
|
||||
cluster: {
|
||||
name: options.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
128
utility-services/valkey/index.ts
Normal file
128
utility-services/valkey/index.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { DeploymentV1 } from "@cdktf/provider-kubernetes/lib/deployment-v1";
|
||||
import { KubernetesProvider } from "@cdktf/provider-kubernetes/lib/provider";
|
||||
import { ServiceV1 } from "@cdktf/provider-kubernetes/lib/service-v1";
|
||||
import { Construct } from "constructs";
|
||||
import { OnePasswordSecret } from "../../utils";
|
||||
|
||||
type ValkeyClusterOptions = {
|
||||
provider: KubernetesProvider;
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
|
||||
export class ValkeyCluster extends Construct {
|
||||
constructor(scope: Construct, id: string, options: ValkeyClusterOptions) {
|
||||
super(scope, id);
|
||||
|
||||
// Labels used by both Deployment and Service
|
||||
const labels = { app: "valkey" };
|
||||
const { provider, name, namespace } = options;
|
||||
|
||||
new OnePasswordSecret(this, "valkey-secret", {
|
||||
provider,
|
||||
name: "valkey",
|
||||
namespace,
|
||||
itemPath: "vaults/Lab/items/valkey",
|
||||
});
|
||||
|
||||
new DeploymentV1(this, "valkey-deployment", {
|
||||
provider,
|
||||
metadata: {
|
||||
name,
|
||||
namespace,
|
||||
labels,
|
||||
},
|
||||
spec: {
|
||||
replicas: "1",
|
||||
strategy: {
|
||||
type: "RollingUpdate",
|
||||
rollingUpdate: {
|
||||
maxSurge: "1",
|
||||
maxUnavailable: "0",
|
||||
},
|
||||
},
|
||||
selector: { matchLabels: labels },
|
||||
template: {
|
||||
metadata: { labels },
|
||||
spec: {
|
||||
container: [
|
||||
{
|
||||
name: "valkey",
|
||||
image: "docker.io/valkey/valkey:8.1.3",
|
||||
port: [{ name: "client", containerPort: 6379 }],
|
||||
env: [
|
||||
{
|
||||
name: "PASSWORD",
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
name: "valkey",
|
||||
key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
command: ["/bin/sh", "-c"],
|
||||
args: ['exec valkey-server --requirepass "$PASSWORD"'],
|
||||
readinessProbe: {
|
||||
tcpSocket: [
|
||||
{
|
||||
port: "6379",
|
||||
},
|
||||
],
|
||||
initialDelaySeconds: 5,
|
||||
periodSeconds: 5,
|
||||
timeoutSeconds: 3,
|
||||
failureThreshold: 5,
|
||||
},
|
||||
livenessProbe: {
|
||||
tcpSocket: [
|
||||
{
|
||||
port: "6379",
|
||||
},
|
||||
],
|
||||
initialDelaySeconds: 20,
|
||||
periodSeconds: 10,
|
||||
timeoutSeconds: 5,
|
||||
failureThreshold: 5,
|
||||
},
|
||||
resources: {
|
||||
requests: {
|
||||
cpu: "100m",
|
||||
memory: "128Mi",
|
||||
},
|
||||
limits: {
|
||||
memory: "512Mi",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
new ServiceV1(this, "valkey-service", {
|
||||
provider,
|
||||
metadata: {
|
||||
name,
|
||||
namespace,
|
||||
labels,
|
||||
annotations: {
|
||||
"external-dns.alpha.kubernetes.io/hostname": "valkey.dogar.dev",
|
||||
"metallb.io/ip-allocated-from-pool": "pool",
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
type: "LoadBalancer",
|
||||
selector: labels,
|
||||
port: [
|
||||
{
|
||||
name: "client",
|
||||
port: 6379,
|
||||
targetPort: "client",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user