Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cdc35d696 | |||
| 66cba5a075 | |||
| d4a3c38847 | |||
| 8eb59643cf | |||
| e6d2b6154a | |||
| c8e688b9ff | |||
| 4a96cb9d07 | |||
| 3d38d60aa5 |
@@ -6,14 +6,14 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- 'k8s-bootstrap/**'
|
- "k8s-bootstrap/**"
|
||||||
- '.gitea/workflows/deploy-k8s-bootstrap.yaml'
|
- ".gitea/workflows/deploy-k8s-bootstrap.yaml"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- 'k8s-bootstrap/**'
|
- "k8s-bootstrap/**"
|
||||||
- '.gitea/workflows/deploy-k8s-bootstrap.yaml'
|
- ".gitea/workflows/deploy-k8s-bootstrap.yaml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
preview:
|
preview:
|
||||||
@@ -27,10 +27,10 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: "24"
|
||||||
|
|
||||||
- name: Restore Stack Config
|
- name: Restore Stack Config
|
||||||
run: echo "${{ secrets.K8S_BOOTSTRAP_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
|
run: echo "${{ secrets.K8S_BOOTSTRAP_PULUMI_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
@@ -57,10 +57,10 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: "24"
|
||||||
|
|
||||||
- name: Restore Stack Config
|
- name: Restore Stack Config
|
||||||
run: echo "${{ secrets.K8S_BOOTSTRAP_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
|
run: echo "${{ secrets.K8S_BOOTSTRAP_PULUMI_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|||||||
@@ -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 }}
|
||||||
+16
-14
@@ -23,11 +23,11 @@ const pve2ApiToken = infraRef.requireOutput(
|
|||||||
) as pulumi.Output<string>;
|
) as pulumi.Output<string>;
|
||||||
|
|
||||||
// Node IPs — static DHCP leases set in the router
|
// Node IPs — static DHCP leases set in the router
|
||||||
const master1Ip = config.require("master1Ip");
|
const master1Ip = infraRef.requireOutput("master1Ip");
|
||||||
const master2Ip = config.require("master2Ip");
|
const master2Ip = infraRef.requireOutput("master2Ip");
|
||||||
const master3Ip = config.require("master3Ip");
|
const master3Ip = infraRef.requireOutput("master3Ip");
|
||||||
const worker1Ip = config.require("worker1Ip");
|
const worker1Ip = infraRef.requireOutput("worker1Ip");
|
||||||
const worker2Ip = config.require("worker2Ip");
|
const worker2Ip = infraRef.requireOutput("worker2Ip");
|
||||||
|
|
||||||
// Pre-shared k3s cluster token
|
// Pre-shared k3s cluster token
|
||||||
const k3sToken = config.requireSecret("k3sToken");
|
const k3sToken = config.requireSecret("k3sToken");
|
||||||
@@ -47,7 +47,9 @@ const worker1VmId = vmIdsOutput.apply((ids) => String(ids.worker1));
|
|||||||
const worker2VmId = vmIdsOutput.apply((ids) => String(ids.worker2));
|
const worker2VmId = vmIdsOutput.apply((ids) => String(ids.worker2));
|
||||||
|
|
||||||
// SSH connection helper
|
// SSH connection helper
|
||||||
function conn(ip: string): command.types.input.remote.ConnectionArgs {
|
function conn(
|
||||||
|
ip: pulumi.Input<string>,
|
||||||
|
): command.types.input.remote.ConnectionArgs {
|
||||||
return { host: ip, user: "ubuntu", privateKey: ciRunnerPrivateKey };
|
return { host: ip, user: "ubuntu", privateKey: ciRunnerPrivateKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ const allStarts = [
|
|||||||
const waitMaster1Ssh = new command.local.Command(
|
const waitMaster1Ssh = new command.local.Command(
|
||||||
"wait-ssh-master-1",
|
"wait-ssh-master-1",
|
||||||
{
|
{
|
||||||
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
||||||
triggers: [master1VmId],
|
triggers: [master1VmId],
|
||||||
interpreter: ["/bin/bash", "-c"],
|
interpreter: ["/bin/bash", "-c"],
|
||||||
},
|
},
|
||||||
@@ -131,7 +133,7 @@ const waitK3sMaster1Ready = new command.remote.Command(
|
|||||||
const waitMaster2Ssh = new command.local.Command(
|
const waitMaster2Ssh = new command.local.Command(
|
||||||
"wait-ssh-master-2",
|
"wait-ssh-master-2",
|
||||||
{
|
{
|
||||||
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
||||||
triggers: [master2VmId],
|
triggers: [master2VmId],
|
||||||
interpreter: ["/bin/bash", "-c"],
|
interpreter: ["/bin/bash", "-c"],
|
||||||
},
|
},
|
||||||
@@ -141,7 +143,7 @@ const waitMaster2Ssh = new command.local.Command(
|
|||||||
const waitMaster3Ssh = new command.local.Command(
|
const waitMaster3Ssh = new command.local.Command(
|
||||||
"wait-ssh-master-3",
|
"wait-ssh-master-3",
|
||||||
{
|
{
|
||||||
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master3Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master3Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
||||||
triggers: [master3VmId],
|
triggers: [master3VmId],
|
||||||
interpreter: ["/bin/bash", "-c"],
|
interpreter: ["/bin/bash", "-c"],
|
||||||
},
|
},
|
||||||
@@ -175,7 +177,7 @@ const joinMaster3 = new command.remote.Command(
|
|||||||
const waitWorker1Ssh = new command.local.Command(
|
const waitWorker1Ssh = new command.local.Command(
|
||||||
"wait-ssh-worker-1",
|
"wait-ssh-worker-1",
|
||||||
{
|
{
|
||||||
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
||||||
triggers: [worker1VmId],
|
triggers: [worker1VmId],
|
||||||
interpreter: ["/bin/bash", "-c"],
|
interpreter: ["/bin/bash", "-c"],
|
||||||
},
|
},
|
||||||
@@ -185,7 +187,7 @@ const waitWorker1Ssh = new command.local.Command(
|
|||||||
const waitWorker2Ssh = new command.local.Command(
|
const waitWorker2Ssh = new command.local.Command(
|
||||||
"wait-ssh-worker-2",
|
"wait-ssh-worker-2",
|
||||||
{
|
{
|
||||||
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
|
||||||
triggers: [worker2VmId],
|
triggers: [worker2VmId],
|
||||||
interpreter: ["/bin/bash", "-c"],
|
interpreter: ["/bin/bash", "-c"],
|
||||||
},
|
},
|
||||||
@@ -227,7 +229,7 @@ const getKubeconfig = new command.remote.Command(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const kubeconfig = pulumi.secret(
|
export const kubeconfig = pulumi.secret(
|
||||||
getKubeconfig.stdout.apply((kc) =>
|
pulumi
|
||||||
kc.replace(/127\.0\.0\.1/g, master1Ip).trim(),
|
.all([getKubeconfig.stdout, master1Ip])
|
||||||
),
|
.apply(([kc, ip]) => kc.replaceAll("127.0.0.1", ip as string).trim()),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"outDir": "bin",
|
"outDir": "bin",
|
||||||
"target": "es2020",
|
"target": "es2024",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
name: k8s-infra
|
||||||
|
description: Cluster-level infrastructure for the k3s homelab cluster
|
||||||
|
runtime:
|
||||||
|
name: nodejs
|
||||||
|
options:
|
||||||
|
packagemanager: npm
|
||||||
@@ -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";
|
||||||
Generated
+2799
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user