mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-01-01 09:10:26 -06:00
new add cluster node dialog
This commit is contained in:
65
setup/setup-worker.sh
Normal file
65
setup/setup-worker.sh
Normal 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
|
||||
@@ -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 "------------------------------------------------------------"
|
||||
@@ -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>
|
||||
|
||||
53
src/app/settings/cluster/add-cluster-node-dialog.tsx
Normal file
53
src/app/settings/cluster/add-cluster-node-dialog.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 || '');
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -37,7 +37,6 @@ class ClusterService {
|
||||
revalidate: 10,
|
||||
tags: [Tags.nodeInfos()]
|
||||
})();
|
||||
|
||||
}
|
||||
|
||||
async setNodeStatus(nodeName: string, schedulable: boolean) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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: [{
|
||||
|
||||
Reference in New Issue
Block a user