Configurare completa a unui cluster Kubernetes cu Rancher, RKE2, NFS storage si Load Balancer pe Hetzner Cloud - totul automatizat cu Terraform si Ansible pentru ~25 EUR/luna.
TL;DR
Configuram un cluster Kubernetes complet functional pe Hetzner Cloud cu un cost total de ~25 EUR/luna: Rancher ca management UI (Docker container), 3 noduri RKE2 cu rolurile etcd + controlplane + worker, NFS storage cu Hetzner Volume, si un Load Balancer -- totul automatizat cu Terraform si Ansible. La final, instalam NFS Subdir External Provisioner pentru volume persistente dinamice.
De Ce Acest Setup?
Daca esti un startup, un freelancer, sau o echipa mica care are nevoie de Kubernetes dar nu vrea sa plateasca 200+ EUR/luna pentru EKS/GKE, Hetzner Cloud este una dintre cele mai bune optiuni cost-performanta din Europa.
Ce obtinem:
- Rancher v2.13.2 -- management UI complet pentru cluster
- RKE2 cu Kubernetes v1.34.2 -- distributie security-focused, CIS compliant
- Calico CNI -- network policies avansate
- 3 noduri all-in-one (etcd + controlplane + worker)
- NFS storage cu Hetzner Volume (100GB) pentru PersistentVolumes dinamice
- Load Balancer Hetzner pentru trafic HTTP/HTTPS
- Totul automatizat cu Terraform + Ansible -- reproducibil 100%
De ce nu am folosit Helm pentru Rancher? Intr-un setup de productie HA, Rancher se instaleaza cu Helm pe un cluster RKE2 dedicat. Dar pentru un environment de development sau un cluster mic, un singur container Docker este mai simplu, mai rapid de repornit si consuma mai putine resurse. Rancher in Docker creeaza si provisioneaza cluster-ul RKE2 pe nodurile separate, deci decuplarea management-workload ramane intacta.
Arhitectura
+-----------------------------------------------------------------+
| Hetzner Cloud - nbg1 (Nuremberg) |
| VPC: 10.10.0.0/16 |
| |
| +-----------------------------+ |
| | Load Balancer (lb11) | |
| | 10.10.0.99 | |
| | Ports: 80, 443 | |
| +--------------+---------------+ |
| | |
| +---------------+ +--------------------------------------+ |
| | Rancher | | RKE2 Cluster (Calico CNI) | |
| | (Docker) | | | |
| | cx32 |---->| +--------+ +--------+ +--------+ | |
| | 10.10.0.100 | | | k8s-1 | | k8s-2 | | k8s-3 | | |
| | Ubuntu 24.04 | | | cpx32 | | cpx32 | | cpx32 | | |
| +--------------+ | | .11 | | .12 | | .13 | | |
| | | etcd | | etcd | | etcd | | |
| +--------------+ | | ctrl | | ctrl | | ctrl | | |
| | NFS | | | worker | | worker | | worker | | |
| | Server | | +----+---+ +----+---+ +----+---+ | |
| | cx22 | | | | | | |
| | 10.10.0.75 |<----|-------+----------+----------+ | |
| | +100GB Vol | | | |
| +--------------+ +--------------------------------------+ |
+-----------------------------------------------------------------+
Fiecare nod K8s poarta toate cele 3 roluri:
- etcd -- baza de date distribuita a clusterului (quorum de 3)
- controlplane -- API server, scheduler, controller manager
- worker -- ruleaza workload-urile aplicatiilor
Acest model all-in-one reduce costurile prin eliminarea nodurilor dedicate, pastrand in acelasi timp redundanta etcd cu quorum de 3 noduri.
Costuri Hetzner Cloud (Februarie 2026, EU)
| Resursa | Tip | Specificatii | Pret/luna |
|----------------|-------|---------------------------|-------------------------|
| Rancher Server | CX32 | 4 vCPU, 8GB RAM, 80GB SSD | 6.80 EUR |
| K8s Node x3 | CPX32 | 4 vCPU AMD, 8GB RAM, 80GB | 35.70 EUR (3x11.90 EUR) |
| NFS Server | CX22 | 2 vCPU, 4GB RAM, 40GB SSD | 3.79 EUR |
| Hetzner Volume | 100GB | NFS storage ext4 | 4.80 EUR |
| Load Balancer | LB11 | Round Robin, 80/443 | 6.49 EUR |
| **TOTAL** | | | **~57.58 EUR/luna** |
Nota: Preturile sunt fara TVA, pentru locatia Germania. Cu optimizari (CX22 pentru Rancher, noduri mai mici), poti ajunge la ~25-30 EUR/luna pentru un cluster minimal. Toate serverele includ 20TB trafic/luna.
Faza 0: Pregatirea Mediului
Cerinte
# Terraform >= 1.0
terraform version
# Ansible
ansible --version
# Hetzner CLI (optional dar util)
hcloud version
# jq (necesar pe Rancher server pentru init script)
jq --version
Structura Proiectului
infrastructure/
+-- terraform/
| +-- 01-variables.tf # Variabile cu validare
| +-- 02-locals.tf # Common tags
| +-- 03-providers.tf # Hetzner provider
| +-- 04-network.tf # VPC + Subnet
| +-- 05-security.tf # SSH keys + Firewall bazic
| +-- 06-storage.tf # NFS Volume 100GB
| +-- 07-compute.tf # Toate serverele + Placement Group
| +-- 08-load-balancer.tf # LB pentru K8s
| +-- 09-security-whitelist.tf # Firewall cu server IPs
| +-- 90-outputs.tf # Output IPs
|
+-- ansible/
+-- rancher.yaml # Playbook principal
+-- nfs1.yaml # Playbook NFS
+-- inventory/
| +-- hosts.ini
+-- roles/
+-- common/ # Update, tools
+-- users/ # SSH users
+-- docker/ # Docker install
+-- nfs/
| +-- tasks/main.yml
| +-- defaults/main.yml
| +-- handlers/main.yml
| +-- templates/exports.j2
+-- rancher/
+-- tasks/main.yml
+-- defaults/main.yml
+-- templates/init_rancher.sh.j2
Faza 1: Terraform -- Provisionare Infrastructura
1.1 Variabile
Definim toate variabilele cu validare. Observa instance_type map-ul care centralizeaza dimensionarea serverelor -- ideal cand vrei sa faci rapid switch intre dev (cx22) si productie (cpx32):
# 01-variables.tf
variable "hcloud_token" {
description = "Hetzner Cloud API token"
sensitive = true
type = string
}
variable "location" {
default = "nbg1"
description = "Hetzner Cloud datacenter location"
type = string
validation {
condition = contains(["nbg1", "fsn1", "hel1", "ash"], var.location)
error_message = "Location must be one of: nbg1, fsn1, hel1, ash."
}
}
variable "os_type" {
default = "debian-12"
description = "Operating system image for the server"
type = string
}
variable "env" {
default = "dev"
description = "Environment name"
type = string
}
variable "is_production" {
description = "Whether this is production environment or dev"
type = bool
default = false
}
variable "project" {
default = "mit"
description = "Name of project"
type = string
}
variable "user_ssh_keys" {
description = "Map of username to SSH public key content"
type = map(string)
default = {
"mcujba" = "ssh-ed25519 AAAAC3Nza... maxim"
}
}
variable "whitelist" {
default = [
"188.0.222.69/32",
"178.169.11.99/32",
]
description = "Whitelisted IP addresses for firewall access"
type = list(string)
}
variable "k8s_count" {
default = 3
description = "Number of Kubernetes worker nodes"
type = number
validation {
condition = var.k8s_count >= 1 && var.k8s_count <= 10
error_message = "Kubernetes worker count must be between 1 and 10."
}
}
variable "instance_type" {
default = {
"k8s" = "cpx32"
"rancher" = "cx32"
"nfs" = "cx22"
}
type = map(string)
description = "Instance types for different server roles"
}
variable "lb_services" {
default = [80, 443]
description = "Ports for load balancer services"
type = list(number)
}
variable "vpc_cidr" {
default = "10.10.0.0/16"
description = "VPC CIDR for environment"
type = string
}
1.2 Networking -- VPC si Subnet
Toate serverele comunica prin reteaua privata. Traficul intern nu contorizeaza la bandwidth:
# 04-network.tf
resource "hcloud_network" "vpc" {
name = "vpc-${var.project}-${var.env}"
ip_range = var.vpc_cidr
}
resource "hcloud_network_subnet" "servers" {
network_id = hcloud_network.vpc.id
type = "cloud"
network_zone = "eu-central"
ip_range = cidrsubnet(var.vpc_cidr, 4, 0)
}
1.3 Security -- SSH Keys si Firewall
Folosim o strategie de dual-firewall: un firewall bazic (creat inainte de servere) si un al doilea care include IP-urile serverelor (creat dupa compute). Aceasta evita dependenta circulara:
# 05-security.tf
resource "hcloud_ssh_key" "users" {
for_each = var.user_ssh_keys
name = "ssh-key-${var.project}-${var.env}-${each.key}"
public_key = each.value
}
resource "hcloud_firewall" "in_filter" {
name = "firewall-${var.project}-${var.env}"
rule {
direction = "in"
protocol = "tcp"
port = "any"
source_ips = concat([var.vpc_cidr], var.whitelist)
}
rule {
direction = "in"
protocol = "icmp"
source_ips = concat([var.vpc_cidr], var.whitelist)
}
}
resource "hcloud_firewall_attachment" "servers" {
firewall_id = hcloud_firewall.in_filter.id
label_selectors = ["Project=${var.project}"]
}
Al doilea firewall care include server IPs:
# 09-security-whitelist.tf
locals {
server_whitelisted_ips = concat(
formatlist("%s/32", hcloud_server.k8s.*.ipv4_address),
[format("%s/32", hcloud_server.rancher.ipv4_address)],
[var.vpc_cidr],
var.whitelist
)
}
resource "hcloud_firewall" "server_access" {
name = "server-access-${var.project}-${var.env}"
rule {
direction = "in"
protocol = "tcp"
port = "any"
source_ips = local.server_whitelisted_ips
}
rule {
direction = "in"
protocol = "udp"
port = "any"
source_ips = local.server_whitelisted_ips
}
rule {
direction = "in"
protocol = "icmp"
source_ips = local.server_whitelisted_ips
}
depends_on = [
hcloud_server.k8s,
hcloud_server.rancher,
hcloud_server.nfs
]
}
1.4 Storage -- Hetzner Volume pentru NFS
Hetzner Volumes sunt block storage-uri persistente. Chiar daca serverul NFS este recreat, datele raman intacte:
# 06-storage.tf
resource "hcloud_volume" "nfs" {
name = "nfs1-${var.project}-${var.env}-volume"
size = 100
location = var.location
format = "ext4"
delete_protection = var.is_production ? true : false
}
resource "hcloud_volume_attachment" "nfs" {
volume_id = hcloud_volume.nfs.id
server_id = hcloud_server.nfs.id
automount = true
depends_on = [hcloud_volume.nfs]
}
1.5 Compute -- Serverele
Punctul cheie: K8s nodurile folosesc un Placement Group de tip spread. Hetzner garanteaza ca serverele din grup sunt plasate pe host-uri fizice diferite -- esential pentru high availability:
# 07-compute.tf
resource "hcloud_placement_group" "k8s" {
name = "k8s-${var.project}-${var.env}"
type = "spread"
}
resource "hcloud_server" "k8s" {
count = var.k8s_count
name = "k8s${count.index + 1}-${var.project}-${var.env}"
image = "ubuntu-24.04"
server_type = lookup(var.instance_type, "k8s", "cpx32")
location = var.location
ssh_keys = [for key in hcloud_ssh_key.users : key.id]
placement_group_id = hcloud_placement_group.k8s.id
public_net {
ipv4_enabled = true
ipv6_enabled = false
}
network {
network_id = hcloud_network.vpc.id
ip = cidrhost(hcloud_network_subnet.servers.ip_range,
count.index + 11)
}
labels = {
Terraform = "true"
Environment = var.env
Project = var.project
type = "Kubernetes"
}
depends_on = [
hcloud_network_subnet.servers,
hcloud_placement_group.k8s,
hcloud_ssh_key.users
]
lifecycle {
ignore_changes = [ssh_keys]
}
}
resource "hcloud_server" "rancher" {
name = "rancher1-${var.project}-${var.env}"
image = "ubuntu-24.04"
server_type = lookup(var.instance_type, "rancher", "cx32")
location = var.location
ssh_keys = [for key in hcloud_ssh_key.users : key.id]
backups = true
public_net {
ipv4_enabled = true
ipv6_enabled = false
}
network {
network_id = hcloud_network.vpc.id
ip = cidrhost(var.vpc_cidr, 100)
}
labels = {
Terraform = "true"
Environment = var.env
Project = var.project
type = "Rancher"
}
depends_on = [hcloud_network_subnet.servers, hcloud_ssh_key.users]
lifecycle { ignore_changes = [ssh_keys] }
}
resource "hcloud_server" "nfs" {
name = "nfs1-${var.project}-${var.env}"
image = var.os_type
server_type = lookup(var.instance_type, "nfs", "cx22")
location = var.location
ssh_keys = [for key in hcloud_ssh_key.users : key.id]
backups = true
public_net {
ipv4_enabled = true
ipv6_enabled = false
}
network {
network_id = hcloud_network.vpc.id
ip = cidrhost(var.vpc_cidr, 75)
}
labels = {
Terraform = "true"
Environment = var.env
Project = var.project
type = "NFS"
}
depends_on = [hcloud_network_subnet.servers, hcloud_volume.nfs]
lifecycle { ignore_changes = [ssh_keys] }
}
1.6 Load Balancer
Load balancer-ul Hetzner directioneaza traficul prin reteaua privata (use_private_ip = true) catre nodurile K8s:
# 08-load-balancer.tf
resource "hcloud_load_balancer" "k8s" {
name = "k8s-lb-${var.project}-${var.env}"
load_balancer_type = "lb11"
location = var.location
algorithm { type = "round_robin" }
}
resource "hcloud_load_balancer_target" "k8s" {
count = var.k8s_count
type = "server"
load_balancer_id = hcloud_load_balancer.k8s.id
server_id = hcloud_server.k8s[count.index].id
use_private_ip = true
}
resource "hcloud_load_balancer_service" "k8s" {
count = length(var.lb_services)
load_balancer_id = hcloud_load_balancer.k8s.id
protocol = "tcp"
listen_port = var.lb_services[count.index]
destination_port = var.lb_services[count.index]
}
resource "hcloud_load_balancer_network" "k8s" {
load_balancer_id = hcloud_load_balancer.k8s.id
subnet_id = hcloud_network_subnet.servers.id
enable_public_interface = true
ip = "10.10.0.99"
}
1.7 Deployment
export TF_VAR_hcloud_token="your-hetzner-api-token"
cd infrastructure/terraform
terraform init
terraform plan
terraform apply
Faza 2: Ansible -- Instalare NFS Server
2.1 Inventory
# ansible/inventory/hosts.ini
[rancher]
rancher1 ansible_host=49.12.xxx.xxx
[kubernetes]
k8s1 ansible_host=49.12.aaa.aaa ansible_all_ipv4_addresses='["49.12.aaa.aaa","10.10.0.11"]'
k8s2 ansible_host=49.12.bbb.bbb ansible_all_ipv4_addresses='["49.12.bbb.bbb","10.10.0.12"]'
k8s3 ansible_host=49.12.ccc.ccc ansible_all_ipv4_addresses='["49.12.ccc.ccc","10.10.0.13"]'
[nfs]
nfs1 ansible_host=49.12.yyy.yyy
[all:vars]
ansible_user=root
ansible_python_interpreter=/usr/bin/python3
2.2 NFS Role
NFS server-ul detecteaza automat Hetzner Volume-ul montat (pattern-ul HC_Volume_*) si exporta directorul catre nodurile K8s prin reteaua privata:
# roles/nfs/tasks/main.yml
- name: Ensure NFS utilities are installed
apt:
name:
- nfs-kernel-server
state: present
update_cache: true
- name: Identify Hetzner mounted volume
set_fact:
mount_volume: "{{ ansible_mounts | json_query(query) }}"
vars:
query: "[?contains(mount, `/mnt/HC_Volume_`)].mount"
- name: Volume is mounted to
debug:
var: mount_volume
- name: Ensure NFS is running
systemd_service:
name: nfs-server
state: started
enabled: true
- name: Copy exports file
template:
src: exports.j2
dest: /etc/exports
owner: root
group: root
mode: 0644
notify: reload nfs
Template exports -- genereaza automat un export per nod K8s:
# roles/nfs/templates/exports.j2
# Do not edit manually - generated by Ansible
{% raw %}
{% for host in groups[mount_hosts] %}
{{ mount_volume | first }} {{ hostvars[host].ansible_all_ipv4_addresses | ansible.utils.ipaddr(app_cidr) | first }}(rw,sync,no_root_squash,no_all_squash,no_subtree_check)
{% endfor %}
{% endraw %}
Generat, fisierul /etc/exports va arata:
/mnt/HC_Volume_12345678 10.10.0.11(rw,sync,no_root_squash,no_all_squash,no_subtree_check)
/mnt/HC_Volume_12345678 10.10.0.12(rw,sync,no_root_squash,no_all_squash,no_subtree_check)
/mnt/HC_Volume_12345678 10.10.0.13(rw,sync,no_root_squash,no_all_squash,no_subtree_check)
Filtrul ansible.utils.ipaddr(app_cidr) selecteaza doar adresa IP care apartine retelei private 10.10.0.0/16, asigurand ca traficul NFS nu iese niciodata pe interfata publica.
Variabile si handler:
# roles/nfs/defaults/main.yml
mount_hosts: kubernetes
app_cidr: 10.10.0.0/16
# roles/nfs/handlers/main.yml
- name: reload nfs
command: 'exportfs -ra'
Playbook NFS:
# nfs1.yaml
- name: Get Gather Facts
gather_facts: yes
hosts:
- kubernetes
- name: Install NFS Solution
become: true
hosts:
- nfs
roles:
- role: nfs
ansible-playbook -i inventory/hosts.ini nfs1.yaml
Faza 3: Ansible -- Instalare Rancher + Crearea Clusterului RKE2
Aceasta este faza cea mai complexa. Playbook-ul face urmatoarele intr-o singura rulare:
- Instaleaza Docker pe serverul Rancher
- Instaleaza
nfs-commonpe nodurile K8s (client NFS) - Porneste containerul Rancher
- Ruleaza init script-ul care: seteaza parola, creeaza API token, creeaza cluster-ul RKE2 via API, genereaza comanda de join
- Executa comanda de join pe fiecare nod K8s cu
--etcd --controlplane --worker
3.1 Variabile Rancher
# roles/rancher/defaults/main.yml
rancherVersion: v2.13.2
kubernetesVersion: v1.34.2+rke2r3
CNI: calico
3.2 Task-uri Rancher
# roles/rancher/tasks/main.yml
- name: Install nfs-common for NFS Client
package:
name: "{{ item }}"
with_items:
- nfs-common
when: "'kubernetes' in group_names"
- name: Check if Rancher server is running
shell: docker ps | grep rancher/rancher
register: server_running
ignore_errors: true
changed_when: false
when: "'rancher' in group_names"
- name: Wait for Rancher to be ready
uri:
url: "https://{{ ansible_default_ipv4.address }}/ping"
method: GET
validate_certs: false
status_code: 200
register: rancher_health
until: rancher_health.status == 200
retries: 30
delay: 10
when: "'rancher' in group_names and server_running is success"
- name: Create Rancher log directory
file:
path: /var/log/rancher
state: directory
recurse: yes
mode: "0755"
when: "'rancher' in group_names"
- name: Create Rancher data directory
file:
path: /opt/rancher
state: directory
mode: "0755"
when: "'rancher' in group_names"
- name: Start Rancher server
command: >
docker run -d
-e CATTLE_BOOTSTRAP_PASSWORD=admin
--restart=unless-stopped
--privileged
-p 443:443
-v /var/log/rancher/auditlog:/var/log/auditlog
-v /opt/rancher:/var/lib/rancher
rancher/rancher:{{ rancherVersion }}
when: not server_running is success and "'rancher' in group_names"
- name: Copy Rancher init script
template:
src: "init_rancher.sh.j2"
dest: /tmp/init_rancher.sh
owner: root
group: root
mode: 0777
when: "'rancher' in group_names"
- name: Run Rancher init script
shell: bash /tmp/init_rancher.sh
register: rancher_token
when: "'rancher' in group_names"
- name: Show registration command
debug:
var: rancher_token.stdout
when: "'rancher' in group_names"
- name: Declare rancher_token fact on K8s hosts
set_fact:
rancher_token_fact: "{{ rancher_token.stdout }}"
delegate_to: "{{ item }}"
delegate_facts: true
with_items: "{{ groups['kubernetes'] }}"
when: "'rancher' in group_names"
- name: Check if Rancher agent is running on nodes
ansible.builtin.service:
name: rancher-system-agent
state: started
register: masters_agent_running
ignore_errors: true
changed_when: false
when: "'kubernetes' in group_names"
- name: Join nodes to Rancher cluster
shell: "{{ item }} --etcd --controlplane --worker"
with_items:
- "{{ hostvars[inventory_hostname].rancher_token_fact }}"
when: masters_agent_running is failed and "'kubernetes' in group_names"
3.3 Init Script-ul -- Automatizarea Completa a API-ului Rancher
Aceasta este inima automatizarii. Script-ul interactioneaza cu Rancher API v3 si v1 pentru a se autentifica, schimba parola, crea un API token, seta server-url, crea cluster-ul RKE2, si genera registration token.
Cluster-ul este creat cu aceasta cerere API:
curl -s https://127.0.0.1/v1/provisioning.cattle.io.clusters \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $LOGINTOKEN" \
--data-raw '{
"type": "provisioning.cattle.io.cluster",
"metadata": {
"namespace": "fleet-default",
"name": "dev-cluster"
},
"spec": {
"kubernetesVersion": "v1.32.5+rke2r1",
"network": {
"type": "networkConfig",
"plugin": "calico"
},
"localClusterAuthEndpoint": {},
"rkeConfig": {}
}
}' --insecure
3.4 Playbook Principal
# rancher.yaml
- name: Install Docker for Rancher Server only
become: true
hosts:
- rancher
roles:
- role: docker
- name: Install Rancher Solution
become: true
hosts:
- rancher
- kubernetes
roles:
- role: rancher
3.5 Executie
ansible-playbook -i inventory/hosts.ini rancher.yaml
Acest playbook dureaza ~10-15 minute. Rancher container-ul are nevoie de ~2-3 minute sa porneasca, apoi fiecare nod are nevoie de ~3-5 minute pentru join si bootstrap-ul RKE2.
Faza 4: Verificare Cluster
4.1 Acces Rancher UI
Deschide https://RANCHER_SERVER_IP in browser. La prima accesare vei vedea un warning de certificat self-signed.
4.2 kubectl
# Verificare noduri
kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s1 Ready control-plane,etcd,worker 10m v1.32.5+rke2r1
k8s2 Ready control-plane,etcd,worker 8m v1.32.5+rke2r1
k8s3 Ready control-plane,etcd,worker 7m v1.32.5+rke2r1
# Verificare pod-uri de sistem
kubectl get pods -A
NAMESPACE NAME READY STATUS
kube-system calico-kube-controllers-xxx 1/1 Running
kube-system calico-node-xxxxx 1/1 Running
kube-system coredns-xxx 1/1 Running
kube-system etcd-k8s1 1/1 Running
kube-system kube-apiserver-k8s1 1/1 Running
kube-system rke2-ingress-nginx-controller-xxxxx 1/1 Running
kube-system rke2-metrics-server-xxx 1/1 Running
Faza 5: NFS Subdir External Provisioner
Acum avem un cluster functional, dar fara storage dinamic. NFS Subdir External Provisioner creeaza automat subdirectoare pe NFS server pentru fiecare PersistentVolumeClaim.
5.1 Instalare cu Helm
helm repo add nfs-subdir-external-provisioner \
https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
helm install nfs-provisioner \
nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--namespace kube-system \
--set nfs.server=10.10.0.75 \
--set nfs.path=/mnt/HC_Volume_12345678 \
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=true \
--set storageClass.reclaimPolicy=Retain \
--set storageClass.archiveOnDelete=true
5.2 Verificare
kubectl get sc
NAME PROVISIONER RECLAIMPOLICY
nfs-client (default) cluster.local/nfs-provisioner-nfs-subdir-... Retain
kubectl get pods -n kube-system | grep nfs
nfs-provisioner-nfs-subdir-external-provisioner-xxx 1/1 Running
5.3 Test -- Creeaza un PVC
# test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-pod
spec:
containers:
- name: test
image: busybox
command: ["sh", "-c", "echo 'NFS works!' > /mnt/data/test.txt && cat /mnt/data/test.txt && sleep 3600"]
volumeMounts:
- name: nfs-vol
mountPath: /mnt/data
volumes:
- name: nfs-vol
persistentVolumeClaim:
claimName: test-nfs-claim
kubectl apply -f test-pvc.yaml
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES
test-nfs-claim Bound pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890 1Gi RWX
kubectl logs test-nfs-pod
NFS works!
# Cleanup
kubectl delete -f test-pvc.yaml
Rezumat Complet
| Faza | Tool | Ce Face | Timp | |------|------|---------|------| | 1 | Terraform | Provisioneaza VPC, servere, LB, firewall, volume | ~3 min | | 2 | Ansible | Instaleaza si configureaza NFS server | ~2 min | | 3 | Ansible | Instaleaza Docker, Rancher, creeaza cluster, join noduri | ~15 min | | 4 | kubectl | Verificare cluster health | ~1 min | | 5 | Helm | Instaleaza NFS provisioner pentru PV dinamice | ~1 min | | Total | | De la zero la cluster functional | ~22 min |
Tips pentru Productie
-
Backup etcd: Configureaza backup automat etcd -- pierderea etcd inseamna pierderea clusterului. RKE2 suporta snapshots automate la
/var/lib/rancher/rke2/server/db/snapshots. -
Rancher HA: Pentru productie, ruleaza Rancher pe un cluster RKE2 dedicat cu Helm, nu in Docker single-container.
-
cert-manager: Instaleaza cert-manager + Let's Encrypt pentru certificate TLS automate pe ingress-ul RKE2 NGINX.
-
Monitoring: RKE2 vine cu metrics-server preinstalat. Adauga Prometheus + Grafana din Rancher Marketplace.
-
Network Policies: Calico suporta NetworkPolicies native. Defineste politici de izolare intre namespace-uri din start.
-
Hetzner Volume backup: Hetzner nu face backup automat la Volumes. Configureaza un cron job cu
rsyncsau snapshot-uri periodice. -
Scaling: Pentru a adauga noduri, schimba
k8s_countin Terraform, ruleazaapply, actualizeaza inventory-ul Ansible, si ruleaza playbook-ul rancher. Noile noduri se vor joina automat.
Resurse
- RKE2 Documentation
- Rancher Manager Docs
- NFS Subdir External Provisioner
- Hetzner Cloud Pricing
- Hetzner Terraform Provider
- Calico Network Policies
Acest articol este bazat pe o configuratie reala de productie. Codul Terraform si Ansible este testat si folosit activ pentru provisionarea clusterelor de development.