Feature/deploy ha containers #10

Merged
kasun merged 7 commits from feature/deploy-HA-containers into main 2026-06-06 04:50:49 +02:00
8 changed files with 3080 additions and 6 deletions
+87
View File
@@ -0,0 +1,87 @@
name: Deploy k8s Apps
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "04-k8s-apps/**"
- ".gitea/workflows/**"
pull_request:
branches:
- main
paths:
- "04-k8s-apps/**"
- ".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_APPS_PULUMI_DEV_YAML }}" | base64 -d > 04-k8s-apps/Pulumi.dev.yaml
- name: Install Dependencies
run: npm install
working-directory: 04-k8s-apps
- name: Preview
uses: pulumi/actions@v5
with:
command: preview
stack-name: dev
work-dir: 04-k8s-apps
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_APPS_PULUMI_DEV_YAML }}" | base64 -d > 04-k8s-apps/Pulumi.dev.yaml
- name: Install Dependencies
run: npm install
working-directory: 04-k8s-apps
- name: Refresh State
uses: pulumi/actions@v5
with:
command: refresh
stack-name: dev
work-dir: 04-k8s-apps
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: 04-k8s-apps
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
+2 -2
View File
@@ -254,11 +254,11 @@ const k3sVms = nodeConfigs.map(
{ {
interface: "scsi0", interface: "scsi0",
datastoreId: node.diskDatastore, datastoreId: node.diskDatastore,
size: node.role === "master" ? 10 : 20, size: 20,
ssd: true, ssd: true,
discard: "on", discard: "on",
}, },
...(node.longhornDiskSize ...(node.longhornDiskSize == null
? [] ? []
: [ : [
{ {
+23 -4
View File
@@ -3,7 +3,6 @@ import * as k8s from "@pulumi/kubernetes";
const config = new pulumi.Config(); const config = new pulumi.Config();
//fetch credentials from k8s-bootstrap
const infraRef = new pulumi.StackReference( const infraRef = new pulumi.StackReference(
`${pulumi.getOrganization()}/k8s-bootstrap/dev`, `${pulumi.getOrganization()}/k8s-bootstrap/dev`,
); );
@@ -20,7 +19,7 @@ const opts = (extras?: pulumi.ResourceOptions): pulumi.ResourceOptions => ({
...extras, ...extras,
}); });
// ── 1. NFS CSI Driver ──────────────────────────────────────────────────────── // ── NFS CSI Driver ────────────────────────────────────────────────────────
const nfsCsiDriver = new k8s.helm.v3.Release( const nfsCsiDriver = new k8s.helm.v3.Release(
"nfs-csi-driver", "nfs-csi-driver",
@@ -57,7 +56,7 @@ new k8s.storage.v1.StorageClass(
opts({ dependsOn: [nfsCsiDriver] }), opts({ dependsOn: [nfsCsiDriver] }),
); );
// ── 2. cert-manager ────────────────────────────────────────────────────────── // ── cert-manager ──────────────────────────────────────────────────────────
const certManager = new k8s.helm.v3.Release( const certManager = new k8s.helm.v3.Release(
"cert-manager", "cert-manager",
@@ -75,7 +74,7 @@ const certManager = new k8s.helm.v3.Release(
opts(), opts(),
); );
// ── 3. Cloudflare token secret + ClusterIssuer ─────────────────────────────── // ── Cloudflare token secret + ClusterIssuer ───────────────────────────────
const cfSecret = new k8s.core.v1.Secret( const cfSecret = new k8s.core.v1.Secret(
"cloudflare-token", "cloudflare-token",
@@ -115,4 +114,24 @@ new k8s.apiextensions.CustomResource(
opts({ dependsOn: [certManager, cfSecret] }), opts({ dependsOn: [certManager, cfSecret] }),
); );
// ── Longhorn ──────────────────────────────────────────────────────────────
new k8s.helm.v3.Release(
"longhorn",
{
name: "longhorn",
chart: "longhorn",
repositoryOpts: { repo: "https://charts.longhorn.io" },
namespace: "longhorn-system",
createNamespace: true,
version: "1.12.0",
values: {
defaultSettings: {
defaultReplicaCount: 3,
},
},
},
opts(),
);
export const storageClass = "truenas-nfs"; export const storageClass = "truenas-nfs";
+6
View File
@@ -0,0 +1,6 @@
name: k8s-apps
description: Application workloads for the k3s homelab cluster
runtime:
name: nodejs
options:
packagemanager: npm
+133
View File
@@ -0,0 +1,133 @@
import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
const config = new pulumi.Config();
//fetch credentials from k8s-bootstrap
const bootstrapRef = new pulumi.StackReference(
`${pulumi.getOrganization()}/k8s-bootstrap/dev`,
);
const kubeconfig = bootstrapRef.requireOutput("kubeconfig");
const domain = config.requireSecret("domain");
const k8sProvider = new k8s.Provider("k3s", { kubeconfig });
const opts = (extras?: pulumi.ResourceOptions): pulumi.ResourceOptions => ({
provider: k8sProvider,
...extras,
});
const ns = new k8s.core.v1.Namespace(
"heimdall",
{ metadata: { name: "heimdall" } },
opts(),
);
const pvc = new k8s.core.v1.PersistentVolumeClaim(
"heimdall-config",
{
metadata: { name: "heimdall-config", namespace: ns.metadata.name },
spec: {
accessModes: ["ReadWriteOnce"],
storageClassName: "longhorn",
resources: { requests: { storage: "50Mi" } },
},
},
opts(),
);
const labels = { app: "heimdall" };
new k8s.apps.v1.Deployment(
"heimdall",
{
metadata: {
name: "heimdall",
namespace: ns.metadata.name,
annotations: { "pulumi.com/patchForce": "true" },
},
spec: {
replicas: 1,
selector: { matchLabels: labels },
template: {
metadata: { labels },
spec: {
containers: [
{
name: "heimdall",
image: "lscr.io/linuxserver/heimdall:latest",
env: [
{ name: "PUID", value: "1000" },
{ name: "PGID", value: "1000" },
],
ports: [{ containerPort: 80 }],
volumeMounts: [{ name: "config", mountPath: "/config" }],
},
],
volumes: [
{
name: "config",
persistentVolumeClaim: { claimName: pvc.metadata.name },
},
],
terminationGracePeriodSeconds: 5,
},
},
},
},
opts(),
);
const svc = new k8s.core.v1.Service(
"heimdall",
{
metadata: { name: "heimdall", namespace: ns.metadata.name },
spec: {
selector: labels,
ports: [{ port: 80, targetPort: 80 }],
type: "ClusterIP",
},
},
opts(),
);
const host = pulumi.interpolate`heimdall.${domain}`;
new k8s.networking.v1.Ingress(
"heimdall",
{
metadata: {
name: "heimdall",
namespace: ns.metadata.name,
annotations: {
"cert-manager.io/cluster-issuer": "letsencrypt-prod",
},
},
spec: {
ingressClassName: "traefik",
tls: [{ hosts: [host], secretName: "heimdall-tls" }],
rules: [
{
host,
http: {
paths: [
{
path: "/",
pathType: "Prefix",
backend: {
service: {
name: svc.metadata.name,
port: { number: 80 },
},
},
},
],
},
},
],
},
},
opts(),
);
export const ingressHost = host;
+2799
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
{
"name": "k8s-apps",
"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": "es2020",
"module": "nodenext",
"moduleResolution": "nodenext",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}