This is a repository for my home infrastructure and Kubernetes cluster. I try to adhere to Infrastructure as Code (IaC) and GitOps practices using tools like Terraform, Kubernetes, Flux, Renovate and GitHub Actions.
There is a template over at onedr0p/cluster-template if you wanted to try and follow along with some of the practices I use here.
This semi hyper-converged cluster runs Talos Linux, an immutable and ephemeral Linux distribution built for Kubernetes, deployed on bare-metal Apple Mac Minis. Rook then provides my workloads with persistent block, object, and file storage; while a seperate server provides file storage for my media.
πΈ Click here to see my Talos configuration.
- actions-runner-controller: Self-hosted Github runners.
- cert-manager: Creates SSL certificates for services in my cluster.
- cilium: Internal Kubernetes container networking interface.
- cloudflared: Enables Cloudflare secure access to my ingresses.
- external-dns: Automatically syncs ingress DNS records to a DNS provider.
- external-secrets: Managed Kubernetes secrets using 1Password Connect.
- ingress-nginx: Kubernetes ingress controller using NGINX as a reverse proxy and load balancer.
- multus: Multi-homed pod networking.
- rook: Distributed block storage for peristent storage.
- sops: Managed secrets for Kubernetes and Terraform which are commited to Git.
- spegel: Stateless cluster local OCI registry mirror.
- tailscale: Private WireGuard based VPN.
- tf-controller: Additional Flux component used to run Terraform from within a Kubernetes cluster.
- volsync: Backup and recovery of persistent volume claims.
Flux watches my kubernetes folder (see Directories below) and makes the changes to my cluster based on the YAML manifests.
The way Flux works for me here is it will recursively search the kubernetes/apps folder until it finds the most top level kustomization.yaml
per directory and then apply all the resources listed in it. That aforementioned kustomization.yaml
will generally only have a namespace resource and one or many Flux kustomizations. Those Flux kustomizations will generally have a HelmRelease
or other resources related to the application underneath it which will be applied.
Renovate watches my entire repository looking for dependency updates, when they are found a PR is automatically created. When some PRs are merged Flux applies the changes to my cluster.
This Git repository contains the following directories under kubernetes.
π kubernetes # Kubernetes cluster defined as code
ββπ bootstrap # Flux installation
ββπ flux # Main Flux configuration of repository
ββπ apps # Apps deployed into my cluster grouped by namespace (see below)
Below is a a high level look at the layout of how my directory structure with Flux works. In this brief example you are able to see that authelia
will not be able to run until lldap
and cloudnative-pg
are running. It also shows that the Cluster
custom resource depends on the cloudnative-pg
Helm chart. This is needed because cloudnative-pg
installs the Cluster
custom resource definition in the Helm chart.
graph TD;
id1[Kustomization: cluster];
id2[Kustomization: cluster-apps];
id3[Kustomization: cluster-apps-cloudnative-pg];
id4[HelmRelease: postgres];
id5[Kustomization: cluster-apps-cloudnative-pg-cluster];
id6[Kustomization: cluster-apps-lldap];
id7[HelmRelease: lldap];
id8[Kustomization: cluster-apps-authelia];
id9[HelmRelease: authelia];
id10[Cluster: postgres];
id1 -->|Creates| id2;
id2 -->|Creates| id3;
id2 -->|Creates| id6;
id2 -->|Creates| id8;
id2 -->|Creates| id5;
id3 -->|Creates| id4;
id5 -->|Depends| id3;
id5 -->|Creates| id10;
id6 -->|Creates| id7;
id6 -->|Depends| id5;
id8 -->|Creates| id9;
id8 -->|Depends| id5;
id8 -->|Depends| id6;
The UDM Pro resolves DNS queries via blocky, which provides first-hop DNS resolution for my network. Blocky forwards requests targeted towards my public domain via k8s-gateway. Last-hop DNS resolution resolves via 1.1.1.1, which is configured as my primary DNS upstream provider. If for any reason blocky becomes unavailable, the UDM Pro is configured to fallback to 1.1.1.1 until blocky becomes available again.
πΈ Click here to see my blocky
configuration or here to see my k8s-gateway
configuration.
Outside DNS records are synced to Cloudflare using external-dns. The only DNS records this instance syncs to Cloudflare
are ones that have an ingress class name of external
and contain an ingress annotation external-dns.alpha.kubernetes.io/target
.
Device | Count | OS Disk Size | Data Disk Size | Ram | Operating System | Purpose |
---|---|---|---|---|---|---|
Apple Mac Mini (3.2GHz Intel i7 + 10GbE) | 3 | 1TB NVMe | - | 64GB | Talos | Kubernetes Workers |
Apple Mac Mini (3.2GHz Intel i7) | 3 | 512GB NVMe | - | 32GB | Talos | Kubernetes Masters |
CyberPower ATS PDU | 1 | - | - | - | - | PDU |
CyberPower UPS | 1 | - | - | - | - | PSU |
Sabrent NVMe M.2 Thunderbolt 3 Enclosure | 6 | - | 2TB NVMe ea. | - | - | Rook Ceph / Workers |
Sonnet 10GbE Thunderbolt 3 Adapter | 3 | - | - | - | - | 10GbE / Masters |
Synology NAS RS1221+ | 1 | - | 8x22TB + 2x2TB NVMe | 32GB | - | NFS |
Ubiquiti UDM Pro | 1 | - | - | - | - | Router |
Ubiquiti USW Enterprise XG 24 | 1 | - | - | - | - | 10GbE Switch |
Many thanks to my friend @onedrop and all the fantastic people who donate their time to the Home Operations Discord community. Be sure to check out kubesearch.dev for ideas on how to deploy applications or get ideas on what you may deploy.
See the latest release notes.
See LICENSE.