Полная настройка Kubernetes-кластера с Rancher, RKE2, NFS-хранилищем и Load Balancer на Hetzner Cloud -- все автоматизировано с помощью Terraform и Ansible примерно за 25 EUR/месяц.
TL;DR
Настраиваем полностью рабочий Kubernetes-кластер на Hetzner Cloud общей стоимостью ~25 EUR/месяц: Rancher в качестве UI для управления (Docker-контейнер), 3 ноды RKE2 с ролями etcd + controlplane + worker, NFS-хранилище на Hetzner Volume и Load Balancer -- все автоматизировано с помощью Terraform и Ansible. В завершение устанавливаем NFS Subdir External Provisioner для динамических персистентных томов.
Почему Именно Такой Setup?
Если вы стартап, фрилансер или небольшая команда, которой нужен Kubernetes, но вы не хотите платить 200+ EUR/месяц за EKS/GKE, Hetzner Cloud -- один из лучших вариантов по соотношению цена-производительность в Европе.
Что мы получим:
- Rancher v2.13.2 -- полноценный UI для управления кластером
- RKE2 с Kubernetes v1.34.2 -- дистрибутив с акцентом на безопасность, CIS compliant
- Calico CNI -- продвинутые сетевые политики
- 3 ноды all-in-one (etcd + controlplane + worker)
- NFS-хранилище на Hetzner Volume (100GB) для динамических PersistentVolumes
- Hetzner Load Balancer для HTTP/HTTPS-трафика
- Полная автоматизация через Terraform + Ansible -- 100% воспроизводимость
Почему мы не использовали Helm для Rancher? В продакшн-окружении с HA Rancher устанавливается через Helm на выделенный RKE2-кластер. Но для окружения разработки или небольшого кластера один Docker-контейнер проще, быстрее перезапускается и потребляет меньше ресурсов. Rancher в Docker создает и провиженит RKE2-кластер на отдельных нодах, поэтому разделение management-workload сохраняется.
Архитектура
+-----------------------------------------------------------------+
| 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 | | | |
| +--------------+ +--------------------------------------+ |
+-----------------------------------------------------------------+
Каждая K8s-нода несет все 3 роли:
- etcd -- распределенная база данных кластера (кворум из 3)
- controlplane -- API server, scheduler, controller manager
- worker -- выполняет рабочие нагрузки приложений
Эта модель all-in-one снижает затраты за счет отказа от выделенных нод, сохраняя при этом отказоустойчивость etcd с кворумом из 3 нод.
Стоимость Hetzner Cloud (Февраль 2026, EU)
| Ресурс | Тип | Характеристики | Цена/месяц |
|----------------|-------|---------------------------|-------------------------|
| 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 |
| **ИТОГО** | | | **~57.58 EUR/месяц** |
Примечание: Цены указаны без НДС для локации Германия. С оптимизациями (CX22 для Rancher, ноды поменьше) можно уложиться в ~25-30 EUR/месяц для минимального кластера. Все серверы включают 20TB трафика/месяц.
Фаза 0: Подготовка Окружения
Требования
# Terraform >= 1.0
terraform version
# Ansible
ansible --version
# Hetzner CLI (опционально, но полезно)
hcloud version
# jq (необходим на Rancher server для init-скрипта)
jq --version
Структура Проекта
infrastructure/
+-- terraform/
| +-- 01-variables.tf # Переменные с валидацией
| +-- 02-locals.tf # Общие теги
| +-- 03-providers.tf # Hetzner provider
| +-- 04-network.tf # VPC + Subnet
| +-- 05-security.tf # SSH-ключи + базовый Firewall
| +-- 06-storage.tf # NFS Volume 100GB
| +-- 07-compute.tf # Все серверы + Placement Group
| +-- 08-load-balancer.tf # LB для K8s
| +-- 09-security-whitelist.tf # Firewall с IP-адресами серверов
| +-- 90-outputs.tf # Выходные IP-адреса
|
+-- ansible/
+-- rancher.yaml # Основной playbook
+-- nfs1.yaml # NFS playbook
+-- inventory/
| +-- hosts.ini
+-- roles/
+-- common/ # Обновление, утилиты
+-- users/ # SSH-пользователи
+-- docker/ # Установка Docker
+-- 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
Фаза 1: Terraform -- Провижининг Инфраструктуры
1.1 Переменные
Определяем все переменные с валидацией. Обратите внимание на map instance_type, который централизует размеры серверов -- идеально, когда нужно быстро переключиться между dev (cx22) и production (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 Сеть -- VPC и Subnet
Все серверы обмениваются данными через частную сеть. Внутренний трафик не учитывается в лимите пропускной способности:
# 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 Безопасность -- SSH-ключи и Firewall
Используем стратегию двойного файрвола: базовый файрвол (создается до серверов) и второй, включающий IP-адреса серверов (создается после compute). Это позволяет избежать циклической зависимости:
# 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}"]
}
Второй файрвол, включающий IP-адреса серверов:
# 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 Хранилище -- Hetzner Volume для NFS
Hetzner Volumes -- это персистентные блочные хранилища. Даже при пересоздании NFS-сервера данные остаются нетронутыми:
# 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 Вычислительные Ресурсы -- Серверы
Ключевой момент: K8s-ноды используют Placement Group типа spread. Hetzner гарантирует, что серверы из группы размещаются на разных физических хостах -- критически важно для 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
Hetzner Load Balancer направляет трафик через частную сеть (use_private_ip = true) к 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 Развертывание
export TF_VAR_hcloud_token="your-hetzner-api-token"
cd infrastructure/terraform
terraform init
terraform plan
terraform apply
Фаза 2: Ansible -- Установка NFS-сервера
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
NFS-сервер автоматически обнаруживает подключенный Hetzner Volume (по паттерну HC_Volume_*) и экспортирует директорию для K8s-нод через частную сеть:
# 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
Шаблон exports -- автоматически генерирует запись экспорта для каждой 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 %}
В результате файл /etc/exports будет выглядеть так:
/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)
Фильтр ansible.utils.ipaddr(app_cidr) выбирает только IP-адрес, принадлежащий частной сети 10.10.0.0/16, гарантируя, что NFS-трафик никогда не выйдет через публичный интерфейс.
Переменные и 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'
NFS Playbook:
# 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
Фаза 3: Ansible -- Установка Rancher + Создание Кластера RKE2
Это самая сложная фаза. Playbook выполняет следующее за один запуск:
- Устанавливает Docker на сервер Rancher
- Устанавливает
nfs-commonна K8s-ноды (NFS-клиент) - Запускает контейнер Rancher
- Выполняет init-скрипт, который: задает пароль, создает API-токен, создает кластер RKE2 через API, генерирует команду join
- Выполняет команду join на каждой K8s-ноде с флагами
--etcd --controlplane --worker
3.1 Переменные Rancher
# roles/rancher/defaults/main.yml
rancherVersion: v2.13.2
kubernetesVersion: v1.34.2+rke2r3
CNI: calico
3.2 Задачи 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-скрипт -- Полная Автоматизация Rancher API
Это сердце автоматизации. Скрипт взаимодействует с Rancher API v3 и v1 для аутентификации, смены пароля, создания API-токена, установки server-url, создания RKE2-кластера и генерации registration token.
Кластер создается следующим 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
# 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 Запуск
ansible-playbook -i inventory/hosts.ini rancher.yaml
Выполнение этого playbook занимает ~10-15 минут. Контейнеру Rancher требуется ~2-3 минуты на запуск, затем каждой ноде нужно ~3-5 минут для присоединения и bootstrap RKE2.
Фаза 4: Проверка Кластера
4.1 Доступ к Rancher UI
Откройте https://RANCHER_SERVER_IP в браузере. При первом обращении вы увидите предупреждение о самоподписанном сертификате.
4.2 kubectl
# Проверка нод
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
# Проверка системных подов
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
Фаза 5: NFS Subdir External Provisioner
Теперь у нас есть работающий кластер, но без динамического хранилища. NFS Subdir External Provisioner автоматически создает поддиректории на NFS-сервере для каждого PersistentVolumeClaim.
5.1 Установка через 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 Проверка
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 Тест -- Создание 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!
# Очистка
kubectl delete -f test-pvc.yaml
Полная Сводка
| Фаза | Инструмент | Что Делает | Время | |------|------------|------------|-------| | 1 | Terraform | Провижинит VPC, серверы, LB, файрвол, том | ~3 мин | | 2 | Ansible | Устанавливает и настраивает NFS-сервер | ~2 мин | | 3 | Ansible | Устанавливает Docker, Rancher, создает кластер, присоединяет ноды | ~15 мин | | 4 | kubectl | Проверка работоспособности кластера | ~1 мин | | 5 | Helm | Устанавливает NFS provisioner для динамических PV | ~1 мин | | Итого | | От нуля до рабочего кластера | ~22 мин |
Советы для Продакшна
-
Бэкап etcd: Настройте автоматический бэкап etcd -- потеря etcd означает потерю кластера. RKE2 поддерживает автоматические снепшоты в
/var/lib/rancher/rke2/server/db/snapshots. -
Rancher HA: Для продакшна запускайте Rancher на выделенном RKE2-кластере через Helm, а не в одном Docker-контейнере.
-
cert-manager: Установите cert-manager + Let's Encrypt для автоматических TLS-сертификатов на RKE2 NGINX ingress.
-
Мониторинг: RKE2 поставляется с предустановленным metrics-server. Добавьте Prometheus + Grafana из Rancher Marketplace.
-
Сетевые Политики: Calico поддерживает NetworkPolicies нативно. Определите политики изоляции между namespace-ами с самого начала.
-
Бэкап Hetzner Volume: Hetzner не делает автоматический бэкап Volumes. Настройте cron job с
rsyncили периодические снепшоты. -
Масштабирование: Чтобы добавить ноды, измените
k8s_countв Terraform, выполнитеapply, обновите inventory Ansible и запустите playbook rancher. Новые ноды присоединятся автоматически.
Ресурсы
- RKE2 Documentation
- Rancher Manager Docs
- NFS Subdir External Provisioner
- Hetzner Cloud Pricing
- Hetzner Terraform Provider
- Calico Network Policies
Эта статья основана на реальной продакшн-конфигурации. Код Terraform и Ansible протестирован и активно используется для провижининга кластеров разработки.