SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor

Стоит ли запускать k3s на одном VPS

Узнайте, когда k3s оправдан для одного узла. Разбираем потребление RAM, конфликт порта 80 при установке и почему Docker Compose иногда остается более надежным решением.

Что такое k3s и что дает один его узел

k3s — это полноценный дистрибутив Kubernetes, упакованный в один бинарный файл. Запуск k3s на одном VPS предоставляет доступ к реальному API Kubernetes без необходимости развертывания control plane из трех узлов. Это сертифицированный дистрибутив, поэтому манифесты, примененные здесь, будут работать и в управляемом кластере в будущем. Установка выполняется одной командой и занимает около минуты. Платой за это является оперативная память, которая становится недоступна для ваших приложений, а также появление новых сценариев сбоев, невозможных при использовании Docker Compose.

Компания SUSE создает k3s для периферийных вычислений и небольших инсталляций, и все отличия от оригинального Kubernetes направлены на уменьшение размера дистрибутива. Хранилищем данных по умолчанию является sqlite, работающий через прослойку kine, а не etcd, поэтому нет необходимости поддерживать кворум etcd. containerd встроен непосредственно в бинарный файл, а не устанавливается отдельно. Тот же файл включает CoreDNS для кластерного DNS, Traefik в качестве ingress-контроллера, ServiceLB (также известный как klipper-lb) для работы сервисов типа LoadBalancer без облачного провайдера, local-path provisioner для постоянных томов (persistent volumes), metrics-server и flannel для сетевого взаимодействия подов. Каждый из этих компонентов запускается по умолчанию. Именно поэтому конфликт портов, описанный ниже, является самой частой первичной проблемой на VPS, где уже были запущены другие службы.

Когда стоит использовать один узел k3s

Используйте k3s, если вам нужен именно Kubernetes API: вы изучаете Kubernetes на подконтрольной машине или нужное вам ПО поставляется только в виде Helm chart. Портируемость манифестов также важна, так как написанный здесь Deployment без изменений переносится в управляемый кластер. Используйте Docker Compose, если вам нужны сами приложения. Compose запускает те же контейнеры с гораздо меньшим количеством компонентов, а Compose file on a VPS спустя год читается проще, чем директория с манифестами.

Чётко понимайте, чего не даёт один узел.

  • Отсутствие высокой доступности. При перезагрузке VPS все рабочие нагрузки останавливаются. Kubernetes пытается перенести под на другой узел, но другого узла нет.
  • Отсутствие rolling update, поддерживающего работу сервиса, если только приложение не допускает работу двух реплик на одной машине с общим томом.
  • Хранилище, привязанное к конкретному серверу по причинам, описанным в разделе local-path ниже.
  • Control plane, который потребляет около гигабайта оперативной памяти независимо от того, развернули вы что-то или нет.

Всё это не делает k3s плохим выбором. Это делает его плохим выбором по той причине, которую обычно называют — надёжность. Если вам на самом деле нужно несколько машин для построения полноценного многоузлового кластера, это решение принимается в первую очередь: Proxmox on your own hardware against a rented VPS определяет, откуда возьмутся узлы, прежде чем k3s определит, что на них будет запущено.

Потребление RAM и CPU в k3s до развертывания каких-либо нагрузок

Проект k3s публикует измеренные показатели, а не оценочные данные. Изучите их внимательно, так как цифры, которые часто цитируют, не соответствуют состоянию простоя k3s.

ChartPublished k3s resource use, 95th percentile, Intel 8375C, k3s v1.26.5
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
  }
]

Серверный узел в этом тесте использовал 1,596 МБ оперативной памяти на 95-м процентиле и около 6 процентов одного ядра. Это опубликованные данные, а не результаты измерений из данного руководства; тест проводился на k3s v1.26.5 со всеми встроенными компонентами, а также со стеком мониторинга Prometheus и Grafana, поэтому цифры включают реальную нагрузку, а не пустой кластер. Замена sqlite на встроенный etcd увеличила потребление до 1,606 МБ. Агентский узел, на котором запущены только kubelet и containerd без control plane, использовал 275 МБ. Документированный минимум для сервера составляет 2 ядра и 2 ГБ оперативной памяти; этот минимум покрывает k3s и встроенные компоненты до запуска ваших рабочих нагрузок.

Практический вывод: на VPS с 2 ГБ памяти control plane и комплектные дополнения оставляют очень мало ресурсов, и при возникновении нагрузки первым делом kubelet начнет вытеснять поды. 4 ГБ — это комфортный минимум для одного узла с несколькими небольшими сервисами. Измеряйте показатели на своем оборудовании, а не доверяйте опубликованным цифрам, включая эти.

free -h
sudo k3s kubectl top node
sudo k3s kubectl get pods -A

Запустите free -h до установки и повторно после того, как все поды в kube-system перейдут в состояние Running. Разница и есть стоимость работы control plane на вашем оборудовании. k3s kubectl top node возвращает error: Metrics API not available в течение первой минуты или двух после установки, так как metrics-server еще не успел собрать данные. Это не является ошибкой. Если вы подбираете размер одного сервера для этой задачи и других работ одновременно, расчеты из оценки RAM и CPU для VPS применимы здесь без изменений.

Установка k3s с фиксацией версии, а не latest

Команда для быстрой установки, которую копируют все, использует версию, на которую в данный момент указывает канал stable. На сервере, который должен работать долго, версию нужно зафиксировать. k3s публикует отдельный канал для каждой минорной версии Kubernetes, поэтому INSTALL_K3S_CHANNEL=v1.36 будет следовать за патч-релизами внутри ветки v1.36 и никогда не обновит минорную версию без вашего ведома. По состоянию на август 2026 года канал stable указывает на 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 -

Чтобы зафиксировать конкретный релиз вместо канала, используйте INSTALL_K3S_VERSION=v1.36.3+k3s1. Знак «плюс» является частью тега. После этого проверьте, что система запущена.

k3s --version
sudo systemctl status k3s
sudo k3s kubectl get node
sudo k3s kubectl get pods -A

kubectl get node должен показать одну ноду со статусом Ready примерно через тридцать секунд, а все поды в kube-system должны перейти в состояние Running или Completed. Если нода зависла в статусе NotReady, это обычно означает, что среда выполнения контейнеров не запустилась; в этом случае изучите sudo journalctl -u k3s -n 100 --no-pager. На нестандартных образах VPS перед отладкой запустите sudo k3s check-config: утилита сообщит об отсутствующих функциях ядра, что позволит найти причину быстрее, чем при анализе логов.

Почему порт 80 уже занят и от чего придется отказаться для решения проблемы

Это ошибка, с которой сталкиваются пользователи VPS, где уже запущен какой-либо сервис. Установка проходит успешно. Однако Traefik не получает адрес, а ваш текущий сайт продолжает работать, поэтому проблема не видна, пока вы не попытаетесь обратиться к Ingress.

Механизм: встроенный чарт Traefik создает Service типа LoadBalancer на портах 80 и 443. ServiceLB обрабатывает этот запрос, создавая DaemonSet из небольших подов с префиксом svclb-, которые занимают эти порты как hostPort на каждом узле. hostPort публикует порт контейнера напрямую в сетевое пространство имен узла, точно так же, как это делает docker run -p 80:80. Если nginx, Caddy, Apache или другой контейнер уже использует порт 80, ядро не позволит занять его повторно, поэтому планировщику негде разместить под.

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 )'

Вы увидите, что под svclb находится в состоянии Pending, а у сервиса нет внешнего адреса:

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. Вы теряете внешний адрес: сервис будет постоянно отображать <pending>, что выглядит как ошибка, хотя это осознанный выбор.

Отключите Traefik и используйте собственный прокси. Выполните установку с флагом --disable=traefik. В этом случае у вас не будет Ingress-контроллера, поэтому объекты Ingress не будут выполнять никаких действий: они останутся в API без контроллера, который их обрабатывает. Это приемлемо, если вы проксируете запросы с хоста на NodePorts, и это честный выбор, если вы уже знаете, как именно хотите обрабатывать HTTP-трафик. Если вы еще не решили, что использовать в качестве фронтенда, определитесь с выбором между nginx, Caddy и Traefik в качестве reverse proxy, прежде чем что-либо отключать.

Оба флага указываются при запуске установщика или в файле конфигурации:

tls-san:
  - k3s.example.com
disable:
  - traefik
  - servicelb

Редактирование этого файла после установки и выполнение sudo systemctl restart k3s также работает, поскольку --disable не просто пропускает компонент при установке. Он также удаляет уже развернутый компонент, поэтому изменения вступают в силу в работающем кластере.

Чтобы сохранить Traefik, но изменить конфигурацию чарта, не редактируйте /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

В этом примере задается одно значение чарта Traefik — доверенные адреса прокси. Тот же механизм позволяет задать любое другое значение, доступное в чарте, включая порты.

Постоянное хранилище на одном узле

k3s поставляется с StorageClass по умолчанию под названием local-path, который работает на базе local-path provisioner от Rancher. PersistentVolumeClaim без указания storageClassName использует именно его. Тома размещаются в /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 до тех пор, пока под фактически не смонтирует его. Команда kubectl describe pvc выведет следующее:

waiting for first consumer to be created before binding

Это нормальное поведение, поэтому создание PVC отдельно и ожидание его готовности не приведет к переходу в состояние Bound.

После привязки том получает привязку к узлу (node affinity), на котором он был создан. Это закрепляет каждый под, использующий данный том, за этим узлом на весь срок жизни тома. На одном узле вы этого не заметите. Если позже добавить второй узел, под, который отказывается перемещаться, будет выглядеть как ошибка планировщика, пока вы не выполните kubectl get pv -o yaml и не обнаружите имя хоста в поле nodeAffinity.

Резервное копирование — ваша задача. Переустановка VPS уничтожает этот каталог, как и скрипт удаления, приведенный ниже. Создавайте резервные копии /var/lib/rancher/k3s/storage, а также базы данных sqlite в /var/lib/rancher/k3s/server/db/state.db. Копирование базы данных следует выполнять при остановленном сервисе, так как это «живая» БД. Альтернативный подход — считать кластер одноразовым и хранить все манифесты в git.

Ingress и 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. Версия v1.21.1 является актуальной на август 2026 года.

sudo k3s kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.1/cert-manager.yaml
apiVersion: 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 server остаётся приватным

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 — это постоянная мишень, и именно через открытый доступ небольшие кластеры часто начинают использоваться для майнинга криптовалюты. Используйте 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, поэтому под завершается с ошибкой ErrImagePull, несмотря на то, что docker images показывает его в списке. Импортируйте его явно:

docker save myapp:0.1 | sudo k3s ctr images import -
sudo k3s crictl images

Затем избегайте использования тега :latest для этого контейнера, так как :latest по умолчанию устанавливает imagePullPolicy в значение Always, и kubelet в любом случае обращается к реестру. Любой другой тег по умолчанию использует IfNotPresent, что задействует импортированный вами образ. Запуск обоих инструментов на одном VPS возможен, при этом важно учитывать, что стандартная установка Docker на VPS и k3s хранят свои образы и правила iptables независимо друг от друга на одной машине.

Как удалить k3s

Установщик создает скрипт для удаления. Частичное удаление или отмена операции не предусмотрены.

sudo /usr/local/bin/k3s-uninstall.sh

Скрипт останавливает и удаляет службу, очищает хранилище данных, удаляет данные постоянных томов (persistent volumes), конфигурацию узла и инструменты, добавленные при установке. На узле-агенте вместо этого используется скрипт k3s-agent-uninstall.sh. Сначала скопируйте все содержимое /var/lib/rancher/k3s/storage за пределы сервера, так как этот каталог будет удален. После этого убедитесь, что никакие процессы не занимают порты или сетевые интерфейсы, используя ip link show и sudo ss -lntp. Оставшиеся интерфейсы cni0 или flannel.1 будут удалены при следующей перезагрузке системы.

Решение о том, что один узел Kubernetes — это избыточный инструмент для текущей задачи, является нормальным результатом, а не ошибкой. Перенос рабочих нагрузок обратно на Compose обычно занимает вторую половину дня.

Типичные сбои и сообщения об ошибках

Узел в состоянии 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. Либо тег отсутствует в доступном узлу реестре, либо вы собрали образ через Docker, но не импортировали его в containerd.

Traefik отвечает, а приложение — нет. Ответ с телом 404 page not found исходит от самого Traefik и означает, что запрос получен, но ни один маршрутизатор не подошел. Проверьте, что Ingress host совпадает с указанным именем, а ingressClassName имеет значение traefik.

DNS кластера не работает, хотя хост разрешает имена корректно. Проверьте CoreDNS с помощью sudo k3s kubectl -n kube-system logs -l k8s-app=kube-dns. Сообщение вида plugin/loop: Loop ... detected for zone "." препятствует запуску CoreDNS. Это происходит, если CoreDNS пересылает запросы на резолвер, который возвращает их обратно, что и делает loopback-адрес в /etc/resolv.conf. Укажите k3s реальный вышестоящий файл через --resolv-conf /run/systemd/resolve/resolv.conf.

FAQ

Стоит ли запускать k3s на одном VPS?

Это имеет смысл, если вам нужен Kubernetes API: для обучения на подконтрольной машине, для обеспечения переносимости развертываний в виде манифестов, для запуска ПО, которое поставляется только в виде Helm chart, или для создания прототипов, которые позже будут перенесены в управляемый кластер. Это не имеет смысла, если вам нужно просто запускать контейнеры, так как Docker Compose справляется с этим при гораздо меньших затратах на обслуживание и освобождает около гигабайта оперативной памяти. Один узел не обеспечивает высокую доступность, поэтому надежность не может быть причиной выбора такого решения.

Почему мой сервис k3s типа LoadBalancer остается в статусе Pending?

ServiceLB создает поды svclb-, которые занимают порты сервиса через hostPort на узле, поэтому они планируются только там, где эти порты свободны. Если nginx или другой прокси уже занял 80 порт, под остается в статусе 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, который сервис продолжает выделять.

Сколько оперативной памяти нужно k3s на VPS?

Документированный минимум для узла-сервера составляет 2 ядра и 2 GB, что покрывает потребности k3s и его встроенных компонентов до запуска ваших рабочих нагрузок. Собственное профилирование проекта показало, что узел-сервер потребляет 1,596 MB с запущенным стеком мониторинга, поэтому считайте 2 GB нижним порогом, а 4 GB — первым объемом, при котором один узел работает комфортно. Измерьте показатели вашей системы с помощью free -h до установки и повторно после того, как каждый под в kube-system перейдет в состояние Running.

Можно ли запускать Docker и k3s на одном VPS?

Да, они остаются изолированными. k3s использует собственный встроенный containerd, поэтому образ, собранный через docker build, не будет виден в k3s, пока вы не выполните docker save myapp:0.1 | sudo k3s ctr images import -. Каждый из них также создает свои правила iptables и свои мостовые сети. Следите за общим объемом памяти, так как Docker, k3s и ваши контейнеры не поместятся на машине с 2 GB RAM.

Как полностью удалить k3s?

Запустите sudo /usr/local/bin/k3s-uninstall.sh на узле-сервере или sudo /usr/local/bin/k3s-agent-uninstall.sh на агенте. Это остановит службу, удалит хранилище данных, удалит данные постоянных томов (persistent volumes) в директории /var/lib/rancher/k3s/storage и удалит встроенные инструменты. Предварительно скопируйте все важные данные с сервера, так как процедуру нельзя отменить. Оставшийся сетевой интерфейс cni0 или flannel.1 исчезнет после следующей перезагрузки.

#kubernetes#k3s#docker-compose#ingress#sizing