Compare commits

..

83 Commits

Author SHA1 Message Date
JamesAllenby 0c1c1a24ee fix: use import instead of include 2026-08-14 23:15:32 +01:00
JamesAllenby 9a5c8288b0 feat: add init target to justfile 2026-08-14 23:10:25 +01:00
JamesAllenby f946f0f2bf fix: ensure kolibri user is system user 2026-08-14 22:21:16 +01:00
JamesAllenby 80d48e6f46 feat: add power and performance playbook 2026-08-14 22:21:03 +01:00
JamesAllenby 8dc6ac2939 feat: add ansible galaxy path 2026-08-14 22:19:18 +01:00
JamesAllenby 7f80884082 feat: add target for fetching ansible dependencies 2026-08-14 22:18:48 +01:00
JamesAllenby 9abb0b010e feat: add variable master and worker counts 2026-08-14 22:08:45 +01:00
JamesAllenby aa0e9f37bf fix: add cluster name to incus cluster 2026-08-09 20:50:02 +01:00
JamesAllenby 5613699540 fix: add roles path to ansible config 2026-08-09 20:49:40 +01:00
JamesAllenby 03dc10c0e5 chore: set minimum tofu version to 1.11.5 2026-07-28 16:47:14 +01:00
JamesAllenby 573afc50a4 doc: readme update 2026-07-28 16:37:33 +01:00
JamesAllenby 6dab1dccd9 chore: rename bare metal to hive 2026-07-28 16:37:20 +01:00
JamesAllenby ce16338203 doc: add quick start guide 2026-07-05 23:02:30 +01:00
JamesAllenby 591541215b fix: down recipe destroys entire tofu instance 2026-07-05 23:00:12 +01:00
JamesAllenby adccf7af22 style: format opentofu code 2026-07-05 22:54:07 +01:00
JamesAllenby 87e90a2327 ci: add yamlfmt config 2026-07-05 22:53:35 +01:00
JamesAllenby 9e0f9697a8 refactor: remove unused load balancer ranges 2026-07-05 22:38:01 +01:00
JamesAllenby 819add7725 refactor: remove longhorn 2026-07-05 22:32:12 +01:00
JamesAllenby 8c665eeff4 refactor: remove metrics-server 2026-07-05 22:31:56 +01:00
JamesAllenby b3279dd23c refactor: remove cilium 2026-07-05 22:31:32 +01:00
JamesAllenby 0a4c621c57 refactor: remove cert-manager 2026-07-05 22:24:18 +01:00
JamesAllenby 3ecf328d2a refactor: remove authentik 2026-07-05 22:23:50 +01:00
JamesAllenby 390e2b29f5 refactor: remove keepalived 2026-07-05 22:23:36 +01:00
JamesAllenby 259abad851 refactor: remove prometheus 2026-07-05 22:21:00 +01:00
JamesAllenby b7a7ed5632 refactor: remove traefik 2026-07-05 22:20:41 +01:00
JamesAllenby 6f62bf2a1b refactor: disable kube-vip svc load balancer 2026-07-05 22:19:34 +01:00
JamesAllenby 1efa0f9426 feat: add prometheus role 2026-07-05 22:19:07 +01:00
JamesAllenby 81bf536ac3 feat: add cert-manager role 2026-07-05 22:18:55 +01:00
JamesAllenby 02af027e43 feat: add authentik role 2026-07-05 22:18:43 +01:00
JamesAllenby dd8e1e3fc9 refactor: remove longhorn and traefik 2026-07-05 22:18:12 +01:00
JamesAllenby 5e11ed8fdc feat: add traefik role 2026-07-01 00:51:20 +01:00
JamesAllenby bcc7922f46 refactor: add l2 announcements and lb ipam to cilium 2026-07-01 00:50:52 +01:00
JamesAllenby 89f28c2738 feat: add longhorn role to site.yaml 2026-07-01 00:23:22 +01:00
JamesAllenby c27709a0ef fix: enable insecure tls for metrics server 2026-07-01 00:23:11 +01:00
JamesAllenby 9fc67c61c0 feat: add metrics server install to site.yaml 2026-07-01 00:10:27 +01:00
JamesAllenby 5f7848a198 feat: enable hubble for cilium 2026-07-01 00:10:17 +01:00
JamesAllenby 4a7030f7ac refactor: rename to kubernetes_metrics_server 2026-07-01 00:10:07 +01:00
JamesAllenby d733c45323 feat: add cluster pod subnet and service subnet for incus 2026-06-30 23:58:07 +01:00
JamesAllenby 20f8648d86 feat: add untaint control plane nodes task 2026-06-30 23:57:52 +01:00
JamesAllenby 74bfab7bd8 fix: clear cni config in reset playbook 2026-06-30 23:41:50 +01:00
JamesAllenby 71edc8d0b7 feat: update site playbook and add cluster network vars 2026-06-30 23:41:27 +01:00
JamesAllenby 3699aac3ae feat: add helm and kubectl to kubernetes_common 2026-06-30 23:40:52 +01:00
JamesAllenby 8ae456bda7 feat: add longhorn role 2026-06-30 23:40:18 +01:00
JamesAllenby 363e218033 feat: add cilium cni role 2026-06-30 23:39:55 +01:00
JamesAllenby 068659e174 refactor: split control plane role into init/join/copy tasks 2026-06-30 23:39:34 +01:00
JamesAllenby 90686995d6 feat: add recipe for launching headlamp 2026-06-30 21:54:40 +01:00
JamesAllenby b18af66f03 fix: update just recipe 2026-06-30 00:28:28 +01:00
JamesAllenby 010f0657b5 feat: update control plane role 2026-06-30 00:25:44 +01:00
JamesAllenby 6a127588f0 fix: use correct permission for static pod manifest 2026-06-30 00:25:14 +01:00
JamesAllenby 248ccb48fe feat: add task for copying admin configurations 2026-06-30 00:25:01 +01:00
JamesAllenby 1f13980b53 feat: install open-iscsi for longhorn 2026-06-30 00:24:43 +01:00
JamesAllenby ff35802ba5 feat: add containerd service task 2026-06-30 00:24:28 +01:00
JamesAllenby b82155193e feat: add reset playbook 2026-06-30 00:23:56 +01:00
JamesAllenby 474a296b60 refactor: remove unused playbooks 2026-06-30 00:23:05 +01:00
JamesAllenby 34812b901c refactor: rename provisioning playbook 2026-06-30 00:22:50 +01:00
JamesAllenby e6d48c6802 feat: set ansible output format to yaml 2026-06-29 22:07:55 +01:00
JamesAllenby 1592c8290d fix: allow metrics-server to work on insecure tls 2026-06-29 01:43:28 +01:00
JamesAllenby 2c0e7245b6 fix: remove taint from control-plane nodes 2026-06-29 01:43:01 +01:00
JamesAllenby d4e7d2ca0e feat: add metric server helm release 2026-06-29 01:29:40 +01:00
JamesAllenby 293df1cfbe feat: install cilium cni 2026-06-29 01:17:38 +01:00
JamesAllenby 8d821d643c fix: add new recipe for destroying instances only 2026-06-29 00:26:44 +01:00
JamesAllenby 480bdf3fe2 doc: add first readme 2026-06-29 00:21:50 +01:00
JamesAllenby 0d079ee672 feat: add keepalived (unused) 2026-06-29 00:21:40 +01:00
JamesAllenby 82b3e5f9dd feat: add kube-vip to control plane role 2026-06-29 00:21:22 +01:00
JamesAllenby 0d5e4f8949 feat: add kube-vip role 2026-06-29 00:18:30 +01:00
JamesAllenby f7865c59cf feat: add correct interface for each inventory 2026-06-29 00:17:57 +01:00
JamesAllenby fbdb8346b6 fix: add become to site playbook 2026-06-29 00:17:31 +01:00
JamesAllenby 23a7020549 fix: use octal string notation 2026-06-28 23:21:33 +01:00
JamesAllenby ccaa1d180c feat: add bare-metal hosts 2026-06-28 23:21:23 +01:00
JamesAllenby b17ab6c8bb feat: provision kolibri automation user 2026-06-28 23:20:59 +01:00
JamesAllenby 9cb2cb5bd2 feat: add ansible.cfg 2026-06-28 20:32:18 +01:00
JamesAllenby 966a0d1e09 feat: add fetch-plugins recipe for proton pass 2026-06-28 19:17:31 +01:00
JamesAllenby e509fd8225 feat: increase master count to 3 2026-06-27 13:06:42 +01:00
JamesAllenby 470fd15b56 feat: initialise k8s cluster 2026-06-27 13:06:34 +01:00
JamesAllenby a1d9ea1a67 feat: remove handler 2026-06-27 00:45:50 +01:00
JamesAllenby c7f2bda277 fix: remove containerd service 2026-06-27 00:41:22 +01:00
JamesAllenby fb471b78a1 feat: remove network tasks 2026-06-27 00:40:40 +01:00
JamesAllenby 732cbf5d4d feat: update apt cache when repo installed 2026-06-27 00:39:58 +01:00
JamesAllenby efa85016cf refactor: move to kubernetes_common 2026-06-27 00:37:35 +01:00
JamesAllenby 872161a16a feat: ansible checkpoint 2026-06-27 00:35:03 +01:00
JamesAllenby 4e0b073a39 fix: rename to homelab for name length requirement 2026-06-26 23:46:47 +01:00
JamesAllenby 83abf1f978 fix: move profile to module 2026-06-26 23:44:30 +01:00
JamesAllenby fba3827961 feat: modularise incus vm 2026-06-26 23:31:14 +01:00
44 changed files with 608 additions and 406 deletions
+3
View File
@@ -2,8 +2,11 @@
.* .*
terraform.tfstate terraform.tfstate
terraform.tfstate.backup terraform.tfstate.backup
ansible/lookup_plugins
ansible/galaxy_roles
!.gitignore !.gitignore
!.editorconfig !.editorconfig
!.vscode !.vscode
!.pre-commit-config.yaml !.pre-commit-config.yaml
!.yamlfmt.yaml
+5
View File
@@ -0,0 +1,5 @@
formatter:
indent: 2
retain_line_breaks_single: true
scan_folded_as_literal: true
indentless_arrays: false
+19 -4
View File
@@ -1,24 +1,39 @@
#!/usr/bin/env just #!/usr/bin/env just
opentofu_dir := "infra/" opentofu_dir := "infra/"
ansible_proton_pass_plugin_ref := "4bd0741c347646060ec59c73f8adf7ed8e706cf3"
ansible_proton_pass_plugin_url := "https://raw.githubusercontent.com/protonpass/proton-pass-ansible-integration/" + ansible_proton_pass_plugin_ref + "/lookup_plugins/proton_pass.py"
init: tofu-init fetch-ansible-deps fetch-ansible-plugins
up: tofu-apply ansible-run-playbook up: tofu-apply ansible-run-playbook
down: tofu-destroy down: tofu-destroy
tofu-init: tofu-init:
tofu -chdir={{ opentofu_dir }} init -upgrade tofu -chdir={{ opentofu_dir }} init -upgrade
tofu-apply: tofu-init tofu-apply:
tofu -chdir={{ opentofu_dir }} apply -auto-approve tofu -chdir={{ opentofu_dir }} apply -auto-approve
tofu-destroy: tofu-init tofu-destroy:
tofu -chdir={{ opentofu_dir }} apply -auto-approve -destroy tofu -chdir={{ opentofu_dir }} apply -auto-approve -destroy
tofu-destroy-instances:
tofu -chdir={{ opentofu_dir }} apply -auto-approve -destroy -target=module.master -target=module.worker
ansible-run-playbook: ansible-run-playbook:
ansible-playbook -i ansible/inventories/incus/incus.yaml -c community.general.incus ansible/playbooks/provisioning.yaml -e ansible_user=root
ansible-playbook -i ansible/inventories/incus/incus.yaml -c community.general.incus ansible/site.yaml ansible-playbook -i ansible/inventories/incus/incus.yaml -c community.general.incus ansible/site.yaml
get-admin-conf: headlamp:
incus exec master-0 -- cat /etc/kubernetes/admin.conf > k8s-admin.conf incus exec master-0 -- cat /etc/kubernetes/admin.conf > .incus-admin.conf
flatpak run io.kinvolk.Headlamp --kubeconfig "$(pwd)/.incus-admin.conf"
clean: tofu-destroy clean: tofu-destroy
git clean -fdx git clean -fdx
fetch-ansible-deps:
ansible-galaxy install --role-file ansible/requirements.yml --roles-path ansible/galaxy_roles
fetch-ansible-plugins:
mkdir -p ansible/lookup_plugins
curl -fsSL -o ansible/lookup_plugins/proton_pass.py {{ ansible_proton_pass_plugin_url }}
+67
View File
@@ -0,0 +1,67 @@
# Home Infrastructure
An Ansible + OpenTofu project for installing a minimal Kubernetes installation
to bare-metal and virtual machines.
## Quick Start
Get started with a local Kubernetes cluster which can be used to design and test
new configuration before pushing them to real machines.
1. Login with Proton Pass and start the SSH daemon.
```shell
pass-cli login
pass-cli ssh-agent daemon start
export SSH_AUTH_SOCK="$HOME/.ssh/proton-pass-agent.sock"
```
2. Install the Proton Pass plugin for Ansible
```shell
just fetch-plugins
```
3. Launch Incus virtual machines
```shell
just up
```
4. Tear down the Incus virtual machines
```shell
just down
```
## Setup
```shell
just fetch-plugins
```
## Bare-metal provisioning
When creating a set of fresh machines, you must run the provisioning step to create the "Kolibri" automation user.
The Kolibri automation user is used for all other playbooks and has permissions to execute passwordless sudo.
```shell
ansible-playbook -i ansible/inventories/bare_metal/hosts.yaml ansible/playbooks/provision.yaml -K -e ansible_user=<your_interactive_user>
```
## Local Testing with Incus
```shell
just up
```
```shell
just headlamp
```
> The above command runs the Flatpak version of Headlamp which may not have permissions
> to access the Kubernetes config file copied from the instance.
>
> You should run `flatpak override --user --filesystem=$(pwd) io.kinvolk.Headlamp`
> to ensure that Headlamp can access the directory contents.
+4
View File
@@ -0,0 +1,4 @@
[defaults]
roles_path = ansible/roles:ansible/galaxy_roles
lookup_plugins = ansible/lookup_plugins
callback_result_format = yaml
-1
View File
@@ -1 +0,0 @@
ansible_python_interpreter: /usr/bin/python3
@@ -0,0 +1,8 @@
ansible_python_interpreter: /usr/bin/python3
ansible_become_exe: sudo.ws
ansible_user: kolibri
vip_interface: eno1
vip_address: 10.0.0.200
cluster_name: hive
cluster_pod_subnet: 172.16.0.0/16
cluster_service_subnet: 10.96.0.0/16
+8
View File
@@ -0,0 +1,8 @@
masters:
hosts:
vineta:
ansible_host: vineta.local
rotfront:
ansible_host: rotfront.local
leng:
ansible_host: leng.local
@@ -0,0 +1,7 @@
ansible_python_interpreter: /usr/bin/python3
ansible_user: kolibri
vip_interface: enp5s0
vip_address: 10.150.0.100
cluster_name: hive
cluster_pod_subnet: 172.16.0.0/16
cluster_service_subnet: 10.96.0.0/16
+1 -1
View File
@@ -1,7 +1,7 @@
plugin: community.general.incus plugin: community.general.incus
strict: true strict: true
remotes: remotes:
- local:home-infrastructure - local:homelab
groups: groups:
masters: "'master' in inventory_hostname" masters: "'master' in inventory_hostname"
workers: "'worker' in inventory_hostname" workers: "'worker' in inventory_hostname"
-137
View File
@@ -1,137 +0,0 @@
- name: Primary Master Initialisation
hosts: masters[0]
tasks:
- name: Initialise high-availability Kubernetes cluster
ansible.builtin.command: >-
kubeadm init --pod-network-cidr="172.16.0.0/16" --control-plane-endpoint="{{ ansible_default_ipv4.address }}:6443" --upload-certs
args:
creates: /etc/kubernetes/admin.conf
- name: Copy Kubernetes Admin Config
ansible.builtin.command: "{{ item }}"
loop:
- mkdir -p $HOME/.kube
- cp /etc/kubernetes/admin.conf $HOME/.kube/config
- chown $(id -u):$(id -g) $HOME/.kube/config
args:
creates: $HOME/.kube/config
- name: Install Pod Network (Calico)
ansible.builtin.shell: kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/calico.yaml > pod_network_setup.txt
args:
chdir: $HOME
creates: pod_network_setup.txt
- name: Add Helm repositories
kubernetes.core.helm_repository:
name: "{{ item.name }}"
repo_url: "{{ item.url }}"
loop:
- name: metrics-server
url: https://kubernetes-sigs.github.io/metrics-server/
- name: prometheus-community
url: https://prometheus-community.github.io/helm-charts
- name: longhorn
url: https://charts.longhorn.io
- name: traefik
url: https://traefik.github.io/charts
- name: minecraft-server-charts
url: https://itzg.github.io/minecraft-server-charts/
- name: Install Metrics Server
kubernetes.core.helm:
release_name: metrics-server
release_namespace: kube-system
chart_ref: metrics-server/metrics-server
chart_version: 3.13.0
values:
args:
- --kubelet-insecure-tls
- name: Install Longhorn
kubernetes.core.helm:
release_name: longhorn
release_namespace: longhorn-system
create_namespace: true
chart_ref: longhorn/longhorn
chart_version: 1.12.0
- name: Install Prometheus
kubernetes.core.helm:
release_name: prometheus
release_namespace: monitoring
create_namespace: true
chart_ref: prometheus-community/prometheus
chart_version: 29.10.0
state: absent
- name: Install Traefik
kubernetes.core.helm:
release_name: traefik
release_namespace: default
create_namespace: true
chart_ref: traefik/traefik
chart_version: 40.2.0
values:
deployment:
kind: DaemonSet
securityContext:
capabilities:
drop: [ALL]
add: [NET_BIND_SERVICE]
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
ports:
web:
port: 80
containerPort: 80
hostPort: 80
websecure:
port: 443
containerPort: 443
hostPort: 443
- name: Install Minecraft
kubernetes.core.helm:
release_name: minecraft
release_namespace: default
chart_ref: minecraft-server-charts/minecraft
chart_version: 5.1.3
values:
minecraftServer:
eula: true
persistence:
dataDir:
enabled: true
- name: Extract Control Plane Decryption Key & Join Token
block:
- name: Generate Fresh Join Command
ansible.builtin.command: kubeadm token create --print-join-command
register: join_command_raw
- name: Upload Certs and Capture Certificate Key
shell: kubeadm init phase upload-certs --upload-certs | tail -n 1
register: cert_key_raw
- name: Set facts across playbooks
ansible.builtin.set_fact:
k8s_join_base: "{{ join_command_raw.stdout }}"
k8s_cert_key: "{{ cert_key_raw.stdout }}"
delegate_to: localhost
delegate_facts: true
- name: Join Secondary Control Planes
hosts: masters:!masters[0] # <--- Targets masters 1 and 2
tasks:
- name: Join Cluster as Control Plane Node
ansible.builtin.shell: "{{ hostvars['localhost']['k8s_join_base'] }} --control-plane --certificate-key {{ hostvars['localhost']['k8s_cert_key'] }}"
args:
creates: /etc/kubernetes/admin.conf
- name: Copy Kubernetes Admin Config
ansible.builtin.shell: "{{ item }}"
loop:
- mkdir -p $HOME/.kube
- cp /etc/kubernetes/admin.conf $HOME/.kube/config
- chown $(id -u):$(id -g) $HOME/.kube/config
args:
creates: $HOME/.kube/config
- name: Remove control plane taint
hosts: masters[0]
tasks:
- name: Remove control plane NoSchedule taint from all nodes
kubernetes.core.k8s_taint:
state: absent
name: "{{ item }}"
taints:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
loop: "{{ groups['masters'] }}"
+7
View File
@@ -0,0 +1,7 @@
---
- name: Configure performance tuning
hosts: all
become: true
roles:
- giovtorres.tuned
- irqbalance
+25
View File
@@ -0,0 +1,25 @@
- name: Create Kolibri automation user
hosts: all
become: true
tasks:
- name: Ensure Kolibri user exists
ansible.builtin.user:
name: kolibri
comment: Hummingbird
create_home: true
groups:
- sudo
shell: /bin/bash
system: true
- name: Ensure authorised keys for Kolibri
ansible.posix.authorized_key:
user: kolibri
key: "{{ lookup('proton_pass', vault_name='Home Lab', item_title='Kolibri Automation', field='Public key') }}"
- name: Allow passwordless sudo for Kolibri
ansible.builtin.copy:
content: "kolibri ALL=(ALL) NOPASSWD: ALL\n"
dest: /etc/sudoers.d/kolibri
mode: "0440"
validate: visudo -cf %s
+13
View File
@@ -0,0 +1,13 @@
- name: Reset nodes
hosts: all
become: true
tasks:
- name: Reset kubeadm
ansible.builtin.command: kubeadm reset -f
args:
removes: /etc/kubernetes/*.conf
- name: Clear CNI configuration
ansible.builtin.file:
path: /etc/cni/net.d
state: absent
-78
View File
@@ -1,78 +0,0 @@
- name: Initialise Master and Worker Nodes
hosts: all
tasks:
- name: Enable IPv4 forwarding
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: 1
sysctl_set: true
- name: Enable Kernel Modules
community.general.modprobe:
name: "{{ item }}"
persistent: present
loop:
- br_netfilter
- dm-crypt
- name: Ensure /sys mount is set to shared for container runtimes
block:
- name: Remount /sys as shared immediately
ansible.builtin.command: mount --make-rshared {{ item }}
loop:
- /
- /sys
- /run
changed_when: false
- name: Create Alpine local.d script for permanent shared mount
ansible.builtin.copy:
dest: /etc/local.d/mount-shared.start
content: |
#!/bin/sh
mount --make-rshared /
mount --make-rshared /sys
mount --make-rshared /run
owner: root
group: root
mode: '0755'
- name: Ensure Alpine local service is enabled on boot
ansible.builtin.service:
name: local
enabled: true
runlevel: default
- name: Install tools
ansible.builtin.package:
name: "{{ item }}"
state: present
loop:
- htop
- name: Install kubectl, kubelet, kubeadm and containerd
ansible.builtin.package:
name: "{{ item }}"
state: present
loop:
- kubectl
- kubeadm
- kubelet
- containerd
- cni-plugins
- helm
- open-iscsi
- name: Start kubelet and containerd
ansible.builtin.service:
name: "{{ item }}"
enabled: true
state: started
loop:
- kubelet
- containerd
- iscsid
- name: Add /opt/cni/bin to containerd CNI binary directories
ansible.builtin.replace:
path: /etc/containerd/config.toml
regexp: "bin_dirs = \\['/usr/libexec/cni'\\]"
replace: "bin_dirs = ['/opt/cni/bin', '/usr/libexec/cni']"
notify: Restart containerd
handlers:
- name: Restart containerd
ansible.builtin.service:
name: containerd
state: restarted
-22
View File
@@ -1,22 +0,0 @@
- name: Extract Worker Join Token from Primary Master
hosts: masters[0] # <--- Targets only the first master safely
gather_facts: false
tasks:
- name: Generate worker join command
ansible.builtin.command: kubeadm token create --print-join-command
register: join_command_raw
changed_when: false # Reading/generating a token text string changes no host state
- name: Save join command globally
ansible.builtin.set_fact:
k8s_worker_join: "{{ join_command_raw.stdout }}"
delegate_to: localhost
delegate_facts: true
- name: Join Worker Nodes to Cluster
hosts: workers
gather_facts: false
tasks:
- name: Join cluster
ansible.builtin.shell: "{{ hostvars['localhost']['k8s_worker_join'] }} >> node_joined.txt"
args:
chdir: $HOME
creates: node_joined.txt
+6
View File
@@ -0,0 +1,6 @@
---
collections:
- ansible.posix
roles:
- name: giovtorres.tuned
version: 2.0.2
+7
View File
@@ -0,0 +1,7 @@
<service-group>
<name replace-wildcards="yes">%h</name>
<service>
<type>_ssh._tcp</type>
<port>22</port>
</service>
</service-group>
+4
View File
@@ -0,0 +1,4 @@
- name: Restart Avahi Daemon
ansible.builtin.service:
name: avahi-daemon
state: restarted
+19
View File
@@ -0,0 +1,19 @@
- name: Install Avahi Daemon
ansible.builtin.apt:
pkg:
- avahi-daemon
state: present
- name: Enable Avahi Daemon Service
ansible.builtin.service:
name: avahi-daemon
state: started
enabled: true
- name: Copy Avahi SSH service configuration
ansible.builtin.copy:
src: ssh.service
dest: /etc/avahi/services/ssh.service
mode: "0644"
notify:
- Restart Avahi Daemon
+6
View File
@@ -1,2 +1,8 @@
- name: Install required packages - name: Install required packages
ansible.builtin.import_tasks: packages.yaml ansible.builtin.import_tasks: packages.yaml
- name: Setup networking configuration
ansible.builtin.import_tasks: network.yaml
- name: Setup Avahi Daemon
ansible.builtin.import_tasks: avahi.yaml
@@ -1,5 +1,5 @@
- name: Enable sysctl 'net.ipv4.ip_forward' - name: Enable IPv4 Forwarding
ansible.posix.sysctl: ansible.posix.sysctl:
name: net.ipv4.ip_forward name: net.ipv4.ip_forward
value: 1 value: "1"
sysctl_set: true sysctl_set: true
+2 -1
View File
@@ -1,5 +1,6 @@
- name: Install Python 3 Dependencies - name: Install Ansible Dependencies
ansible.builtin.apt: ansible.builtin.apt:
pkg: pkg:
- python3-debian - python3-debian
- python3-kubernetes
state: present state: present
+11
View File
@@ -0,0 +1,11 @@
---
- name: Install irqbalance
ansible.builtin.package:
name: irqbalance
state: present
- name: Ensure irqbalance service
ansible.builtin.service:
name: irqbalance
state: started
enabled: true
@@ -2,5 +2,5 @@
ansible.builtin.import_tasks: repo.yaml ansible.builtin.import_tasks: repo.yaml
- name: Install Kubernetes packages - name: Install Kubernetes packages
ansible.builtin.import_tasks: packages.yaml ansible.builtin.import_tasks: packages.yaml
- name: Configure networking - name: Ensure services running
ansible.builtin.import_tasks: networking.yaml ansible.builtin.import_tasks: services.yaml
@@ -4,9 +4,6 @@
- kubeadm - kubeadm
- kubelet - kubelet
- containerd - containerd
- kubectl
- helm
update_cache: true update_cache: true
- name: Activate containerd service
ansible.builtin.service:
name: containerd
enabled: true
state: started
@@ -0,0 +1,18 @@
- name: Add Kubernetes APT repository
ansible.builtin.deb822_repository:
name: kubernetes
types: deb
uris: https://pkgs.k8s.io/core:/stable:/v1.36/deb/
suites: /
signed_by: https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key
state: present
- name: Add Helm APT repository
ansible.builtin.deb822_repository:
name: helm
types: deb
uris: https://packages.buildkite.com/helm-linux/helm-debian/any/
suites: any
components: main
signed_by: https://packages.buildkite.com/helm-linux/helm-debian/gpgkey
state: present
@@ -0,0 +1,5 @@
- name: Ensure containerd is running
ansible.builtin.service:
name: containerd
state: started
enabled: true
@@ -0,0 +1,18 @@
- name: Setup Kubernetes admin config for root user
become: true
block:
- name: Ensure .kube directory
ansible.builtin.file:
path: "{{ ansible_facts['env']['HOME'] }}/.kube"
state: directory
owner: "{{ ansible_facts['user_id'] }}"
group: "{{ ansible_facts['user_id'] }}"
mode: "0755"
- name: Copy admin configuration
ansible.builtin.copy:
src: /etc/kubernetes/admin.conf
dest: "{{ ansible_facts['env']['HOME'] }}/.kube/config"
remote_src: true
owner: "{{ ansible_facts['user_id'] }}"
group: "{{ ansible_facts['user_id'] }}"
mode: "0600"
@@ -0,0 +1,21 @@
- name: Check if cluster is initialised
ansible.builtin.stat:
path: /etc/kubernetes/admin.conf
register: kubernetes_control_plane_admin_conf
run_once: true
- name: Initialise Kubernetes cluster
when: not kubernetes_control_plane_admin_conf.stat.exists
block:
- name: Copy kubeadm config
ansible.builtin.template:
src: kubeadm-config.yaml.j2
dest: /tmp/kubeadm-config.yaml
mode: "0600"
run_once: true
- name: Initialise Kubernetes with kubeadm
ansible.builtin.command: kubeadm init --config /tmp/kubeadm-config.yaml
args:
creates: /etc/kubernetes/admin.conf
run_once: true
@@ -0,0 +1,20 @@
- name: Generate join command
ansible.builtin.command: kubeadm token create --print-join-command --kubeconfig /etc/kubernetes/admin.conf
register: kubernetes_control_plane_join_command
run_once: true
changed_when: false
- name: Get certificate key
ansible.builtin.command: kubeadm init phase upload-certs --upload-certs --kubeconfig /etc/kubernetes/admin.conf
register: kubernetes_control_plane_certificate_key
run_once: true
changed_when: false
- name: Join additional control planes
ansible.builtin.command: >-
{{ kubernetes_control_plane_join_command.stdout }}
--control-plane
--certificate-key
{{ kubernetes_control_plane_certificate_key.stdout_lines[-1] }}
args:
creates: /etc/kubernetes/admin.conf
@@ -0,0 +1,5 @@
- name: Ensure kube-vip static pod manifest exists
ansible.builtin.template:
src: kube-vip.yaml.j2
dest: /etc/kubernetes/manifests/kube-vip.yaml
mode: "0600"
@@ -0,0 +1,14 @@
- name: Configure kube-vip static pod manifest
ansible.builtin.import_tasks: kube-vip.yaml
- name: Initialise Kubernetes cluster
ansible.builtin.import_tasks: init-cluster.yaml
- name: Join nodes as control planes
ansible.builtin.import_tasks: join-cluster.yaml
- name: Copy Kubernetes admin configuration on all nodes
ansible.builtin.import_tasks: copy-config.yaml
- name: Remove control plane scheduling restrictions
ansible.builtin.import_tasks: untaint.yaml
@@ -0,0 +1,7 @@
- name: Remove control plane NoSchedule taint
kubernetes.core.k8s_taint:
state: absent
name: "{{ ansible_facts['hostname'] }}"
taints:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
@@ -0,0 +1,70 @@
apiVersion: v1
kind: Pod
metadata:
name: kube-vip
namespace: kube-system
spec:
containers:
- args:
- manager
env:
- name: vip_arp
value: "true"
- name: port
value: "6443"
- name: vip_nodename
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: vip_interface
value: {{ vip_interface }}
- name: vip_subnet
value: "32"
- name: dns_mode
value: first
- name: dhcp_mode
value: ipv4
- name: cp_enable
value: "true"
- name: cp_namespace
value: kube-system
- name: svc_enable
value: "false"
- name: vip_leaderelection
value: "true"
- name: vip_leasename
value: plndr-cp-lock
- name: vip_leaseduration
value: "15"
- name: vip_renewdeadline
value: "10"
- name: vip_retryperiod
value: "2"
- name: address
value: {{ vip_address }}
- name: prometheus_server
value: :2112
image: ghcr.io/kube-vip/kube-vip:v1.2.1
imagePullPolicy: IfNotPresent
name: kube-vip
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
volumeMounts:
- mountPath: /etc/kubernetes/admin.conf
name: kubeconfig
hostAliases:
- hostnames:
- kubernetes
ip: 127.0.0.1
hostNetwork: true
volumes:
- hostPath:
path: /etc/kubernetes/admin.conf
name: kubeconfig
status: {}
@@ -0,0 +1,18 @@
# Configuration reference can be found at https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta4/
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
clusterName: "{{ cluster_name }}"
controlPlaneEndpoint: "{{ vip_address }}"
networking:
podSubnet: "{{ cluster_pod_subnet }}"
serviceSubnet: "{{ cluster_service_subnet }}"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failSwapOn: false
memorySwap:
swapBehavior: LimitedSwap
@@ -1,24 +0,0 @@
---
- name: Initialise high-availability Kubernetes cluster
ansible.builtin.command: >-
kubeadm init
--pod-network-cidr="172.16.0.0/16"
--control-plane-endpoint="{{ ansible_default_ipv4.address }}:6443"
--upload-certs
args:
creates: /etc/kubernetes/admin.conf
- name: Copy Kubernetes Admin Config
ansible.builtin.command: "{{ item }}"
loop:
- mkdir -p $HOME/.kube
- cp /etc/kubernetes/admin.conf $HOME/.kube/config
- chown $(id -u):$(id -g) $HOME/.kube/config
args:
creates: $HOME/.kube/config
- name: Install Pod Network (Calico)
ansible.builtin.shell: kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/calico.yaml > pod_network_setup.txt
args:
chdir: $HOME
creates: pod_network_setup.txt
@@ -1,8 +0,0 @@
- name: Add Kubernetes APT repository
ansible.builtin.deb822_repository:
name: kubernetes
types: deb
uris: https://pkgs.k8s.io/core:/stable:/v1.36/deb/
suites: /
signed_by: https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key
state: present
+10 -4
View File
@@ -1,9 +1,15 @@
- name: Bootstrap - name: Apply power and performance settings
ansible.builtin.import_playbook: playbooks/performance.yaml
- name: Baseline configuration
hosts: all hosts: all
become: true
roles: roles:
- common - common
- name: Setup Kubernetes masters - kubernetes_common
- name: Initialise Kubernetes Cluster
hosts: masters hosts: masters
become: true
roles: roles:
- kubernetes_node - kubernetes_control_plane
- kubernetes_master
+20 -111
View File
@@ -3,14 +3,10 @@
# ------ # ------
locals { locals {
image = "ubuntu/26.04"
root_disk_size = "16GiB" root_disk_size = "16GiB"
ext_disk_size = "16GiB" ext_disk_size = "16GiB"
network_cidr = "10.150.0.1/24" network_cidr = "10.150.0.1/24"
master_count = 1
worker_count = 1
image = "ubuntu/26.04"
} }
# --------- # ---------
@@ -18,7 +14,7 @@ locals {
# --------- # ---------
resource "incus_project" "this" { resource "incus_project" "this" {
name = "home-infrastructure" name = "homelab"
description = "An emulated home infrastructure stack." description = "An emulated home infrastructure stack."
} }
@@ -32,35 +28,9 @@ resource "incus_image" "this" {
} }
} }
resource "incus_profile" "this" {
name = "Default"
description = "The default profile applied to both masters and workers."
project = incus_project.this.name
depends_on = [incus_network.this]
config = {
"limits.cpu" = var.cpus
"limits.memory" = var.memory
"boot.autostart" = false
"security.secureboot" = false
}
device {
name = "root"
type = "disk"
properties = {
"pool" = "default"
"path" = "/"
"size" = local.root_disk_size
}
}
}
resource "incus_network" "this" { resource "incus_network" "this" {
name = "deskpi-cluster" name = "homelab"
description = "An emulated network for the DeskPi Super6C cluster project." description = "An emulated home infrastructure stack."
project = incus_project.this.name project = incus_project.this.name
type = "bridge" type = "bridge"
@@ -70,93 +40,32 @@ resource "incus_network" "this" {
} }
} }
resource "incus_instance" "master" { module "master" {
count = local.master_count source = "./modules/incus_vm"
count = var.master_count
name = "master-${count.index}" name = "master-${count.index}"
description = "Kubernetes Master ${count.index}"
project = incus_project.this.name project = incus_project.this.name
type = "virtual-machine"
image = incus_image.this.fingerprint image = incus_image.this.fingerprint
profiles = [incus_profile.this.name] cpu = 2
memory = "2GiB"
wait_for { network = incus_network.this.name
type = "agent" ipv4_address = cidrhost(local.network_cidr, 2 + (2 * count.index))
}
device {
name = "eth0"
type = "nic"
properties = {
"network" = incus_network.this.name
"ipv4.address" = cidrhost(local.network_cidr, 2 + (2 * count.index))
}
}
device {
name = "ext"
type = "disk"
properties = {
"pool" = "default"
"source" = incus_storage_volume.master[count.index].name
}
}
} }
resource "incus_storage_volume" "master" { module "worker" {
count = local.master_count source = "./modules/incus_vm"
count = var.worker_count
name = "master-ext-${count.index}"
description = "External drive for master ${count.index}"
pool = "default"
project = incus_project.this.name
content_type = "block"
config = {
"size" = local.ext_disk_size
}
}
resource "incus_instance" "worker" {
count = local.worker_count
name = "worker-${count.index}" name = "worker-${count.index}"
description = "Kubernetes Worker ${count.index}"
project = incus_project.this.name project = incus_project.this.name
type = "virtual-machine"
image = incus_image.this.fingerprint image = incus_image.this.fingerprint
profiles = [incus_profile.this.name] cpu = 2
memory = "2GiB"
wait_for { network = incus_network.this.name
type = "agent" ipv4_address = cidrhost(local.network_cidr, 3 + (2 * count.index))
}
device {
name = "eth0"
type = "nic"
properties = {
"network" = incus_network.this.name
"ipv4.address" = cidrhost(local.network_cidr, 3 + (2 * count.index))
}
}
}
# -------
# Outputs
# -------
output "master_addresses" {
value = incus_instance.master[*].ipv4_address
}
output "worker_addresses" {
value = incus_instance.worker[*].ipv4_address
} }
# --------- # ---------
@@ -164,12 +73,12 @@ output "worker_addresses" {
# --------- # ---------
terraform { terraform {
required_version = "~> 1" required_version = ">= 1.11.5"
required_providers { required_providers {
incus = { incus = {
source = "lxc/incus" source = "lxc/incus"
version = "~> 1" version = "~> 1.1"
} }
} }
} }
+79
View File
@@ -0,0 +1,79 @@
terraform {
required_providers {
incus = {
source = "lxc/incus"
version = ">= 1.1.1"
}
}
}
resource "incus_instance" "this" {
name = var.name
description = var.description
project = var.project
type = "virtual-machine"
image = var.image
profiles = concat([incus_profile.this.name], var.profiles)
wait_for {
type = "agent"
}
device {
name = "eth0"
type = "nic"
properties = {
"network" = var.network
"ipv4.address" = var.ipv4_address
}
}
device {
name = "ext"
type = "disk"
properties = {
"pool" = "default"
"source" = incus_storage_volume.this.name
}
}
}
resource "incus_storage_volume" "this" {
name = "${var.name}-ext"
description = "External disk for ${var.name}"
pool = "default"
project = var.project
content_type = "block"
config = {
"size" = "16GiB"
}
}
resource "incus_profile" "this" {
name = var.name
description = "Default profile for ${var.name}"
project = var.project
config = {
"limits.cpu" = var.cpu
"limits.memory" = var.memory
"boot.autostart" = false
"security.secureboot" = true
}
device {
name = "root"
type = "disk"
properties = {
"pool" = "default"
"path" = "/"
"size" = "16GiB"
}
}
}
View File
+55
View File
@@ -0,0 +1,55 @@
variable "name" {
type = string
description = "The name for this instance."
nullable = false
}
variable "description" {
type = string
description = "The description for this instance."
nullable = false
default = ""
}
variable "project" {
type = string
description = "The project this instance belongs to."
nullable = true
}
variable "image" {
type = string
description = "The image for this instance."
nullable = false
}
variable "profiles" {
type = list(string)
description = "The profiles for this instance."
nullable = false
default = []
}
variable "network" {
type = string
description = "The network for this instance."
nullable = false
}
variable "ipv4_address" {
type = string
description = "The IPv4 address for this instance."
nullable = false
}
variable "cpu" {
type = number
description = "The number of CPUs for this instance."
nullable = false
}
variable "memory" {
type = string
description = "The amount of memory allocated for this instance."
nullable = false
}
+14
View File
@@ -1,3 +1,17 @@
variable "master_count" {
description = "The number of master nodes to provision."
type = number
default = 1
nullable = false
}
variable "worker_count" {
description = "The number of worker nodes to provision."
type = number
default = 0
nullable = false
}
variable "cpus" { variable "cpus" {
description = "The number of CPU cores allocated to each virtual machine." description = "The number of CPU cores allocated to each virtual machine."
type = number type = number