k3s на VPS: коли один вузол справді виправданий
k3s запускає справжній Kubernetes API на одному VPS, але забирає RAM і може конфліктувати з портом 80 під час інсталяції. Коли Docker Compose кращий?.
Що таке k3s і що дає один його вузол
k3s — це повний дистрибутив Kubernetes, упакований в один бінарний файл. Запуск на одному VPS дає справжній Kubernetes API без control plane із трьох машин. Це сертифікований дистрибутив Kubernetes, тому manifest, який застосовується тут, згодом можна застосувати і в керованому кластері. Інсталяція виконується однією командою і займає приблизно хвилину. Ціна цього рішення — пам’ять, яка більше недоступна для ваших застосунків, а також набір сценаріїв відмов, які не виникають у Docker Compose.
SUSE розробляє k3s для edge-майданчиків і невеликих інсталяцій. Кожна відмінність від upstream Kubernetes потрібна для зменшення розміру. Типове сховище даних — sqlite через shim під назвою kine, а не etcd, тому підтримувати кворум etcd не потрібно. containerd вбудований у бінарний файл, а не встановлюється окремо. Той самий бінарний файл також постачає CoreDNS для DNS кластера, Traefik як ingress controller, ServiceLB (також називається klipper-lb), щоб сервіси LoadBalancer працювали без cloud provider, local-path provisioner для persistent volumes, metrics-server і flannel для мережі pod. Усі вони запускаються за замовчуванням. Тому описаний нижче конфлікт портів є найпоширенішою першою проблемою на VPS, який уже використовувався для інших завдань.
Коли один вузол k3s має сенс
Користуйтеся k3s, коли вам потрібен саме Kubernetes API: ви вивчаєте Kubernetes на машині під власним керуванням або потрібне програмне забезпечення поширюється лише у вигляді Helm chart. Переносимість маніфестів також має значення, оскільки Deployment, який ви створюєте тут, можна без змін перенести до керованого кластера. Використовуйте Docker Compose, коли вам потрібні саме застосунки. Compose запускає ті самі контейнери зі значно меншою кількістю компонентів, а файл Compose на VPS простіше читати через рік, ніж каталог маніфестів.
Чітко усвідомлюйте, чого один вузол не забезпечує.
- Високу доступність. Після перезавантаження VPS усі робочі навантаження зупиняються. Kubernetes переносить pod на інший вузол, але іншого вузла немає.
- Rolling update без зупинки сервісу, якщо тільки застосунок не підтримує роботу двох реплік на одній машині зі спільним томом.
- Сховище, прив’язане до цього сервера, з причини, описаної нижче в розділі про local-path.
- Control plane, який споживає близько гігабайта RAM незалежно від того, розгортаєте ви щось чи ні.
Це не означає, що k3s є невдалим вибором. Це означає, що він є невдалим вибором з причини, яку зазвичай називають, — надійності. Якщо насправді вам потрібно кілька машин, щоб створити повноцінний кластер із кількома вузлами, спочатку ухваліть це рішення: Proxmox на власному обладнанні чи орендований VPS визначає, звідки беруться вузли, а вже потім k3s визначає, що на них запускати.
Скільки RAM і CPU споживає k3s до розгортання будь-яких застосунків
Проєкт k3s публікує виміряні показники, а не оцінки. Уважно ознайомтеся з ними, оскільки цифра, яку зазвичай наводять, не відповідає споживанню k3s у режимі простою.
The data behind this chart
[
{
"label": "Server, sqlite datastore",
"ram_mb": "1,596",
"cpu_percent_of_one_core": 6
},
{
"label": "Server, embedded etcd",
"ram_mb": "1,606",
"cpu_percent_of_one_core": 6
},
{
"label": "Agent node only",
"ram_mb": "275",
"cpu_percent_of_one_core": 3
}
]У цьому тесті server node споживав 1,596 MB RAM на 95-му перцентилі та приблизно 6 відсотка одного ядра CPU. Це опубліковані показники, а не вимірювання з цього посібника. Тест виконували з k3s v1.26.5, з увімкненими всіма packaged components, а також стеком моніторингу Prometheus і Grafana. Тому показник враховує реальне навантаження, а не порожній кластер. Заміна sqlite на embedded etcd збільшила споживання до 1,606 MB. Agent node, на якому працюють kubelet і containerd без control plane, споживав 275 MB. Задокументований мінімум для server — 2 cores і 2 GB RAM. Цей мінімум охоплює k3s і його packaged components до запуску ваших workload.
Практичний висновок: на VPS із 2 GB control plane і вбудовані add-ons залишають дуже мало вільних ресурсів. Під час нестачі ресурсів kubelet насамперед починає виселяти pods. 4 GB — комфортний мінімум для одного node з кількома невеликими сервісами. Виміряйте ресурси на власному сервері, а не покладайтеся на будь-які опубліковані показники, зокрема й цей.
free -h
sudo k3s kubectl top node
sudo k3s kubectl get pods -AЗапустіть free -h до встановлення, а потім ще раз після того, як кожен pod у kube-system матиме стан Running. Різниця покаже, скільки control plane споживає на вашому обладнанні. k3s kubectl top node повертає error: Metrics API not available протягом першої або другої хвилини після встановлення, оскільки metrics-server ще нічого не зібрав. Це не помилка. Якщо ви одночасно плануєте використовувати один сервер для цього та іншої роботи, наведені в розділі про розрахунок RAM і CPU для VPS правила розрахунку застосовуються без змін.
Установіть k3s із фіксацією версії, а не на latest
Команда швидкого запуску, яку зазвичай копіюють, встановлює версію, на яку в день запуску вказує stable channel. Для сервера, який має працювати тривалий час, зафіксуйте версію. k3s публікує окремий channel для кожної minor-версії Kubernetes, тому INSTALL_K3S_CHANNEL=v1.36 отримує лише patch-релізи в межах v1.36 і не переходить автоматично на іншу minor-версію. Станом на August 2026 stable channel вказує на v1.36.3+k3s1.
Спочатку створіть файл конфігурації, а потім виконайте інсталяцію. Під час запуску k3s читає /etc/rancher/k3s/config.yaml, тому його параметри застосовуються під час першого запуску та всіх наступних.
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'EOF'
tls-san:
- k3s.example.com
EOF
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=v1.36 sh -Щоб зафіксувати один точний реліз, а не channel, використовуйте INSTALL_K3S_VERSION=v1.36.3+k3s1. Знак плюс є частиною tag. Потім перевірте, що сервіс запустився.
k3s --version
sudo systemctl status k3s
sudo k3s kubectl get node
sudo k3s kubectl get pods -Akubectl get node має приблизно за тридцять секунд показати один node зі STATUS Ready, а кожен pod у kube-system має перейти до стану Running або Completed. Якщо node завис у стані NotReady, зазвичай це означає, що container runtime не запустився, тому перегляньте sudo journalctl -u k3s -n 100 --no-pager. На нетиповому образі VPS виконайте sudo k3s check-config до початку іншої діагностики: команда показує відсутні можливості ядра, що значно швидше, ніж аналіз журналів.
Чому порт 80 уже використовується і чим доведеться поступитися, щоб це виправити
Це типова помилка на VPS, де вже працює якийсь сервіс. Встановлення завершується успішно. Після цього Traefik не отримує адресу, а сайт, який уже працював, продовжує відкриватися. Тому проблема стає помітною лише під час спроби звернутися до ingress.
Механізм такий: bundled chart Traefik створює Service типу LoadBalancer на портах 80 і 443. ServiceLB обробляє цей запит, створюючи DaemonSet із невеликих pod-ів, назви яких мають префікс svclb-. Вони резервують ці номери портів як hostPort на кожному вузлі. hostPort публікує порт контейнера безпосередньо в мережевому просторі імен вузла, так само як це робить docker run -p 80:80. Якщо nginx, Caddy, Apache або інший контейнер уже займає порт 80, ядро не може видати його вдруге, тому планувальнику нікуди розмістити pod.
sudo k3s kubectl -n kube-system get pods
sudo k3s kubectl -n kube-system get svc traefik
sudo ss -lntp '( sport = :80 or sport = :443 )'Ви побачите pod svclb у стані Pending, а Service — без зовнішньої адреси:
svclb-traefik-8f2c1a-r6k9x 0/2 Pending 0 3m
traefik LoadBalancer 10.43.62.11 <pending> 80:31480/TCP,443:30219/TCPКоманда kubectl -n kube-system describe pod svclb-traefik-... безпосередньо показує причину:
0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.Команда ss -lntp показує, який процес утримує порт. Є кілька способів вирішити проблему, і кожен має свою ціну.
Передайте порти k3s. Зупиніть і вимкніть наявний вебсервер, а потім дозвольте Traefik використовувати порти 80 і 443. Це правильний варіант, якщо VPS має бути вузлом k3s і на ньому більше нічого не працюватиме, а всі наявні сервіси будуть доступні через Ingress.
Вимкніть ServiceLB і залиште наявний проксі. Виконайте встановлення з параметром --disable=servicelb. Service типу LoadBalancer все одно виділяє NodePort, тому Traefik буде доступний на високому порту, наприклад 31480, а nginx або Caddy проксуватиме запити до 127.0.0.1:31480. Ви втрачаєте зовнішню адресу: Service назавжди показуватиме <pending>. Це може виглядати як помилка, хоча насправді є свідомим вибором.
Вимкніть Traefik і маршрутизуйте запити власним проксі. Виконайте встановлення з параметром --disable=traefik. У такому разі ingress controller не буде, тому Ingress-об’єкти взагалі нічого не робитимуть: вони залишаться в API, але жоден controller не стежитиме за ними. Це нормально, якщо ви маршрутизуєте запити від host proxy до NodePort. Це також чесний вибір, якщо ви вже знаєте, як оброблятимете HTTP. Якщо ви ще не вирішили, що має працювати перед застосунками, спочатку визначтеся з вибором між nginx, Caddy і Traefik як reverse proxy, а вже потім щось вимикайте.
Обидва параметри потрібно вказати під час встановлення або у файлі конфігурації:
tls-san:
- k3s.example.com
disable:
- traefik
- servicelbМожна також відредагувати цей файл після встановлення та виконати sudo systemctl restart k3s, оскільки --disable не лише пропускає компонент під час встановлення. Він також видаляє вже розгорнутий компонент, тому зміна застосовується до кластера, який працює.
Щоб залишити Traefik, але змінити конфігурацію chart, не редагуйте /var/lib/rancher/k3s/server/manifests/traefik.yaml. k3s перезаписує цей файл значеннями за замовчуванням під час кожного запуску. Натомість створіть окремий файл у тому самому каталозі, оскільки все, що міститься в /var/lib/rancher/k3s/server/manifests, автоматично застосовується під час запуску та після зміни файлу на диску.
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
forwardedHeaders:
trustedIPs:
- 10.0.0.0/8У цьому прикладі задано одне значення chart Traefik — довірені адреси proxy. Цей самий механізм дає змогу задавати будь-які інші значення, доступні в chart, зокрема порти.
Постійне сховище на одному вузлі
k3s постачається зі стандартним StorageClass під назвою local-path, який використовує Rancher's local-path provisioner. PersistentVolumeClaim без storageClassName отримує цей StorageClass. Томи зберігаються в /var/lib/rancher/k3s/storage: для кожного тому створюється окремий підкаталог на локальному диску вузла.
sudo k3s kubectl get storageclass
sudo k3s kubectl get pvc -A
sudo ls -l /var/lib/rancher/k3s/storageФраза «на локальному диску вузла» має два наслідки. Вони проявляються не одразу, а під час подальшої експлуатації.
StorageClass використовує volumeBindingMode: WaitForFirstConsumer, тому новий PVC залишається у стані Pending, доки pod фактично не змонтує його. Команда kubectl describe pvc виводить:
waiting for first consumer to be created before bindingЦе нормальна поведінка. Тому створення PVC без pod і очікування не змінить його стан.
Після прив’язування том отримує node affinity для вузла, на якому його було створено. Усі pod, які використовують цей claim, можуть працювати лише на цьому вузлі протягом усього часу існування тому. На одному вузлі ви цього не помітите. Якщо пізніше додати другий вузол, pod, який не переміщується, може виглядати як помилка scheduler. Виконайте kubectl get pv -o yaml і перевірте hostname у nodeAffinity.
Резервне копіювання потрібно налаштувати самостійно. Пересоздання VPS видаляє цей каталог. Те саме робить наведений нижче скрипт видалення. Створюйте резервну копію /var/lib/rancher/k3s/storage, а також sqlite datastore у /var/lib/rancher/k3s/server/db/state.db. Копіюйте datastore, коли сервіс зупинений, оскільки це активна база даних. Альтернативний підхід — вважати кластер одноразовим і зберігати всі manifest у git.
Вхідний трафік і TLS
Якщо Traefik залишено увімкненим, достатньо стандартного об’єкта Ingress.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: traefik
tls:
- hosts:
- hello.example.com
secretName: hello-tls
rules:
- host: hello.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port:
number: 80Сертифікати не з’являються автоматично. Зазвичай використовують cert-manager, встановлений із опублікованого маніфесту, а також один ClusterIssuer. Станом на August 2026 актуальною є версія v1.21.1.
sudo k3s kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.1/cert-manager.yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
email: you@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- http01:
ingress:
ingressClassName: traefikПід час перевірки HTTP-01 сервер ACME (automatic certificate management environment) підключається до http://hello.example.com/.well-known/acme-challenge/... із публічного інтернету. Тому запис DNS A уже має вказувати на VPS, а порт 80 має бути доступним для Traefik. Якщо ви вимкнули ServiceLB і розмістили власний проксі перед ним, цей проксі також має передавати запити до шляху перевірки. Інакше cert-manager зависає, а в об’єкті Challenge з’являється таке повідомлення:
Waiting for HTTP-01 challenge propagation: wrong status code '404', expected '200'Контролюйте випуск сертифіката за допомогою sudo k3s kubectl describe certificate hello-tls і sudo k3s kubectl get order,challenge -A.
kubeconfig і чому API-сервер залишається приватним
k3s записує облікові дані адміністратора до /etc/rancher/k3s/k3s.yaml. Файл належить root і за замовчуванням створюється з правами 600. Він містить клієнтський сертифікат із правами cluster-admin, тому той, хто може його прочитати, отримує повний контроль над кластером.
sudo k3s kubectl get node
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodeОкремо встановлений kubectl без заданої змінної KUBECONFIG завершується помилкою The connection to the server localhost:8080 was refused - did you specify the right host or port?, оскільки використовує значення за замовчуванням, яке не пов’язане з k3s. Установіть KUBECONFIG або використовуйте sudo k3s kubectl — ця команда самостійно читає правильний файл.
Ви побачите рекомендацію використовувати --write-kubeconfig-mode 644, щоб звичайний користувач міг запускати kubectl. Важливо розуміти наслідок: облікові дані cluster-admin стають доступними для читання кожному локальному обліковому запису на машині. На сервері з одним адміністратором це може бути прийнятним компромісом. На спільному сервері — ні. Копіювання файлу надає доступ одному користувачеві, не відкриваючи його для інших:
mkdir -p ~/.kube
sudo install -o $USER -g $USER -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/configРядок server: у цьому файлі містить https://127.0.0.1:6443. Щоб використовувати kubectl із ноутбука, не відкривайте порт 6443 для інтернету. Публічний Kubernetes API постійно перебуває під атаками, а відкритий API може призвести до того, що невеликий кластер почнуть використовувати для майнінгу криптовалюти сторонні особи. Передайте трафік через SSH і залиште адресу у файлі без змін:
ssh -N -L 6443:127.0.0.1:6443 user@your-vps
KUBECONFIG=./k3s.yaml kubectl get nodeЯкщо потрібно підключатися до API через адресу приватної мережі, під час встановлення використайте tls-san із зазначенням цього імені або адреси, а потім змініть у скопійованому файлі рядок server: відповідно до неї. Без запису SAN (subject alternative name) kubectl відхиляє підключення:
x509: certificate is valid for 127.0.0.1, 10.43.0.1, not 203.0.113.10Документовані вхідні порти кластера: TCP 6443 для API, UDP 8472 для flannel VXLAN між вузлами та TCP 10250 для метрик kubelet. На одному вузлі жоден із них не потрібно відкривати для інтернету.
containerd — це не Docker
k3s використовує власний вбудований containerd і не має спільного сховища образів із Docker. Образ, який ви щойно зібрали за допомогою docker build, невидимий для k3s, тому pod завершується з помилкою ErrImagePull, навіть якщо docker images показує цей образ. Імпортуйте його явно:
docker save myapp:0.1 | sudo k3s ctr images import -
sudo k3s crictl imagesПісля цього не використовуйте для цього контейнера тег :latest, оскільки за замовчуванням :latest має значення imagePullPolicy для Always, і kubelet у будь-якому разі звертається до registry. Для будь-якого іншого тегу за замовчуванням використовується IfNotPresent, який застосовує імпортований вами образ. Одночасний запуск обох компонентів на одному VPS працює. Важливо знати, що звичайне встановлення Docker на VPS і k3s на одному комп’ютері мають окремі сховища образів і власні правила iptables.
Як видалити k3s
Інсталятор створює скрипт видалення. Часткового видалення та скасування цієї операції немає.
sudo /usr/local/bin/k3s-uninstall.shСкрипт зупиняє та видаляє сервіс, видаляє сховище даних, видаляє дані persistent volume, видаляє конфігурацію вузла та видаляє інструменти, додані інсталятором. На вузлі-агенті скрипт має назву k3s-agent-uninstall.sh. Спочатку скопіюйте все з /var/lib/rancher/k3s/storage на інший носій або сервер, оскільки цей каталог також буде видалено. Після цього перевірте за допомогою ip link show і sudo ss -lntp, що жоден процес не утримує порти або мережеві інтерфейси. Залишковий інтерфейс cni0 або flannel.1 буде видалено під час наступного перезавантаження.
Виявити, що один вузол Kubernetes створює більше зайвої складності, ніж потрібно для завдання, — нормальний результат, а не помилка. Перенесення таких робочих навантажень назад до Compose зазвичай займає один робочий день.
Типові сценарії відмов і повідомлення, які ви побачите
Node NotReady або k3s перезапускається в циклі. Спочатку прочитайте sudo journalctl -u k3s -n 200 --no-pager. На невеликому VPS поширена причина — процес завершує kernel out-of-memory killer. У dmesg це буде рядок із назвою k3s-server. Задокументований мінімум у 2 GB — це справді нижня межа.
Pod завис у стані Pending. kubectl describe pod щоразу вказує причину. Insufficient memory або Insufficient cpu означає, що на вузлі більше немає вільних ресурсів. didn't have free ports — це описана вище колізія hostPort. waiting for first consumer для PVC означає, що WaitForFirstConsumer працює як належить.
ImagePullBackOff. Або такого тегу немає в жодному registry, до якого має доступ вузол, або ви зібрали image за допомогою Docker і не імпортували його в containerd.
Traefik відповідає, а застосунок — ні. Тіло відповіді 404 page not found надходить від самого Traefik і означає, що запит надійшов, але жоден router не підійшов. Перевірте, що host Ingress відповідає імені, яке ви ввели, а ingressClassName має значення traefik.
DNS у кластері не працює, хоча host успішно визначає імена. Перевірте CoreDNS за допомогою sudo k3s kubectl -n kube-system logs -l k8s-app=kube-dns. Повідомлення на кшталт plugin/loop: Loop ... detected for zone "." не дає CoreDNS запуститися. Це відбувається тому, що CoreDNS пересилає запити до resolver, який пересилає їх назад до CoreDNS. Саме це робить loopback-адреса у /etc/resolv.conf. Вкажіть для k3s фактичний upstream-файл за допомогою --resolv-conf /run/systemd/resolve/resolv.conf.
FAQ
Чи варто запускати k3s на одному VPS?
Це має сенс, якщо вам потрібен саме Kubernetes API: щоб вивчати його на машині під власним контролем, зберігати розгортання у вигляді переносних manifest-файлів, запускати програмне забезпечення, яке публікує лише Helm chart, або створювати систему, яку згодом буде перенесено в керований кластер. Якщо вам потрібно лише запускати контейнери, k3s недоцільний, оскільки Docker Compose робить це з набагато меншими витратами на обслуговування й залишає приблизно на один гігабайт більше вільної RAM. Один вузол не забезпечує high availability, тому надійність не є причиною обирати таку конфігурацію.
Чому мій сервіс k3s LoadBalancer залишається у стані Pending?
ServiceLB створює svclb- pods, які резервують порти сервісу на вузлі через hostPort. Тому вони запускаються лише там, де ці порти вільні. Якщо nginx або інший проксі вже використовує порт 80, pod залишається у стані Pending, а сервіс не отримує зовнішню адресу. kubectl -n kube-system describe pod svclb-... повідомляє 0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports. Звільніть порт або перевстановіть систему з --disable=servicelb і налаштуйте проксіювання на NodePort, який сервіс усе одно виділяє.
Скільки RAM потребує k3s на VPS?
Задокументований мінімум для server node становить 2 ядра та 2 GB. Цього достатньо для k3s і його пакетних компонентів до запуску ваших workloads. Власне профілювання проєкту показало, що server node використовує 1,596 MB із запущеним на ньому monitoring stack. Тому вважайте 2 GB мінімальним обсягом, а 4 GB — першим розміром, за якого один вузол працює комфортно. Перевірте власну машину за допомогою free -h до інсталяції та повторіть перевірку після того, як кожен pod у kube-system перейде у стан Running.
Чи можна запускати Docker і k3s на одному VPS?
Так, і вони залишаються окремими. k3s використовує власний вбудований containerd, тому образ, створений за допомогою docker build, не буде йому доступний, доки ви не виконаєте docker save myapp:0.1 | sudo k3s ctr images import -. Кожна система також створює власні правила iptables і власні bridge networks. Контролюйте загальний обсяг RAM, оскільки Docker, k3s і ваші контейнери не помістяться на машині з 2 GB.
Як повністю видалити k3s?
Виконайте sudo /usr/local/bin/k3s-uninstall.sh на server node або sudo /usr/local/bin/k3s-agent-uninstall.sh на agent. Команда зупиняє сервіс, видаляє datastore, видаляє дані persistent volumes у /var/lib/rancher/k3s/storage і прибирає bundled tools. Спочатку скопіюйте з машини всі дані, які потрібно зберегти, оскільки скасувати цю операцію неможливо. Залишковий мережевий інтерфейс cni0 або flannel.1 зникне після наступного перезавантаження.