This repository is the single source of truth for the blueberry-k3s K3S cluster running on Raspberry Pi 4.
Architecture Philosophy: Minimal, deterministic, reproducible GitOps for edge/SBC environments. See AGENTS.md for detailed guardrails and constraints.
- Cluster Name:
blueberry-k3s - Hardware: Raspberry Pi 4 (8GB RAM)
- Architecture: aarch64
- Kubernetes: K3S (single server node, may scale to +2 agent nodes)
- Storage: USB-attached
- GitOps: FluxCD v2.4.0
.
├── clusters/
│ └── blueberry-k3s/ # Cluster-specific entrypoint
│ ├── flux-system/ # Flux controllers and GitRepository source
│ ├── infrastructure.yaml # Infrastructure Kustomization
│ └── kustomization.yaml # Root composition
├── infrastructure/
│ └── monitoring/ # Prometheus + Grafana observability stack
├── apps/ # Application workloads (empty initially)
├── .github/
│ └── workflows/ # CI validation (lint, kubeconform, policy checks)
└── AGENTS.md # Repository guardrails and architectural philosophy
Before bootstrapping Flux, ensure:
-
K3S installed on
blueberry-k3s- K3S should be configured with your desired settings
kubectlaccess to the cluster
-
Flux CLI installed (v2.4.0)
curl -s https://fluxcd.io/install.sh | sudo bash -
Git repository access
- SSH key or personal access token configured
- Write access to this repository
-
No port conflicts
- Cockpit runs on port 9090
- Prometheus configured to use port 9091
- Grafana uses port 3000
-
Fork or clone this repository
-
Update the GitRepository URL in
clusters/blueberry-k3s/flux-system/gotk-sync.yaml:spec: url: ssh://git@github.com/YOUR_ORG/YOUR_REPO
-
Bootstrap Flux:
flux bootstrap git \ --url=ssh://git@github.com/YOUR_ORG/YOUR_REPO \ --branch=main \ --path=clusters/blueberry-k3s \ --private-key-file=/path/to/ssh/key
Or, if using GitHub directly:
flux bootstrap github \ --owner=YOUR_ORG \ --repository=YOUR_REPO \ --branch=main \ --path=clusters/blueberry-k3s \ --personal
-
Verify reconciliation:
flux get kustomizations flux get helmreleases -A
-
Access Grafana:
kubectl port-forward -n monitoring svc/grafana 3000:80
- URL: http://localhost:3000
- Default credentials:
admin/admin(change immediately)
-
Access Prometheus (optional):
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9091:9091
Prometheus (kube-prometheus-stack v67.4.0):
- Prometheus Operator + Prometheus server
- Port: 9091 (to avoid Cockpit conflict on 9090)
- Retention: 7 days / 4GB
- Scrape interval: 60s (tuned for edge IO constraints)
- Resource limits: 1 CPU / 1.5GB RAM
- Disabled: Alertmanager, node-exporter, kube-state-metrics (can enable later)
- No persistence (emptyDir) - can be added later if needed
Grafana (v8.7.2 / image 11.4.0):
- Pre-configured Prometheus datasource
- Default dashboards: Kubernetes cluster overview, pod monitoring
- Resource limits: 500m CPU / 512MB RAM
- No persistence (emptyDir)
- Default credentials:
admin/admin⚠️ Change after first login
Resource Usage (approximate):
- Total CPU: ~1.3 cores (requests) / ~2 cores (limits)
- Total RAM: ~900MB (requests) / ~2.3GB (limits)
- Acceptable for 8GB Raspberry Pi 4 with headroom for workloads
# Overall health
flux check
# Reconciliation status
flux get sources git
flux get kustomizations
# HelmRelease status
flux get helmreleases -A# Reconcile infrastructure
flux reconcile kustomization infrastructure --with-source
# Reconcile specific HelmRelease
flux reconcile helmrelease -n monitoring kube-prometheus-stack
flux reconcile helmrelease -n monitoring grafana# Flux controller logs
flux logs --level=error --all-namespaces
# Prometheus operator logs
kubectl logs -n monitoring -l app.kubernetes.io/name=prometheus-operator
# Grafana logs
kubectl logs -n monitoring -l app.kubernetes.io/name=grafanaHelmRelease stuck or failing:
kubectl describe helmrelease -n monitoring kube-prometheus-stack
kubectl describe helmrelease -n monitoring grafanaPrometheus not scraping:
# Check Prometheus targets
kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9091:9091
# Visit http://localhost:9091/targetsGrafana datasource issues:
- Verify Prometheus service name:
kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9091 - Check Grafana datasource config in Grafana UI
All upgrades must be done via Git commits (PRs recommended).
- Update chart version in
infrastructure/monitoring/*-helmrelease.yaml - Review upstream changelog
- Test reconciliation:
flux reconcile helmrelease -n monitoring <name> - Monitor for errors:
flux logs
# Check current version
flux version
# Upgrade Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash
# Upgrade controllers
flux install --export > clusters/blueberry-k3s/flux-system/gotk-components.yaml
git add clusters/blueberry-k3s/flux-system/gotk-components.yaml
git commit -m "chore: upgrade Flux to vX.Y.Z"
git pushTo rollback to a previous state:
# Find known-good commit
git log --oneline
# Revert to commit
git revert <commit-sha>
git push
# Or hard reset (use with caution)
git reset --hard <commit-sha>
git push --forceFlux will reconcile to the reverted state automatically.
Note: CRD changes and stateful components may not rollback cleanly. Always test upgrades in a non-production environment first.
- Create component directory under
infrastructure/orapps/ - Add manifests or HelmRelease
- Update parent kustomization.yaml to reference new component
- Commit and push
- Verify reconciliation:
flux get kustomizations
Example:
mkdir -p infrastructure/ingress
# Add manifests...
echo " - ingress" >> infrastructure/kustomization.yaml
git add infrastructure/
git commit -m "feat: add ingress-nginx"
git pushPull requests are automatically validated with:
- yamllint: YAML syntax and formatting
- kustomize build: Ensure manifests build successfully
- kubeconform: Kubernetes schema validation
- Policy checks: No
:latesttags, explicit namespaces
See .github/workflows/validate.yaml for details.
This cluster runs on a Raspberry Pi 4 with limited resources:
- RAM: 8GB total (K3S + system overhead ~1-2GB)
- CPU: 4 cores (ARM Cortex-A72)
- Storage: USB-attached (limited IO bandwidth, avoid write-heavy workloads)
Design Principles:
- Conservative resource requests/limits
- Minimal scrape intervals for Prometheus
- No persistent storage by default (can be added later)
- Disabled non-essential exporters and controllers
- Single-replica deployments (no HA)
See AGENTS.md for full architectural constraints.
admin / admin. Change these immediately after first login.
For production use, consider:
- Implementing SOPS encryption for secrets (see Flux SOPS guide)
- Setting up proper ingress with TLS
- Configuring authentication for Prometheus/Grafana
- Enabling RBAC policies
See AGENTS.md for contribution guidelines and architectural guardrails.
Key principles:
- Keep changes minimal and justified
- Pin all versions (charts, images)
- Test in CI before merging
- Document resource impact
- Ensure reproducibility
See LICENSE