361 lines
21 KiB
Markdown
361 lines
21 KiB
Markdown
# Template for deploying k3s backed by Flux
|
|
|
|
Highly opinionated template for deploying a single [k3s](https://k3s.io) cluster with [Ansible](https://www.ansible.com) and [Terraform](https://www.terraform.io) backed by [Flux](https://toolkit.fluxcd.io/) and [SOPS](https://toolkit.fluxcd.io/guides/mozilla-sops/).
|
|
|
|
The purpose here is to showcase how you can deploy an entire Kubernetes cluster and show it off to the world using the [GitOps](https://www.weave.works/blog/what-is-gitops-really) tool [Flux](https://toolkit.fluxcd.io/). When completed, your Git repository will be driving the state of your Kubernetes cluster. In addition with the help of the [Ansible](https://github.com/ansible-collections/community.sops), [Terraform](https://github.com/carlpett/terraform-provider-sops) and [Flux](https://toolkit.fluxcd.io/guides/mozilla-sops/) SOPS integrations you'll be able to commit Age encrypted secrets to your public repo.
|
|
|
|
## Overview
|
|
|
|
- [Introduction](https://github.com/k8s-at-home/template-cluster-k3s#-introduction)
|
|
- [Prerequisites](https://github.com/k8s-at-home/template-cluster-k3s#-prerequisites)
|
|
- [Repository structure](https://github.com/k8s-at-home/template-cluster-k3s#-repository-structure)
|
|
- [Lets go!](https://github.com/k8s-at-home/template-cluster-k3s#-lets-go)
|
|
- [Post installation](https://github.com/k8s-at-home/template-cluster-k3s#-installation)
|
|
- [Thanks](https://github.com/k8s-at-home/template-cluster-k3s#-thanks)
|
|
|
|
## 👋 Introduction
|
|
|
|
The following components will be installed in your [k3s](https://k3s.io/) cluster by default. They are only included to get a minimum viable cluster up and running. You are free to add / remove components to your liking but anything outside the scope of the below components are not supported by this template.
|
|
|
|
Feel free to read up on any of these technologies before you get started to be more familiar with them.
|
|
|
|
- [cert-manager](https://cert-manager.io/) - SSL certificates - with Cloudflare DNS challenge
|
|
- [calico](https://www.tigera.io/project-calico/) - CNI (container network interface)
|
|
- [echo-server](https://github.com/Ealenn/Echo-Server) - REST Server Tests (Echo-Server) API (useful for debugging HTTP issues)
|
|
- [external-dns](https://github.com/kubernetes-sigs/external-dns) - Automatically configure external DNS servers for Kubernetes Ingresses and Services
|
|
- [flux](https://toolkit.fluxcd.io/) - GitOps tool for deploying manifests from the `cluster` directory
|
|
- [hajimari](https://github.com/toboshii/hajimari) - start page with ingress discovery
|
|
- [k8s_gateway](https://github.com/ori-edge/k8s_gateway) - DNS resolver for all types of external Kubernetes resources
|
|
- [kube-vip](https://kube-vip.io/) - layer 2 load balancer for the Kubernetes control plane
|
|
- [local-path-provisioner](https://github.com/rancher/local-path-provisioner) - default storage class provided by k3s
|
|
- [metallb](https://metallb.universe.tf/) - bare metal load balancer
|
|
- [reloader](https://github.com/stakater/Reloader) - restart pods when Kubernetes `configmap` or `secret` changes
|
|
- [reflector](https://github.com/emberstack/kubernetes-reflector) - mirror `configmap`s or `secret`s to other Kubernetes namespaces
|
|
- [system-upgrade-controller](https://github.com/rancher/system-upgrade-controller) - automate upgrading k3s
|
|
- [traefik](https://traefik.io) - ingress controller
|
|
|
|
For provisioning the following tools will be used:
|
|
|
|
- [Ubuntu](https://ubuntu.com/download/server) - this is a pretty universal operating system that supports running all kinds of home related workloads in Kubernetes
|
|
- [Ansible](https://www.ansible.com) - this will be used to provision the Ubuntu operating system to be ready for Kubernetes and also to install k3s
|
|
- [Terraform](https://www.terraform.io) - in order to help with the DNS settings this will be used to provision an already existing Cloudflare domain and DNS settings
|
|
|
|
## 📝 Prerequisites
|
|
|
|
### 💻 Systems
|
|
|
|
- One or more nodes with a fresh install of [Ubuntu Server 22.04](https://ubuntu.com/download/server). These nodes can be bare metal or VMs.
|
|
- A [Cloudflare](https://www.cloudflare.com/) account with a domain, this will be managed by Terraform.
|
|
- Some experience in debugging problems and a positive attitude ;)
|
|
|
|
📍 It is recommended to have 3 master nodes for a highly available control plane.
|
|
|
|
### 🔧 Tools
|
|
|
|
📍 You should install the below CLI tools on your workstation. Make sure you pull in the latest versions.
|
|
|
|
#### Required
|
|
|
|
| Tool | Purpose |
|
|
|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
|
| [ansible](https://www.ansible.com) | Preparing Ubuntu for Kubernetes and installing k3s |
|
|
| [direnv](https://github.com/direnv/direnv) | Exports env vars based on present working directory |
|
|
| [flux](https://toolkit.fluxcd.io/) | Operator that manages your k8s cluster based on your Git repository |
|
|
| [age](https://github.com/FiloSottile/age) | A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability. |
|
|
| [go-task](https://github.com/go-task/task) | A task runner / simpler Make alternative written in Go |
|
|
| [ipcalc](http://jodies.de/ipcalc) | Used to verify settings in the configure script |
|
|
| [jq](https://stedolan.github.io/jq/) | Used to verify settings in the configure script |
|
|
| [kubectl](https://kubernetes.io/docs/tasks/tools/) | Allows you to run commands against Kubernetes clusters |
|
|
| [sops](https://github.com/mozilla/sops) | Encrypts k8s secrets with Age |
|
|
| [terraform](https://www.terraform.io) | Prepare a Cloudflare domain to be used with the cluster |
|
|
|
|
#### Optional
|
|
|
|
| Tool | Purpose |
|
|
|--------------------------------------------------------|----------------------------------------------------------|
|
|
| [helm](https://helm.sh/) | Manage Kubernetes applications |
|
|
| [kustomize](https://kustomize.io/) | Template-free way to customize application configuration |
|
|
| [pre-commit](https://github.com/pre-commit/pre-commit) | Runs checks pre `git commit` |
|
|
| [gitleaks](https://github.com/zricethezav/gitleaks) | Scan git repos (or files) for secrets |
|
|
| [prettier](https://github.com/prettier/prettier) | Prettier is an opinionated code formatter. |
|
|
|
|
### ⚠️ pre-commit
|
|
|
|
It is advisable to install [pre-commit](https://pre-commit.com/) and the pre-commit hooks that come with this repository.
|
|
[sops-pre-commit](https://github.com/k8s-at-home/sops-pre-commit) and [gitleaks](https://github.com/zricethezav/gitleaks) will check to make sure you are not by accident committing your secrets un-encrypted.
|
|
|
|
After pre-commit is installed on your machine run:
|
|
|
|
```sh
|
|
task pre-commit:init
|
|
```
|
|
**Remember to run this on each new clone of the repository for it to have effect.**
|
|
|
|
Commands are of interest, for learning purposes:
|
|
|
|
This command makes it so pre-commit runs on `git commit`, and also installs environments per the config file.
|
|
```
|
|
pre-commit install --install-hooks
|
|
```
|
|
This command checks for new versions of hooks, though it will occasionally make mistakes, so verify its results.
|
|
```
|
|
pre-commit autoupdate
|
|
```
|
|
|
|
## 📂 Repository structure
|
|
|
|
The Git repository contains the following directories under `cluster` and are ordered below by how Flux will apply them.
|
|
|
|
- **base** directory is the entrypoint to Flux
|
|
- **crds** directory contains custom resource definitions (CRDs) that need to exist globally in your cluster before anything else exists
|
|
- **core** directory (depends on **crds**) are important infrastructure applications (grouped by namespace) that should never be pruned by Flux
|
|
- **apps** directory (depends on **core**) is where your common applications (grouped by namespace) could be placed, Flux will prune resources here if they are not tracked by Git anymore
|
|
|
|
```
|
|
cluster
|
|
├── apps
|
|
│ ├── default
|
|
│ ├── kube-system
|
|
│ ├── networking
|
|
│ └── system-upgrade
|
|
├── base
|
|
│ └── flux-system
|
|
├── core
|
|
│ ├── cert-manager
|
|
│ ├── kube-system
|
|
│ ├── metallb-system
|
|
│ └── namespaces
|
|
└── crds
|
|
├── cert-manager
|
|
├── system-upgrade-controller
|
|
└── traefik
|
|
```
|
|
|
|
## 🚀 Lets go!
|
|
|
|
Very first step will be to create a new repository by clicking the **Use this template** button on this page.
|
|
|
|
Clone the repo to you local workstation and `cd` into it.
|
|
|
|
📍 **All of the below commands** are run on your **local** workstation, **not** on any of your cluster nodes.
|
|
|
|
### 🔐 Setting up Age
|
|
|
|
📍 Here we will create a Age Private and Public key. Using SOPS with Age allows us to encrypt and decrypt secrets.
|
|
|
|
1. Create a Age Private / Public Key
|
|
|
|
```sh
|
|
age-keygen -o age.agekey
|
|
```
|
|
|
|
2. Set up the directory for the Age key and move the Age file to it
|
|
|
|
```sh
|
|
mkdir -p ~/.config/sops/age
|
|
mv age.agekey ~/.config/sops/age/keys.txt
|
|
```
|
|
|
|
3. Export the `SOPS_AGE_KEY_FILE` variable in your `bashrc`, `zshrc` or `config.fish` and source it, e.g.
|
|
|
|
```sh
|
|
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
|
|
source ~/.bashrc
|
|
```
|
|
|
|
4. Fill out the Age public key in the `.config.env` under `BOOTSTRAP_AGE_PUBLIC_KEY`, **note** the public key should start with `age`...
|
|
|
|
### ☁️ Global Cloudflare API Key
|
|
|
|
In order to use Terraform and `cert-manager` with the Cloudflare DNS challenge you will need to create a API key.
|
|
|
|
1. Head over to Cloudflare and create a API key by going [here](https://dash.cloudflare.com/profile/api-tokens).
|
|
|
|
2. Under the `API Keys` section, create a global API Key.
|
|
|
|
3. Use the API Key in the configuration section below.
|
|
|
|
📍 You may wish to update this later on to a Cloudflare **API Token** which can be scoped to certain resources. I do not recommend using a Cloudflare **API Key**, however for the purposes of this template it is easier getting started without having to define which scopes and resources are needed. For more information see the [Cloudflare docs on API Keys and Tokens](https://developers.cloudflare.com/api/).
|
|
|
|
### 📄 Configuration
|
|
|
|
📍 The `.config.env` file contains necessary configuration that is needed by Ansible, Terraform and Flux.
|
|
|
|
1. Copy the `.config.sample.env` to `.config.env` and start filling out all the environment variables. **All are required** unless otherwise noted in the comments.
|
|
|
|
2. Once that is done, verify the configuration is correct by running `./configure.sh --verify`
|
|
|
|
3. If you do not encounter any errors run `./configure.sh` to start having the script wire up the templated files and place them where they need to be.
|
|
|
|
### ⚡ Preparing Ubuntu with Ansible
|
|
|
|
📍 Here we will be running a Ansible Playbook to prepare Ubuntu for running a Kubernetes cluster.
|
|
|
|
📍 Nodes are not security hardened by default, you can do this with [dev-sec/ansible-collection-hardening](https://github.com/dev-sec/ansible-collection-hardening) or something similar.
|
|
|
|
1. Ensure you are able to SSH into you nodes from your workstation with using your private ssh key. This is how Ansible is able to connect to your remote nodes.
|
|
|
|
2. Install the deps by running `task ansible:deps`
|
|
|
|
3. Verify Ansible can view your config by running `task ansible:list`
|
|
|
|
4. Verify Ansible can ping your nodes by running `task ansible:adhoc:ping`
|
|
|
|
5. Finally, run the Ubuntu Prepare playbook by running `task ansible:playbook:ubuntu-prepare`
|
|
|
|
6. If everything goes as planned you should see Ansible running the Ubuntu Prepare Playbook against your nodes.
|
|
|
|
### ⛵ Installing k3s with Ansible
|
|
|
|
📍 Here we will be running a Ansible Playbook to install [k3s](https://k3s.io/) with [this](https://galaxy.ansible.com/xanmanning/k3s) wonderful k3s Ansible galaxy role. After completion, Ansible will drop a `kubeconfig` in `./provision/kubeconfig` for use with interacting with your cluster with `kubectl`.
|
|
|
|
1. Verify Ansible can view your config by running `task ansible:list`
|
|
|
|
2. Verify Ansible can ping your nodes by running `task ansible:adhoc:ping`
|
|
|
|
3. Run the k3s install playbook by running `task ansible:playbook:k3s-install`
|
|
|
|
4. If everything goes as planned you should see Ansible running the k3s install Playbook against your nodes.
|
|
|
|
5. Verify the nodes are online
|
|
|
|
```sh
|
|
kubectl --kubeconfig=./provision/kubeconfig get nodes
|
|
# NAME STATUS ROLES AGE VERSION
|
|
# k8s-0 Ready control-plane,master 4d20h v1.21.5+k3s1
|
|
# k8s-1 Ready worker 4d20h v1.21.5+k3s1
|
|
```
|
|
|
|
### ☁️ Configuring Cloudflare DNS with Terraform
|
|
|
|
📍 Review the Terraform scripts under `./provision/terraform/cloudflare/` and make sure you understand what it's doing (no really review it). If your domain already has existing DNS records **be sure to export those DNS settings before you continue**.
|
|
|
|
1. Pull in the Terraform deps by running `task terraform:init:cloudflare`
|
|
|
|
2. Review the changes Terraform will make to your Cloudflare domain by running `task terraform:plan:cloudflare`
|
|
|
|
3. Finally have Terraform execute the task by running `task terraform:apply:cloudflare`
|
|
|
|
If Terraform was ran successfully you can log into Cloudflare and validate the DNS records are present. The cluster application `external-dns` will be managing the rest of the DNS records you will need.
|
|
|
|
### 🔹 GitOps with Flux
|
|
|
|
📍 Here we will be installing [flux](https://toolkit.fluxcd.io/) after some quick bootstrap steps.
|
|
|
|
1. Verify Flux can be installed
|
|
|
|
```sh
|
|
flux --kubeconfig=./provision/kubeconfig check --pre
|
|
# ► checking prerequisites
|
|
# ✔ kubectl 1.21.5 >=1.18.0-0
|
|
# ✔ Kubernetes 1.21.5+k3s1 >=1.16.0-0
|
|
# ✔ prerequisites checks passed
|
|
```
|
|
|
|
2. Pre-create the `flux-system` namespace
|
|
|
|
```sh
|
|
kubectl --kubeconfig=./provision/kubeconfig create namespace flux-system --dry-run=client -o yaml | kubectl --kubeconfig=./provision/kubeconfig apply -f -
|
|
```
|
|
|
|
3. Add the Age key in-order for Flux to decrypt SOPS secrets
|
|
|
|
```sh
|
|
cat ~/.config/sops/age/keys.txt |
|
|
kubectl --kubeconfig=./provision/kubeconfig \
|
|
-n flux-system create secret generic sops-age \
|
|
--from-file=age.agekey=/dev/stdin
|
|
```
|
|
|
|
📍 Variables defined in `./cluster/base/cluster-secrets.sops.yaml` and `./cluster/base/cluster-settings.yaml` will be usable anywhere in your YAML manifests under `./cluster`
|
|
|
|
4. **Verify** the `./cluster/base/cluster-secrets.sops.yaml` and `./cluster/core/cert-manager/secret.sops.yaml` files are **encrypted** with SOPS
|
|
|
|
5. If you verified all the secrets are encrypted, you can delete the `tmpl` directory now
|
|
|
|
6. Push you changes to git
|
|
|
|
```sh
|
|
git add -A
|
|
git commit -m "initial commit"
|
|
git push
|
|
```
|
|
|
|
7. Install Flux
|
|
|
|
📍 Due to race conditions with the Flux CRDs you will have to run the below command twice. There should be no errors on this second run.
|
|
|
|
```sh
|
|
kubectl --kubeconfig=./provision/kubeconfig apply --kustomize=./cluster/base/flux-system
|
|
# namespace/flux-system configured
|
|
# customresourcedefinition.apiextensions.k8s.io/alerts.notification.toolkit.fluxcd.io created
|
|
# ...
|
|
# unable to recognize "./cluster/base/flux-system": no matches for kind "Kustomization" in version "kustomize.toolkit.fluxcd.io/v1beta1"
|
|
# unable to recognize "./cluster/base/flux-system": no matches for kind "GitRepository" in version "source.toolkit.fluxcd.io/v1beta1"
|
|
# unable to recognize "./cluster/base/flux-system": no matches for kind "HelmRepository" in version "source.toolkit.fluxcd.io/v1beta1"
|
|
# unable to recognize "./cluster/base/flux-system": no matches for kind "HelmRepository" in version "source.toolkit.fluxcd.io/v1beta1"
|
|
# unable to recognize "./cluster/base/flux-system": no matches for kind "HelmRepository" in version "source.toolkit.fluxcd.io/v1beta1"
|
|
# unable to recognize "./cluster/base/flux-system": no matches for kind "HelmRepository" in version "source.toolkit.fluxcd.io/v1beta1"
|
|
```
|
|
|
|
8. Verify Flux components are running in the cluster
|
|
|
|
```sh
|
|
kubectl --kubeconfig=./provision/kubeconfig get pods -n flux-system
|
|
# NAME READY STATUS RESTARTS AGE
|
|
# helm-controller-5bbd94c75-89sb4 1/1 Running 0 1h
|
|
# kustomize-controller-7b67b6b77d-nqc67 1/1 Running 0 1h
|
|
# notification-controller-7c46575844-k4bvr 1/1 Running 0 1h
|
|
# source-controller-7d6875bcb4-zqw9f 1/1 Running 0 1h
|
|
```
|
|
|
|
🎉 **Congratulations** if all goes smooth you'll have a Kubernetes cluster managed by Flux, your Git repository is driving the state of your cluster.
|
|
|
|
Now it's time to pause and go get some coffee ☕ because next is describing how DNS is handled. 🧠
|
|
|
|
## 📣 Post installation
|
|
|
|
### 🌐 DNS
|
|
|
|
📍 The `external-dns` application created in the `networking` namespace will handle creating public DNS records. By default, `echo-server` is the only public domain exposed on your Cloudflare domain. In order to make additional applications public you must set an ingress annotation like in the `HelmRelease` for `echo-server`. You do not need to use Terraform to create additional DNS records unless you need a record outside the purposes of your Kubernetes cluster (e.g. setting up MX records).
|
|
|
|
`k8s_gateway` is deployed on the IP choosen for `${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}`. Inorder to test DNS you can point your clients DNS to the `${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}` IP address and load `https://hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` in your browser.
|
|
|
|
You can also try debugging with the command `dig`, e.g. `dig @${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR} hajimari.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` and you should get a valid answer containing your `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP address.
|
|
|
|
If your router (or Pi-Hole, Adguard Home or whatever) supports conditional DNS forwarding (also know as split-horizon DNS) you may have DNS requests for `${SECRET_DOMAIN}` only point to the `${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}` IP address. This will ensure only DNS requests for `${SECRET_DOMAIN}` will only get routed to your `k8s_gateway` service thus providing DNS resolution to your cluster applications/ingresses.
|
|
|
|
To access services from the outside world port forwarded `80` and `443` in your router to the `${BOOTSTRAP_METALLB_TRAEFIK_ADDR}` IP, in a few moments head over to your browser and you _should_ be able to access `https://echo-server.${BOOTSTRAP_CLOUDFLARE_DOMAIN}` from a device outside your LAN.
|
|
|
|
Now if nothing is working, that is expected. This is DNS after all!
|
|
|
|
### 👉 Troubleshooting
|
|
|
|
Our [wiki](https://github.com/k8s-at-home/template-cluster-k3s/wiki) (WIP, contributions welcome) is a good place to start troubleshooting issues. If that doesn't cover your issue, come join and say Hi in our [Discord](https://discord.gg/k8s-at-home) server by starting a new thread in the #kubernetes support channel.
|
|
|
|
You may also open a issue on this GitHub repo or open a [discussion on GitHub](https://github.com/k8s-at-home/organization/discussions).
|
|
|
|
### 🤖 Integrations
|
|
|
|
Our Check out our [wiki](https://github.com/k8s-at-home/template-cluster-k3s/wiki) (WIP, contributions welcome) for more integrations!
|
|
|
|
## ❔ What's next
|
|
|
|
The world is your cluster and the first thing you might want to do is to have storage backed by something other than local disk.
|
|
|
|
In no particular order, here are some popular storage related items you could install and use in your cluster:
|
|
|
|
* [rook-ceph](https://github.com/rook/rook)
|
|
* [nfs-subdir-external-provisioner](https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner)
|
|
* [democratic-csi](https://github.com/democratic-csi/democratic-csi)
|
|
* [csi-driver-nfs](https://github.com/kubernetes-csi/csi-driver-nfs)
|
|
* [longhorn](https://github.com/longhorn/longhorn)
|
|
|
|
Community member @Whazor created [this website](https://whazor.github.io/k8s-at-home-search/) as a means to search Helm Releases across GitHub. You may use it as a means to get ideas on how to configure an applications' Helm values.
|
|
|
|
Many people have shared their awesome repositories over at [awesome-home-kubernetes](https://github.com/k8s-at-home/awesome-home-kubernetes).
|
|
|
|
## 🤝 Thanks
|
|
|
|
Big shout out to all the authors and contributors to the projects that we are using in this repository.
|