A 3-node high-availability Kubernetes cluster running Talos Linux on Mac Mini hardware.
┌─────────────────────────────────────────────────────────────────┐
│ Talos Homelab Cluster │
│ 3-Node HA Control Plane │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ mac-mini-1 │ │ mac-mini-2 │ │ mac-mini-3 │
│ 192.168.1.223│ │ 192.168.1.162│ │ 192.168.1.215│
│ │ │ │ │ │
│ Control Plane│ │ Control Plane│ │ Control Plane│
│ etcd Member │◄────►│ etcd Member │◄────►│ etcd Member │
│ 63GB RAM │ │ 63GB RAM │ │ 63GB RAM │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└─────────────────────┴─────────────────────┘
│
LAN (enp68s0)
Thunderbolt Networking
| Component | Details |
|---|---|
| Talos Version | v1.11.2 |
| Kubernetes Version | v1.34.0 |
| CNI | Flannel |
| Node Count | 3 (all control plane) |
| etcd | 3-member quorum (HA) |
| Custom Extensions | Thunderbolt (v1.11.2) |
| Network | Static IPs, Thunderbolt networking |
| Hostname | IP Address | MAC Address | Role | Resources |
|---|---|---|---|---|
| mac-mini-1 | 192.168.1.223 | 00:30:93:12:59:a1 | Control Plane | 63GB RAM, 12-core CPU |
| mac-mini-2 | 192.168.1.162 | 00:30:93:12:54:ce | Control Plane | 63GB RAM, 12-core CPU |
| mac-mini-3 | 192.168.1.215 | 00:30:93:12:59:99 | Control Plane | 63GB RAM, 12-core CPU |
- Primary Interface:
enp68s0(Thunderbolt-based 10G) - Ignored Interface:
enp4s0(onboard Ethernet) - Gateway: 192.168.1.1
- Subnet: 192.168.1.0/24
- External Network Adapter: Sonnet Technologies Solo 10G SFP+ Thunderbolt 3 Edition
- Storage: NVMe (
/dev/nvme0n1)
~/talos-k8s-cluster/
├── README.md # This file
├── controlplane.yaml # Control plane machine config
├── worker.yaml # Worker machine config (unused)
├── talosconfig # Talos client configuration
└── patches/
├── mac-mini-patch.yaml # Base patch (all nodes)
├── mac-mini-1-patch.yaml # Node 1 specific config
├── mac-mini-2-patch.yaml # Node 2 specific config
├── mac-mini-3-patch.yaml # Node 3 specific config
└── hostname-mac-mini-*.yaml # Hostname patches
Applied to all nodes - includes Thunderbolt extension and disk configuration:
machine:
install:
disk: /dev/nvme0n1
extensions:
- image: ghcr.io/siderolabs/thunderbolt:latest
kernel:
modules:
- name: thunderbolt
- name: thunderbolt_net
kubelet:
extraArgs:
rotate-server-certificates: trueEach node has a patch with its static IP and hostname configuration:
machine:
install:
extraKernelArgs:
- talos.network.interface.ignore=enp4s0
network:
hostname: mac-mini-1 # or mac-mini-2, mac-mini-3
interfaces:
- interface: enp68s0
addresses:
- 192.168.1.223/24 # Unique per node
routes:
- network: 0.0.0.0/0
gateway: 192.168.1.1The cluster uses a custom Talos image with the Thunderbolt extension baked in:
- Schematic ID:
edf6a4b727a7aa090af375d1e82a198c11c202b272ca9c0d7334cfda7b3220fd - Installer Image:
factory.talos.dev/metal-installer/edf6a4b727a7aa090af375d1e82a198c11c202b272ca9c0d7334cfda7b3220fd:v1.11.2 - ISO: Available at factory.talos.dev
customization:
extraKernelArgs:
- talos.network.interface.ignore=enp4s0
systemExtensions:
officialExtensions:
- siderolabs/thunderboltAdd these to your ~/.zshrc (or ~/.bashrc):
# Talos Homelab Cluster
export CONTROL_PLANE_1_IP=192.168.1.223
export CONTROL_PLANE_2_IP=192.168.1.162
export CONTROL_PLANE_3_IP=192.168.1.215
export CLUSTER_NAME=macmini-cluster
export DISK_NAME=nvme0n1
export TALOSCONFIG=~/.talos/configReload your shell after adding:
source ~/.zshrcTalos is a modern OS for Kubernetes - it has no shell access, no SSH, and is configured entirely via API.
┌─────────────────────────────────────────────────────────────┐
│ Your Workstation │
├─────────────────────────────────────────────────────────────┤
│ │
│ talosctl kubectl │
│ (Talos API) (Kubernetes API) │
│ │ │ │
│ │ │ │
└──────┼──────────────────────────────────┼───────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Talos Linux Node │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Talos System Services (apid, trustd, etc.) │ │
│ │ • OS Configuration │ │
│ │ • Network Setup │ │
│ │ • Disk Management │ │
│ │ • System Extensions │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Kubernetes Components (kubelet, etc.) │ │
│ │ • Pods │ │
│ │ • Deployments │ │
│ │ • Services │ │
│ │ • ConfigMaps, Secrets, etc. │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Purpose: Control Talos Linux itself (the operating system layer)
Common Commands:
# View system logs
talosctl dmesg --nodes $CONTROL_PLANE_1_IP
# Check service status
talosctl service etcd status --nodes $CONTROL_PLANE_1_IP
# Interactive dashboard
talosctl dashboard --nodes $CONTROL_PLANE_1_IP
# Apply configuration changes
talosctl patch machineconfig --nodes $CONTROL_PLANE_1_IP --patch @patch.yaml
# Upgrade Talos OS
talosctl upgrade --nodes $CONTROL_PLANE_1_IP --image factory.talos.dev/...
# Reboot node
talosctl reboot --nodes $CONTROL_PLANE_1_IP
# Check etcd cluster
talosctl etcd members --nodes $CONTROL_PLANE_1_IP
# View network interfaces
talosctl get links --nodes $CONTROL_PLANE_1_IPUse talosctl for: OS config, networking, disk management, system services, node operations
Purpose: Control Kubernetes workloads (applications and services)
Common Commands:
# View nodes
kubectl get nodes
# View pods
kubectl get pods -A
# Deploy application
kubectl apply -f deployment.yaml
# View logs
kubectl logs pod-name -n namespace
# Execute commands in pod
kubectl exec -it pod-name -- /bin/sh
# View services
kubectl get services -A
# Check cluster info
kubectl cluster-infoUse kubectl for: Deploying apps, managing pods, services, ingress, storage, RBAC
| Aspect | talosctl | kubectl |
|---|---|---|
| Layer | Operating System | Kubernetes |
| Manages | Talos nodes, networking, system config | Pods, deployments, services |
| Authentication | Talos CA certificates | Kubernetes CA certificates |
| Config File | ~/.talos/config or talosconfig |
~/.kube/config |
| Port | 50000 (Talos API) | 6443 (Kubernetes API) |
Since kubelet certificate rotation is enabled, you must install an automatic CSR approver to prevent certificate approval warnings:
kubectl apply -f https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yamlThis controller automatically approves kubelet serving certificate signing requests, eliminating the need for manual intervention.
Verify it's running:
kubectl get pods -n kubelet-serving-cert-approverYou should see the kubelet-serving-cert-approver pod in Running state.
# Talos health check
talosctl health --nodes $CONTROL_PLANE_1_IP
# Kubernetes health check
kubectl get nodes
kubectl get pods -A# System logs (Talos)
talosctl dmesg --follow --nodes $CONTROL_PLANE_1_IP
# Service logs (Talos)
talosctl logs kubelet --follow --nodes $CONTROL_PLANE_1_IP
# Pod logs (Kubernetes)
kubectl logs -f pod-name -n namespace# Graceful shutdown all nodes
talosctl shutdown --nodes $CONTROL_PLANE_1_IP,$CONTROL_PLANE_2_IP,$CONTROL_PLANE_3_IP
# Reboot single node
talosctl reboot --nodes $CONTROL_PLANE_1_IP# Check network interfaces
talosctl get links --nodes $CONTROL_PLANE_1_IP
# Verify Thunderbolt module loaded
talosctl read /proc/modules --nodes $CONTROL_PLANE_1_IP | grep thunderbolt
# Check routes
talosctl get routes --nodes $CONTROL_PLANE_1_IP# Apply a patch to change config
talosctl patch machineconfig \
--nodes $CONTROL_PLANE_1_IP \
--patch @patches/your-patch.yaml# Upgrade Talos OS
talosctl upgrade \
--nodes $CONTROL_PLANE_1_IP,$CONTROL_PLANE_2_IP,$CONTROL_PLANE_3_IP \
--image factory.talos.dev/metal-installer/YOUR_SCHEMATIC_ID:v1.11.2 \
--preserve
# Upgrade Kubernetes
talosctl upgrade-k8s --to 1.34.0If you need to rebuild the cluster:
curl -LO https://factory.talos.dev/image/edf6a4b727a7aa090af375d1e82a198c11c202b272ca9c0d7334cfda7b3220fd/v1.11.2/metal-amd64.isoCreate bootable USB and boot all three Mac Minis.
export CONTROL_PLANE_1_IP=192.168.1.223
export CONTROL_PLANE_2_IP=192.168.1.162
export CONTROL_PLANE_3_IP=192.168.1.215
export CLUSTER_NAME=macmini-cluster
export DISK_NAME=nvme0n1talosctl gen config $CLUSTER_NAME https://$CONTROL_PLANE_1_IP:6443 \
--config-patch @patches/mac-mini-patch.yaml# Mac Mini 1
talosctl apply-config --insecure \
--nodes $CONTROL_PLANE_1_IP \
--file controlplane.yaml \
--config-patch @patches/mac-mini-1-patch.yaml
# Mac Mini 2
talosctl apply-config --insecure \
--nodes $CONTROL_PLANE_2_IP \
--file controlplane.yaml \
--config-patch @patches/mac-mini-2-patch.yaml
# Mac Mini 3
talosctl apply-config --insecure \
--nodes $CONTROL_PLANE_3_IP \
--file controlplane.yaml \
--config-patch @patches/mac-mini-3-patch.yaml# Set endpoints
talosctl config endpoint $CONTROL_PLANE_1_IP $CONTROL_PLANE_2_IP $CONTROL_PLANE_3_IP
# Set context
talosctl config context macmini-clusterIMPORTANT: Run this ONLY ONCE on ONE node!
talosctl bootstrap --nodes $CONTROL_PLANE_1_IPtalosctl kubeconfig --nodes $CONTROL_PLANE_1_IPkubectl get nodes
talosctl health --nodes $CONTROL_PLANE_1_IPThis prevents certificate rotation warnings:
kubectl apply -f https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yamlCheck Thunderbolt module:
talosctl read /proc/modules --nodes $CONTROL_PLANE_1_IP | grep thunderboltShould show:
thunderbolt_net 49152 0 - Live
thunderbolt 491520 1 thunderbolt_net, Live
Verify network interface:
talosctl get links --nodes $CONTROL_PLANE_1_IPenp68s0 should show OPER STATE: up
# Check etcd members
talosctl etcd members --nodes $CONTROL_PLANE_1_IP
# Check etcd service status
talosctl service etcd status --nodes $CONTROL_PLANE_1_IPIf you see a diagnostic about "kubelet server certificate rotation is enabled, but CSR is not approved":
# Check pending CSRs
kubectl get csr
# Approve all pending CSRs
kubectl get csr -o json | jq -r '.items[] | select(.status == {}) | .metadata.name' | xargs kubectl certificate approve
# Or manually approve each one
kubectl certificate approve <csr-name>Install automatic CSR approver (recommended to prevent this issue):
kubectl apply -f https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yamlThis controller automatically approves kubelet serving certificate requests, preventing manual intervention.
# List all nodes
kubectl get nodes
# Delete old node registrations
kubectl delete node old-node-name# Talos interactive dashboard
talosctl dashboard --nodes $CONTROL_PLANE_1_IP,$CONTROL_PLANE_2_IP,$CONTROL_PLANE_3_IP
# Install k9s for Kubernetes
brew install k9s
k9s# Install stern for better log streaming
brew install stern
# Stream logs from all pods in namespace
stern -n kube-system . Internet
│
│
┌─────────▼─────────┐
│ Router/Gateway │
│ 192.168.1.1 │
└─────────┬─────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────────▼─────────┐ ┌──▼────────────┐ ┌▼──────────────┐
│ mac-mini-1 │ │ mac-mini-2 │ │ mac-mini-3 │
│ 192.168.1.223 │ │ 192.168.1.162│ │ 192.168.1.215│
├───────────────────┤ ├───────────────┤ ├───────────────┤
│ Thunderbolt Port │ │Thunderbolt Port│ │Thunderbolt Port│
│ │ │ │ │ │ │ │ │
│ ┌────▼────┐ │ │ ┌────▼────┐ │ │ ┌────▼────┐ │
│ │Sonnet │ │ │ │Sonnet │ │ │ │Sonnet │ │
│ │10G SFP+ │ │ │ │10G SFP+ │ │ │ │10G SFP+ │ │
│ └────┬────┘ │ │ └────┬────┘ │ │ └────┬────┘ │
│ │ │ │ │ │ │ │ │
│ enp68s0 (UP) │ │ enp68s0 (UP) │ │ enp68s0 (UP) │
│ enp4s0 (IGN) │ │ enp4s0 (IGN) │ │ enp4s0 (IGN) │
└─────────────────┘ └───────────────┘ └───────────────┘
Flannel Overlay Network: 10.244.0.0/16
Pod Network communication across all nodes
- No SSH: Talos has no SSH access. All management is via
talosctlAPI. - Immutable: OS is immutable and configured declaratively.
- Secure Boot: Custom image supports SecureBoot if needed.
- etcd Quorum: 3-node cluster can survive 1 node failure.
- Thunderbolt Networking: Critical for Mac Mini networking; baked into custom image.
- Reboot Safe: Cluster tested and verified to survive full power cycles.
This repository uses a branch-based workflow with automated validation.
main- Production-ready configurations, protected branchdev- Development and testing branch
# 1. Start from latest main
git checkout main
git pull origin main
# 2. Create/switch to dev branch
git checkout -b dev
# 3. Make your changes
vim patches/new-feature.yaml
# 4. Commit changes
git add patches/new-feature.yaml
git commit -m "Add new feature configuration"
# 5. Push to dev branch
git push origin dev
# 6. Create Pull Request on GitHub (dev → main)
# GitHub will automatically run validation checks
# 7. After PR is merged, sync your local main
git checkout main
git pull origin main
# 8. Clean up and start fresh for next change
git branch -d dev
git checkout -b dev
git push -u origin devGitHub Actions automatically validates all YAML files on:
- Every push to any branch
- Every pull request
Validation checks:
- YAML syntax correctness
- File structure validation
The workflow runs yamllint on all files in the patches/ directory.
To enforce validation before merging to main:
- Go to: Settings → Branches → Add rule
- Branch name pattern:
main - Enable:
- ☑️ Require a pull request before merging
- ☑️ Require status checks to pass before merging
- Select:
validateworkflow
With branch protection enabled, you cannot merge to main unless all checks pass.
The following files are not committed to Git (sensitive data):
talosconfig # Contains authentication certificates
controlplane.yaml # Contains cluster secrets
worker.yaml # Contains cluster secrets
*.key # Private keys
These files remain local only and are listed in .gitignore.
# Check what's changed
git status
# View commit history
git log --oneline
# See what's ignored
git status --ignored
# View difference before committing
git diff
# Undo uncommitted changes
git checkout -- <file>
# Pull latest from remote
git pull origin main✅ Production Ready
- All nodes operational
- etcd quorum healthy
- Thunderbolt networking functional
- Survives reboots gracefully
- Static IPs persistent
- Custom extensions loaded
Last Updated: October 13, 2025 Cluster Built: October 13, 2025