new add cluster node dialog

This commit is contained in:
biersoeckli
2024-11-23 10:11:43 +00:00
parent b7cea79ebe
commit 3edbbc5b3b
10 changed files with 151 additions and 10 deletions

65
setup/setup-worker.sh Normal file
View File

@@ -0,0 +1,65 @@
#!/bin/bash
# curl -sfL https://get.quickstack.dev/setup-worker.sh | K3S_URL=<https://<IP-ADDRESS-OR-HOSTNAME-OF-MASTERNODE>:6443> JOIN_TOKEN=<TOKEN> sh -
if [ -z "${K3S_URL}" ]; then
echo "Error: Missing parameter 'K3S_URL'."
echo "Example K3S_URL https://<IP-ADDRESS-OR-HOSTNAME-OF-MASTERNODE>:6443"
exit 1
fi
if [ -z "${JOIN_TOKEN}" ]; then
echo "Error: Missing parameter 'JOIN_TOKEN'."
exit 1
fi
k3sUrl="$1"
joinToken="$2"
wait_until_all_pods_running() {
# Waits another 5 seconds to make sure all pods are registered for the first time.
sleep 5
while true; do
OUTPUT=$(sudo k3s kubectl get pods -A --no-headers 2>&1)
# Checks if there are no resources found --> Kubernetes ist still starting up
if echo "$OUTPUT" | grep -q "No resources found"; then
echo "Kubernetes is still starting up..."
else
# Extracts the STATUS column from the kubectl output and filters out the values "Running" and "Completed".
STATUS=$(echo "$OUTPUT" | awk '{print $4}' | grep -vE '^(Running|Completed)$')
# If the STATUS variable is empty, all pods are running and the loop can be exited.
if [ -z "$STATUS" ]; then
echo "Pods started successfully."
break
else
echo "Waiting for all pods to come online..."
fi
fi
# Waits for X seconds before checking the pod status again.
sleep 10
done
# Waits another 5 seconds to make sure all pods are ready.
sleep 5
sudo kubectl get node
sudo kubectl get pods -A
}
# install nfs-common
sudo apt-get update
sudo apt-get install nfs-common -y
# Installation of k3s
curl -sfL https://get.k3s.io | K3S_URL=${K3S_URL} K3S_TOKEN=${JOIN_TOKEN} sh -
# Check for Ready node, takes ~30 seconds
sudo k3s kubectl get node
echo "Waiting for Kubernetes to start..."
wait_until_all_pods_running

View File

@@ -61,6 +61,8 @@ echo "Waiting for Cert-Manager to start..."
wait_until_all_pods_running
sudo kubectl -n cert-manager get pod
joinTokenForOtherNodes=$(sudo cat /var/lib/rancher/k3s/server/node-token)
# deploy QuickStack
cat <<EOF > quickstack-setup-job.yaml
apiVersion: v1
@@ -103,6 +105,8 @@ spec:
env:
- name: START_MODE
value: "setup"
- name: K3S_JOIN_TOKEN
value: "$joinTokenForOtherNodes"
imagePullPolicy: Always
restartPolicy: Never
backoffLimit: 0
@@ -113,8 +117,7 @@ wait_until_all_pods_running
sudo kubectl logs -f job/quickstack-setup-job -n quickstack
# evaluate url to add node to cluster
joinTokenForOtherNodes=$(sudo cat /var/lib/rancher/k3s/server/node-token)
echo "To add a worker node to the cluster, run the following command on the worker node:"
echo "------------------------------------------------------------"
echo "curl -sfL https://get.k3s.io | K3S_URL=https://<IP-ADDRESS-OR-HOSTNAME-OF-MASTERNODE>:6443 K3S_TOKEN=$joinTokenForOtherNodes sh -"
echo "curl -sfL https://get.quickstack.dev/setup-worker.sh | K3S_URL=https://<IP-ADDRESS-OR-HOSTNAME-OF-MASTERNODE>:6443 JOIN_TOKEN=$joinTokenForOtherNodes sh -"
echo "------------------------------------------------------------"

View File

@@ -32,7 +32,7 @@ export default function RootLayout({
<NavBar />
<main className="flex w-full flex-col items-center">
<div className="w-full max-w-8xl px-4 lg:px-8">
<div className="p-4 hidden flex-col md:flex">
<div className="p-4 flex-col md:flex">
<Suspense fallback={<FullLoadingSpinner />}>
{children}
</Suspense>

View File

@@ -0,0 +1,53 @@
'use client'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button"
import { Code } from "@/components/custom/code"
export default function AddClusterNodeDialog({ children, clusterJoinToken }: { children: React.ReactNode; clusterJoinToken?: string }) {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [command, setCommand] = useState<string>(``);
useEffect(() => {
setCommand(`curl -sfL https://get.quickstack.dev/setup-worker.sh | K3S_URL=https://MASTER-IP:6443 JOIN_TOKEN=${clusterJoinToken ?? ''} sh -`);
}, [clusterJoinToken]);
return (
<>
<div onClick={() => setIsOpen(true)}>
{children}
</div>
<Dialog open={!!isOpen} onOpenChange={(isOpened) => setIsOpen(false)}>
<DialogContent className="sm:max-w-[700px]">
<DialogHeader>
<DialogTitle>Add Cluster Node</DialogTitle>
<DialogDescription>
Add a new quickstack cluster node by running the following command on the node you want to add.
</DialogDescription>
</DialogHeader>
<Code>{command}</Code>
<div><p className="font-semibold mt-2">Note:</p>
<ul className="list-disc list-inside text-xs text-slate-500">
<li>Replace MASTER-IP with the IP address or hostname of the master node.</li>
<li>Ensure the node you want to add has access to the internet and the master node's IP address.</li>
<li>Run the command on the node you want to add to the cluster.</li>
<li className={clusterJoinToken ? '' : 'text-red-500'}>If the token is invalid or not shown in command above, run <Code className="text-xs">sudo cat /var/lib/rancher/k3s/server/node-token</Code> on your master node to retrieve a new one.</li>
</ul>
</div>
<DialogFooter>
<Button onClick={() => setIsOpen(false)}>Close</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}

View File

@@ -4,16 +4,23 @@ import { getAuthUserSession } from "@/server/utils/action-wrapper.utils";
import PageTitle from "@/components/custom/page-title";
import clusterService from "@/server/services/node.service";
import NodeInfo from "./nodeInfo";
import AddClusterNodeDialog from "./add-cluster-node-dialog";
import { Button } from "@/components/ui/button";
import paramService, { ParamService } from "@/server/services/param.service";
export default async function ClusterInfoPage() {
const session = await getAuthUserSession();
const nodeInfo = await clusterService.getNodeInfo();
const clusterJoinToken = await paramService.getString(ParamService.K3S_JOIN_TOKEN);
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<PageTitle
<PageTitle
title={'Cluster'}
subtitle={`View all Nodes of your current QuickStack cluster.`}>
<AddClusterNodeDialog clusterJoinToken={clusterJoinToken}>
<Button>Add Cluster Node</Button>
</AddClusterNodeDialog>
</PageTitle>
<NodeInfo nodeInfos={nodeInfo} />
</div>

View File

@@ -1,10 +1,11 @@
'use client'
import { ReactElement } from "react";
import { toast } from "sonner";
export function Code({ children, copieable = true, copieableValue }: { children: string | null | undefined, copieable?: boolean, copieableValue?: string }) {
export function Code({ children, copieable = true, copieableValue, className }: { children: string | null | undefined, copieable?: boolean, copieableValue?: string, className?: string }) {
return (children &&
<code className={'relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold ' + (copieable ? 'cursor-pointer' : '')}
<code className={(className ?? '') + ' relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold ' + (copieable ? 'cursor-pointer' : '')}
onClick={() => {
if (!copieable) return;
navigator.clipboard.writeText(copieableValue || children || '');

View File

@@ -4,7 +4,7 @@ import next from 'next'
import socketIoServer from './socket-io.server'
import quickStackService from './server/services/qs.service'
import { CommandExecutorUtils } from './server/utils/command-executor.utils'
import k3s from './server/adapter/kubernetes-api.adapter'
import paramService, { ParamService } from './server/services/param.service'
// Source: https://nextjs.org/docs/app/building-your-application/configuring/custom-server
@@ -28,6 +28,14 @@ async function initializeNextJs() {
// update database
console.log('Running db migration...');
await CommandExecutorUtils.runCommand('npx prisma migrate deploy');
if (process.env.K3S_JOIN_TOKEN && process.env.K3S_JOIN_TOKEN.trim()) {
console.log('Saving K3S_JOIN_TOKEN to database...');
await paramService.save({
name: ParamService.K3S_JOIN_TOKEN,
value: process.env.K3S_JOIN_TOKEN
})
}
}
const app = next({ dev })

View File

@@ -37,7 +37,6 @@ class ClusterService {
revalidate: 10,
tags: [Tags.nodeInfos()]
})();
}
async setNodeStatus(nodeName: string, schedulable: boolean) {

View File

@@ -8,6 +8,7 @@ export class ParamService {
static readonly QS_SERVER_HOSTNAME = 'qsServerHostname';
static readonly DISABLE_NODEPORT_ACCESS = 'disableNodePortAccess';
static readonly LETS_ENCRYPT_MAIL = 'letsEncryptMail';
static readonly K3S_JOIN_TOKEN = 'k3sJoinToken';
async get(name: string) {
return await unstable_cache(async (name: string) => await dataAccess.client.parameter.findFirstOrThrow({

View File

@@ -120,8 +120,8 @@ class QuickStackService {
this.CLUSTER_ISSUER_NAME, // name of the custom resource
clusterIssuerBody, // object manifest
undefined, undefined, undefined, {
headers: { 'Content-Type': 'application/merge-patch+json' },
}
headers: { 'Content-Type': 'application/merge-patch+json' },
}
);
} else {
// create
@@ -270,6 +270,10 @@ class QuickStackService {
...nextAuthHostname ? [{
name: 'NEXTAUTH_URL',
value: `https://${nextAuthHostname}`
}] : [],
...process.env.K3S_JOIN_TOKEN ? [{
name: 'K3S_JOIN_TOKEN',
value: process.env.K3S_JOIN_TOKEN
}] : []
],
volumeMounts: [{