feat: Add HA Kubernetes cluster with Terraform + Ansible
Some checks failed
Terraform / Validate (push) Failing after 17s
Terraform / Plan (push) Has been skipped
Terraform / Apply (push) Has been skipped

- 3x CX23 control plane nodes (HA)
- 4x CX33 worker nodes
- k3s with embedded etcd
- Hetzner CCM for load balancers
- Gitea CI/CD workflows
- Backblaze B2 for Terraform state
This commit is contained in:
2026-02-28 20:24:55 +00:00
parent 3e8eb072b5
commit 3b3084b997
27 changed files with 1324 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
---
hcloud_token: ""
cluster_name: "k8s-cluster"

View File

@@ -0,0 +1,40 @@
---
- name: Check if Hetzner CCM is already deployed
command: kubectl get namespace hetzner-cloud-system
register: ccm_namespace
failed_when: false
changed_when: false
- name: Create Hetzner CCM namespace
command: kubectl create namespace hetzner-cloud-system
when: ccm_namespace.rc != 0
changed_when: true
- name: Create Hetzner cloud secret
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: hcloud
namespace: hetzner-cloud-system
stringData:
token: "{{ hcloud_token }}"
network: "{{ cluster_name }}-network"
no_log: true
when: hcloud_token is defined
- name: Deploy Hetzner CCM
kubernetes.core.k8s:
state: present
src: "{{ item }}"
loop:
- https://raw.githubusercontent.com/hetznercloud/hcloud-cloud-controller-manager/main/deploy/ccm-networks.yaml
when: ccm_namespace.rc != 0
- name: Wait for CCM pods to be ready
command: kubectl rollout status deployment/hcloud-cloud-controller-manager -n hetzner-cloud-system
changed_when: false
retries: 30
delay: 10

View File

@@ -0,0 +1,2 @@
---
common_upgrade_packages: false

View File

@@ -0,0 +1,58 @@
---
- name: Update apt cache
apt:
update_cache: true
cache_valid_time: 3600
- name: Upgrade packages
apt:
upgrade: dist
when: common_upgrade_packages | default(false)
- name: Install required packages
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- software-properties-common
- jq
- htop
- vim
state: present
- name: Disable swap
command: swapoff -a
changed_when: true
- name: Remove swap from fstab
mount:
name: swap
fstype: swap
state: absent
- name: Load br_netfilter module
modprobe:
name: br_netfilter
state: present
- name: Persist br_netfilter module
copy:
dest: /etc/modules-load.d/k8s.conf
content: |
br_netfilter
overlay
mode: "0644"
- name: Configure sysctl for Kubernetes
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
reload: true
loop:
- { name: net.bridge.bridge-nf-call-iptables, value: 1 }
- { name: net.bridge.bridge-nf-call-ip6tables, value: 1 }
- { name: net.ipv4.ip_forward, value: 1 }

View File

@@ -0,0 +1,4 @@
---
k3s_version: latest
k3s_server_url: ""
k3s_token: ""

View File

@@ -0,0 +1,30 @@
---
- name: Check if k3s agent is already installed
stat:
path: /usr/local/bin/k3s-agent
register: k3s_agent_binary
- name: Download k3s install script
get_url:
url: https://get.k3s.io
dest: /tmp/install-k3s.sh
mode: "0755"
when: not k3s_agent_binary.stat.exists
- name: Install k3s agent
environment:
INSTALL_K3S_VERSION: "{{ k3s_version if k3s_version != 'latest' else '' }}"
K3S_URL: "{{ k3s_server_url }}"
K3S_TOKEN: "{{ k3s_token }}"
command: /tmp/install-k3s.sh agent
args:
creates: /usr/local/bin/k3s-agent
when: not k3s_agent_binary.stat.exists
- name: Wait for k3s agent to be ready
command: systemctl is-active k3s-agent
register: agent_status
until: agent_status.stdout == "active"
retries: 30
delay: 10
changed_when: false

View File

@@ -0,0 +1,3 @@
---
k3s_version: latest
k3s_token: ""

View File

@@ -0,0 +1,56 @@
---
- name: Check if k3s is already installed
stat:
path: /usr/local/bin/k3s
register: k3s_binary
- name: Download k3s install script
get_url:
url: https://get.k3s.io
dest: /tmp/install-k3s.sh
mode: "0755"
when: not k3s_binary.stat.exists
- name: Install k3s server (primary)
environment:
INSTALL_K3S_VERSION: "{{ k3s_version if k3s_version != 'latest' else '' }}"
K3S_TOKEN: "{{ k3s_token }}"
command: /tmp/install-k3s.sh server --cluster-init
args:
creates: /usr/local/bin/k3s
when:
- not k3s_binary.stat.exists
- k3s_primary | default(false)
- name: Install k3s server (secondary)
environment:
INSTALL_K3S_VERSION: "{{ k3s_version if k3s_version != 'latest' else '' }}"
K3S_TOKEN: "{{ k3s_token }}"
command: /tmp/install-k3s.sh server --server https://{{ k3s_primary_ip }}:6443
args:
creates: /usr/local/bin/k3s
when:
- not k3s_binary.stat.exists
- not (k3s_primary | default(false))
- name: Wait for k3s to be ready
command: kubectl get nodes
register: k3s_ready
until: k3s_ready.rc == 0
retries: 30
delay: 10
changed_when: false
- name: Copy kubeconfig to default location for root
file:
src: /etc/rancher/k3s/k3s.yaml
dest: /root/.kube/config
state: link
force: true
- name: Ensure .kube directory exists for ansible user
file:
path: "/home/{{ ansible_user }}/.kube"
state: directory
mode: "0755"
when: ansible_user != 'root'