Compare commits

...
2 Commits
Author SHA1 Message Date
kasun 10a4578ce4 Merge pull request 'feature: join k3s-worker-3 into the cluster' (#31) from feature/add-3rd-k8s-worker into main
Deploy k8s Infra / Pulumi Preview (push) Skipped
Deploy k8s Infra / Pulumi Deploy (push) Successful in 1m0s
Reviewed-on: #31
2026-07-28 22:56:50 +02:00
kasun 3f98874008 feature: join k3s-worker-3 into the cluster
Deploy k8s Bootstrap / Pulumi Preview (pull_request) Successful in 45s
Deploy k8s Bootstrap / Bootstrap k3s Cluster (pull_request) Skipped
Deploy k8s Infra / Pulumi Preview (pull_request) Successful in 47s
Deploy k8s Infra / Pulumi Deploy (pull_request) Skipped
Wires the k8s-bootstrap join sequence (start, SSH wait, k3s agent join,
Longhorn disk mount) and the k8s-infra Longhorn disk patch for worker-3.
Its VM was already provisioned by proxmox-infra in an earlier branch.
2026-07-28 22:52:22 +02:00
3 changed files with 41 additions and 6 deletions
+3 -2
View File
@@ -10,7 +10,7 @@ Bootstraps a k3s cluster on the Proxmox VMs created by `proxmox-infra`. Starts V
2. Waits for port 22 to open on each VM (bash `/dev/tcp`) 2. Waits for port 22 to open on each VM (bash `/dev/tcp`)
3. Installs k3s on `k3s-master-1` with `--cluster-init --tls-san <master1Ip>` 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 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 5. Joins `k3s-worker-1`, `k3s-worker-2`, and `k3s-worker-3` as agent nodes
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` 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` 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`
@@ -28,9 +28,10 @@ pulumi config set master2Ip "192.168.1.x"
pulumi config set master3Ip "192.168.1.x" pulumi config set master3Ip "192.168.1.x"
pulumi config set worker1Ip "192.168.1.x" pulumi config set worker1Ip "192.168.1.x"
pulumi config set worker2Ip "192.168.1.x" pulumi config set worker2Ip "192.168.1.x"
pulumi config set worker3Ip "192.168.1.x"
``` ```
Proxmox credentials (`pve1Endpoint`, `pve1ApiToken`, `pve2Endpoint`, `pve2ApiToken`) are read automatically from `proxmox-infra` via StackReference — do **not** set them here. Proxmox credentials (`pve1Endpoint`, `pve1ApiToken`, `pve2Endpoint`, `pve2ApiToken`, `pve3Endpoint`, `pve3ApiToken`) are read automatically from `proxmox-infra` via StackReference — do **not** set them here.
## Prerequisites ## Prerequisites
+37 -3
View File
@@ -21,6 +21,12 @@ const pve2Endpoint = infraRef.requireOutput(
const pve2ApiToken = infraRef.requireOutput( const pve2ApiToken = infraRef.requireOutput(
"pve2ApiToken", "pve2ApiToken",
) as pulumi.Output<string>; ) as pulumi.Output<string>;
const pve3Endpoint = infraRef.requireOutput(
"pve3Endpoint",
) as pulumi.Output<string>;
const pve3ApiToken = infraRef.requireOutput(
"pve3ApiToken",
) as pulumi.Output<string>;
// Node IPs — static DHCP leases set in the router // Node IPs — static DHCP leases set in the router
const master1Ip = infraRef.requireOutput("master1Ip"); const master1Ip = infraRef.requireOutput("master1Ip");
@@ -28,6 +34,7 @@ const master2Ip = infraRef.requireOutput("master2Ip");
const master3Ip = infraRef.requireOutput("master3Ip"); const master3Ip = infraRef.requireOutput("master3Ip");
const worker1Ip = infraRef.requireOutput("worker1Ip"); const worker1Ip = infraRef.requireOutput("worker1Ip");
const worker2Ip = infraRef.requireOutput("worker2Ip"); const worker2Ip = infraRef.requireOutput("worker2Ip");
const worker3Ip = infraRef.requireOutput("worker3Ip");
// Pre-shared k3s cluster token // Pre-shared k3s cluster token
const k3sToken = config.requireSecret("k3sToken"); const k3sToken = config.requireSecret("k3sToken");
@@ -45,6 +52,7 @@ const master2VmId = vmIdsOutput.apply((ids) => String(ids.master2));
const master3VmId = vmIdsOutput.apply((ids) => String(ids.master3)); const master3VmId = vmIdsOutput.apply((ids) => String(ids.master3));
const worker1VmId = vmIdsOutput.apply((ids) => String(ids.worker1)); const worker1VmId = vmIdsOutput.apply((ids) => String(ids.worker1));
const worker2VmId = vmIdsOutput.apply((ids) => String(ids.worker2)); const worker2VmId = vmIdsOutput.apply((ids) => String(ids.worker2));
const worker3VmId = vmIdsOutput.apply((ids) => String(ids.worker3));
// SSH connection helper // SSH connection helper
function conn( function conn(
@@ -83,6 +91,11 @@ const startWorker2 = new command.local.Command("start-worker-2", {
triggers: [worker2VmId], triggers: [worker2VmId],
interpreter: ["/bin/bash", "-c"], interpreter: ["/bin/bash", "-c"],
}); });
const startWorker3 = new command.local.Command("start-worker-3", {
create: pulumi.interpolate`curl -sf -k -X POST -H "Authorization: PVEAPIToken=${pve3ApiToken}" "${pve3Endpoint}/api2/json/nodes/pve/qemu/${worker3VmId}/status/start" 2>/dev/null || true`,
triggers: [worker3VmId],
interpreter: ["/bin/bash", "-c"],
});
const allStarts = [ const allStarts = [
startMaster1, startMaster1,
@@ -90,6 +103,7 @@ const allStarts = [
startMaster3, startMaster3,
startWorker1, startWorker1,
startWorker2, startWorker2,
startWorker3,
]; ];
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -194,6 +208,16 @@ const waitWorker2Ssh = new command.local.Command(
{ dependsOn: [joinMaster3] }, { dependsOn: [joinMaster3] },
); );
const waitWorker3Ssh = new command.local.Command(
"wait-ssh-worker-3",
{
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker3Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
triggers: [worker3VmId],
interpreter: ["/bin/bash", "-c"],
},
{ dependsOn: [joinMaster3] },
);
const joinWorker1 = new command.remote.Command( const joinWorker1 = new command.remote.Command(
"join-k3s-worker-1", "join-k3s-worker-1",
{ {
@@ -214,12 +238,22 @@ const joinWorker2 = new command.remote.Command(
{ dependsOn: [waitWorker2Ssh] }, { dependsOn: [waitWorker2Ssh] },
); );
const joinWorker3 = new command.remote.Command(
"join-k3s-worker-3",
{
connection: conn(worker3Ip),
create: pulumi.interpolate`curl -sfL https://get.k3s.io | sudo K3S_URL=https://${master1Ip}:6443 K3S_TOKEN='${k3sToken}' sh -s - --node-name k3s-worker-3`,
triggers: [worker3VmId],
},
{ dependsOn: [waitWorker3Ssh] },
);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Step 5: Format and mount Longhorn disks // Step 5: Format and mount Longhorn disks
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const workers = [worker1Ip, worker2Ip]; const workers = [worker1Ip, worker2Ip, worker3Ip];
const workerSshReader = [waitWorker1Ssh, waitWorker2Ssh]; const workerSshReader = [waitWorker1Ssh, waitWorker2Ssh, waitWorker3Ssh];
const mountLonghornDisks = workers.map((worker, i) => { const mountLonghornDisks = workers.map((worker, i) => {
return new command.remote.Command( return new command.remote.Command(
@@ -250,7 +284,7 @@ const getKubeconfig = new command.remote.Command(
create: `sudo cat /etc/rancher/k3s/k3s.yaml`, create: `sudo cat /etc/rancher/k3s/k3s.yaml`,
triggers: [master1VmId], triggers: [master1VmId],
}, },
{ dependsOn: [joinWorker1, joinWorker2] }, { dependsOn: [joinWorker1, joinWorker2, joinWorker3] },
); );
export const kubeconfig = pulumi.secret( export const kubeconfig = pulumi.secret(
+1 -1
View File
@@ -143,7 +143,7 @@ const longhorn = new k8s.helm.v3.Release(
); );
// Patches Longhorn instances to use the mountend Longhorn storage on the worker nodes. // Patches Longhorn instances to use the mountend Longhorn storage on the worker nodes.
["k3s-worker-1", "k3s-worker-2"].forEach( ["k3s-worker-1", "k3s-worker-2", "k3s-worker-3"].forEach(
(nodeName) => (nodeName) =>
new k8s.apiextensions.CustomResourcePatch( new k8s.apiextensions.CustomResourcePatch(
`${nodeName}-longhorn-disks`, `${nodeName}-longhorn-disks`,