Compare commits

..

1 Commits

Author SHA1 Message Date
JamesAllenby c132c24145 feat: add kubernetes roles 2026-06-23 23:28:40 +01:00
44 changed files with 423 additions and 617 deletions
-3
View File
@@ -2,11 +2,8 @@
.* .*
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
-8
View File
@@ -9,16 +9,8 @@ repos:
args: args:
- --autofix - --autofix
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/google/yamlfmt
rev: v0.21.0
hooks:
- id: yamlfmt
- repo: https://github.com/tofuutils/pre-commit-opentofu - repo: https://github.com/tofuutils/pre-commit-opentofu
rev: v2.4.2 rev: v2.4.2
hooks: hooks:
- id: tofu_validate - id: tofu_validate
- id: tofu_fmt - id: tofu_fmt
- repo: https://github.com/ansible/ansible-lint
rev: v26.4.0
hooks:
- id: ansible-lint
-5
View File
@@ -1,5 +0,0 @@
formatter:
indent: 2
retain_line_breaks_single: true
scan_folded_as_literal: true
indentless_arrays: false
+4 -19
View File
@@ -1,39 +1,24 @@
#!/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-apply: tofu-init
tofu -chdir={{ opentofu_dir }} apply -auto-approve tofu -chdir={{ opentofu_dir }} apply -auto-approve
tofu-destroy: tofu-destroy: tofu-init
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
headlamp: get-admin-conf:
incus exec master-0 -- cat /etc/kubernetes/admin.conf > .incus-admin.conf incus exec master-0 -- cat /etc/kubernetes/admin.conf > k8s-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
@@ -1,67 +0,0 @@
# 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
@@ -1,4 +0,0 @@
[defaults]
roles_path = ansible/roles:ansible/galaxy_roles
lookup_plugins = ansible/lookup_plugins
callback_result_format = yaml
+2
View File
@@ -0,0 +1,2 @@
---
ansible_python_interpreter: /usr/bin/python3
@@ -1,8 +0,0 @@
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
@@ -1,8 +0,0 @@
masters:
hosts:
vineta:
ansible_host: vineta.local
rotfront:
ansible_host: rotfront.local
leng:
ansible_host: leng.local
@@ -1,7 +0,0 @@
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
+3 -2
View File
@@ -1,7 +1,8 @@
---
plugin: community.general.incus plugin: community.general.incus
strict: true strict: true
remotes: remotes:
- local:homelab - local:home-infrastructure
groups: groups:
masters: "'master' in inventory_hostname" control_planes: "'master' in inventory_hostname"
workers: "'worker' in inventory_hostname" workers: "'worker' in inventory_hostname"
+154
View File
@@ -0,0 +1,154 @@
- 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
@@ -1,7 +0,0 @@
---
- name: Configure performance tuning
hosts: all
become: true
roles:
- giovtorres.tuned
- irqbalance
-25
View File
@@ -1,25 +0,0 @@
- 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
@@ -1,13 +0,0 @@
- 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
+87
View File
@@ -0,0 +1,87 @@
- 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
+24
View File
@@ -0,0 +1,24 @@
- 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
@@ -1,6 +0,0 @@
---
collections:
- ansible.posix
roles:
- name: giovtorres.tuned
version: 2.0.2
-7
View File
@@ -1,7 +0,0 @@
<service-group>
<name replace-wildcards="yes">%h</name>
<service>
<type>_ssh._tcp</type>
<port>22</port>
</service>
</service-group>
-4
View File
@@ -1,4 +0,0 @@
- name: Restart Avahi Daemon
ansible.builtin.service:
name: avahi-daemon
state: restarted
-19
View File
@@ -1,19 +0,0 @@
- 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
+1 -6
View File
@@ -1,8 +1,3 @@
---
- 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
+2 -2
View File
@@ -1,6 +1,6 @@
- name: Install Ansible Dependencies ---
- name: Install Python 3 Dependencies
ansible.builtin.apt: ansible.builtin.apt:
pkg: pkg:
- python3-debian - python3-debian
- python3-kubernetes
state: present state: present
-11
View File
@@ -1,11 +0,0 @@
---
- name: Install irqbalance
ansible.builtin.package:
name: irqbalance
state: present
- name: Ensure irqbalance service
ansible.builtin.service:
name: irqbalance
state: started
enabled: true
@@ -1,18 +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
- 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
@@ -1,5 +0,0 @@
- name: Ensure containerd is running
ansible.builtin.service:
name: containerd
state: started
enabled: true
@@ -1,18 +0,0 @@
- 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"
@@ -1,21 +0,0 @@
- 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
@@ -1,20 +0,0 @@
- 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
@@ -1,5 +0,0 @@
- 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"
@@ -1,14 +0,0 @@
- 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
@@ -1,7 +0,0 @@
- 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
@@ -1,70 +0,0 @@
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: {}
@@ -1,18 +0,0 @@
# 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,6 +1,9 @@
---
- name: Install Kubernetes APT repository - name: Install Kubernetes APT repository
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: Ensure services running
ansible.builtin.import_tasks: services.yaml - name: Configure networking
ansible.builtin.import_tasks: networking.yaml
@@ -1,5 +1,6 @@
- name: Enable IPv4 Forwarding ---
- name: Enable sysctl 'net.ipv4.ip_forward'
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
@@ -1,9 +1,14 @@
---
- name: Install Kubernetes packages - name: Install Kubernetes packages
ansible.builtin.apt: ansible.builtin.apt:
pkg: pkg:
- 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,9 @@
---
- 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
+5 -10
View File
@@ -1,15 +1,10 @@
- name: Apply power and performance settings ---
ansible.builtin.import_playbook: playbooks/performance.yaml - name: Bootstrap
- name: Baseline configuration
hosts: all hosts: all
become: true
roles: roles:
- common - common
- kubernetes_common
- name: Initialise Kubernetes Cluster - name: Setup Kubernetes nodes
hosts: masters hosts: all
become: true
roles: roles:
- kubernetes_control_plane - kubernetes_node
+117 -26
View File
@@ -3,10 +3,14 @@
# ------ # ------
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"
} }
# --------- # ---------
@@ -14,7 +18,7 @@ locals {
# --------- # ---------
resource "incus_project" "this" { resource "incus_project" "this" {
name = "homelab" name = "home-infrastructure"
description = "An emulated home infrastructure stack." description = "An emulated home infrastructure stack."
} }
@@ -28,9 +32,35 @@ 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 = "homelab" name = "deskpi-cluster"
description = "An emulated home infrastructure stack." description = "An emulated network for the DeskPi Super6C cluster project."
project = incus_project.this.name project = incus_project.this.name
type = "bridge" type = "bridge"
@@ -40,32 +70,93 @@ resource "incus_network" "this" {
} }
} }
module "master" { resource "incus_instance" "master" {
source = "./modules/incus_vm" count = local.master_count
count = var.master_count
name = "master-${count.index}" name = "master-${count.index}"
project = incus_project.this.name description = "Kubernetes Master ${count.index}"
image = incus_image.this.fingerprint project = incus_project.this.name
cpu = 2
memory = "2GiB"
network = incus_network.this.name type = "virtual-machine"
ipv4_address = cidrhost(local.network_cidr, 2 + (2 * count.index)) image = incus_image.this.fingerprint
profiles = [incus_profile.this.name]
wait_for {
type = "agent"
}
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
}
}
} }
module "worker" { resource "incus_storage_volume" "master" {
source = "./modules/incus_vm" count = local.master_count
count = var.worker_count
name = "worker-${count.index}" name = "master-ext-${count.index}"
project = incus_project.this.name description = "External drive for master ${count.index}"
image = incus_image.this.fingerprint pool = "default"
cpu = 2 project = incus_project.this.name
memory = "2GiB"
network = incus_network.this.name content_type = "block"
ipv4_address = cidrhost(local.network_cidr, 3 + (2 * count.index))
config = {
"size" = local.ext_disk_size
}
}
resource "incus_instance" "worker" {
count = local.worker_count
name = "worker-${count.index}"
description = "Kubernetes Worker ${count.index}"
project = incus_project.this.name
type = "virtual-machine"
image = incus_image.this.fingerprint
profiles = [incus_profile.this.name]
wait_for {
type = "agent"
}
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
} }
# --------- # ---------
@@ -73,12 +164,12 @@ module "worker" {
# --------- # ---------
terraform { terraform {
required_version = ">= 1.11.5" required_version = "~> 1"
required_providers { required_providers {
incus = { incus = {
source = "lxc/incus" source = "lxc/incus"
version = "~> 1.1" version = "~> 1"
} }
} }
} }
-79
View File
@@ -1,79 +0,0 @@
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
@@ -1,55 +0,0 @@
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,17 +1,3 @@
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