Kubernetes в полном сборе славится аппетитом к железу и словарю терминов: отдельно контрольная плоскость, отдельно хранилище состояния, отдельно сеть, вход, сертификаты и дюжина контроллеров вокруг. Для pet-проекта или песочницы для обучения этот парад избыточен. K3s упаковывает тот же Kubernetes в один бинарник: контрольная плоскость и воркер в одном процессе, встроенные сеть и вход, сертификационная подсистема ставится одной командой. Название честно намекает: половина от k8s, округлённая вверх. Ниже полный цикл на одной VPS: установка, первое приложение, ingress на встроенном Traefik и автоматические сертификаты для домена.

Один бинарник вместо тяжёлой платформы и что уже внутри

K3s это полноценный Kubernetes, собранный так, чтобы жить на одном сервере без свиты. Проект родился в стане Rancher, теперь части SUSE, с прицелом на оборудование, где обычному кластеру тесно: одноплатные машины, модемы, периферия, серверы с гигабайтом памяти и процессорами ARM. Внутри одного процесса сидят серверная часть и воркер, а вместо распределённого хранилища etcd используется локальная база SQLite через прослойку kine: для одиночного узла этого достаточно, состояние кластера лежит в файлах под /var/lib/rancher/k3s/server/db, и резервная копия этих файлов спасает весь кластер целиком. Бинарник размером в несколько десятков мегабайт тащит в себе целый дистрибутив платформы, и это же свойство делает обновление тривиальным: повторный запуск скрипта установки накатывает свежую версию поверх старой, сохранив состояние.

Из коробки приезжает всё, без чего обычный Kubernetes требует отдельных манифестов. Состав встроенных компонентов выглядит так:

  1. Контейнерный рантайм containerd для исполнения подов;
  2. Сеть узла на flannel и раздача имён через CoreDNS;
  3. Входная точка Traefik актуальной третьей ветки;
  4. Томы через local-path provisioner и встроенная эмуляция LoadBalancer;
  5. metrics-server, благодаря которому kubectl top работает сразу. На осень 2026 актуальный релиз K3s повторяет Kubernetes 1.37, и это не урезанная пародия, а сертифицированная сборка: манифесты и привычки с большого кластера переезжают без перевода. Для закрытых сетей существует и автономная установка из архива образов, без обращения к внешним реестрам. Когда одиночный узел вырастает в план на высокую доступность, тот же установщик умеет поднять режим с встроенным etcd и несколькими серверами, так что путь наверх не требует переезда на другой дистрибутив.

Аппетит к железу скромный: серверу с парой ядер и гигабайтом-двумя памяти хватает на всю связку плюс пару приложений. Полноценный кластер требует кратного размера, и в этом разница между обучением за копейки и бюджетом на мини ферму.

Установка скриптом одной командой и первый запуск с kubectl

Установка укладывается в строку, знакомую любому, кто ставил K3s хоть раз:

curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes
NAME   STATUS   ROLES                  AGE   VERSION
srv1   Ready    control-plane,master   60s   v1.37.1+k3s1

Скрипт скачивает бинарник, регистрирует службу systemd и через минуту кластер уже принимает команды. Желающие на стабильность вместо экспериментального канала задают переменную окружения с каналом stable, а диагностика встроенной утилитой k3s check-config показывает готовность ядра к контейнерным делам. Снаружи сервер слушает 6443 для API и 10250 для служебного канала, и оба порта разумно держать закрытыми от мира: администратор ходит по ssh, приложения попадают внутрь через 80 и 443. Конфигурация доступа живёт в /etc/rancher/k3s/k3s.yaml, и чтобы работать привычным kubectl, файл копируют в стандартное место:

mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER ~/.kube/config
kubectl get pods -A

Список подов системных пространств имён показывает весь вшитый комплект: traefik, coredns, metrics-server, local-path-provisioner. Журнал службы читается привычным journalctl -u k3s, а файл юнита лежит в /etc/systemd/system/k3s.service и правится для нестандартных флагов запуска. Для удаления кластера служит скрипт k3s-uninstall.sh из каталога установки: эксперименты на учебном сервере заканчиваются чисто, без осколков в системе.

Приложение в кластере через deployment и service

Классический путь приложения в Kubernetes начинается с деплоймента и сервиса. Императивные команды подходят для пробы:

kubectl create deployment web --image=nginx:stable
kubectl expose deployment web --port=80
kubectl get pods -o wide
kubectl scale deployment web --replicas=2

Деплоймент держит нужное число копий пода, сервис даёт им общее имя и адрес внутри кластера. Колонка NODE в выводе не даст соврать: обе копии живут на одном сервере, ведь кластер из одного узла. Приказы выполняются честно: упавший под пересоздаётся, а scale меняет число копий мгновенно. Для жизни приложение описывают манифестом:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:stable
          readinessProbe:
            httpGet:
              path: /
              port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Проба готовности в описании говорит платформе, когда под действительно готов принимать трафик, и ingress не отправит запрос в контейнер, который ещё поднимается. Применяется манифест командой kubectl apply -f app.yaml, изменения в файле применяются повторно тем же путём, а kubectl describe pod и kubectl logs отвечают на вопросы, если что-то пошло не по плану. Заглянуть в приложение с локальной машины помогает kubectl port-forward svc/web 8080:80, а картину потребления рисует kubectl top pods, работающий благодаря встроенному metrics-server. Обновление образа тоже держится в семействе честных команд:

kubectl set image deployment/web web=nginx:stable
kubectl rollout status deployment/web
kubectl rollout undo deployment/web

Первая подменяет образ, вторая показывает раскатку в реальном времени, третья возвращает прошлое состояние, если свежий образ закапризничал. Дальше живёт декларация, а не память о набранных командах.

Ingress на встроенном Traefik и маршрутизация по имени хоста

Сервис внутри кластера это ещё не сайт: наружу его выпускает ingress, и в K3s эту роль уже исполняет Traefik, слушая порты 80 и 443 сервера. Правило входа описывается манифестом:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: site
spec:
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Применяется одной командой:

kubectl apply -f ingress.yaml
kubectl get ingress
kubectl describe ingress site

Дальше работает DNS: A-запись домена указывает на адрес сервера, и запросы по имени хоста попадают в сервис web. Проверить правило можно и до DNS, эмулируя имя хоста заголовком:

CODE7 Манифест читается как маршрутка: имя хоста, путь, сервис, порт, и добавить рядом второе приложение с другим доменом стоит ещё восемь строк того же текста. Тип Prefix в пути означает сам путь и всё под ним, так что статике и API в одном домене хватает двух записей с разными путями. Встроенный klipper тем временем навешивает на вход балансировщик сервисов типа LoadBalancer, а в описании ingress видны адрес и события, если правило собралось криво.

Установка cert manager и выпуск сертификатов Let's Encrypt

Свой домен без HTTPS сегодня выглядит сыро, и следующий шаг закрывает вопрос навсегда. Подсистема cert-manager ставится одним манифестом из релиза:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.2/cert-manager.yaml
kubectl get pods -n cert-manager

Три пода подсистемы поднимаются за минуту. Дальше описывается издатель сертификатов, общий на весь кластер:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
    privateKeySecretRef:
      name: letsencrypt-key
    solvers:
      - http01:
          ingress:
            class: traefik

Опытные руки сначала неделю гоняют тестовый издатель с адресом сервера staging из той же конторы, чтобы эксперименты не выбивали лимиты боевого центра выдачи, и только потом переключаются на боевой адрес. В ingress добавляется секция TLS с именем секрета и аннотация с издателем:

metadata:
  name: site
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: site-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

После применения начинается самое интересное: подсистема создаёт запрос сертификата, проходит проверку владения доменом через 80 порт и складывает готовый ключ с цепочкой в секрет site-tls, а Traefik уже отвечает по 443. Команда kubectl get certificate показывает статус Ready, при заминке диагноз читается в ресурсах certificaterequest и challenge, где виден каждый шаг проверки, а самая частая причина молчания банальна: A-запись домена ещё не разъехалась по DNS. Для сертификатов на все поддомены разом нужен способ подтверждения через DNS запись, он настраивается отдельным решателем в издателе, а первому проекту хватает проверки через 80 порт. Продление происходит само, задолго до истечения, и в журналы о нём заглядывают раз в сезон из любопытства.

Отключение лишнего флагами и ресурсы одного сервера

Встроенный комплект удобен, пока не мешает. Если вход берёт на себя внешний прокси, Traefik отключают, если LoadBalancer не нужен, отключают эмулятор, если метрики не смотрят, экономят и их. Флаги передаются скрипту установки:

curl -sfL https://get.k3s.io | sh -s - --disable traefik,servicelb,metrics-server

Тонкая настройка живёт не в аргументах службы, а в файле /etc/rancher/k3s/config.yaml, где те же опции описываются списком disable, и после правки кластер перезапускается командой systemctl restart k3s. Такой файл читается легче, чем длинная строка аргументов, и переживает обновления установки. Для команды из нескольких человек права на конфигурацию расширяют флагом write-kubeconfig-mode, а рост проекта с одного узла начинается командой k3s agent на новой машине: токен узла лежит в /var/lib/rancher/k3s/server/node-token, и вторая машина присоединяется к первой за пару минут, превращая песочницу в маленький, но настоящий кластер. Один сервер закрывает и типичные роли: витрина с блогом, учебная среда для команды, пайплайн сборки, площадка для испытания манифестов перед большим кластером. Проверить версию после установки, а заодно убедиться, что служба поднялась, помогает пара k3s --version и systemctl is-active k3s.

Приложениями из реестров чартов заведует встроенный контроллер: манифест вида HelmChart из встроенной API группы для чартов описывает имя чарта, его версию и значения, а кластер сам ставит и обновляет пакет, как будто это обычный ресурс платформы. Никаких отдельных установок и ручных обновлений, конфигурация рядом с остальными манифестами проекта.

Один сервер тянет весь этот зоопарк удивительно спокойно: контрольная плоскость занимает доли ядра, и гигабайта-двух памяти хватает на кластер плюс приложение. Границы у схемы понятные: узел один, значит падение сервера останавливает всё сразу, а состояние требует аккуратности с копиями. Резервирование сводится к остановке службы и копированию каталога базы, задача на две минуты по cron. Восстановление симметрично: каталог возвращается на место, служба стартует, кластер забывает о неприятности. Для pet-проекта, портфолио, домашней лаборатории и учебной площадки, где каждый вечер можно сносить и пересобирать мир, это честная цена. Навыки при этом копятся настоящие: те же манифесты, тот же kubectl, тот же ingress и сертификаты, что и в больших кластерах, только на железе за копейки, а не за десятки тысяч.