From 188651954edf72d2122c09dc0d50f1604cf9ae82 Mon Sep 17 00:00:00 2001 From: Chase Dumphord Date: Sat, 6 Dec 2025 22:22:42 -0600 Subject: [PATCH] Initial commit: Ced's K3s HomeLab --- ced-k3s-homelab-full.zip | Bin 0 -> 13449 bytes k3s-homelab-full/Makefile | 37 ++ k3s-homelab-full/README.md | 57 +++ k3s-homelab-full/bootstrap.sh | 48 +++ k3s-homelab-full/cluster-setup.md | 340 ++++++++++++++++++ .../dashboards/ced_cluster_overview.json | 139 +++++++ .../dashboards/ced_ingress_metallb.json | 70 ++++ .../dashboards/ced_nodes_detail.json | 98 +++++ .../diagrams/ced-k3s-from-text.txt | 41 +++ k3s-homelab-full/docs/per-node-notes.md | 43 +++ k3s-homelab-full/kube-prom-values.yaml | 30 ++ .../manifests/demo-app/demo-nginx.yaml | 54 +++ .../manifests/ingress/grafana-ingress.yaml | 19 + .../manifests/ingress/prometheus-ingress.yaml | 19 + .../manifests/metallb/ipaddresspool.yaml | 8 + .../manifests/metallb/l2advertisement.yaml | 8 + k3s-homelab-full/scripts/03_label_nodes.sh | 19 + .../scripts/10_install_metallb.sh | 17 + .../scripts/20_install_ingress_nginx.sh | 14 + .../scripts/30_install_ceds_noc.sh | 17 + 20 files changed, 1078 insertions(+) create mode 100644 ced-k3s-homelab-full.zip create mode 100644 k3s-homelab-full/Makefile create mode 100644 k3s-homelab-full/README.md create mode 100644 k3s-homelab-full/bootstrap.sh create mode 100644 k3s-homelab-full/cluster-setup.md create mode 100644 k3s-homelab-full/dashboards/ced_cluster_overview.json create mode 100644 k3s-homelab-full/dashboards/ced_ingress_metallb.json create mode 100644 k3s-homelab-full/dashboards/ced_nodes_detail.json create mode 100644 k3s-homelab-full/diagrams/ced-k3s-from-text.txt create mode 100644 k3s-homelab-full/docs/per-node-notes.md create mode 100644 k3s-homelab-full/kube-prom-values.yaml create mode 100644 k3s-homelab-full/manifests/demo-app/demo-nginx.yaml create mode 100644 k3s-homelab-full/manifests/ingress/grafana-ingress.yaml create mode 100644 k3s-homelab-full/manifests/ingress/prometheus-ingress.yaml create mode 100644 k3s-homelab-full/manifests/metallb/ipaddresspool.yaml create mode 100644 k3s-homelab-full/manifests/metallb/l2advertisement.yaml create mode 100644 k3s-homelab-full/scripts/03_label_nodes.sh create mode 100644 k3s-homelab-full/scripts/10_install_metallb.sh create mode 100644 k3s-homelab-full/scripts/20_install_ingress_nginx.sh create mode 100644 k3s-homelab-full/scripts/30_install_ceds_noc.sh diff --git a/ced-k3s-homelab-full.zip b/ced-k3s-homelab-full.zip new file mode 100644 index 0000000000000000000000000000000000000000..49a5997eb536dc00e9fc0d204c2e4538d15fa994 GIT binary patch literal 13449 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 literal 0 HcmV?d00001 diff --git a/k3s-homelab-full/Makefile b/k3s-homelab-full/Makefile new file mode 100644 index 0000000..6130647 --- /dev/null +++ b/k3s-homelab-full/Makefile @@ -0,0 +1,37 @@ +.PHONY: help label-nodes install-metallb install-ingress install-noc demo-app grafana-ingress prometheus-ingress all + +help: + @echo "Ced's K3s HomeLab Makefile" + @echo + @echo "Targets:" + @echo " label-nodes - Label ingress/data/monitoring nodes" + @echo " install-metallb - Install and configure MetalLB" + @echo " install-ingress - Install ingress-nginx via Helm" + @echo " install-noc - Install Prometheus + Grafana (kube-prometheus-stack)" + @echo " demo-app - Deploy demo nginx app + ingress" + @echo " grafana-ingress - Apply Grafana ingress" + @echo " prometheus-ingress - Apply Prometheus ingress" + @echo " all - MetalLB + ingress + NOC + demo app + ingresses" + +label-nodes: + ./scripts/03_label_nodes.sh + +install-metallb: + ./scripts/10_install_metallb.sh + +install-ingress: + ./scripts/20_install_ingress_nginx.sh + +install-noc: + ./scripts/30_install_ceds_noc.sh + +demo-app: + kubectl apply -f manifests/demo-app/demo-nginx.yaml + +grafana-ingress: + kubectl apply -f manifests/ingress/grafana-ingress.yaml + +prometheus-ingress: + kubectl apply -f manifests/ingress/prometheus-ingress.yaml + +all: label-nodes install-metallb install-ingress install-noc demo-app grafana-ingress prometheus-ingress diff --git a/k3s-homelab-full/README.md b/k3s-homelab-full/README.md new file mode 100644 index 0000000..b443453 --- /dev/null +++ b/k3s-homelab-full/README.md @@ -0,0 +1,57 @@ +# Ced's K3s HomeLab (Raspberry Pi Cluster) + +This repo captures the working configuration of your HA K3s cluster: + +- 3× control-plane nodes: `k3s-django-1/2/3` +- 9× worker nodes split into: + - Ingress: `k3s-node-1..3` + - Data: `k3s-node-4..6` + - Monitoring: `k3s-node-7..9` +- MetalLB for LoadBalancer IPs on your HomeLab VLAN +- ingress-nginx as the main ingress controller +- kube-prometheus-stack (Prometheus + Grafana + Alertmanager + exporters) +- Ced's NOC dashboards for Grafana + +You can use this as a GitHub repo, import the dashboards into Grafana, and keep long‑term documentation of Ced's HomeLab. + +## Quick Start (once K3s cluster is up) + +On `k3s-django-1` with `KUBECONFIG` pointing at the cluster: + +```bash +git clone +cd k3s-homelab-full + +# 1) Label nodes into groups +./scripts/03_label_nodes.sh + +# 2) Install MetalLB +./scripts/10_install_metallb.sh + +# 3) Install ingress-nginx +./scripts/20_install_ingress_nginx.sh + +# 4) Install Ced's NOC (kube-prometheus-stack) +./scripts/30_install_ceds_noc.sh + +# 5) Deploy demo nginx ingress + Grafana/Prometheus ingress rules +kubectl apply -f manifests/demo-app/demo-nginx.yaml +kubectl apply -f manifests/ingress/grafana-ingress.yaml +kubectl apply -f manifests/ingress/prometheus-ingress.yaml +``` + +Then update your desktop `hosts` file to point: + +```text +10.10.30.251 demo.local grafana.local prometheus.local +``` + +## Structure + +- `cluster-setup.md` – step-by-step narrative of the full setup. +- `kube-prom-values.yaml` – values file for `kube-prometheus-stack` (Ced's NOC). +- `dashboards/` – starter Grafana dashboards in JSON. +- `scripts/` – helper scripts to label nodes and install MetalLB, ingress, and NOC. +- `manifests/` – YAML manifests for MetalLB, demo app, and ingresses. +- `docs/per-node-notes.md` – inventory of each node (IP, role, hardware). +- `diagrams/ced-k3s-from-text.txt` – text that can be imported into draw.io ("Arrange → Insert → Advanced → From text"). diff --git a/k3s-homelab-full/bootstrap.sh b/k3s-homelab-full/bootstrap.sh new file mode 100644 index 0000000..b8dde5b --- /dev/null +++ b/k3s-homelab-full/bootstrap.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "==============================================" +echo " Ced's K3s HomeLab Bootstrap" +echo "==============================================" +echo +echo "This script assumes:" +echo " - K3s control-plane is already up (k3s-django-1/2/3)" +echo " - Worker nodes are joined (k3s-node-1..9)" +echo " - KUBECONFIG is set to point to the cluster" +echo +read -p "Press ENTER to continue or Ctrl+C to abort... " _ + +echo "[1/6] Labeling nodes into ingress/data/monitoring groups..." +./scripts/03_label_nodes.sh + +echo "[2/6] Installing MetalLB (IP pool 10.10.30.251-254)..." +./scripts/10_install_metallb.sh + +echo "[3/6] Installing ingress-nginx (LoadBalancer via MetalLB)..." +./scripts/20_install_ingress_nginx.sh + +echo "[4/6] Installing Ced's NOC (kube-prometheus-stack)..." +./scripts/30_install_ceds_noc.sh + +echo "[5/6] Deploying demo nginx app + ingress..." +kubectl apply -f manifests/demo-app/demo-nginx.yaml + +echo "[6/6] Applying ingress for Grafana and Prometheus..." +kubectl apply -f manifests/ingress/grafana-ingress.yaml +kubectl apply -f manifests/ingress/prometheus-ingress.yaml + +echo +echo "==============================================" +echo " Done!" +echo " - Demo app: http://demo.local" +echo " - Grafana: http://grafana.local" +echo " - Prometheus: http://prometheus.local" +echo +echo "Remember to add these to your desktop hosts file pointing to the" +echo "MetalLB ingress IP (e.g. 10.10.30.251):" +echo +echo " 10.10.30.251 demo.local grafana.local prometheus.local" +echo +echo "You can now log into Grafana and import dashboards from the" +echo "dashboards/ folder in this repo." +echo "==============================================" diff --git a/k3s-homelab-full/cluster-setup.md b/k3s-homelab-full/cluster-setup.md new file mode 100644 index 0000000..b9d43dc --- /dev/null +++ b/k3s-homelab-full/cluster-setup.md @@ -0,0 +1,340 @@ +# Ced's K3s Cluster Setup – End-to-End + +This document describes how your current K3s cluster is built so you can re‑create it later or explain it in GitHub/OneNote. + +--- + +## 1. Base Networking + +**HomeLab VLAN:** `10.10.30.0/24` +**Gateway:** UniFi Dream Router +**DHCP:** Configured to *exclude* `.251–.254` so MetalLB can safely use that range. + +- Proxmox VE: `10.10.30.250` +- MetalLB pool: `10.10.30.251–10.10.30.254` + +Subnets you have overall (for reference): + +- Default: `10.10.1.0/24` (not used) +- Main: `10.10.10.0/24` +- IoT: `10.10.20.0/24` +- HomeLab: `10.10.30.0/24` +- Guest: `10.10.99.0/24` + +--- + +## 2. K3s Control Plane + +### Nodes + +- `k3s-django-1` – `10.10.30.72` (control-plane, etcd, master) +- `k3s-django-2` – `10.10.30.245` (control-plane, etcd, master) +- `k3s-django-3` – `10.10.30.128` (control-plane, etcd, master) + +All three are Debian 12 (Bookworm, ARM64 on Raspberry Pi). + +### 2.1 Install K3s on the first control-plane node + +On `k3s-django-1`: + +```bash +curl -sfL https://get.k3s.io | sh -s - server +``` + +Then capture the version, token, and kubeconfig: + +```bash +k3s --version + +sudo cat /var/lib/rancher/k3s/server/node-token + +mkdir -p ~/.kube +sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config +sudo chown $(id -u):$(id -g) ~/.kube/config +``` + +Update the kubeconfig to point to the node's real IP instead of `127.0.0.1`: + +```bash +sed -i 's/https:\/\/127.0.0.1:6443/https:\/\/10.10.30.72:6443/' ~/.kube/config +export KUBECONFIG=$HOME/.kube/config +``` + +Check: + +```bash +kubectl get nodes -o wide +``` + +You should see `k3s-django-1` as `Ready control-plane,etcd,master`. + +### 2.2 Join additional control-plane nodes + +Get the join token from `k3s-django-1`: + +```bash +TOKEN=$(sudo cat /var/lib/rancher/k3s/server/node-token) +echo "$TOKEN" +``` + +On `k3s-django-2` and `k3s-django-3`: + +```bash +export K3S_URL="https://10.10.30.72:6443" +export K3S_TOKEN="" + +curl -sfL https://get.k3s.io | K3S_URL=$K3S_URL K3S_TOKEN=$K3S_TOKEN sh -s - server +``` + +Verify from `k3s-django-1`: + +```bash +kubectl get nodes -o wide +``` + +You should now see all three `k3s-django-*` nodes as control-plane,etcd,master. + +--- + +## 3. Worker Nodes + +You have 9 worker nodes grouped as: + +- Ingress: `k3s-node-1`, `k3s-node-2`, `k3s-node-3` +- Data: `k3s-node-4`, `k3s-node-5`, `k3s-node-6` +- Monitoring: `k3s-node-7`, `k3s-node-8`, `k3s-node-9` + +### 3.1 Join workers as agents + +On each worker node (`k3s-node-N`), use: + +```bash +export K3S_URL="https://10.10.30.72:6443" +export K3S_TOKEN="" + +curl -sfL https://get.k3s.io | K3S_URL=$K3S_URL K3S_TOKEN=$K3S_TOKEN sh -s - agent +``` + +Confirm from `k3s-django-1`: + +```bash +kubectl get nodes -o wide +``` + +All 9 agents should show as `Ready` with no special roles yet. + +### 3.2 Label nodes by function + +From `k3s-django-1` (or any node with kubeconfig): + +```bash +# Ingress group +kubectl label node k3s-node-1 ingress-node=true +kubectl label node k3s-node-2 ingress-node=true +kubectl label node k3s-node-3 ingress-node=true + +# Data group +kubectl label node k3s-node-4 data-node=true +kubectl label node k3s-node-5 data-node=true +kubectl label node k3s-node-6 data-node=true + +# Monitoring group +kubectl label node k3s-node-7 monitoring-node=true +kubectl label node k3s-node-8 monitoring-node=true +kubectl label node k3s-node-9 monitoring-node=true + +kubectl get nodes -L ingress-node -L data-node -L monitoring-node +``` + +Additionally, you used `kubectl label node ...` to set the `ROLES` field (`ingress`, `data`, `monitoring`) for documentation. + +--- + +## 4. MetalLB + +### 4.1 Install MetalLB + +```bash +kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml +``` + +Wait for pods: + +```bash +kubectl get pods -n metallb-system -o wide +``` + +### 4.2 Configure IPAddressPool and L2Advertisement + +MetalLB is configured with an address pool in `manifests/metallb/ipaddresspool.yaml`: + +```yaml +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: ceds-pool + namespace: metallb-system +spec: + addresses: + - 10.10.30.251-10.10.30.254 +``` + +and `manifests/metallb/l2advertisement.yaml`: + +```yaml +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: ceds-l2 + namespace: metallb-system +spec: + ipAddressPools: + - ceds-pool +``` + +Apply: + +```bash +kubectl apply -f manifests/metallb/ipaddresspool.yaml +kubectl apply -f manifests/metallb/l2advertisement.yaml +``` + +Check: + +```bash +kubectl get ipaddresspools.metallb.io -n metallb-system +kubectl get l2advertisements.metallb.io -n metallb-system +``` + +--- + +## 5. Ingress-NGINX + +### 5.1 Install Helm & ingress-nginx + +On `k3s-django-1`: + +```bash +curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash + +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm repo update +``` + +Install ingress-nginx targeting the ingress node group: + +```bash +helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.service.type=LoadBalancer --set controller.nodeSelector.ingress-node="true" +``` + +Check: + +```bash +kubectl get pods -n ingress-nginx +kubectl get svc -n ingress-nginx ingress-nginx-controller +``` + +You should see `EXTERNAL-IP` assigned from MetalLB, e.g. `10.10.30.251`. + +--- + +## 6. Demo Ingress App + +`manifests/demo-app/demo-nginx.yaml` contains: + +- A `Deployment` with 3 replicas of `nginx` +- A `ClusterIP` Service +- An Ingress pointing `demo.local` to the service via ingress-nginx + +Apply: + +```bash +kubectl apply -f manifests/demo-app/demo-nginx.yaml +``` + +Update your desktop hosts file: + +```text +10.10.30.251 demo.local +``` + +Open `http://demo.local` in a browser and you should see the nginx welcome page, served via: + +Desktop → MetalLB VIP (10.10.30.251) → ingress-nginx → demo-nginx Service → Pods. + +--- + +## 7. Ced's NOC (kube-prometheus-stack) + +Namespace: + +```bash +kubectl create namespace monitoring +``` + +Helm repo: + +```bash +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update +``` + +Install with your custom values file: + +```bash +helm install ceds-noc prometheus-community/kube-prometheus-stack --namespace monitoring -f kube-prom-values.yaml +``` + +`kube-prom-values.yaml` (included in this repo) pins Prometheus, Alertmanager, and Grafana to the `monitoring-node=true` nodes, configures PVCs for metrics and Grafana data, and exposes Grafana via a LoadBalancer (MetalLB). + +Check pods & services: + +```bash +kubectl get pods -n monitoring +kubectl get svc -n monitoring +``` + +You should see: + +- `ceds-noc-grafana` as a `LoadBalancer` with an `EXTERNAL-IP` like `10.10.30.252` +- Prometheus, Alertmanager, kube-state-metrics, node exporters, etc. + +Open Grafana at: + +```text +http://10.10.30.252 +``` + +Username: `admin` +Password: either the one in `kube-prom-values.yaml` (if set) or via: + +```bash +kubectl --namespace monitoring get secret ceds-noc-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo +``` + +--- + +## 8. Grafana Dashboards (Ced's NOC) + +Use Grafana → Dashboards → New → Import, and upload: + +- `dashboards/ced_cluster_overview.json` +- `dashboards/ced_nodes_detail.json` +- `dashboards/ced_ingress_metallb.json` + +Choose the kube-prometheus-stack Prometheus datasource. + +You now have a live NOC for: + +- Cluster health (nodes, pods, API server) +- Per‑node CPU, RAM, disk, pod counts +- Ingress and MetalLB metrics + +--- + +## 9. Future Enhancements + +- Add Loki + Promtail for logs +- Add Prometheus alerting rules (node down, high CPU, low disk, etc) +- Add Alertmanager integrations (Discord/Slack/email) +- Use GitOps (FluxCD or ArgoCD) to manage this repo as the source of truth. diff --git a/k3s-homelab-full/dashboards/ced_cluster_overview.json b/k3s-homelab-full/dashboards/ced_cluster_overview.json new file mode 100644 index 0000000..0ff9af1 --- /dev/null +++ b/k3s-homelab-full/dashboards/ced_cluster_overview.json @@ -0,0 +1,139 @@ +{ + "id": null, + "uid": "ced-cluster-overview", + "title": "Ced's K3s Cluster Overview", + "tags": [ + "k3s", + "cluster", + "ced-noc" + ], + "timezone": "browser", + "schemaVersion": 38, + "version": 1, + "refresh": "30s", + "panels": [ + { + "type": "stat", + "title": "Total Nodes", + "id": 1, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 0 + }, + "targets": [ + { + "expr": "count(kube_node_info)", + "refId": "A" + } + ] + }, + { + "type": "stat", + "title": "Ready Nodes", + "id": 2, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 0 + }, + "targets": [ + { + "expr": "count(kube_node_status_condition{condition=\"Ready\",status=\"true\"})", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "Node CPU Usage (%)", + "id": 3, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 4 + }, + "targets": [ + { + "expr": "100 - (avg by (instance) (irate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)", + "legendFormat": "{{instance}}", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "Node Memory Usage (%)", + "id": 4, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 4 + }, + "targets": [ + { + "expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100", + "legendFormat": "{{instance}}", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "Pods per Namespace", + "id": 5, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 12 + }, + "targets": [ + { + "expr": "count(kube_pod_info) by (namespace)", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "API Server Request Rate", + "id": 6, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 12 + }, + "targets": [ + { + "expr": "sum(rate(apiserver_request_total[5m])) by (verb)", + "legendFormat": "{{verb}}", + "refId": "A" + } + ] + } + ], + "templating": { + "list": [ + { + "name": "namespace", + "type": "query", + "datasource": "Prometheus", + "query": "label_values(kube_pod_info, namespace)", + "current": { + "text": "All", + "value": ".*" + }, + "includeAll": true, + "multi": true, + "regex": "" + } + ] + } +} \ No newline at end of file diff --git a/k3s-homelab-full/dashboards/ced_ingress_metallb.json b/k3s-homelab-full/dashboards/ced_ingress_metallb.json new file mode 100644 index 0000000..b21c71e --- /dev/null +++ b/k3s-homelab-full/dashboards/ced_ingress_metallb.json @@ -0,0 +1,70 @@ +{ + "id": null, + "uid": "ced-ingress", + "title": "Ced's Ingress & MetalLB", + "tags": [ + "ingress", + "metallb", + "ced-noc" + ], + "timezone": "browser", + "schemaVersion": 38, + "version": 1, + "refresh": "30s", + "panels": [ + { + "type": "graph", + "title": "NGINX Ingress HTTP Requests", + "id": 1, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "targets": [ + { + "expr": "sum(rate(nginx_ingress_controller_requests[5m])) by (ingress)", + "legendFormat": "{{ingress}}", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "NGINX Ingress 5xx Rate", + "id": 2, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "targets": [ + { + "expr": "sum(rate(nginx_ingress_controller_requests{status=~\"5..\"}[5m])) by (ingress)", + "legendFormat": "{{ingress}}", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "MetalLB Announced IPs", + "id": 3, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "targets": [ + { + "expr": "sum(metallb_speaker_announced)", + "legendFormat": "announced", + "refId": "A" + } + ] + } + ] +} \ No newline at end of file diff --git a/k3s-homelab-full/dashboards/ced_nodes_detail.json b/k3s-homelab-full/dashboards/ced_nodes_detail.json new file mode 100644 index 0000000..577d461 --- /dev/null +++ b/k3s-homelab-full/dashboards/ced_nodes_detail.json @@ -0,0 +1,98 @@ +{ + "id": null, + "uid": "ced-node-detail", + "title": "Ced's K3s Nodes Detail", + "tags": [ + "k3s", + "nodes", + "ced-noc" + ], + "timezone": "browser", + "schemaVersion": 38, + "version": 1, + "refresh": "30s", + "templating": { + "list": [ + { + "name": "node", + "type": "query", + "datasource": "Prometheus", + "query": "label_values(kube_node_info, node)", + "multi": false, + "includeAll": false + } + ] + }, + "panels": [ + { + "type": "stat", + "title": "Node CPU Usage (%)", + "id": 1, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 0 + }, + "targets": [ + { + "expr": "100 - (avg(irate(node_cpu_seconds_total{mode=\"idle\",instance=~\"$node\"}[5m])) * 100)", + "refId": "A" + } + ] + }, + { + "type": "stat", + "title": "Node Memory Usage (%)", + "id": 2, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 0 + }, + "targets": [ + { + "expr": "(1 - (node_memory_MemAvailable_bytes{instance=~\"$node\"} / node_memory_MemTotal_bytes{instance=~\"$node\"})) * 100", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "Disk Usage (root fs)", + "id": 3, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 4 + }, + "targets": [ + { + "expr": "1 - (node_filesystem_avail_bytes{mountpoint=\"/\",instance=~\"$node\"} / node_filesystem_size_bytes{mountpoint=\"/\",instance=~\"$node\"})", + "legendFormat": "rootfs", + "refId": "A" + } + ] + }, + { + "type": "graph", + "title": "Kubelet Pod Count", + "id": 4, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 4 + }, + "targets": [ + { + "expr": "kubelet_running_pod_count{instance=~\"$node\"}", + "legendFormat": "{{instance}}", + "refId": "A" + } + ] + } + ] +} \ No newline at end of file diff --git a/k3s-homelab-full/diagrams/ced-k3s-from-text.txt b/k3s-homelab-full/diagrams/ced-k3s-from-text.txt new file mode 100644 index 0000000..e6e9300 --- /dev/null +++ b/k3s-homelab-full/diagrams/ced-k3s-from-text.txt @@ -0,0 +1,41 @@ +# Ced's K3s HomeLab (for draw.io text import) + +# Paste this into draw.io via: Arrange → Insert → Advanced → From text + +Ced HomeLab + VLAN 10.10.30.0/24 + UniFi Dream Router (10.10.30.1) + Proxmox VE (10.10.30.250) + MetalLB VIPs (10.10.30.251-254) + +Control Plane (Raspberry Pi) + k3s-django-1 (10.10.30.72) [control-plane, etcd] + k3s-django-2 (10.10.30.245) [control-plane, etcd] + k3s-django-3 (10.10.30.128) [control-plane, etcd] + +Ingress Nodes + k3s-node-1 (10.10.30.219) [ingress-node=true] + k3s-node-2 (10.10.30.134) [ingress-node=true] + k3s-node-3 (10.10.30.222) [ingress-node=true] + +Data Nodes + k3s-node-4 (10.10.30.126) [data-node=true] + k3s-node-5 (10.10.30.239) [data-node=true] + k3s-node-6 (10.10.30.208) [data-node=true] + +Monitoring Nodes + k3s-node-7 (10.10.30.198) [monitoring-node=true] + k3s-node-8 (10.10.30.216) [monitoring-node=true] + k3s-node-9 (10.10.30.29) [monitoring-node=true] + +Kubernetes Services + MetalLB (L2) + IPAddressPool: 10.10.30.251-10.10.30.254 + ingress-nginx + Service: LoadBalancer → MetalLB VIP (e.g. 10.10.30.251) + kube-prometheus-stack (Ced's NOC) + Prometheus + Alertmanager + Grafana (LoadBalancer via MetalLB) + Demo nginx app + Ingress: demo.local → ingress-nginx → demo-nginx Service diff --git a/k3s-homelab-full/docs/per-node-notes.md b/k3s-homelab-full/docs/per-node-notes.md new file mode 100644 index 0000000..3395134 --- /dev/null +++ b/k3s-homelab-full/docs/per-node-notes.md @@ -0,0 +1,43 @@ +# Ced's K3s Node Inventory + +Use this file to track hardware details, OS, and roles for each node in the cluster. + +## Control Plane Nodes + +| Name | IP | Role | Hardware | OS / Version | Storage | Notes | +|---------------|-------------|------------------------------|--------------------|------------------------------|----------------|---------------------| +| k3s-django-1 | 10.10.30.72 | control-plane,etcd,master | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | SD / SSD | Primary API / etcd | +| k3s-django-2 | 10.10.30.245| control-plane,etcd,master | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | SD / SSD | | +| k3s-django-3 | 10.10.30.128| control-plane,etcd,master | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | SD / SSD | | + +## Worker Nodes – Ingress + +| Name | IP | Role | Hardware | OS / Version | Storage | Notes | +|------------|--------------|---------|--------------------|-----------------------------|---------|---------------------| +| k3s-node-1 | 10.10.30.219 | ingress | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | +| k3s-node-2 | 10.10.30.134 | ingress | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | +| k3s-node-3 | 10.10.30.222 | ingress | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | + +## Worker Nodes – Data + +| Name | IP | Role | Hardware | OS / Version | Storage | Notes | +|------------|--------------|------|--------------------|-----------------------------|---------|---------------------| +| k3s-node-4 | 10.10.30.126 | data | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | e.g. databases | +| k3s-node-5 | 10.10.30.239 | data | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | +| k3s-node-6 | 10.10.30.208 | data | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | + +## Worker Nodes – Monitoring + +| Name | IP | Role | Hardware | OS / Version | Storage | Notes | +|------------|-------------|-------------|--------------------|-----------------------------|---------|-------------------------| +| k3s-node-7 | 10.10.30.198| monitoring | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | Prometheus, Loki, etc. | +| k3s-node-8 | 10.10.30.216| monitoring | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | +| k3s-node-9 | 10.10.30.29 | monitoring | Raspberry Pi 4B ? | Debian 12 (Bookworm, ARM64) | | | + +## Other Notes + +- Proxmox VE: 10.10.30.250 +- MetalLB VIP range: 10.10.30.251–10.10.30.254 +- Ingress-nginx LB example: 10.10.30.251 + +Use this file to track future changes (upgrading SD → SSD, RAM sizes, Pi models, etc.). diff --git a/k3s-homelab-full/kube-prom-values.yaml b/k3s-homelab-full/kube-prom-values.yaml new file mode 100644 index 0000000..ed6c956 --- /dev/null +++ b/k3s-homelab-full/kube-prom-values.yaml @@ -0,0 +1,30 @@ +prometheus: + prometheusSpec: + retention: 15d + scrapeInterval: 15s + nodeSelector: + monitoring-node: "true" + storageSpec: + volumeClaimTemplate: + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 20Gi + +alertmanager: + enabled: true + alertmanagerSpec: + nodeSelector: + monitoring-node: "true" + +grafana: + enabled: true + adminPassword: "cedrocks" + service: + type: LoadBalancer + nodeSelector: + monitoring-node: "true" + persistence: + enabled: true + size: 10Gi diff --git a/k3s-homelab-full/manifests/demo-app/demo-nginx.yaml b/k3s-homelab-full/manifests/demo-app/demo-nginx.yaml new file mode 100644 index 0000000..b36f042 --- /dev/null +++ b/k3s-homelab-full/manifests/demo-app/demo-nginx.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demo-nginx + labels: + app: demo-nginx +spec: + replicas: 3 + selector: + matchLabels: + app: demo-nginx + template: + metadata: + labels: + app: demo-nginx + spec: + containers: + - name: nginx + image: nginx:stable + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: demo-nginx +spec: + selector: + app: demo-nginx + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + type: ClusterIP +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: demo-nginx-ingress + annotations: + kubernetes.io/ingress.class: "nginx" +spec: + rules: + - host: demo.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: demo-nginx + port: + number: 80 diff --git a/k3s-homelab-full/manifests/ingress/grafana-ingress.yaml b/k3s-homelab-full/manifests/ingress/grafana-ingress.yaml new file mode 100644 index 0000000..cb02cf3 --- /dev/null +++ b/k3s-homelab-full/manifests/ingress/grafana-ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: grafana-ingress + namespace: monitoring + annotations: + kubernetes.io/ingress.class: "nginx" +spec: + rules: + - host: grafana.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: ceds-noc-grafana + port: + number: 80 diff --git a/k3s-homelab-full/manifests/ingress/prometheus-ingress.yaml b/k3s-homelab-full/manifests/ingress/prometheus-ingress.yaml new file mode 100644 index 0000000..42e4262 --- /dev/null +++ b/k3s-homelab-full/manifests/ingress/prometheus-ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: prometheus-ingress + namespace: monitoring + annotations: + kubernetes.io/ingress.class: "nginx" +spec: + rules: + - host: prometheus.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: ceds-noc-kube-prometheus-s-prometheus + port: + number: 9090 diff --git a/k3s-homelab-full/manifests/metallb/ipaddresspool.yaml b/k3s-homelab-full/manifests/metallb/ipaddresspool.yaml new file mode 100644 index 0000000..ea179cd --- /dev/null +++ b/k3s-homelab-full/manifests/metallb/ipaddresspool.yaml @@ -0,0 +1,8 @@ +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: ceds-pool + namespace: metallb-system +spec: + addresses: + - 10.10.30.251-10.10.30.254 diff --git a/k3s-homelab-full/manifests/metallb/l2advertisement.yaml b/k3s-homelab-full/manifests/metallb/l2advertisement.yaml new file mode 100644 index 0000000..69f07dc --- /dev/null +++ b/k3s-homelab-full/manifests/metallb/l2advertisement.yaml @@ -0,0 +1,8 @@ +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: ceds-l2 + namespace: metallb-system +spec: + ipAddressPools: + - ceds-pool diff --git a/k3s-homelab-full/scripts/03_label_nodes.sh b/k3s-homelab-full/scripts/03_label_nodes.sh new file mode 100644 index 0000000..ee260db --- /dev/null +++ b/k3s-homelab-full/scripts/03_label_nodes.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[Label] Ingress nodes 1-3" +kubectl label node k3s-node-1 ingress-node=true --overwrite +kubectl label node k3s-node-2 ingress-node=true --overwrite +kubectl label node k3s-node-3 ingress-node=true --overwrite + +echo "[Label] Data nodes 4-6" +kubectl label node k3s-node-4 data-node=true --overwrite +kubectl label node k3s-node-5 data-node=true --overwrite +kubectl label node k3s-node-6 data-node=true --overwrite + +echo "[Label] Monitoring nodes 7-9" +kubectl label node k3s-node-7 monitoring-node=true --overwrite +kubectl label node k3s-node-8 monitoring-node=true --overwrite +kubectl label node k3s-node-9 monitoring-node=true --overwrite + +kubectl get nodes -L ingress-node -L data-node -L monitoring-node diff --git a/k3s-homelab-full/scripts/10_install_metallb.sh b/k3s-homelab-full/scripts/10_install_metallb.sh new file mode 100644 index 0000000..843159a --- /dev/null +++ b/k3s-homelab-full/scripts/10_install_metallb.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE=metallb-system + +echo "[MetalLB] Installing CRDs & core components..." +kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml + +echo "[MetalLB] Waiting for pods..." +kubectl rollout status -n ${NAMESPACE} deploy/controller --timeout=180s || true + +echo "[MetalLB] Applying IPAddressPool and L2Advertisement (10.10.30.251-10.10.30.254)..." +kubectl apply -f manifests/metallb/ipaddresspool.yaml +kubectl apply -f manifests/metallb/l2advertisement.yaml + +kubectl get ipaddresspools.metallb.io -n ${NAMESPACE} +kubectl get l2advertisements.metallb.io -n ${NAMESPACE} diff --git a/k3s-homelab-full/scripts/20_install_ingress_nginx.sh b/k3s-homelab-full/scripts/20_install_ingress_nginx.sh new file mode 100644 index 0000000..b8cc7a8 --- /dev/null +++ b/k3s-homelab-full/scripts/20_install_ingress_nginx.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE=ingress-nginx + +echo "[Ingress] Adding Helm repo for ingress-nginx..." +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx >/dev/null 2>&1 || true +helm repo update + +echo "[Ingress] Installing ingress-nginx (LoadBalancer, pinned to ingress-node=true)..." +helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx --namespace ${NAMESPACE} --create-namespace --set controller.service.type=LoadBalancer --set controller.nodeSelector.ingress-node="true" + +kubectl get pods -n ${NAMESPACE} +kubectl get svc -n ${NAMESPACE} ingress-nginx-controller diff --git a/k3s-homelab-full/scripts/30_install_ceds_noc.sh b/k3s-homelab-full/scripts/30_install_ceds_noc.sh new file mode 100644 index 0000000..3bc2eaa --- /dev/null +++ b/k3s-homelab-full/scripts/30_install_ceds_noc.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAMESPACE=monitoring + +echo "[NOC] Creating monitoring namespace (if not exists)..." +kubectl create namespace ${NAMESPACE} 2>/dev/null || true + +echo "[NOC] Adding prometheus-community Helm repo..." +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts >/dev/null 2>&1 || true +helm repo update + +echo "[NOC] Installing kube-prometheus-stack as 'ceds-noc'..." +helm upgrade --install ceds-noc prometheus-community/kube-prometheus-stack --namespace ${NAMESPACE} -f kube-prom-values.yaml + +kubectl get pods -n ${NAMESPACE} +kubectl get svc -n ${NAMESPACE}