Приложение в Managed Kubernetes на выделенном сервере

Как развернуть приложение в кластере Managed Kubernetes на выделенном сервере

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

Всем привет! С вами на связи Евгений Листраткин, ведущий инженер команды администрирования клиентских сервисов. Мы предоставляем услуги DevOps as a Service как в дата-центрах Selectel, так и на любых других площадках.

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

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

В современном мире IT контейнерные приложения стали де‑факто стандартом, а их оркестрация все чаще переходит под управление Kubernetes. Любая подобная система требует точной настройки базовых компонентов и круглосуточной поддержки — только так можно гарантировать стабильность и доступность развернутых приложений.

Однако в последние годы ситуация на кадровом IT-рынке заметно усложнилась. Поток соискателей вырос, компании внедряют жесткие фильтры отсева, а кандидаты пытаются их обойти. Из‑за обилия недостоверных резюме и описаний позиций поиск по‑настоящему квалифицированных специалистов занимает гораздо больше времени.

Добавим к этому урезанные бюджеты и растущий акцент на FinOps-практики. В результате получаем серьезно возросшую нагрузку на действующих инженеров: им приходится поддерживать новые приложения и сервисы, решать сетевые проблемы и брать на себя задачи с незакрытых должностей.

Когда растут обязанности инженеров, а закрытие вакансий буксует, логичным шагом становится переход на managed-продукты. Да, это не «серебряная пуля» от всех фундаментальных проблем, но облегчить жизнь такие решения точно способны.

По данным исследования State of DevOps 2025, доля managed-решений за прошлый год немного снизилась, хотя общее использование Kubernetes продолжает расти. Самостоятельная поддержка кластеров — управление master-нодами, настройка сетевой связности и интеграция с облачными сервисами — все еще сохраняет внушительную долю в общем объеме инсталляций.

Зачастую выбор в пользу self-hosted продиктован ограничениями облачных платформ. Инженерам может не хватать гибкости в управлении системными компонентами, нужного уровня изоляции инфраструктуры, производительности или экономической выгоды.

Частый пример таких барьеров — сложности с выделенными серверами. У одних провайдеров их просто нет, у других worker-ноды в managed-кластерах можно развернуть только на виртуальных машинах.

Все это вынуждало инженеров расчехлять инструменты самостоятельной настройки вроде Kubespray, а в самых требовательных проектах — собирать платформу с полного нуля. Мы в Selectel решили устранить эти препятствия — теперь кластеры могут включать группы нод на выделенных серверах, а их развертывание занимает не больше 60 минут. Можно снова отложить Kubespray подальше и избавить себя от лишней головной боли с контролем master-нод.

Преимущества

Чем же так выгодно использование выделенных серверов в кластерах?

Главное преимущество — полноценная зарезервированная мощность. Все вычислительные ресурсы полностью изолированы от соседей. Ну и куда же без FinOps — выделенный сервер обходится дешевле, чем облачные конфигурации аналогичного объема.

Как и облачные ресурсы, инсталляции на выделенных серверах имеют свои особенности. Из главного: пока не поддерживается автомасштабирование, а также популярная опция подключения сетевых дисков.

С подробным описанием Managed Kubernetes можно ознакомиться на странице продукта, а со всеми техническими особенностями сервиса — в документации.

Следить за доступными ресурсами кластера тоже пока приходится самостоятельно, а масштабировать вычислительные мощности — вручную. Для этого нужно увеличивать количество машин в существующей группе или добавлять новые нод-группы с доступными конфигурациями выделенных серверов.

Про расширение дискового пространства, а также о том, как в таких условиях работать с дисковой подсистемой и использовать persistent volumes в приложениях, поговорим на конкретных примерах ниже.

Создание кластера

Развертывание приложений на выделенных серверах логично начать с создания самого кластера. Как упоминалось ранее, управление master-нодами берет на себя облачный провайдер. Единственное отличие от классической инфраструктуры на виртуальных машинах кроется на этапе настройки нод-групп.

Как создать кластер Managed Kubernetes на выделенном сервере подробно описано в нашей документации.

Откроем раздел Managed Kubernetes и создадим новый кластер.

Интерфейс панели управления Selectel: стартовый экран раздела Managed Kubernetes с кнопкой создания готового кластера.

На этапе создания master-нод нужно выбрать фундаментальные параметры, которые в дальнейшем нельзя изменить без полного пересоздания кластера.

Регион — нод-группы будут развернуты в выбранной локации при условии, что имеются свободные ресурсы.

CNI (Container Network Interface) — по умолчанию применяется Calico, но также доступен Cilium.

Тип кластера — отказоустойчивый вариант резервирует master-ноды в трех экземплярах в разных пулах. Это значит, что control plane выйдет из строя только при одновременном отказе их всех. Базовый тип разворачивается без резервирования и больше подвержен рискам недоступности, но отлично подходит для тестовых и dev-сред.

Тип API (публичный или приватный) — при выборе приватного варианта внешний доступ к API закрыт, в таком случае для управления кластером придется самостоятельно настраивать jump host и прокидывать туннель.

Настройки кластера Managed Kubernetes: выбор региона Санкт-Петербург, версии K8s 1.33.7, сетевого плагина Calico и отказоустойчивого типа из 3 мастер-нод.

С master-нодами разобрались, переходим к worker-узлам. На этом этапе появляется возможность вместо облачных  выбрать выделенные серверы — их доступные настройки и рассмотрим.

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

Конфигурация ноды — набор ресурсов для каждого worker-узла. Здесь есть специфичный для выделенных серверов параметр — тарифный план, который определяет, в том числе, и минимальный срок аренды.

Также доступна настройка разметки диска. Помимо штатных корневого и загрузочного разделов, можно выделить /storage для локальных persistent volumes. Такой раздел хорошо подходит для постоянных данных в подах, но важно помнить, что они не мигрируют между серверами. Если не планируется запускать сервисы, жестко привязанные к конкретной ноде, то логичнее отдать все пространство под корневой раздел и эфемерные хранилища, которые им пользуются.

Разметка диска при конфигурации ноды выделенного сервера: распределение пространства под корневой раздел, boot и локальный storage.

Количество нод — задать значение больше, чем есть машин нужной конфигурации в наличии, не получится — доступный лимит отображается еще на этапе выбора. После создания кластера группу можно масштабировать, если останутся свободные серверы, или добавить новые нод-группы с другими аппаратными конфигурациями.

Метки и тейнты — базовый инструмент Kubernetes для распределения приложений по узлам.

В документации K8s есть подробные статьи с объяснением концепции и примерами:

CIDR подсетей — настройка внутренней адресации кластера и связности с глобальным роутером. При выборе кастомных сетей стоит свериться с документацией по допустимым диапазонам (см. п. 8).

Параметры групп нод в облачной панели: выбор типа «Выделенный сервер», привязка к приватному пулу и настройка облачной сети для кластера.

После того как все требуемые параметры выбраны, переходим к настройке автоматизации, а затем — к условиям тарификации и подтверждению стоимости. Наливаем кофе и ждем. В течение 5−60 минут статусы кластера и нод-группы должны смениться на ACTIVE.

Для сторонников подхода IaC (Infrastructure as Code) предусмотрено развертывание через Terraform. Правда, пока функциональность ограничена созданием master-нод — собрать нод‑группу на базе выделенных серверов таким способом еще не получится.

О том, как описать создание кластера кодом, можно почитать в нашей документации.

Мне пришлось срочно допивать кофе уже через 17 минут. Пришло оповещение, что обе машины на связи и нод-группа готова. Возвращаемся в панель управления на вкладку Настройки и забираем kubeconfig для подключения. Поскольку API был выбран публичным, можно обойтись без лишних телодвижений и сразу приступить к делу — достаточно передать полученный конфиг в любимый GUI-клиент или положить в рабочий каталог kubectl.

Вкладка настроек развернутого кластера gybrid-cluster с публичным Kube API и кнопкой для скачивания конфигурационного файла kubeconfig.

Полученный kubeconfig окажется временным. В целях безопасности сертификаты автоматически обновятся через несколько недель, после чего файл придется скачать заново.

Для работы с одиночным кластером правильнее применять ролевую модель (RBAC) на базе сервисных аккаунтов. Если же инсталляций несколько, лучше организовать единую точку входа через OIDC-провайдера — например, Keycloak.

В сети есть множество подробных инструкций о том, как подружить Keycloak с Kubernetes буквально за пару минут. Не обязательно все делать самостоятельно — можно воспользоваться облачным сервером с уже преднастроенным образом.

Настройка кластера

Оказавшись внутри кластера, перейдем к установке системных компонентов, без которых полноценная работа сервисов просто невозможна. Начнем с главного — Ingress-контроллера, который станет ядром балансировки внешнего трафика.

Самый простой путь его развернуть — открыть вкладку Приложения в панели управления и выбрать нужный инструмент в локальном маркетплейсе. Приверженцы классического подхода могут установить его через традиционный Helm-чарт.

Пошаговая установка Ingress Controller на примере Helm-чарта — в нашей документации.

Раздел маркетплейса K8s «Приложения»: карточка доступного для установки системного компонента NGINX Ingress Controller.

Через некоторое время приложение успешно установится. По умолчанию будет создан IngressClass с именем nginx, который пригодится при дальнейшей настройке.

Фактически этот класс работает как целеуказатель на конкретный Ingress-контроллер. Он совершенно необходим, если таких контроллеров в кластере несколько — например, когда нужно отделить внутренний или служебный трафик от основного.

Вкладка установленных приложений кластера: статус Installed для контроллера входящего трафика NGINX Ingress Controller.

Тем временем в сервисе балансировщиков появился новый объект, привязанный к кластеру и его контроллеру. Выделенный публичный IP‑адрес станет внешней точкой входа для приложений — именно его предстоит прописать в A‑записях DNS.

Раздел облачных балансировщиков нагрузки: автоматически созданный инстанс с выделенным публичным IP-адресом для балансировки трафика Ingress-контроллера.

Перед развертыванием приложения нужно выбрать доменное имя и подготовить сертификат. Если тестовая или рабочая зона уже существует, достаточно добавить A‑запись, указывающую на настроенный ранее балансировщик. Если домена нет, его можно зарегистрировать тут же в панели Selectel.

Как только все готово, самое время позаботиться об HTTPS-сертификатах. Стандартом для управления ими в Kubernetes уже давно стал cert-manager. Установим этот прекрасный контроллер в кластер.

Далее исходим из того, что kubectl уже настроен, а конфигурационный файл успешно импортирован.

Все описанные операции можно выполнить и через графические интерфейсы (GUI). Однако поскольку логика работы сильно отличается от утилиты к утилите, примеры для конкретных визуальных клиентов здесь не приводятся — при необходимости разбираться с ними предстоит самостоятельно.


      helm repo add jetstack https://charts.jetstack.io

helm repo update

helm install cert-manager jetstack/cert-manager \
    --namespace cert-manager \
    --create-namespace \
    --set crds.enabled=true

Убедимся, что в кластере появились поды контроллера — именно они будут отвечать за выпуск сертификатов:


      kubectl get pods --namespace cert-manager
Вывод консоли терминала: статус подов контроллера cert-manager, все три системных компонента работают корректно в статусе Running.

Чтобы автоматизировать получение сертификатов через HTTP challenge, добавим объект ClusterIssuer. Логика этой проверки проста: сертификат выдается на конкретный домен, чья DNS-запись должна указывать на источник запроса.

Созданный ClusterIssuer сможет обслуживать всю инфраструктуру. Главное — не забыть прописать в его настройках корректный IngressClass, который был задан при развертывании Ingress-контроллера:


      apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
    name: le-prod # Имя issuer, на который нужно ссылаться при выпуске сертификатов
spec:
    acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: my-wonderful-email@example.com # Почта администратора для уведомлений от центра сертификации
        privateKeySecretRef:
            name: le-prod-key
        solvers:
            - http01:
                  ingress:
                      # Класс созданного контроллера
                      class: nginx

С этого момента cert-manager берет под контроль все объекты типа Certificate и автоматически обновляет их незадолго до истечения срока действия. Это особенно актуально на фоне инициативы по сокращению времени жизни сертификатов Let’s Encrypt как минимум вдвое.

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

Если этот манифест не зашит в Helm-чарт приложения, при удалении сервиса про него часто забывают. В итоге «осиротевшие» ресурсы продолжают «висеть» в кластере и безуспешно пытаются обновиться — что, конечно, обречено на провал, если A-запись домена уже удалена или изменена.

Более надежный и корректный подход — запрашивать выпуск прямо через аннотации в том Ingress-манифесте, для которого и нужен HTTPS.


      apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: ...
    namespace: ...
    annotations:
        cert-manager.io/cluster-issuer: le-prod
...
spec:
...

При таком подходе жизненный цикл TLS-секрета полностью берет на себя cert-manager. Появилось Ingress-правило с аннотацией — сертификат автоматически выпущен, удален ресурс — исчез и связанный с ним секрет.

Мем с персонажем Тарасом Бульбой и фразой «Я тебя породил, я тебя и убью», иллюстрирующий автоматическое удаление TLS-секретов сертификатов контроллером cert-manager.

Более сложные сценарии, требующие wildcard-сертификатов, можно реализовать с помощью объектов Issuer на основе DNS-записей. Пример настройки такого механизма для доменов Selectel есть в нашем репозитории на GitHub.

Приложения в кластере

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

Начнем с самого простого случая — stateless-сервиса, которому не нужно хранить состояние и данные в своем окружении. Для работы понадобятся три манифеста:

  • Deployment — описание самого приложения и его среды;
  • Service — сетевая абстракция над группой объектов;
  • Ingress — сущность, маршрутизирующая внешний трафик к сервисам внутри кластера.

Не будем мудрствовать и представим, что гипотетический многомиллиардный проект состоит из одного пода с Nginx — без дополнительных конфигов, учета best practices, строгой изоляции и прочих доработок для prod-окружений.


      ---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: nginx
    namespace: default
    labels:
        app: nginx
spec:
    replicas: 1
    selector:
        matchLabels:
            app: nginx
    template:
        metadata:
            labels:
                app: nginx
        spec:
            containers:
                - name: nginx
                  image: nginx
                  ports:
                      - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
    name: nginx
    namespace: default
spec:
    selector:
        app: nginx
    ports:
        - protocol: TCP
          port: 80
          targetPort: 80
    type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
    name: nginx
    namespace: default
    annotations:
        cert-manager.io/cluster-issuer: le-prod # Аннотация для автоматического выпуска сертификата через cert-manager
spec:
    ingressClassName: nginx # Целеуказатель на созданный ранее Ingress-контроллер
    tls:
        - hosts:
              - my.wonderful.ru
          secretName: tls-my-wonderful-ru
    rules:
        - host: my.wonderful.ru
          http:
              paths:
                  - path: /
                    pathType: Prefix
                    backend:
                        service:
                            name: nginx
                            port:
                                number: 80

Применим подготовленные манифесты к кластеру. Способ деплоя может быть любым: команда kubectl apply, интерфейс K8s IDE или готовый тестовый конвейер поставки. После этого проверим результат.

Консольный вывод K8s со списком развернутых ресурсов: запущенные deployment, service и настроенный ingress для тестового приложения NGINX.
Стартовая веб-страница браузера с дефолтным сообщением Welcome to nginx!, подтверждающая успешную публикацию stateless-сервиса в кластере.

Все отлично — с базовыми задачами кластер на выделенных серверах справляется без проблем.

Теперь немного усложним сценарий. Допустим, сервису потребовалось место для постоянных данных — например, многомиллиардный проект через простую конфигурацию Nginx и отдельный location с методом PUT складывает файлы прямо «себе под ноги» для дальнейшей обработки.

Такие ресурсы выделяются с помощью объектов PersistentVolume (PV) и настроенных StorageClass. Еще на этапе развертывания кластера пространство на сервере было размечено под локальный класс хранения —по умолчанию он называется local-storage. Однако важно помнить про его главную особенность: диск жестко привязан к конкретному железу. Если под переедет на другую ноду, доступ к накопленным файлам будет потерян.

Мем с актером Андреем Мироновым из фильма «Бриллиантовая рука» и текстом «Шеф, все пропало», отражающий потерю локальных данных (PV) при перезапуске пода на другой ноде.

В облачных кластерах эта проблема решается через StorageClass на основе сетевых дисков, но для выделенных серверов такая опция пока недоступна. Это не приговор — отличной, а местами и более выигрышной альтернативой станут диски на базе файлового хранилища.

Как и у сетевых дисков, здесь есть разные типы накопителей со своими лимитами и скоростью доступа. Однако у файлового хранилища есть важное преимущество — поддержка режима ReadWriteMany (RWX). Это значит, что один и тот же том можно примонтировать к нескольким подам одновременно.

Кроме того, при миграции подов между нодами нет задержек, поскольку этот режим не требует атомарного переподключения (reattach) сетевого диска.

Для сравнения: у зарубежных провайдеров подобный режим для PV доступен либо с огромным минимальным объемом (от 1 ТБ против 50 ГБ в Selectel), либо через FUSE-тома поверх S3. В последнем случае активная работа с файлами серьезно ударит по бюджету, так как биллинг учитывает каждое обращение к объектному хранилищу.

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

Интерфейс создания файлового хранилища в Selectel: настройка имени, региона, подсети, а также выбор базового HDD-диска и протокола NFSvБлок с реквизитами подключения созданного файлового хранилища: выделенный IP-адрес монтирования и пример команды mount для среды GNU/Linux.

Файловое хранилище также можно полностью развернуть через Terraform. Как это сделать — в нашей документации.

Дождемся, пока оно будет готово, и скопируем нужные параметры из информационного блока.

Вывод терминала с отчетом пакетного менеджера Helm об успешной установке CSI-провайдера nfs-subdir-external-provisioner в системный неймспейс.

В нашем случае для подключения понадобятся два параметра: IP-адрес хранилища и путь к выделенному каталогу:

10.20.0.2:/shares/share-74056a5a-69a4-4881-9908-5288e0c22215

Подготовим кластер к работе с новым хранилищем: добавим CSI-контроллер для NFS. Разберем, почему для выделения вольюмов выбран именно он.

Главная причина — встроенная логика изоляции данных. Под каждый PVC в файловом хранилище автоматически создается отдельный каталог, который и становится точкой монтирования для пода.

Благодаря этому данные разных PVC надежно изолированы друг от друга — такого результата не всегда удается добиться стандартными контроллерами.


      helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner

helm repo update

helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --namespace kube-system \
    --set nfs.server=10.20.0.2 \
    --set nfs.path=/shares/share-74056a5a-69a4-4881-9908-5288e0c22215 \
    --set storageClass.pathPattern='${.PVC.namespace}/${.PVC.name}'

# nfs.server — IP-адрес файлового хранилища;
# nfs.path — его корневой каталог;
# storageClass.pathPattern — шаблон для создания подкаталогов.
Скриншот информации о состоянии кластера.

Helm рапортует об успешной установке. Заодно в кластере появился StorageClass с именем nfs-client — именно он будет автоматически выделять вольюмы по каждому запросу согласно заданным правилам.

Запросим выделение вольюма с помощью манифеста PersistentVolumeClaim (PVC). Пусть вся магия его создания останется на плечах контроллера и нового StorageClass.


      apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: pvc
    namespace: default
spec:
    storageClassName: nfs-client # Имя созданного StorageClass
    accessModes:
        - ReadWriteMany # Режим доступа, позволяющий монтировать вольюм к нескольким подам
    resources:
        requests:
            storage: 1Gi

Убедимся, что PVC успешно создан.


      kubectl get pvc
kubectl get pv
Консольный вывод команды kubectl get pvc: успешный статус Bound для выделенного тома PersistentVolumeClaim в кластере.
Консольный вывод команды kubectl get pv: параметры созданного тома PersistentVolume с требуемым режимом доступа ReadWriteMany (RWX).

Теперь можно обновить манифест Deployment, подключив к приложению созданный вольюм.


      ---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: nginx
    namespace: default
    labels:
        app: nginx
spec:
    replicas: 1
    selector:
        matchLabels:
            app: nginx
    template:
        metadata:
            labels:
                app: nginx
        spec:
            volumes: # Определение подключаемого вольюма
                - name: upload
                  persistentVolumeClaim:
                      claimName: pvc # Имя созданного PVC
            containers:
                - name: nginx
                  image: nginx
                  ports:
                      - containerPort: 80
                  volumeMounts:
                      - name: upload
                        mountPath: /usr/share/nginx/html/upload # Путь для постоянного хранения файлов в контейнере

Убедимся, что вольюм успешно подключен к поду, а сам деплоймент работает без ошибок:


      kubectl describe deployment nginx
Вывод команды kubectl describe deployment: детальная информация о приложении NGINX с одной рабочей репликой и успешно примонтированным сетевым томом upload.

Теперь воспользуемся преимуществами режима ReadWriteMany и увеличим количество реплик сервиса — например, до трех.


      ---
...
spec:
    replicas: 3
...
Вывод терминала K8s после масштабирования: статус приложения NGINX, успешно запущенного в три реплики с конкурентным доступом к общему сетевому диску.

Все прошло, как задумывалось. Теперь в кластере запущено три экземпляра сервиса. У каждого из них к пути /usr/share/nginx/html/upload примонтирован один и тот же PV — данные сохранятся при перезагрузке подов и будут доступны всем репликам приложения одновременно.

Заключение

Как видим, развертывание Kubernetes-кластеров на выделенных серверах не является чем‑то сложным или своеобразным. Передача управления мастер-нодами облачному провайдеру снимает головную боль инженеров, связанную с администрированием Control Plane и высвобождает время для более важных задач.

Более того, провайдер предлагает готовые сервисы, расширяющие функциональность кластера и закрывающие специфические потребности инфраструктуры. Мы в Selectel продолжает развивать свои продукты, чтобы сохранить ставшие всем привычными возможности облачных инсталляций.

Коллеги из команды Managed Kubernetes поделились планами: в ближайшем будущем для кластеров на выделенных серверах появится поддержка сетевых дисков, а также полноценный процесс обновления минорных версий Kubernetes.