Create low price kubernetes for home pets
Using hetzner.com, server type cx32 4 vcpu, 8 Gb memory, 6.30/month euro.
Prepare server
Terraform configs
Main file
File main.tf
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.23.0"
}
}
}
# Configure the Hetzner Cloud Provider
provider "hcloud" {
token = var.hcloud_token
}
resource "hcloud_ssh_key" "ssh" {
name = "My ssh key"
public_key = file("~/.ssh/id_rsa.pub")
}
Locals
File locals.tf
# this file contains immutable constants
locals {
http_port = 80
https_port = 443
all_ips = ["0.0.0.0/0", "::/0"]
ssh_port = 3300
tcp_protocol = "tcp"
udp_protocol = "udp"
any_protocol = "-1"
icmp_protocol = "icmp"
vpn_traffic = "17100"
mail_smtp = 25
mail_imaps = 993
mail_smtps = 465
mail_smtptls = 587
network_cidr = "10.0.0.0/16"
network_subnet= "10.0.0.0/24"
network_zone = "eu-central"
image = "ubuntu-24.04"
server_type = "cx32"
home_ips = ["<home_ip>/32"]
cluster_ips = ["<cluster_ip>/32"]
trusted_ips = concat(local.home_ips, local.cluster_ips)
location = "nbg1"
hcloud_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
Firewall
File firewall.tf
# allow incoming icmp protocol from all
resource "hcloud_firewall" "allow_icmp_trusted" {
name = "allow_icmp_trusted"
rule {
direction = "in"
protocol = "icmp"
source_ips = local.trusted_ips
}
}
# allow https connection from trusted ip, see locals.tf
resource "hcloud_firewall" "allow_http_https" {
name = "allow_http_https"
rule {
direction = "in"
protocol = local.tcp_protocol
port = local.https_port
source_ips = local.all_ips
}
rule {
direction = "in"
protocol = local.tcp_protocol
port = local.http_port
source_ips = local.all_ips
}
}
# allow ssh connection from trusted ip, see locals.tf
resource "hcloud_firewall" "allow_ssh_trusted" {
name = "allow_ssh_trusted"
rule {
direction = "in"
protocol = local.tcp_protocol
port = local.ssh_port
source_ips = local.trusted_ips
}
}
User init
File userdata/k8s.sh
#!/bin/bash
# Add user
useradd -m devops -s /bin/bash
cp -Rp /root/.ssh /home/devops/ && chown -R devops:devops /home/devops/
echo "devops ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/devops
# Configure ssh to non standart post
cat > /etc/ssh/sshd_config.d/sshd.conf << EOF
Port 3300
PermitRootLogin no
PasswordAuthentication no
EOF
systemctl restart ssh.service
# Ugrade
apt-get update && apt-get upgrade -y
# Install additions sofrware
apt-get install ca-certificates curl apt-transport-https htop gnupg software-properties-common -y
# https://kubernetes.io/docs/setup/production-environment/container-runtimes/#install-and-configure-prerequisites
# sysctl params required by setup, params persist across reboots
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# Apply sysctl params without reboot
sudo sysctl --system
cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
# Added kubernetes repository
mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg # allow unprivileged APT programs to read this keyring
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' > /etc/apt/sources.list.d/kubernetes.list
chmod 644 /etc/apt/sources.list.d/kubernetes.list # helps tools such as command-not-found to work correctly
# Add the CRI-O repository
PROJECT_PATH=prerelease:/main
curl -fsSL https://pkgs.k8s.io/addons:/cri-o:/$PROJECT_PATH/deb/Release.key |
gpg --dearmor -o /etc/apt/keyrings/cri-o-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://pkgs.k8s.io/addons:/cri-o:/$PROJECT_PATH/deb/ /" |
tee /etc/apt/sources.list.d/cri-o.list
apt-get update
# Disable swap
sed -ri 's/^([^#].*?\sswap\s+sw\s+.*)$/# \1/g' /etc/fstab
swapoff -a
# Install kubernetes tools
apt-get install -y kubelet kubeadm kubectl cri-o
apt-mark hold kubelet kubeadm kubectl
systemctl daemon-reload
systemctl enable --now kubelet
systemctl enable --now crio.service
systemctl start crio.service
systemctl start kubelet
shutdown -r now
Server
File nodes.tf
resource "hcloud_server" "this" {
name = "main"
server_type = local.server_type
image = local.image
location = local.location
user_data = file("${path.module}/userdata/k8s.sh")
public_net {
ipv4_enabled = true
ipv6_enabled = false
}
lifecycle {
ignore_changes = [
user_data
]
}
firewall_ids = [
hcloud_firewall.allow_ssh_trusted.id,
hcloud_firewall.allow_http_https.id,
hcloud_firewall.allow_icmp_trusted.id
]
ssh_keys = [
hcloud_ssh_key.ssh.id
]
}
output "host_ip" {
description = "Host ip"
value = {
endpoint = "ssh devopsx@${hcloud_server.this.ipv4_address} -p 3300"
}
}
Apply terraform
terraform init
terraform apply
Setup kubernetes
Login to server
ssh devopsx@server_ip -p 3300
Init kubernetes cluster
kubeadm init --kubernetes-version=v1.30.2
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
Setup network calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/canal.yaml
Install MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.5/config/manifests/metallb-native.yaml
IP=$(ip addr show $(netstat -rn | grep 'UG '| grep ' 0.0.0.0 '| head -1| awk '{print $8}') | grep 'inet ' | awk '{print $2}' | cut -f1 -d'/')
cat > /root/metalbl.yaml << EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production
namespace: metallb-system
spec:
addresses:
- ${IP}/32
EOF
kubectl -n metallb-system apply -f /root/metalbl.yaml