2 Commits

Author SHA1 Message Date
kasun 7cdc35d696 added nfs and cert management
Deploy k8s Infra / Pulumi Deploy (pull_request) Has been skipped
Deploy Proxmox Infra / Pulumi Preview (pull_request) Successful in 1m21s
Deploy Proxmox Infra / Pulumi Deploy (pull_request) Has been skipped
Deploy k8s Infra / Pulumi Preview (pull_request) Successful in 1m7s
2026-06-01 16:18:35 +02:00
kasun 66cba5a075 Merge pull request 'Enhancement/improve k8s bootstrap deployment' (#4) from enhancement/improve-k8s-bootstrap-deployment into main
Deploy k8s Bootstrap / Pulumi Preview (push) Has been skipped
Deploy k8s Bootstrap / Bootstrap k3s Cluster (push) Successful in 4m24s
Deploy Proxmox Infra / Pulumi Preview (push) Has been skipped
Deploy Proxmox Infra / Pulumi Deploy (push) Successful in 16m19s
Reviewed-on: #4
2026-06-01 03:34:23 +02:00
6 changed files with 3045 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
name: Deploy k8s Infra
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'k8s-infra/**'
- '.gitea/workflows/**'
pull_request:
branches:
- main
paths:
- 'k8s-infra/**'
- '.gitea/workflows/**'
jobs:
preview:
name: Pulumi Preview
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Restore Stack Config
run: echo "${{ secrets.K8S_INFRA_PULUMI_DEV_YAML }}" | base64 -d > k8s-infra/Pulumi.dev.yaml
- name: Install Helm
uses: azure/setup-helm@v4
- name: Install Dependencies
run: npm ci
working-directory: k8s-infra
- name: Preview
uses: pulumi/actions@v5
with:
command: preview
stack-name: dev
work-dir: k8s-infra
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
deploy:
name: Pulumi Deploy
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Restore Stack Config
run: echo "${{ secrets.K8S_INFRA_PULUMI_DEV_YAML }}" | base64 -d > k8s-infra/Pulumi.dev.yaml
- name: Install Helm
uses: azure/setup-helm@v4
- name: Install Dependencies
run: npm ci
working-directory: k8s-infra
- name: Refresh State
uses: pulumi/actions@v5
with:
command: refresh
stack-name: dev
work-dir: k8s-infra
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
- name: Deploy
uses: pulumi/actions@v5
with:
command: up
stack-name: dev
work-dir: k8s-infra
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
+6
View File
@@ -0,0 +1,6 @@
name: k8s-infra
description: Cluster-level infrastructure for the k3s homelab cluster
runtime:
name: nodejs
options:
packagemanager: npm
+117
View File
@@ -0,0 +1,117 @@
import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
const config = new pulumi.Config();
//fetch credentials from k8s-bootstrap
const infraRef = new pulumi.StackReference(
`${pulumi.getOrganization()}/k8s-bootstrap/dev`,
);
const kubeconfig = infraRef.requireOutput("kubeconfig");
const truenasHost = config.requireSecret("truenasHost");
const truenasNfsPath = config.requireSecret("truenasNfsPath");
const cloudflareToken = config.requireSecret("cloudflareApiToken");
const letsencryptEmail = config.requireSecret("letsencryptEmail");
const k8sProvider = new k8s.Provider("k3s", { kubeconfig });
const opts = (extras?: pulumi.ResourceOptions): pulumi.ResourceOptions => ({
provider: k8sProvider,
...extras,
});
// ── 1. NFS CSI Driver ────────────────────────────────────────────────────────
const nfsCsiDriver = new k8s.helm.v3.Release(
"nfs-csi-driver",
{
name: "csi-driver-nfs",
chart: "csi-driver-nfs",
repositoryOpts: {
repo: "https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts",
},
namespace: "kube-system",
version: "4.12.0",
values: {
kubeletDir: "/var/lib/kubelet",
},
},
opts(),
);
new k8s.storage.v1.StorageClass(
"truenas-nfs",
{
metadata: { name: "truenas-nfs" },
provisioner: "nfs.csi.k8s.io",
parameters: {
server: truenasHost,
share: truenasNfsPath,
mountPermissions: "0",
},
reclaimPolicy: "Retain",
volumeBindingMode: "Immediate",
allowVolumeExpansion: true,
mountOptions: ["nfsvers=4.1"],
},
opts({ dependsOn: [nfsCsiDriver] }),
);
// ── 2. cert-manager ──────────────────────────────────────────────────────────
const certManager = new k8s.helm.v3.Release(
"cert-manager",
{
name: "cert-manager",
chart: "cert-manager",
repositoryOpts: { repo: "https://charts.jetstack.io" },
namespace: "cert-manager",
createNamespace: true,
values: {
installCRDs: true,
},
},
opts(),
);
// ── 3. Cloudflare token secret + ClusterIssuer ───────────────────────────────
const cfSecret = new k8s.core.v1.Secret(
"cloudflare-token",
{
metadata: { name: "cloudflare-api-token", namespace: "cert-manager" },
stringData: { "api-token": cloudflareToken },
},
opts({ dependsOn: [certManager] }),
);
new k8s.apiextensions.CustomResource(
"letsencrypt-prod",
{
apiVersion: "cert-manager.io/v1",
kind: "ClusterIssuer",
metadata: { name: "letsencrypt-prod" },
spec: {
acme: {
server: "https://acme-v02.api.letsencrypt.org/directory",
email: letsencryptEmail,
privateKeySecretRef: { name: "letsencrypt-prod-account-key" },
solvers: [
{
dns01: {
cloudflare: {
apiTokenSecretRef: {
name: cfSecret.metadata.name,
key: "api-token",
},
},
},
},
],
},
},
},
opts({ dependsOn: [certManager, cfSecret] }),
);
export const storageClass = "truenas-nfs";
+2799
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
{
"name": "k8s-infra",
"main": "index.ts",
"devDependencies": {
"@types/node": "^18",
"typescript": "^5.0.0"
},
"dependencies": {
"@pulumi/kubernetes": "^4.0.0",
"@pulumi/pulumi": "^3.113.0"
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2024",
"module": "nodenext",
"moduleResolution": "nodenext",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}