Compare commits

...
14 Commits
Author SHA1 Message Date
kasun f8f14db80a Merge pull request 'changed grafana persistence to postgresql db' (#29) from persist-grafana-data-on-postgres into main
Deploy Monitoring / Pulumi Preview (push) Has been skipped
Deploy Monitoring / Pulumi Deploy (push) Successful in 1m37s
2026-07-08 22:47:53 +02:00
kasun ea35bb55e4 changed grafana persistence to postgresql db
Deploy Monitoring / Pulumi Preview (pull_request) Successful in 54s
Deploy Monitoring / Pulumi Deploy (pull_request) Has been skipped
2026-07-08 22:44:30 +02:00
kasun 7a54674ba2 added 2nd replica to prometheus
Deploy Monitoring / Pulumi Preview (push) Has been skipped
Deploy Monitoring / Pulumi Deploy (push) Successful in 1m6s
2026-07-06 00:14:59 +02:00
kasun 93a5eac028 set longer scrape and evaluation intervals
Deploy Monitoring / Pulumi Preview (push) Has been skipped
Deploy Monitoring / Pulumi Deploy (push) Successful in 1m2s
2026-07-05 21:41:47 +02:00
kasun 21c3d0402e added prometheus retention details
Deploy Monitoring / Pulumi Preview (push) Has been skipped
Deploy Monitoring / Pulumi Deploy (push) Successful in 1m2s
2026-07-05 21:24:23 +02:00
kasun 189fe17cf6 added taints to keep regular workloads of master nodes
Deploy k8s Infra / Pulumi Preview (push) Has been skipped
Deploy k8s Infra / Pulumi Deploy (push) Successful in 54s
2026-07-05 19:17:54 +02:00
kasun 0d233c1544 updated CLAUDE.MD with new Longhorn settings for dedicated mounts and persistence only set in worker nodes
Deploy k8s Infra / Pulumi Preview (push) Has been skipped
Deploy k8s Infra / Pulumi Deploy (push) Successful in 55s
2026-07-05 18:33:20 +02:00
kasun 71732df894 Merge pull request 'Bug/fix longhorn disk pressure' (#28) from bug/fix-longhorn-diskPressure into main
Deploy k8s Infra / Pulumi Preview (push) Has been skipped
Deploy k8s Infra / Pulumi Deploy (push) Successful in 1m11s
2026-07-05 03:52:05 +02:00
kasun 135458c9ac added patch to longhorn to use dedicated mount for longhorn to avoid using root disk and causing diskPressure
Deploy k8s Bootstrap / Pulumi Preview (pull_request) Successful in 3m18s
Deploy k8s Bootstrap / Bootstrap k3s Cluster (pull_request) Has been skipped
Deploy k8s Infra / Pulumi Preview (pull_request) Successful in 1m7s
Deploy k8s Infra / Pulumi Deploy (pull_request) Has been skipped
2026-07-05 03:46:42 +02:00
kasun b803ec937c added mounting of dedicated longhorn storage to worker nodes. 2026-07-05 03:45:06 +02:00
kasun bcccd79148 decreased replicas for prometheus from 3 to 2 2026-07-05 02:08:43 +02:00
kasun 7274953cd2 Merge pull request 'added longhorn pod deletion policy for stateful apps' (#27) from bug/add-longhorn-pod-deletion-policy into main
Deploy k8s Infra / Pulumi Preview (push) Has been skipped
Deploy k8s Infra / Pulumi Deploy (push) Successful in 14m21s
2026-06-20 03:32:49 +02:00
kasun 3b1c2f4901 replaced deletion policy with param
Deploy k8s Infra / Pulumi Preview (pull_request) Successful in 14m5s
Deploy k8s Infra / Pulumi Deploy (pull_request) Has been skipped
2026-06-20 03:14:46 +02:00
kasun 8b913e921f added longhorn pod deletion policy for stateful apps
Deploy k8s Infra / Pulumi Preview (pull_request) Failing after 15m19s
Deploy k8s Infra / Pulumi Deploy (pull_request) Has been skipped
2026-06-20 02:51:09 +02:00
5 changed files with 116 additions and 11 deletions
+2 -1
View File
@@ -11,7 +11,8 @@ Bootstraps a k3s cluster on the Proxmox VMs created by `proxmox-infra`. Starts V
3. Installs k3s on `k3s-master-1` with `--cluster-init --tls-san <master1Ip>`
4. Joins `k3s-master-2` and `k3s-master-3` as embedded etcd nodes
5. Joins `k3s-worker-1` and `k3s-worker-2` as agent nodes
6. Reads `/etc/rancher/k3s/k3s.yaml` from master-1 via SSH, patches the server URL, and exports it as the secret stack output `kubeconfig`
6. Formats the workers' dedicated `scsi1` disk (ext4, if not already formatted) and mounts it at `/mnt/longhorn-extra` (via `/etc/fstab`) — this is the disk `03-k8s-infra` later registers as an extra Longhorn disk, to keep Longhorn off the root filesystem and avoid `DiskPressure`
7. Reads `/etc/rancher/k3s/k3s.yaml` from master-1 via SSH, patches the server URL, and exports it as the secret stack output `kubeconfig`
VM IDs and the CI runner SSH private key are read automatically from the `proxmox-infra` stack output via StackReference — no manual setup needed for those.
+26 -1
View File
@@ -215,7 +215,32 @@ const joinWorker2 = new command.remote.Command(
);
// ---------------------------------------------------------------------------
// Step 5: Read kubeconfig from master-1, patch server URL for external access
// Step 5: Format and mount Longhorn disks
// ---------------------------------------------------------------------------
const workers = [worker1Ip, worker2Ip];
const workerSshReader = [waitWorker1Ssh, waitWorker2Ssh];
const mountLonghornDisks = workers.map((worker, i) => {
return new command.remote.Command(
`mount-longhorn-disk-${i}`,
{
connection: conn(worker),
create: `set -euo pipefail;
DISK_ID=$(ls /dev/disk/by-id/ | grep 'scsi1');
DISK_PATH="/dev/disk/by-id/$DISK_ID";
sudo blkid "$DISK_PATH" >/dev/null 2>&1 || sudo mkfs.ext4 "$DISK_PATH";
sudo mkdir -p /mnt/longhorn-extra;
echo ""$DISK_PATH" /mnt/longhorn-extra ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab;
sudo mount -a;
df -h /mnt/longhorn-extra`,
},
{ dependsOn: workerSshReader[i] },
);
});
// ---------------------------------------------------------------------------
// Step 6: Read kubeconfig from master-1, patch server URL for external access
// ---------------------------------------------------------------------------
const getKubeconfig = new command.remote.Command(
+1 -1
View File
@@ -21,7 +21,7 @@ CI installs the `helm` CLI (`azure/setup-helm`) before running Pulumi.
| `truenas-nfs` StorageClass | Backed by an NFS share on TrueNAS (`Retain` reclaim policy) |
| cert-manager | Certificate lifecycle management |
| `letsencrypt-prod` ClusterIssuer | DNS-01 via Cloudflare, issues Let's Encrypt TLS certs |
| Longhorn | Default block storage (`longhorn` StorageClass, 3 replicas) — used by app/monitoring PVCs |
| Longhorn | Default block storage (`longhorn` StorageClass, 2 replicas) — used by app/monitoring PVCs. Worker nodes get an extra `longhorn-extra` disk at `/mnt/longhorn-extra` (a dedicated mount set up by `k8s-bootstrap`, patched onto the Longhorn `Node` CR here) so Longhorn data stays off the root disk and doesn't cause `DiskPressure`. `nodeDownPodDeletionPolicy` deletes both the StatefulSet and Deployment pod on node-down so stateful workloads reschedule automatically |
| kube-vip | DaemonSet that ARP-announces a virtual IP for LoadBalancer Services |
| kube-vip-cloud-provider | Allocates LoadBalancer IPs from a single-address pool (`kubeVipAddress`) — k3s's built-in ServiceLB is not used |
| `traefik` ServicePatch | Pins Traefik's LoadBalancer IP to `kubeVipAddress` |
+54 -4
View File
@@ -122,7 +122,7 @@ new k8s.apiextensions.CustomResource(
// ── Longhorn ──────────────────────────────────────────────────────────────
new k8s.helm.v3.Release(
const longhorn = new k8s.helm.v3.Release(
"longhorn",
{
name: "longhorn",
@@ -133,13 +133,46 @@ new k8s.helm.v3.Release(
version: "1.12.0",
values: {
defaultSettings: {
defaultReplicaCount: 3,
defaultReplicaCount: 2,
nodeDownPodDeletionPolicy: "delete-both-statefulset-and-deployment-pod",
createDefaultDiskLabeledNodes: true,
},
},
},
opts(),
);
// Patches Longhorn instances to use the mountend Longhorn storage on the worker nodes.
["k3s-worker-1", "k3s-worker-2"].forEach(
(nodeName) =>
new k8s.apiextensions.CustomResourcePatch(
`${nodeName}-longhorn-disks`,
{
apiVersion: "longhorn.io/v1beta2",
kind: "Node",
metadata: {
name: nodeName,
namespace: "longhorn-system",
annotations: { "pulumi.com/patchForce": "true" },
},
spec: {
disks: {
"longhorn-extra": {
allowScheduling: true,
diskDriver: "",
diskType: "filesystem",
evictionRequested: false,
path: "/mnt/longhorn-extra",
storageReserved: 0,
tags: [],
},
},
},
},
opts({ dependsOn: [longhorn] }),
),
);
// ── kube-vip ────────────
const kubeVipNamespace = "kube-system";
@@ -148,6 +181,22 @@ const controlPlaneTolerations = [
{ key: "node-role.kubernetes.io/control-plane", effect: "NoSchedule" },
];
// Keeps regular workloads off the master nodes; kube-vip and
// kube-vip-cloud-provider below explicitly tolerate these same taints.
["k3s-master-1", "k3s-master-2", "k3s-master-3"].forEach((nodeName) => {
new k8s.core.v1.NodePatch(
`${nodeName}-control-plane-taint`,
{
metadata: {
name: nodeName,
annotations: { "pulumi.com/patchForce": "true" },
},
spec: { taints: controlPlaneTolerations },
},
opts(),
);
});
const kubeVipServiceAccount = new k8s.core.v1.ServiceAccount(
"kube-vip",
{ metadata: { name: "kube-vip", namespace: kubeVipNamespace } },
@@ -336,7 +385,9 @@ const kubeVipPool = new k8s.core.v1.ConfigMap(
"kube-vip-pool",
{
metadata: { name: "kubevip", namespace: kubeVipNamespace },
data: { "range-global": pulumi.interpolate`${kubeVipAddress}-${kubeVipAddress}` },
data: {
"range-global": pulumi.interpolate`${kubeVipAddress}-${kubeVipAddress}`,
},
},
opts(),
);
@@ -387,4 +438,3 @@ new k8s.core.v1.ServicePatch(
},
opts({ dependsOn: [kubeVip, kubeVipCloudProvider] }),
);
+33 -4
View File
@@ -13,6 +13,10 @@ const appsRef = new pulumi.StackReference(
const kubeconfig = bootstrapRef.requireOutput("kubeconfig");
const domain = appsRef.requireOutput("domain");
const grafanaAdminPassword = config.requireSecret("grafanaAdminPassword");
const grafanaDbUser = config.requireSecret("grafanaDbUser");
const grafanaDbPassword = config.requireSecret("grafanaDbPassword");
const grafanaDbHost = config.requireSecret("grafanaDbHost");
const grafanaDbName = config.require("grafanaDbName");
const k8sProvider = new k8s.Provider("k3s", { kubeconfig });
@@ -24,6 +28,15 @@ const monitoringNs = new k8s.core.v1.Namespace(
const grafanaHost = pulumi.interpolate`grafana.${domain}`;
const grafanaDbSecret = new k8s.core.v1.Secret(
"grafana-db-credentials",
{
metadata: { namespace: monitoringNs.metadata.name },
stringData: { password: grafanaDbPassword },
},
{ provider: k8sProvider },
);
new k8s.helm.v3.Release(
"kube-prometheus-stack",
{
@@ -38,6 +51,7 @@ new k8s.helm.v3.Release(
values: {
grafana: {
adminPassword: grafanaAdminPassword,
replicas: 2,
ingress: {
enabled: true,
ingressClassName: "traefik",
@@ -45,14 +59,29 @@ new k8s.helm.v3.Release(
hosts: [grafanaHost],
tls: [{ secretName: "grafana-tls", hosts: [grafanaHost] }],
},
persistence: {
enabled: true,
storageClassName: "longhorn",
size: "2Gi",
env: {
GF_DATABASE_TYPE: "postgres",
GF_DATABASE_HOST: grafanaDbHost,
GF_DATABASE_NAME: grafanaDbName,
GF_DATABASE_USER: grafanaDbUser,
GF_DATABASE_SSL_MODE: "disable",
},
envValueFrom: {
GF_DATABASE_PASSWORD: {
secretKeyRef: {
name: grafanaDbSecret.metadata.name,
key: "password",
},
},
},
},
prometheus: {
prometheusSpec: {
retention: "7d",
retentionSize: "15GB",
scrapeInterval: "60s",
evaluationInterval: "60s",
replicas: "2",
storageSpec: {
volumeClaimTemplate: {
spec: {