First Bare Metal Cluster
Purpose: For platform engineers, walks through deploying a first Kubernetes cluster on bare metal servers using openCenter (20 minutes configuration + 30–50 minutes deployment).
What You'll Do
- Initialize a bare metal cluster configuration with node IPs and VIP
- Validate, generate the GitOps repository, and deploy
- Verify nodes, MetalLB, Longhorn storage, and platform services are running
End result: A 6-node HA Kubernetes cluster on bare metal with MetalLB load balancing, Longhorn distributed storage, Kyverno policies, monitoring, and GitOps — ready for application workloads (~50 minutes total).
Prerequisites
- openCenter CLI installed (CLI Installation)
- At least 3 servers for control plane + 3 for workers (or 3 combined for non-HA)
- Ubuntu 22.04 or Rocky Linux 9 installed on all nodes
- SSH access to all nodes from the machine running the CLI
- Servers reachable on a shared L2/L3 network
- A virtual IP address available for the Kubernetes API (used by kube-vip or MetalLB)
- DNS records or wildcard domain for ingress
- A Git repository for GitOps
Step 1: Initialize the Cluster Configuration
opencenter cluster init my-baremetal-cluster --org my-org --type baremetal
This creates the configuration at ~/.config/opencenter/clusters/my-org/.my-baremetal-cluster-config.yaml and auto-generates SOPS Age keys and an SSH key pair.
Edit the configuration:
opencenter cluster edit my-baremetal-cluster
Key sections:
opencenter:
cluster:
cluster_name: my-baremetal-cluster
organization: my-org
infrastructure:
provider: baremetal
baremetal:
masters:
- ip: 10.0.1.10
hostname: master-1
- ip: 10.0.1.11
hostname: master-2
- ip: 10.0.1.12
hostname: master-3
workers:
- ip: 10.0.1.20
hostname: worker-1
- ip: 10.0.1.21
hostname: worker-2
- ip: 10.0.1.22
hostname: worker-3
vip: 10.0.1.100
vip_interface: eth0
ssh_user: ubuntu
ssh_key_path: ~/.ssh/id_ed25519
kubernetes:
version: 1.33.5
control_plane_count: 3
worker_count: 3
cni: calico
services:
keycloak:
enabled: true
kube-prometheus-stack:
enabled: true
loki:
enabled: true
longhorn:
enabled: true
metallb:
enabled: true
velero:
enabled: true
secrets:
sops:
age_keys:
- age1... # Auto-generated during init
Step 2: Validate Configuration
opencenter cluster validate my-baremetal-cluster
Validation checks: schema compliance, SSH connectivity to all nodes (if --validation=online), node IP uniqueness, VIP availability, and network configuration.
Step 3: Generate GitOps Repository
opencenter cluster generate my-baremetal-cluster
This generates:
- Kubespray inventory with node IPs and security hardening (
k8s_hardening.yml) - FluxCD application manifests with MetalLB and Longhorn enabled
- SOPS-encrypted secrets
For bare metal, there is no Terraform infrastructure step — the nodes already exist.
Step 4: Deploy the Cluster
opencenter cluster deploy my-baremetal-cluster
The deploy command:
- Prepares nodes via Ansible (container runtime, kernel parameters,
br_netfilter, IP forwarding) (5–10 minutes) - Installs Kubernetes via Kubespray with containerd, etcd HA, kube-vip for API VIP (20–35 minutes)
- Bootstraps FluxCD which reconciles platform services from openCenter-gitops-base (10–15 minutes)
Step 5: Verify the Cluster
# Check cluster status
opencenter cluster status my-baremetal-cluster
# Verify all nodes
kubectl get nodes
# Confirm MetalLB is advertising IPs
kubectl get svc -A | grep LoadBalancer
# Check Longhorn storage
kubectl get sc
kubectl get volumes -n longhorn-system
# Check FluxCD reconciliation
flux get kustomizations
Check Your Work
- All nodes show
Readystatus - kube-vip is serving the API on the VIP address (
curl -k https://10.0.1.100:6443/healthz) - MetalLB speaker pods are running on every node
- Longhorn storage class is available as default (
kubectl get sc) - FluxCD kustomizations show
Ready=True
Platform Services Deployed
After FluxCD reconciles, these services from openCenter-gitops-base are running:
| Service | Version | Namespace |
|---|---|---|
| cert-manager | v1.18.2 | cert-manager |
| Gateway API (Envoy) | latest | envoy-gateway-system |
| Keycloak | 26.4.2 | keycloak |
| Kyverno | 3.6.0 | kyverno |
| kube-prometheus-stack | 77.6.0 | observability |
| Loki | 6.45.2 | observability |
| Longhorn | 1.11.0 | longhorn-system |
| MetalLB | 0.15.2 | metallb-system |
| Velero | 10.1.1 | velero |
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| SSH connection refused | Wrong key or user | Verify ssh_user and ssh_key_path; test with ssh -i ~/.ssh/id_ed25519 ubuntu@10.0.1.10 |
| Nodes not joining | Firewall blocking 6443, 10250, 2379-2380 | Open required ports between all nodes |
| VIP unreachable | Wrong interface name | Check vip_interface matches the host NIC (ip addr show) |
| No storage class | Longhorn prerequisites missing | Ensure open-iscsi is installed on all workers |
Next Steps
- Configure MetalLB — Define IP address pools for LoadBalancer services
- Configure Longhorn — Tune storage replication and retention
- Deploy Your First Application — Ship a workload via GitOps
- Day 2 Operations — Upgrades, drift detection, backups