In this tutorial, you will learn how to set up a lightweight Kubernetes cluster on Hetzner Cloud using k3s, install the Hetzner Cloud Controller Manager (HCCM), and deploy the Ingress NGINX Controller to expose services via a Hetzner Load Balancer.
What you’ll achieve:
- A running Kubernetes cluster with nodes in Hetzner Cloud
- Automatic Load Balancer creation using HCCM
- An NGINX Ingress Controller with a test application exposed
Prerequisites:
- Basic knowledge of Kubernetes concepts
- Installed command-line tools:
Lets create and upload a SSH key, which is used by k3sup to access our servers during the Kubernetes installation process.
ssh-keygen -t ed25519 -f ./hcloud-k3s
hcloud ssh-key create --name k3s-key --public-key-from-file ./hcloud-k3s.pubOur cluster will consist of a single control plane with a single worker. These servers will be located in Falkenstein, use Ubunut as a base image and use the server-type cpx22.
hcloud server create --name tutorial-control-plane \
--type cpx22 \
--location fsn1 \
--image ubuntu-24.04 \
--ssh-key k3s-key
hcloud server create --name tutorial-worker \
--type cpx22 \
--location fsn1 \
--image ubuntu-24.04 \
--ssh-key k3s-keyUse hcloud server list to check that the servers are running and note their public IP addresses.
k3sup install \
--ip=<CONTROL_PLANE_PUBLIC_IP> \
--ssh-key ./hcloud-k3s \
--local-path=./kubeconfig \
--k3s-extra-args="\
--kubelet-arg=cloud-provider=external \
--disable-cloud-controller \
--disable-network-policy \
--disable=traefik \
--disable=servicelb \
--node-ip='<CONTROL_PLANE_PUBLIC_IP>'"cloud-provider=externalprepares the cluster for an external cloud controller. In our case the hcloud-cloud-controller-manager.--disable-network-policy,--disable=traefikand--disable=servicelbremoves k3s builtin components, which would collide with the products we deploy in this tutorial.
k3sup join \
--ip=<WORKER_PUBLIC_IP> \
--server-ip=<CONTROL_PLANE_PUBLIC_IP> \
--user=root \
--ssh-key ./hcloud-k3s \
--k3s-extra-args="\
--kubelet-arg=cloud-provider=external \
--node-ip='<WORKER_PUBLIC_IP>'"Set the kubeconfig file:
export KUBECONFIG=./kubeconfig
kubectl get nodes -o widekubectl -n kube-system create secret generic hcloud --from-literal=token=<YOUR_HCLOUD_API_TOKEN>helm repo add hcloud https://charts.hetzner.cloud
helm repo update
helm install hccm hcloud/hcloud-cloud-controller-manager -n kube-systemIngress Nginx will deploy a Kubernetes Service of type LoadBalancer. Here we configure the annotations added to the Kubernetes Service via Helm values. These annotations are read by the HCCM to configure the Hetzner Cloud Load Balancer.
# values.yml
controller:
service:
annotations:
load-balancer.hetzner.cloud/location: "fsn1"
load-balancer.hetzner.cloud/name: "tutorial-lb"helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
-f values.yml✅ After a couple of seconds, check for the Hetzner Cloud Load Balancer:
hcloud load-balancer list# hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app
spec:
replicas: 2
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: hashicorp/http-echo
args: ["-text=Hello from Kubernetes!"]
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: hello-service
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: hello.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-service
port:
number: 80kubectl apply -f hello-app.yamlWe can now access the application. To do this without creating a proper DNS entry or editing /etc/hosts, we use a small workaround: temporarily resolve hello.local to the Load Balancer’s public IPv4 address.
curl --resolve hello.local:80:$(hcloud load-balancer describe tutorial-lb --output json | jq -r .public_net.ipv4.ip) http://hello.local🚀 You should see:
Hello from Kubernetes!
As this tutorial was by no means a production setup, lets cleanup the resources we created.
hcloud server delete tutorial-control-plane tutorial-worker
hcloud load-balancer delete tutorial-lb
hcloud ssh-key delete k3s-keyRelated resources:
- HCloud Cloud Controller Manager
- HCloud Cloud Controller Manager - Annotations # x-releaser-pleaser-version
- Ingress NGINX Annotations