Как развернуть приложение в управляемом кластере Kubernetes с Helm, Ingress и TLS 

Валерий Волков

Время прочтения 39 минут

Для развертывания приложения в managed Kubernetes нужны готовый контейнерный образ, доступ к кластеру через kubectl, Helm, Ingress Controller, домен и TLS.

Базовая цепочка выглядит так: Container Registry → Deployment → Pod → Service → Ingress → домен → HTTPS.

Helm удобно использовать для установки, обновления и rollback релизов, а Kubernetes берет на себя поддержание replicas и Rolling Update. Для private Registry нужен imagePullSecret, а для автоматического TLS — cert-manager.

Перед production стоит проверить probes, requests/limits, права service account, хранение Secrets, фиксированные версии образов и совместимость rollback с миграциями базы данных.

Что развернем и настроим в этой статье

Добро пожаловать!

Развертывание приложения в Kubernetes заметно отличается от привычного сценария с одной виртуальной машиной. Здесь недостаточно просто скопировать файлы на сервер, запустить процесс и открыть порт.

Приложение запускается внутри Pod, управляется через Deployment, получает стабильный внутренний адрес через Service, публикуется наружу через Ingress, а HTTPS обычно настраивается отдельным механизмом выдачи TLS-сертификатов. Если образ хранится в private Container Registry, кластеру дополнительно нужны учетные данные для его загрузки.

Чтобы всем этим было удобнее управлять, поверх Kubernetes часто используют Helm. Он позволяет собрать Deployment, Service, Ingress и другие ресурсы в один chart, вынести изменяемые параметры в values.yaml, а затем обновлять и откатывать приложение как отдельный release.

В этой статье пройдем полный путь: подключимся к управляемому кластеру через kubectl, подготовим доступ к закрытому Container Registry, разберемся с Deployment и Service, соберем Helm chart, установим Ingress Controller, подключим домен и TLS. После этого обновим релиз, выполним rollback, изменим количество replicas и разберем базовую диагностику Pod.

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

Как будет устроено развертывание приложения

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

В Kubernetes внешний запрос проходит через несколько уровней. Каждый из них решает свою задачу, поэтому проблема с одним компонентом не обязательно означает, что не работает всё приложение.

Упрощенно путь пользователя будет выглядеть так:

Отдельно от этой цепочки работают Helm и Container Registry.

Container Registry хранит образ приложения, из которого Kubernetes создает контейнеры. Helm, в свою очередь, помогает описать и управлять всеми Kubernetes-ресурсами как одним релизом.

Какие компоненты участвуют в схеме

Начнем с самого приложения.

В Kubernetes оно запускается не напрямую на виртуальной машине, а внутри контейнера. Этот контейнер находится в Pod — минимальной единице, которую Kubernetes размещает на рабочих узлах кластера.

Но вручную создавать отдельный Pod обычно не нужно.

Для этого используется Deployment. Он описывает желаемое состояние приложения: какой образ запускать, сколько экземпляров должно работать и каким образом их обновлять.

Если один Pod аварийно завершится, Deployment через связанные механизмы Kubernetes создаст новый.

Следующий компонент — Service.

Pod не стоит воспринимать как постоянный сервер. Он может быть пересоздан, получить другой IP или переехать на другой узел. Поэтому другим компонентам приложения неудобно обращаться к конкретному Pod напрямую.

Service дает стабильную точку доступа к группе подходящих Pod и направляет трафик на работающие экземпляры приложения.

Но Service сам по себе еще не означает удобную публикацию сайта в интернет.

Для этого используется Ingress.

Ingress описывает правила маршрутизации: например, запросы к app.example.com должны попадать в определенный Service.

Но сам объект Ingress ничего не принимает из интернета. Его правила должен исполнять отдельный Ingress Controller.

Именно Controller фактически принимает внешний HTTP- и HTTPS-трафик и направляет его дальше внутри кластера.

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

Наконец, Helm объединяет все эти ресурсы в единый управляемый релиз.

Получается такая логика:

А Helm управляет описанием этой схемы и ее версиями.

Чем управляемый Kubernetes отличается от обычной виртуальной машины

На обычной VPS пользователь обычно отвечает почти за всё самостоятельно.

Нужно установить операционную систему и пакеты, настроить сетевые службы, запустить приложение, следить за процессом и продумывать восстановление после сбоя.

В Kubernetes подход другой.

Пользователь описывает желаемое состояние, а сам кластер старается его поддерживать.

Например, вместо требования «запусти процесс приложения» задается состояние: «должно постоянно работать три экземпляра этого контейнера».

Если один экземпляр исчезнет, Kubernetes постарается создать замену.

В managed Kubernetes часть инфраструктурной работы дополнительно берет на себя облачный провайдер. Обычно он обслуживает управляющую часть кластера — control plane, API Kubernetes и связанные системные компоненты.

Пользователю не нужно вручную поднимать собственный API Server, etcd и остальные элементы control plane.

Но это не значит, что managed Kubernetes полностью управляет самим приложением.

Пользователь по-прежнему отвечает за:

  • Deployment;
  • Service;
  • Ingress;
  • Контейнерные образы;
  • Секреты;
  • Ресурсы Pod;
  • Обновления приложения;
  • Права доступа;
  • Диагностику нагрузки.

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

Что понадобится до начала работы

Первое обязательное условие — уже созданный Kubernetes-кластер.

От провайдера понадобится kubeconfig или другой поддерживаемый способ получить доступ к Kubernetes API.

На рабочей машине должны быть установлены kubectl и Helm.

kubectl будет использоваться для прямой работы с Kubernetes: просмотра Pod, Service, событий и других ресурсов.

Helm понадобится для установки и дальнейшего управления нашим приложением как release.

Также нужен готовый контейнерный образ приложения.

В статье будем исходить из того, что приложение уже собрано в Docker-образ и загружено в Container Registry.

Если Registry закрытый, понадобятся credentials, которые Kubernetes сможет использовать для загрузки образа.

Для публикации приложения потребуется домен или поддомен. Позже его DNS-запись будет направлена на внешний адрес Ingress Controller.

И наконец, для HTTPS понадобится возможность подтвердить владение доменом, чтобы cert-manager смог получить сертификат через ACME.

Что подготовить перед развертыванием

До начала практической части удобно собрать все необходимые данные в одном месте.

Тогда на следующих этапах не придется останавливать настройку из-за отсутствующего endpoint Registry, неизвестного имени образа или неподготовленного домена.

Компонент Зачем нужен Что должно быть готово 
Managed Kubernetes Среда запуска приложения Созданный кластер и доступ к Kubernetes API 
kubeconfig Подключение kubectl к кластеру Файл или данные от облачного провайдера 
kubectl Управление ресурсами Kubernetes Установлен на рабочей машине 
Helm Установка и управление release Установлен и доступен в терминале 
Container Registry Хранение образа приложения Загруженный образ с понятным tag 
Registry credentials Загрузка private image Учетные данные с правом чтения образа 
Домен Публичный адрес приложения Доступ к управлению DNS 
Ingress Controller Прием внешнего трафика Установим позже в статье 
cert-manager Автоматическая работа с TLS Установим перед выпуском сертификата 
Приложение То, что будем разворачивать Контейнер должен запускаться и слушать известный порт 

На этом этапе ничего в кластере еще не меняем. Мы только собрали архитектуру и убедились, что все исходные данные доступны.

Подключаемся к Kubernetes-кластеру через kubectl

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

Первый практический этап — настроить kubectl так, чтобы он обращался именно к нужному Kubernetes API и выполнял команды в правильном окружении.

Это особенно важно в managed Kubernetes, потому что сам control plane обслуживает провайдер, а пользователь обычно получает готовые параметры подключения.

Что такое kubeconfig

kubectl не подключается к кластеру «сам по себе».

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

Обычно kubeconfig содержит несколько типов данных:

  • Адрес Kubernetes API;
  • Сведения о кластере;
  • Учетные данные пользователя;
  • Сертификаты или токены;
  • Context;
  • Выбранный namespace.

В managed Kubernetes такой файл обычно можно скачать из панели провайдера или получить через его CLI.

По умолчанию kubectl ищет конфигурацию в пользовательском каталоге:

~/.kube/config.

Но при необходимости можно использовать и другой файл.

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

Context и почему важно проверять активный кластер

Один kubeconfig может содержать сразу несколько кластеров.

Например, у разработчика могут одновременно быть:

  • Тестовый кластер;
  • Staging;
  • Production;
  • Отдельный локальный Kubernetes.

Чтобы kubectl понимал, с каким именно кластером работать, используется context.

Context связывает между собой:

  • Кластер;
  • Пользователя;
  • При необходимости namespace.

Условно можно представить его так:

context = cluster + credentials + namespace.

Это очень полезно, но одновременно создает риск.

Если человек привык работать в тестовом кластере, а активным context внезапно оказался production, обычная изменяющая команда может примениться совсем не туда.

Поэтому перед серьезными операциями стоит выработать привычку сначала проверять активный context.

Особенно это важно перед:

  • Установкой Helm release;
  • Удалением ресурсов;
  • Обновлением Deployment;
  • Масштабированием;
  • Rollback.

Чем больше кластеров используется в работе, тем полезнее явно контролировать текущий context.

Как понять, что кластер действительно доступен

Сам факт наличия kubeconfig еще ничего не гарантирует.

Конфигурация может быть устаревшей, credentials — отозванными, а endpoint API — недоступным из текущей сети.

Поэтому подключение лучше проверять в несколько этапов.

Сначала нужно убедиться, что kubectl вообще знает, какой context активен.

Затем проверить доступ к Kubernetes API.

После этого полезно получить список узлов кластера. Если control plane отвечает и пользователь имеет соответствующие права, kubectl сможет показать worker nodes.

Для managed Kubernetes это особенно наглядная проверка: мы видим не только сам API, но и рабочие узлы, на которых позже будут запускаться Pod.

Дополнительно стоит посмотреть namespaces.

Namespace позволяет логически разделять ресурсы внутри одного кластера. Например, приложения могут находиться в default, staging, production или отдельном namespace проекта.

Если не указать namespace явно, kubectl будет использовать тот, который задан в context, либо default.

Поэтому перед развертыванием полезно заранее понимать, где именно будут создаваться ресурсы.

Команды подключения и проверки

Если провайдер выдал отдельный kubeconfig, его можно использовать через переменную окружения или перенести в стандартный путь.

После этого базовая проверка выглядит так:

Что проверить Команда Что показывает 
Текущий context kubectl config current-context Какой кластер выбран сейчас 
Все доступные contexts kubectl config get-contexts Какие окружения доступны 
Информацию о Kubernetes API kubectl cluster-info Адреса основных компонентов кластера 
Рабочие узлы kubectl get nodes Доступные nodes и их состояние 
Namespaces kubectl get namespaces Логические области внутри кластера 
Ресурсы текущего namespace kubectl get all Основные объекты, которые уже существуют 

Если нужно переключиться на другой context:

kubectl config use-context <context-name>

При использовании отдельного kubeconfig удобно сначала указать его явно:

export KUBECONFIG=/path/to/kubeconfig

А затем уже выполнять остальные проверки.

Нормальный результат на этом этапе выглядит так: kubectl показывает правильный context, cluster-info отвечает без ошибки, worker nodes имеют состояние Ready, а список namespaces загружается успешно.

Теперь у нас есть подтвержденный доступ к кластеру и понятно, в каком context будут выполняться следующие действия.

Дальше подготовим вторую важную часть схемы — контейнерный образ приложения и доступ Kubernetes к private Container Registry.

Подготавливаем образ приложения и private Container Registry

Подключение к кластеру уже проверено, поэтому теперь можно перейти к тому, что Kubernetes будет непосредственно запускать.

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

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

Почему Kubernetes запускает контейнерный образ, а не исходный код

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

Его задача — запустить уже подготовленный контейнер в нужном количестве экземпляров и поддерживать заданное состояние.

То есть логика обычно такая:

Это разделяет два разных процесса.

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

Kubernetes отвечает уже за эксплуатацию этой версии: размещение Pod, перезапуск после сбоя, масштабирование и обновление.

Такой подход делает развертывание предсказуемее. Один и тот же образ можно запустить в тестовом и production-кластере, не пересобирая зависимости на каждом узле.

Зачем нужен Container Registry

Container Registry — это хранилище контейнерных образов.

После сборки образ отправляется в Registry, а Kubernetes при создании Pod скачивает его оттуда на нужный рабочий узел.

Образ обычно указывается вместе с Registry, именем проекта и тегом версии.

Например, логически ссылка может выглядеть так:

registry.example.com/myproject/webapp:1.0.0.

Здесь 1.0.0 — тег конкретной версии приложения.

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

Если постоянно использовать latest, становится сложнее определить, какой именно код работает в конкретном Pod, а обновление и rollback становятся менее предсказуемыми.

Поэтому для production лучше привязывать релизы к конкретным версиям образа.

Чем private Registry отличается от публичного

Из публичного Registry образ можно скачать без дополнительной аутентификации.

Например, так часто работают публичные образы Nginx, Redis и других популярных сервисов.

Private Registry требует подтверждения доступа.

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

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

Для Kubernetes ситуация похожая, только credentials должен получить уже сам кластер.

Если Deployment указывает private image, а Kubernetes не знает учетные данные Registry, Pod не сможет скачать образ.

В таком случае он часто переходит в состояние вроде ImagePullBackOff или ErrImagePull.

То есть проблема возникает еще до запуска приложения: контейнер просто не из чего создать.

Как Kubernetes получает доступ к private Registry

Учетные данные Registry в Kubernetes обычно сохраняются в Secret специального типа для Docker Registry.

Такой Secret содержит данные, необходимые для аутентификации:

  • Адрес Registry;
  • Имя пользователя;
  • Пароль или токен;
  • При необходимости email.

После этого Deployment может ссылаться на Secret через imagePullSecrets.

Вот схема для упрощения:

Важно понимать, что Secret должен находиться в том же namespace, где создается Pod.

Если Secret создан в default, а Deployment находится в production, Kubernetes не сможет просто использовать его из другого namespace.

Это одна из типичных причин, почему вроде бы правильно созданный imagePullSecret не работает.

Еще один момент — Secret не делает Registry credentials безопасными автоматически.

Kubernetes хранит Secret в специальном объекте, но его содержимое не стоит считать полноценным внешним менеджером секретов. Доступ к Secrets нужно ограничивать через RBAC, а сами учетные данные — выдавать с минимально необходимыми правами.

Команды для Registry и imagePullSecret

Теперь можно перейти к практической части.

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

Например:

registry.example.com/myproject/webapp:1.0.0

Если нужно проверить доступ к Registry локально, можно выполнить вход через Docker:

docker login registry.example.com

Теперь создадим Secret для Kubernetes:

    kubectl create secret docker-registry registry-secret \
  --docker-server=registry.example.com \
  --docker-username=<username> \
  --docker-password=<password> \
  --docker-email=<email> \
  --namespace=default

Если Registry использует токен вместо пароля, в --docker-password передается именно токен.

Проверим, что Secret создан:

kubectl get secret registry-secret --namespace=default

Можно посмотреть его тип:

    kubectl get secret registry-secret \
  --namespace=default \
  -o jsonpath='{.type}'

Ожидаемый тип:

kubernetes.io/dockerconfigjson

Мини-таблица команд

Задача Команда Что проверяем 
Войти в private Registry локально docker login registry.example.com Credentials действительно работают 
Создать imagePullSecret kubectl create secret docker-registry registry-secret ... Kubernetes получает данные Registry 
Проверить Secret kubectl get secret registry-secret -n default Объект создан в нужном namespace 
Проверить тип Secret kubectl get secret registry-secret -n default -o jsonpath='{.type}' Используется kubernetes.io/dockerconfigjson
Посмотреть Secrets namespace kubectl get secrets -n default Secret доступен рядом с будущим Deployment 

Теперь Kubernetes знает, откуда брать образ приложения и какими учетными данными пользоваться при обращении к закрытому Registry.

Следующий шаг — описать само приложение внутри кластера: создать Deployment, который будет управлять Pod, и Service, который даст этим Pod стабильную точку доступа.

Разбираемся с Deployment и Service

Здесь появляются два ключевых объекта: Deployment и Service. Первый отвечает за жизненный цикл Pod, второй — за стабильный доступ к ним внутри кластера.

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

Зачем нужен Deployment

Deployment описывает желаемое состояние приложения.

В нем обычно задаются:

  • Контейнерный образ;
  • Количество replicas;
  • Порты контейнера;
  • Переменные окружения;
  • Ограничения ресурсов;
  • Стратегия обновления;
  • ImagePullSecrets, если образ находится в private Registry.

Сам Deployment не запускает приложение напрямую. Он управляет ReplicaSet, а уже ReplicaSet поддерживает нужное количество Pod.

Выглядит всё это так:

Например, если указано три replicas, Kubernetes будет стремиться постоянно держать три подходящих Pod.

Если один из них завершится с ошибкой или узел станет недоступен, кластер постарается создать замену.

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

Почему нельзя обращаться напрямую к Pod

Каждый Pod получает собственный IP внутри кластера.

На первый взгляд можно было бы просто обращаться к нему напрямую. Но такой адрес нельзя считать постоянным.

Pod может быть пересоздан из-за:

  • Обновления Deployment;
  • Сбоя приложения;
  • Перезапуска узла;
  • Изменения количества replicas;
  • Переноса нагрузки на другой node.

После пересоздания новый Pod может получить другой IP.

Поэтому связывать другие компоненты приложения с конкретным адресом Pod ненадежно.

Особенно очевидна проблема при нескольких replicas. Если одновременно работают три Pod, клиенту уже нужно каким-то образом выбирать между ними и учитывать, что набор экземпляров постоянно может меняться.

Для этого Kubernetes использует Service.

Что делает Service

Service создает стабильную сетевую точку доступа к группе Pod.

Он находит подходящие экземпляры по labels и направляет трафик на них.

Например, Deployment может назначать Pod метку:

app: webapp.

Service в таком случае выбирает все Pod с этой же меткой и направляет запросы на них.

Получается схема:

Если один Pod исчезнет, Service перестанет направлять на него трафик. Если Deployment создаст новый экземпляр с подходящей меткой, он автоматически попадет в группу.

Для нашего приложения достаточно Service типа ClusterIP.

Он создает внутреннюю точку доступа, доступную внутри Kubernetes-кластера.

Публиковать сам Service напрямую в интернет пока не нужно: позже внешний трафик будет принимать Ingress Controller и передавать его в этот Service.

Как связаны containerPort, port и targetPort

На этом этапе легко запутаться сразу в нескольких номерах портов.

У нас будут встречаться containerPort, port и targetPort, но они относятся к разным уровням.

containerPort описывает порт, на котором приложение слушает внутри контейнера.

Например, если веб-приложение работает на порту 8080, именно это значение можно указать в Deployment.

Сам по себе containerPort не публикует приложение наружу. Он в первую очередь описывает, какой порт использует контейнер.

targetPort находится уже в Service и указывает, куда именно нужно отправлять трафик внутри Pod.

Если приложение слушает 8080, targetPort обычно тоже будет равен 8080.

А port — это порт самого Service.

Например, можно сделать так:

Тогда другие компоненты Kubernetes будут обращаться к Service по порту 80, а Service перенаправит запросы в контейнер на 8080.

Это удобно, потому что внешний для приложения интерфейс не обязан совпадать с внутренним портом самого процесса.

Главное — чтобы targetPort соответствовал тому порту, который приложение действительно слушает внутри контейнера.

Практика: создаем Deployment и Service

Теперь можно собрать минимальные манифесты.

Для примера используем:

  • Приложение webapp;
  • Образ registry.example.com/myproject/webapp:1.0.0;
  • Один Pod;
  • Порт приложения 8080;
  • Ранее созданный Secret registry-secret.

Создадим Deployment:

    apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      imagePullSecrets:
        - name: registry-secret
      containers:
        - name: webapp
          image: registry.example.com/myproject/webapp:1.0.0
          ports:
            - containerPort: 8080

Здесь особенно важно, чтобы selector.matchLabels совпадал с labels в шаблоне Pod.

Теперь добавим Service:

    apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  type: ClusterIP
  selector:
    app: webapp
  ports:
    - port: 80
      targetPort: 8080

Service выбирает Pod с меткой app: webapp и направляет входящий трафик с собственного порта 80 на порт 8080 внутри контейнера.

После сохранения, например в файлы deployment.yaml и service.yaml, применим их к кластеру.

Задача Команда Что проверяем 
Создать Deployment kubectl apply -f deployment.yaml Kubernetes принял описание приложения 
Создать Service kubectl apply -f service.yaml Создана внутренняя точка доступа 
Проверить Deployment kubectl get deployment webapp Нужное количество replicas доступно 
Проверить Pod kubectl get pods Pod находится в состоянии Running
Проверить Service kubectl get service webapp Service создан и имеет ClusterIP
Проверить связанные ресурсы kubectl get pods -l app=webapp Selector действительно находит Pod 

Если Pod не переходит в Running, пока не стоит двигаться к Ingress и TLS. Сначала нужно убедиться, что само приложение успешно запускается внутри Kubernetes.

Теперь у нас уже есть рабочее приложение внутри кластера и стабильный Service перед ним. 

Упаковываем приложение в Helm chart

Deployment и Service уже работают, но пока они существуют как отдельные YAML-манифесты.

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

Здесь и появляется Helm.

Он не заменяет Kubernetes, а добавляет над обычными манифестами более удобный слой управления: шаблоны, параметры, версии релизов и единый процесс установки.

Зачем нужен Helm, если уже есть YAML

Обычный Kubernetes-манифест — это уже готовое описание конкретного ресурса.

Например, в Deployment напрямую указаны:

  • Имя приложения;
  • Контейнерный образ;
  • Tag;
  • Количество replicas;
  • Порт;
  • Имя Secret;
  • Другие параметры.

Проблема начинается, когда часть этих значений нужно менять.

Допустим, для тестового окружения используется один image tag и одна replica, а для production — другой tag и три replicas.

Без Helm приходится либо вручную менять YAML, либо хранить несколько почти одинаковых файлов.

Helm позволяет вынести изменяемые значения отдельно и оставить общую структуру ресурсов в шаблонах.

В результате один и тот же chart можно использовать для разных окружений, меняя только параметры.

Кроме того, Helm запоминает установленные релизы. Это пригодится позже, когда будем обновлять приложение и выполнять rollback.

Из чего состоит Helm chart

Helm chart — это каталог с описанием приложения.

В простом случае внутри него есть несколько основных частей.

Chart.yaml содержит сведения о самом chart: имя, версию и служебные метаданные.

values.yaml хранит значения, которые подставляются в шаблоны.

Каталог templates содержит Kubernetes-манифесты, но уже в шаблонном виде.

Структура выглядит так:

Ingress добавим немного позже, а пока нас интересуют Deployment и Service.

Основная идея Helm проста: сама структура Kubernetes-ресурсов остается в templates, а изменяемые параметры выносятся в values.yaml.

Что лучше вынести в values.yaml

В values.yaml стоит помещать те значения, которые могут меняться между версиями или окружениями.

Например:

  • Имя контейнерного образа;
  • Image tag;
  • Количество replicas;
  • Порт Service;
  • Порт приложения;
  • Имя imagePullSecret;
  • Домен;
  • Настройки Ingress;
  • Requests и limits;
  • Параметры TLS.

Не стоит превращать values.yaml в свалку всех возможных значений.

Если параметр никогда не меняется и является частью самой логики chart, его можно оставить внутри шаблона.

А вот то, что администратор действительно будет менять при развертывании, лучше вынести отдельно.

Например, обновление версии приложения тогда сводится к изменению одного image tag, а не поиску нужной строки внутри Deployment.

Как Helm формирует итоговые Kubernetes-манифесты

Helm не создает какой-то отдельный тип ресурсов.

В конечном итоге Kubernetes все равно получает обычные Deployment, Service, Ingress, Secret и другие объекты.

Разница только в том, как эти манифесты готовятся.

Helm берет шаблоны из каталога templates, подставляет значения из values.yaml и формирует итоговый YAML.

Процесс выглядит так:

Например, внутри шаблона Deployment вместо жестко заданного образа может использоваться значение из values.yaml.

То же самое относится к количеству replicas и портам.

Благодаря этому chart остается универсальным, а конкретная конфигурация задается отдельно.

Перед установкой итоговые манифесты можно проверить заранее. Это полезно, потому что позволяет увидеть, что именно Helm собирается передать Kubernetes API.

Практика: создаем и проверяем Helm chart

Создадим заготовку chart:

helm create webapp

Helm создаст стандартную структуру с несколькими готовыми шаблонами.

Лишние файлы можно удалить или адаптировать под наше приложение.

Для начала можно использовать такой фрагмент values.yaml:

    replicaCount: 1
image:
  repository: registry.example.com/myproject/webapp
  tag: "1.0.0"
  pullPolicy: IfNotPresent
imagePullSecrets:
  - name: registry-secret
service:
  type: ClusterIP
  port: 80
  targetPort: 8080

После этого шаблоны Deployment и Service должны использовать эти значения вместо жестко прописанных параметров.

Перед установкой chart стоит сначала проверить его структуру и синтаксис.

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

Задача Команда Что проверяем 
Создать заготовку chart helm create webapp Создана стандартная структура Helm 
Проверить chart helm lint ./webapp Ошибки структуры и шаблонов 
Посмотреть итоговый YAML helm template webapp ./webapp Какие манифесты будут переданы Kubernetes 
Установить release helm install webapp ./webapp Создание ресурсов приложения 
Посмотреть установленные releases helm list Helm видит установленный release 
Проверить созданные ресурсы kubectl get pods,svc Deployment и Service действительно работают 

Если chart уже установлен и нужно применить изменения, вместо повторного install позже будем использовать обновление release.

Теперь Deployment и Service управляются уже не как набор независимых YAML-файлов, а как единый Helm release.

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

Устанавливаем Ingress Controller

Зачем нужен Ingress

Перед самой установкой надо бы разобраться что есть что и разделить два понятия. 

Service типа ClusterIP работает только внутри кластера.

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

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

Например, можно задать, что запросы к app.example.com должны направляться в Service webapp, а запросы к api.example.com — уже в другой Service.

То есть Ingress позволяет связать внешний адрес приложения с внутренними Kubernetes-ресурсами.

При этом он не является отдельным веб-сервером и сам по себе трафик не принимает.

Чем Ingress отличается от Ingress Controller

Ingress — это объект Kubernetes с правилами.

Ingress Controller — это компонент, который эти правила реально выполняет.

По итогу:

  • Ingress → описывает, куда направлять запросы
  • Ingress Controller → принимает запросы и применяет эти правила

Если создать Ingress, но не установить Controller, правила просто некому будет исполнять.

Именно Controller:

  • слушает внешний HTTP- и HTTPS-трафик;
  • отслеживает объекты Ingress в Kubernetes API;
  • понимает, какой домен и путь к какому Service относятся;
  • направляет запросы дальше внутрь кластера.

Одним из самых распространенных вариантов остается NGINX Ingress Controller.

Как трафик проходит от внешнего адреса до приложения

После установки Controller путь запроса становится уже почти полным.

Пользователь обращается к домену, DNS направляет его на внешний адрес, этот адрес принадлежит Ingress Controller или связанному с ним облачному балансировщику.

Дальше Controller смотрит правила Ingress и определяет, в какой Service отправить запрос.

Service уже выбирает подходящий Pod.

Получается такая цепочка:

Это один из главных принципов Kubernetes-сети: каждый слой решает свою задачу.

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

Что важно учитывать в managed Kubernetes

В управляемом Kubernetes внешний адрес Ingress Controller часто создается через инфраструктуру самого облачного провайдера.

Например, Service Controller может иметь тип LoadBalancer, после чего провайдер автоматически создаст внешний балансировщик и привяжет его к кластеру.

Именно его IP или hostname позже понадобится указать в DNS.

Но здесь есть важный нюанс: механизм зависит от конкретного managed Kubernetes.

У одного провайдера внешний IP появляется автоматически через несколько секунд.

У другого нужно отдельно создать Load Balancer.

У третьего используются специальные annotations или собственный контроллер облачного балансировщика.

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

Есть и еще один практический момент: после создания Service типа LoadBalancer внешний адрес может какое-то время отображаться как pending.

Это не обязательно означает ошибку. Облачной платформе нужно время, чтобы создать внешний ресурс и связать его с Kubernetes.

Команды установки и проверки Ingress Controller

Для примера установим NGINX Ingress Controller через Helm.

Сначала добавим официальный репозиторий chart и обновим его индекс:

    helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

Теперь установим Controller в отдельный namespace:

    helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace

После установки проверим Pod:

kubectl get pods -n ingress-nginx

И Service:

kubectl get svc -n ingress-nginx

Нас особенно интересует Service контроллера типа LoadBalancer.

Когда внешний адрес будет назначен, он появится в соответствующем поле.

Для более подробной проверки можно посмотреть ресурсы самого Helm release:

helm list -n ingress-nginx

Мини-таблица команд

Задача Команда Что проверяем 
Добавить Helm-репозиторий helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx Репозиторий chart доступен 
Обновить индекс Helm helm repo update Получены актуальные версии chart 
Установить Controller helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace Созданы ресурсы Ingress Controller 
Проверить Pod kubectl get pods -n ingress-nginx Controller находится в Running
Проверить Service kubectl get svc -n ingress-nginx Назначен внешний адрес 
Проверить Helm release helm list -n ingress-nginx Release успешно установлен 

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

Подключаем домен к приложению

Ingress Controller уже получил внешний адрес, поэтому теперь можно связать этот адрес с нормальным доменным именем.

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

Что именно должна указывать DNS-запись

DNS-запись должна вести на внешний адрес, через который Ingress Controller принимает трафик.

В managed Kubernetes это обычно один из двух вариантов:

  • Внешний IP;
  • DNS-имя облачного Load Balancer.

Если провайдер выдал внешний IP, обычно создают A-запись.

Например, домен app.example.com должен указывать на внешний адрес балансировщика.

Если вместо IP выдано DNS-имя, может использоваться CNAME.

Важно не направлять домен на IP отдельного Pod или Service типа ClusterIP. Эти адреса предназначены для внутренней сети Kubernetes и не являются стабильной публичной точкой входа.

Почему DNS нужно настроить до TLS

На следующем этапе cert-manager будет получать TLS-сертификат для нашего домена.

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

При проверке HTTP-01 запрос приходит на тот же домен, для которого выпускается сертификат. Ingress Controller и cert-manager должны правильно обработать этот запрос и вернуть специальный ответ.

Если DNS еще указывает не туда, проверка просто не дойдет до Kubernetes.

В результате cert-manager может быть настроен правильно, Ingress тоже будет существовать, но Certificate останется в ожидании или завершится ошибкой.

Поэтому последовательность важна:

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

Почему изменение DNS видно не мгновенно

После изменения записи новый адрес может появиться не у всех пользователей сразу.

DNS активно использует кэширование.

У каждой записи есть TTL — время, в течение которого DNS-резолвер может хранить полученный ответ.

Например, если старое значение было закэшировано на час, часть клиентов может продолжать использовать его до окончания TTL.

Кроме того, свой кэш могут иметь:

  • Операционная система;
  • Браузер;
  • Домашний роутер;
  • DNS-сервер интернет-провайдера;
  • Публичный DNS-резолвер.

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

Для проверки лучше смотреть DNS напрямую несколькими инструментами и не ориентироваться только на браузер.

Команды проверки домена

Сначала посмотрим внешний адрес Ingress Controller.

Задача Команда Что проверяем 
Посмотреть Service контроллера kubectl get svc -n ingress-nginx Внешний IP или hostname 
Проверить A-запись dig app.example.com Какой IP возвращает DNS 
Получить краткий DNS-ответ dig +short app.example.com Итоговый адрес без лишнего вывода 
Проверить домен через nslookup nslookup app.example.com Ответ DNS другим клиентом 
Проверить HTTP curl -I http://app.example.com Доходит ли запрос до внешней точки входа 

Если DNS настроен правильно, dig или nslookup должны возвращать адрес, связанный с Ingress Controller или его Load Balancer.

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

Теперь домен связан с внешней точкой входа Kubernetes. Осталось объяснить Ingress Controller, какой именно Service должен получать запросы к этому имени.

Публикуем приложение через Ingress

DNS уже приводит пользователя к Ingress Controller, но Controller пока не знает, куда направлять запросы к app.example.com.

Для этого создается объект Ingress.

В нем описывается связь между внешним доменом и внутренним Service приложения.

Как Ingress связывает домен и Service

Ingress работает по правилам.

Обычно правило содержит:

  • Имя хоста;
  • Путь;
  • Service назначения;
  • Порт Service.

Для нашего приложения схема будет такой:

Когда Ingress Controller получает запрос к app.example.com, он находит подходящее правило и передает трафик в Service webapp.

Сам Service уже знает, какие Pod соответствуют его selector.

В итоге образуется полная цепочка:

Именно поэтому Ingress не нужно связывать напрямую с Pod. Он работает через стабильный Service.

Что такое IngressClass

В одном Kubernetes-кластере теоретически может работать несколько Ingress Controller.

Например, один может обслуживать публичный трафик, другой — внутренние сервисы.

Чтобы Kubernetes понимал, какой Controller должен обрабатывать конкретный Ingress, используется IngressClass.

Если мы установили ingress-nginx, обычно соответствующий класс называется nginx.

Ingress с таким классом будет обрабатываться именно NGINX Ingress Controller.

Это особенно важно в managed Kubernetes, где провайдер может уже устанавливать собственный контроллер или предоставлять несколько вариантов публикации приложений.

Если указать неправильный IngressClass, ресурс может существовать в Kubernetes, но нужный Controller просто не станет его обслуживать.

Как маршрутизировать несколько приложений

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

Можно использовать разные домены.

Например:

  1. app.example.com → Service webapp
  2. api.example.com → Service api
  3. admin.example.com → Service admin

Можно маршрутизировать и по пути.

Например:

  1. example.com/ → frontend
  2. example.com/api/ → api-service

Это позволяет использовать один Ingress Controller и один внешний Load Balancer для нескольких приложений.

Но чем сложнее правила, тем важнее внимательно следить за host, path и порядком маршрутизации.

Для нашего примера оставим самый понятный вариант: один домен и один Service.

Практика: создаем Ingress

Создадим манифест ingress.yaml:

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

Здесь ingressClassName связывает правило с NGINX Ingress Controller, host задает домен, а backend указывает на Service webapp.

Теперь применим манифест и проверим результат.

Задача Команда Что проверяем 
Создать Ingress kubectl apply -f ingress.yaml Ресурс принят Kubernetes 
Посмотреть Ingress kubectl get ingress Host, класс и внешний адрес 
Получить подробности kubectl describe ingress webapp Backend, правила и события 
Проверить Service kubectl get svc webapp Service существует и использует порт 80 
Проверить HTTP curl -I http://app.example.com Запрос проходит через Ingress к приложению 

Если всё настроено правильно, запрос к http://app.example.com уже должен доходить до приложения.

Теперь публичная HTTP-цепочка полностью работает: DNS приводит запрос к Ingress Controller, Ingress выбирает нужный Service, а тот передает трафик в Pod.

Остается закрыть последний важный вопрос внешней публикации — добавить TLS и перевести приложение на HTTPS.

Настраиваем TLS для HTTPS

Как TLS работает вместе с Ingress

Когда пользователь открывает https://app.example.com, TLS-соединение завершается на уровне Ingress Controller.

То есть именно Controller предъявляет браузеру сертификат и устанавливает защищенное соединение.

Дальше внутри кластера запрос уже направляется в Service и затем в Pod.

Упрощенно схема выглядит так:

Сам Ingress указывает, какой TLS Secret нужно использовать для конкретного домена.

В этом Secret находятся сертификат и закрытый ключ.

Важно понимать, что TLS здесь защищает внешний участок соединения до Ingress Controller. Внутри кластера трафик до Service и Pod может идти уже по HTTP, если отдельно не настроено внутреннее шифрование.

Зачем нужен cert-manager

Сертификат можно создать вручную и самому загрузить его в Kubernetes Secret.

Но тогда придется самостоятельно следить за сроком действия, обновлять сертификат и заменять Secret до истечения срока.

Для одного тестового приложения это возможно. Для production — неудобно.

cert-manager автоматизирует этот процесс.

Он умеет:

  • Создавать запрос на сертификат;
  • Подтверждать владение доменом;
  • Получать сертификат от центра сертификации;
  • Сохранять его в Kubernetes Secret;
  • Следить за сроком действия;
  • Автоматически выполнять продление.

То есть вместо ручной работы с сертификатами Kubernetes получает отдельный контроллер, который поддерживает их состояние примерно так же, как Deployment поддерживает состояние Pod.

Как проходит проверка домена через ACME

Для автоматического выпуска сертификатов часто используется протокол ACME.

Один из распространенных способов подтверждения — HTTP-01 challenge.

Логика простая.

Центр сертификации должен убедиться, что мы действительно контролируем домен app.example.com.

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

cert-manager временно создает необходимые Kubernetes-ресурсы, чтобы этот запрос дошел до кластера и получил нужное содержимое.

Поэтому DNS мы настраивали раньше TLS.

Если домен не указывает на Ingress Controller, проверочный запрос просто не попадет в кластер и выпуск сертификата не завершится.

Цепочка выглядит так:

После успешной проверки cert-manager получает сертификат и сохраняет его в Secret.

Что такое ClusterIssuer и Certificate

В cert-manager есть несколько собственных ресурсов Kubernetes.

Один из основных — Issuer или ClusterIssuer.

Он описывает, откуда получать сертификаты и каким способом подтверждать домены.

Разница между ними в области действия.

Issuer работает только внутри одного namespace.

ClusterIssuer доступен всему кластеру.

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

Certificate — это уже запрос на конкретный сертификат.

В нем описываются:

  • Доменные имена;
  • Имя Secret для сертификата;
  • Используемый Issuer или ClusterIssuer.

При интеграции cert-manager с Ingress отдельный Certificate нередко создается автоматически на основании аннотаций и TLS-настроек Ingress.

То есть мы можем описать HTTPS прямо в Ingress, а cert-manager самостоятельно создаст и будет обслуживать нужный сертификат.

Практика: устанавливаем cert-manager и выпускаем сертификат

Сначала установим cert-manager через Helm.

Добавим репозиторий:

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

Теперь установим cert-manager в отдельный namespace:

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

После установки создадим ClusterIssuer.

Например:

    apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: admin@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx

Сохраним его, например, как clusterissuer.yaml.

Теперь добавим TLS в Ingress:

    metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: webapp-tls
  rules:
    - host: app.example.com

После применения Ingress cert-manager увидит запрос на сертификат, выполнит ACME-проверку и создаст Secret webapp-tls.

Команды установки и проверки

Задача Команда Что проверяем 
Добавить репозиторий cert-manager helm repo add jetstack https://charts.jetstack.io Helm знает источник chart 
Обновить индекс helm repo update Получены актуальные версии 
Установить cert-manager helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true Controller и CRD установлены 
Проверить Pod cert-manager kubectl get pods -n cert-manager Все компоненты находятся в Running
Создать ClusterIssuer kubectl apply -f clusterissuer.yaml Настроен источник сертификатов 
Проверить ClusterIssuer kubectl get clusterissuer Issuer находится в готовом состоянии 
Проверить Certificate kubectl get certificate Сертификат создан 
Проверить Secret TLS kubectl get secret webapp-tls Сертификат сохранен в Kubernetes 
Проверить HTTPS curl -I https://app.example.com Приложение доступно по TLS 

Если Certificate долго остается неготовым, полезно дополнительно посмотреть состояние ACME-ресурсов и события cert-manager.

Теперь внешний путь полностью собран: домен ведет на Ingress Controller, Ingress направляет запросы в Service, а TLS автоматически обслуживается через cert-manager.

Обновляем приложение через Helm

Приложение уже опубликовано по HTTPS, поэтому теперь можно перейти к тому, что в реальной эксплуатации происходит постоянно — выпуску новой версии.

Именно здесь Helm начинает особенно хорошо показывать свою пользу. Вместо ручного редактирования нескольких Kubernetes-ресурсов мы меняем параметры релиза и применяем обновление одной операцией.

Что происходит при обновлении Helm release

Helm release — это установленный экземпляр chart с конкретным набором значений.

Когда мы выполняем обновление, Helm заново формирует итоговые Kubernetes-манифесты на основе chart и текущих параметров, сравнивает их с существующим состоянием и передает изменения Kubernetes API.

Дальше уже сам Kubernetes решает, какие объекты нужно обновить.

Например, если изменилась только версия контейнерного образа, Service и Ingress могут остаться без изменений, а Deployment получит новую конфигурацию Pod.

То есть Helm отвечает за описание нового состояния, а Kubernetes — за фактическое применение этого состояния внутри кластера.

Это важное разделение: Helm не «перезапускает контейнеры» напрямую. Он обновляет ресурсы Kubernetes, после чего уже контроллеры кластера выполняют необходимую работу.

Почему image tag лучше изменять явно

Для обновления приложения обычно меняется image tag.

Например, вместо версии 1.0.0 release начинает использовать 1.1.0.

Такой подход намного предсказуемее, чем постоянное использование latest.

Если все релизы ссылаются на один и тот же tag latest, становится сложнее ответить на простой вопрос: какой именно образ сейчас работает в кластере?

Кроме того, Kubernetes не всегда обязан повторно загружать образ с тем же именем и тегом, особенно если политика загрузки это не требует.

Фиксированные версии дают явную историю: 1.0.0 → 1.1.0 → 1.2.0

И она хорошо совпадает с историей Helm revisions.

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

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

Как Kubernetes выполняет Rolling Update

Когда Deployment получает новый шаблон Pod, Kubernetes не обязан сначала остановить все старые экземпляры, а потом запускать новые.

По умолчанию Deployment использует стратегию RollingUpdate.

Новые Pod создаются постепенно, а старые удаляются по мере готовности новых.

Упрощенно процесс может выглядеть так:

Благодаря этому приложение может оставаться доступным во время обновления.

Но есть важное условие: Kubernetes должен понимать, когда новый Pod действительно готов принимать трафик.

Для этого особенно полезны readiness probes, которые мы отдельно упомянем в production-разделе.

Без корректной проверки готовности Pod может считаться рабочим раньше, чем приложение фактически завершило запуск.

Команды обновления и проверки

Допустим, новая версия образа имеет tag 1.1.0.

Обновить release можно, передав новое значение через Helm:

    helm upgrade webapp ./webapp \
  --set image.tag=1.1.0

Если значения хранятся в отдельном файле, можно сначала изменить values.yaml, а затем выполнить обычный upgrade.

После обновления полезно проверить не только Helm, но и Deployment.

Задача Команда Что проверяем 
Обновить release helm upgrade webapp ./webapp --set image.tag=1.1.0 Применена новая конфигурация chart 
Посмотреть историю helm history webapp Создана новая revision 
Проверить rollout Deployment kubectl rollout status deployment/webapp Новые Pod успешно заменили старые 
Посмотреть Pod kubectl get pods Новые экземпляры находятся в Running
Проверить образ Deployment kubectl get deployment webapp -o jsonpath='{.spec.template.spec.containers[0].image}' Используется нужная версия образа 
Проверить Helm release helm status webapp Release находится в корректном состоянии 

Теперь обновление приложения выполняется как управляемая операция: Helm создает новую revision, а Kubernetes постепенно заменяет Pod.

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

Откатываем неудачный релиз

Допустим, версия 1.1.0 успешно установилась, Pod перешли в Running, но после публикации выяснилось, что приложение работает неправильно.

Вместо ручного восстановления предыдущих YAML Helm позволяет вернуться к одной из прошлых revisions release.

Почему Helm хранит историю release

Каждое успешное изменение Helm release получает собственный номер revision.

Например:

  1. REVISION 1 → первоначальная установка
  2. REVISION 2 → версия приложения 1.1.0
  3. REVISION 3 → следующая конфигурация

Благодаря этому Helm знает, с какими Kubernetes-манифестами и параметрами был связан каждый этап истории.

Это полезно не только для rollback.

История помогает понять:

  • Сколько раз изменялся release;
  • Когда происходили обновления;
  • Какое состояние было до текущего;
  • К какой revision можно вернуться.

То есть Helm release становится не просто способом установки YAML, а еще и историей изменений приложения внутри Kubernetes.

Что происходит при rollback

Rollback берет состояние выбранной предыдущей revision и снова применяет его как актуальное состояние release.

Например, если текущая revision 2 содержит образ 1.1.0, а revision 1 использовала 1.0.0, возврат к revision 1 снова изменит Deployment в сторону предыдущей конфигурации.

После этого Kubernetes выполнит новый rollout и постепенно заменит Pod.

Важно понимать, что rollback — это не «отмена времени».

Helm создает новое изменение в истории, основанное на старой конфигурации.

То есть после отката история не исчезает. Наоборот, появляется новая revision, которая отражает выполненный rollback.

Это удобно для аудита: видно не только что состояние вернулось назад, но и когда именно это произошло.

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

Вот здесь находится одно из самых важных ограничений.

Helm хорошо управляет ресурсами, входящими в release, но приложение часто зависит от внешних систем.

Например:

  • Базы данных;
  • Очереди;
  • Внешние API;
  • Object Storage;
  • Миграции схемы;
  • Данные пользователей.

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

После этого Helm rollback вернул контейнер приложения к версии 1.0.0.

Но база сама по себе не обязана вернуться к прежней схеме.

В результате старая версия приложения может оказаться несовместимой уже с новой структурой данных.

Поэтому production rollback нужно продумывать вместе с миграциями.

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

Helm rollback решает проблему Kubernetes-ресурсов, но не заменяет полноценную стратегию отката всего приложения.

Команды отката и проверки

Сначала посмотрим историю release:

helm history webapp

Предположим, нужно вернуться к revision 1.

Выполняем rollback:

helm rollback webapp 1

После этого проверяем rollout Deployment и состояние Pod.

Задача Команда Что проверяем 
Посмотреть revisions helm history webapp Доступные состояния release 
Вернуться к revision helm rollback webapp 1 Предыдущая конфигурация снова применена 
Проверить Helm release helm status webapp Rollback завершился корректно 
Дождаться Deployment kubectl rollout status deployment/webapp Старые Pod заменены нужной версией 
Проверить Pod kubectl get pods Все экземпляры работают 
Проверить текущий образ kubectl get deployment webapp -o jsonpath='{.spec.template.spec.containers[0].image}' Deployment снова использует нужный tag 
Посмотреть историю после rollback helm history webapp Rollback появился как новая revision 

После отката стоит проверить не только состояние Kubernetes, но и само приложение снаружи: открыть основной URL, выполнить health check и убедиться, что оно корректно работает с базой и другими зависимостями.

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

Масштабируем приложение

Что означает количество replicas

replicas — это количество Pod, которое Deployment должен поддерживать одновременно.

Если указано значение 1, Kubernetes стремится держать один экземпляр приложения.

Если указать 3, кластер будет поддерживать уже три одинаковых Pod с одной и той же конфигурацией контейнера.

Упрощенно:

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

Это полезно сразу по двум причинам.

Во-первых, несколько replicas позволяют распределять нагрузку между экземплярами приложения.

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

Но важно понимать, что само по себе увеличение replicas не решает все вопросы высокой доступности. Если все Pod зависят от одной внешней базы данных или одного узкого места, масштабирование только приложения не устранит эту зависимость.

Как Service распределяет запросы между Pod

Service не привязан к одному конкретному Pod.

Он выбирает все экземпляры, которые подходят под его selector.

Например, если Service ищет Pod с меткой app: webapp, а Deployment создал три таких экземпляра, все они становятся backend для одного Service.

Схема выглядит так:

Клиент при этом продолжает обращаться к одному домену и одному Service.

Ему не нужно знать:

  • Сколько Pod сейчас работает;
  • Какие у них IP;
  • На каких узлах они находятся;
  • Какой экземпляр был пересоздан.

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

Именно поэтому масштабирование приложения обычно не требует менять Ingress или DNS.

Чем ручное масштабирование отличается от HPA

Количество replicas можно менять вручную.

Например, администратор заранее знает, что нагрузка вырастет, и увеличивает число Pod с двух до пяти.

Это простая и понятная схема, но решение о масштабировании принимает человек.

HPA — Horizontal Pod Autoscaler — автоматизирует этот процесс.

Он может менять количество replicas на основе метрик, например загрузки CPU или памяти, если соответствующая инфраструктура метрик настроена.

Логика тогда выглядит так:

При снижении нагрузки количество Pod может уменьшаться обратно.

Для базовой статьи достаточно ручного масштабирования. HPA уже требует отдельного разговора о Metrics Server, requests ресурсов, порогах и поведении autoscaler.

Есть еще один важный нюанс именно для Helm.

Если значение replicaCount хранится в values.yaml, лучше изменять его через Helm. Иначе можно вручную масштабировать Deployment через kubectl, а при следующем helm upgrade Helm снова применит значение из chart.

То есть временная ручная настройка и декларативная конфигурация Helm могут разойтись.

Команды масштабирования и проверки

Если приложение управляется Helm, предпочтительный вариант — изменить replicaCount.

Например:

    helm upgrade webapp ./webapp \
  --set replicaCount=3

После этого Deployment должен постепенно создать дополнительные Pod.

Для временного ручного изменения можно использовать kubectl:

kubectl scale deployment webapp --replicas=3

Но при следующем Helm upgrade стоит помнить о значении, записанном в chart.

Задача Команда Что проверяем 
Изменить число replicas через Helm helm upgrade webapp ./webapp --set replicaCount=3 Helm release хранит новое значение 
Масштабировать Deployment вручную kubectl scale deployment webapp --replicas=3 Kubernetes меняет число Pod напрямую 
Проверить Deployment kubectl get deployment webapp Желаемое и доступное число replicas совпадает 
Посмотреть Pod kubectl get pods -l app=webapp Созданы все экземпляры приложения 
Следить за изменениями kubectl get pods -l app=webapp -w Pod появляются и переходят в Running

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

Диагностируем проблемы с Pod

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

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

Поэтому базовая диагностика Kubernetes строится не вокруг одной «универсальной команды», а вокруг последовательной проверки нескольких уровней.

Что означают состояния Pod

Состояние Pod дает первое представление о том, что происходит.

Running означает, что Pod запущен, но само по себе это еще не гарантирует, что приложение внутри полностью готово обслуживать запросы.

Pending означает, что Pod создан, но пока не смог перейти к нормальному запуску. Причиной может быть нехватка ресурсов, невозможность назначить Pod на node или ожидание тома.

ImagePullBackOff обычно указывает на проблему при загрузке контейнерного образа.

Например:

  • Неправильное имя image;
  • Неверный tag;
  • Private Registry недоступен;
  • Не работает imagePullSecret.

CrashLoopBackOff означает другую ситуацию: контейнер запускается, завершается с ошибкой, Kubernetes пытается запустить его снова, и цикл повторяется.

То есть уже по состоянию можно понять, на каком этапе искать проблему.

Почему Pod может не запускаться

Причин довольно много, но их удобно делить по категориям.

Первая — контейнерный образ.

Если Kubernetes не может его скачать, стоит проверять Registry, tag и credentials.

Вторая — само приложение.

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

Третья — ресурсы.

Если Pod требует больше CPU или памяти, чем кластер может предоставить, scheduler может оставить его в Pending.

Четвертая — зависимости.

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

И наконец, проблема может быть вообще не в Pod. Сам Pod работает нормально, но Service использует неправильный selector или Ingress направляет трафик не на тот порт.

Поэтому диагностика должна постепенно двигаться от самого контейнера наружу.

Чем logs отличаются от describe

logs и describe отвечают на разные вопросы.

Логи показывают то, что выводит само приложение внутри контейнера.

Например, там можно увидеть:

  • Stack trace;
  • Ошибку подключения к базе;
  • Неправильную конфигурацию;
  • Сообщение о занятом порте;
  • Падение процесса.

describe показывает уже взгляд Kubernetes на ресурс.

Там особенно важна секция Events, где могут появляться сообщения о:

  • Невозможности скачать образ;
  • Ошибке монтирования тома;
  • Проблеме scheduling;
  • Неудачной readiness probe;
  • Нехватке ресурсов.

Поэтому если контейнер вообще не успевает нормально запуститься, describe часто полезнее логов.

Если же Pod уже запускается, но приложение падает внутри, в первую очередь стоит смотреть logs.

Где искать проблему, если приложение не открывается

Если внешний домен не отвечает, не стоит сразу обвинять Ingress.

Лучше идти по цепочке снизу вверх.

Сначала проверить Pod.

Если они не находятся в рабочем состоянии, искать дальше пока бессмысленно.

Затем проверить Service и убедиться, что он действительно выбирает нужные Pod.

После этого проверить Ingress: правильный ли host, Service и порт указаны в правилах.

Только затем переходить к внешнему уровню:

Например, если приложение работает при обращении к Service изнутри кластера, но не открывается по домену, значит проблема уже выше — в Ingress, DNS или TLS.

Такой порядок экономит время: вместо хаотичной проверки всего сразу мы постепенно сокращаем область поиска.

Команды базовой диагностики

Что проверить Команда Что искать 
Состояние Pod kubectl get pods Running, Pending, CrashLoopBackOff, ImagePullBackOff
Подробности Pod kubectl describe pod <pod-name> Events, scheduling, image pull, probes 
Логи контейнера kubectl logs <pod-name> Ошибки самого приложения 
Логи предыдущего запуска kubectl logs <pod-name> --previous Причину падения перезапущенного контейнера 
Deployment kubectl get deployment webapp Доступные и желаемые replicas 
Service kubectl get svc webapp Порт и тип Service 
Endpoints kubectl get endpoints webapp Есть ли Pod за Service 
Ingress kubectl get ingress Host, класс и внешний адрес 
Подробности Ingress kubectl describe ingress webapp Backend и события Controller 
Helm release helm status webapp Состояние установленного release 
История релизов helm history webapp Последние обновления и rollback 

Если Service не имеет endpoint, обычно стоит проверить labels Pod и selector Service. Если endpoint есть, но домен не работает, следующий уровень проверки — Ingress и DNS.

Так диагностика Kubernetes превращается из поиска «почему всё сломалось» в понятную последовательность проверок от Pod до внешнего HTTPS-доступа.

Проверяем развертывание целиком

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

Такая финальная проверка особенно полезна перед production: она позволяет убедиться, что Kubernetes, Helm, Ingress и TLS работают не изолированно, а как одна связанная система.

Для начала проверим сам кластер и убедимся, что kubectl обращается в правильное окружение:

Что проверяем Команда Что должно быть 
Активный context kubectl config current-context Выбран нужный кластер 
Рабочие узлы kubectl get nodes Nodes находятся в состоянии Ready
Namespaces kubectl get namespaces Нужный namespace существует 

Если context неправильный, дальше лучше не продолжать. Все последующие команды должны выполняться именно в том окружении, где развернуто приложение.

Теперь проверим само приложение:

Что проверяем Команда Что должно быть 
Deployment kubectl get deployment webapp Желаемое и доступное число replicas совпадает 
Pod kubectl get pods -l app=webapp Все Pod находятся в Running
Service kubectl get svc webapp Service существует и использует нужный порт 
Endpoints kubectl get endpoints webapp Service видит рабочие Pod 
Helm release helm status webapp Release находится в рабочем состоянии 
История release helm history webapp Видны установки, обновления и rollback 

Если количество готовых Pod меньше ожидаемого, стоит остановиться именно здесь и сначала разобраться с Deployment. Проверять DNS или TLS при неработающем приложении пока бессмысленно.

После внутренней части переходим к внешнему доступу:

Что проверяем Команда Что должно быть
Ingress kubectl get ingress Указаны нужный host, класс и внешний адрес
Подробности Ingress kubectl describe ingress webapp Backend ведет на правильный Service
TLS-сертификат kubectl get certificate Certificate находится в состоянии Ready
TLS Secret kubectl get secret webapp-tls Secret существует
DNS dig +short app.example.com Домен указывает на внешний адрес кластера
HTTP/HTTPS curl -I https://app.example.comПриложение отвечает по HTTPS

В конечном итоге вся схема должна выглядеть так:

А поверх нее отдельно работают Helm и cert-manager: Helm управляет версиями релиза, а cert-manager поддерживает TLS-сертификат.

Если все проверки проходят, значит развертывание действительно собрано целиком: кластер доступен, Pod работают в нужном количестве, Service видит их, Ingress принимает внешний трафик, домен разрешается правильно, TLS активен, а Helm хранит историю релиза и позволяет управлять обновлениями.

После этого остается последний слой — production-настройки: ограничение прав, хранение секретов, probes, requests и limits, фиксированные версии образов и безопасный rollback.

Как безопасно использовать такую схему в production

Не хранить Registry credentials и Kubernetes secrets в Git

Private Container Registry требует учетные данные, а самому приложению часто нужны пароли от базы данных, API-токены и другие секреты.

Хранить такие значения непосредственно в Helm chart или открытом values.yaml не стоит.

Особенно плохой сценарий — когда пароль или Registry token попадает в Git вместе с исходным кодом. Даже если потом удалить его из текущей версии файла, секрет может остаться в истории репозитория.

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

Поэтому доступ к Secrets нужно ограничивать через RBAC, а для более сложной production-инфраструктуры можно использовать отдельный secret manager и передавать значения в Kubernetes уже на этапе развертывания.

Если credential попал в публичный репозиторий, лог или другой недоверенный источник, безопаснее считать его скомпрометированным и заменить.

Ограничивать права service account и kubeconfig

Kubernetes позволяет очень детально управлять правами через RBAC.

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

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

То же относится к CI/CD. Если pipeline должен только обновлять один Helm release в определенном namespace, ему необязательно выдавать административный доступ ко всему кластеру.

Отдельного внимания заслуживает kubeconfig.

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

Чем меньше область действия конкретных credentials, тем меньше последствия их утечки.

Использовать readiness и liveness probes

Состояние Running еще не означает, что приложение действительно готово принимать пользовательские запросы.

Контейнер может уже работать, но приложение внутри него еще, например, выполняет инициализацию или устанавливает соединение с зависимостями.

Для этого Kubernetes использует readiness probe.

Она отвечает на вопрос: можно ли уже направлять трафик в этот Pod?

Пока проверка готовности не проходит, Service не должен использовать Pod как готовый экземпляр приложения.

Liveness probe решает другую задачу. Она помогает понять, что процесс формально существует, но само приложение перестало нормально работать и его следует перезапустить.

Это особенно важно при Rolling Update.

Без корректной readiness probe новый Pod может слишком рано попасть под пользовательский трафик, а старый уже начнет удаляться.

Поэтому probes — не просто дополнительная диагностика, а важная часть безопасного обновления приложения.

Задавать requests и limits для Pod

Если вообще не задавать требования к ресурсам, Kubernetes сложнее планировать размещение Pod по узлам.

requests описывают количество ресурсов, которое контейнеру нужно для нормальной работы и которое scheduler учитывает при размещении.

limits задают верхнюю границу использования ресурса.

Это особенно важно в общем кластере, где одновременно работают несколько приложений.

Без ограничений один контейнер может начать потреблять слишком много CPU или памяти и влиять на другие workloads.

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

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

Использовать фиксированные версии образов и Helm chart

Production должен быть воспроизводимым.

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

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

То же относится к Helm chart.

Если версия chart меняется, желательно понимать, какие именно шаблоны использовались для конкретного release.

В результате любой релиз можно описать комбинацией:

  • Версии приложения;
  • Версии контейнерного образа;
  • Версии chart;
  • Набора конфигурационных значений.

Чем точнее известна эта комбинация, тем проще воспроизвести старое состояние и понять разницу между двумя развертываниями.

Продумывать rollback вместе с миграциями базы данных

Helm rollback отлично возвращает Kubernetes-ресурсы к предыдущему состоянию, но внешние данные живут по собственным правилам.

Особенно это касается базы данных.

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

Например, старая версия может не понимать новые таблицы, типы данных или обязательные поля.

Поэтому migration strategy должна разрабатываться вместе с release strategy.

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

Это особенно важно при Rolling Update, когда в течение короткого периода в кластере могут одновременно существовать Pod двух версий.

А для необратимых изменений нужен отдельный план восстановления, а не надежда на один helm rollback.

Что проверить перед production

Перед публикацией приложения удобно пройти короткий чеклист:

Что проверить Почему это важно 
kubeconfig защищен и не опубликован Может предоставлять доступ к Kubernetes API 
Registry credentials не находятся в Git Утечка позволит скачать private image 
Secrets доступны только нужным компонентам Снижает последствия компрометации 
Service account имеет минимальные права Ограничивает действия приложения и автоматизации 
Используются фиксированные версии image Делает релизы воспроизводимыми 
Helm chart версионируется Позволяет понять конфигурацию каждого release 
Настроена readiness probe Трафик идет только в готовые Pod 
Настроена liveness probe Зависший контейнер может быть автоматически восстановлен 
Заданы requests и limits Кластер предсказуемее распределяет ресурсы 
Работает несколько replicas, если нужна отказоустойчивость Отказ одного Pod не останавливает приложение 
TLS Certificate находится в ReadyHTTPS действительно обслуживается корректно 
Проверена стратегия rollback Неудачный релиз можно быстро вернуть 
Миграции совместимы со схемой обновления Rollback приложения не ломается из-за базы 
Логи и события Kubernetes доступны Проблему можно диагностировать после развертывания 
Проверен внешний HTTPS-доступ Вся цепочка от DNS до Pod работает целиком 

После этого production-развертывание уже можно рассматривать не просто как набор работающих Kubernetes-объектов, а как управляемую систему, в которой заранее предусмотрены обновления, сбои и восстановление.

Заключение

Managed Kubernetes позволяет вынести значительную часть управления самим кластером на облачного провайдера, но логика развертывания приложения по-прежнему остается за пользователем. Для публикации сервиса нужно связать контейнерный образ, Deployment, Service, Ingress, домен и TLS в одну последовательную схему.

В статье мы прошли весь этот путь: подключились через kubectl, настроили доступ к private Container Registry, упаковали ресурсы в Helm chart, установили Ingress Controller и cert-manager, а затем разобрали обновление, rollback, масштабирование и диагностику Pod.

Helm при этом делает релизы управляемыми, а Kubernetes берет на себя поддержание нужного количества экземпляров и постепенное применение изменений. Но надежность production зависит уже от деталей: probes, ограничений ресурсов, минимальных прав, безопасного хранения секретов и продуманной работы с миграциями.

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

FAQ

Можно ли развернуть приложение в Kubernetes без Helm?

Да. Kubernetes умеет работать напрямую с обычными YAML-манифестами, и для небольшого приложения этого может быть достаточно.

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

Почему Pod находится в Running, но приложение всё равно не открывается?

Состояние Running означает только то, что Pod запущен. Проблема может находиться выше по цепочке: Service не выбирает Pod, Ingress указывает на неправильный порт, DNS еще не обновился или TLS-сертификат не готов.

Поэтому диагностику лучше выполнять последовательно: Pod → Service → Ingress → DNS → TLS.

Почему private image не скачивается из Container Registry?

Чаще всего проблема связана с imagePullSecret, неправильным именем образа или tag.

Также Secret должен находиться в том же namespace, что и Pod. Если credentials созданы в другом namespace, Kubernetes не сможет использовать их напрямую.

Обязательно ли устанавливать Ingress Controller?

Если вы хотите использовать объект Ingress — да, нужен компонент, который будет его обслуживать.

Сам Ingress содержит только правила маршрутизации. Реальный внешний трафик принимает Ingress Controller.

Чем Helm rollback отличается от Kubernetes rollout undo?

kubectl rollout undo работает с историей конкретного Deployment.

Helm rollback возвращает состояние всего Helm release, поэтому может затронуть сразу несколько связанных ресурсов: Deployment, Service, Ingress и другие объекты из chart.

При этом ни один из этих механизмов автоматически не откатывает внешнюю базу данных или выполненные миграции.

Нужно ли использовать несколько replicas для любого production-приложения?

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

При этом само приложение должно поддерживать горизонтальное масштабирование. Например, пользовательские сессии и важное состояние лучше не хранить только внутри памяти одного Pod.

Что лучше использовать для production: image tag или digest?

Фиксированный tag вроде 1.4.2 намного лучше latest, потому что позволяет явно понимать версию приложения.

Digest дает еще более строгую фиксацию: он указывает на конкретное содержимое образа и не меняется даже при повторной публикации того же tag.

Что произойдет, если cert-manager не сможет продлить сертификат?

Существующий сертификат продолжит работать до окончания срока действия, но новый не будет выпущен вовремя.

Поэтому в production стоит следить за состоянием Certificate, событиями cert-manager и ошибками ACME, а не рассчитывать только на автоматизацию.

Список источников

  1. Kubernetes Documentation — Deployments
  2. Kubernetes Documentation — Services, Load Balancing, and Networking
  3. Kubernetes Documentation — Ingress
  4. Helm Documentation — Using Helm
  5. cert-manager Documentation

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами