ускорить обратную связь по PR без очереди на staging
Эфемерные namespace Kubernetes для preview pull request: автоматизация, изоляция и удаление
11 минут
Общий staging превращается в очередь и дрейф конфигурации. В материале — один namespace на pull request через Helm и GitHub Actions, квоты ресурсов, маршрутизация preview-трафика и удаление окружения при закрытии PR.
Почему общий staging становится узким местом поставки
Команды с одним или двумя долгоживущими staging-кластерами рано или поздно упираются в одни и те же проблемы: разработчики ждут свободное окружение, конфигурация расходится с production, а дефекты воспроизводятся нестабильно. Локальные тесты помогают на уровне unit-слоя, но не проверяют ingress, общие зависимости и кластерные политики. Масштабировать статические окружения клонированием целых кластеров дорого и тяжело в эксплуатации. Эфемерные preview-окружения меняют постоянство на изоляцию: каждый pull request получает свой срез общего кластера, а ресурсы исчезают после merge или закрытия PR.
Эфемерные namespace: жизненный цикл, изоляция и выбор инструментов
Эфемерное окружение — короткоживущий namespace Kubernetes, создаваемый под одну задачу изменения. CI собирает неизменяемые образы с тегом commit SHA, разворачивает их в namespace, запускает smoke- или интеграционные проверки и отдаёт preview URL ревьюерам. Удаление срабатывает на событии pull_request closed и дублируется janitor-задачами для осиротевших namespace. Есть два распространённых пути. CI-driven деплой через Helm или kubectl — самый быстрый старт, он совпадает с workflow ниже. GitOps-контроллеры вроде Argo CD или Flux могут управлять теми же namespace через Application или Kustomization на PR с automated prune — это удобно, если production уже синхронизируется из Git и preview должны следовать тому же контракту.
Эталонный workflow: GitHub Actions, Helm и один namespace на PR
Держите стабильное имя namespace на pull request (например pr-42), а не вшивайте хэш коммита в имя. Тогда helm upgrade --install на каждом synchronize обновляет окружение без осиротевших namespace. Аутентифицируйте workflow в целевой кластер, помечайте namespace метками для последующей очистки, применяйте манифесты квот до установки chart и публикуйте preview URL в PR. Job teardown удаляет namespace по точному имени при закрытии PR — kubectl не принимает wildcard в имени namespace, поэтому janitor по меткам остаётся резервным путём при сбоях CI.
name: Ephemeral Environment Deployment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
deploy-ephemeral:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Authenticate to Kubernetes
uses: azure/k8s-set-context@v4
with:
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Set namespace name
id: namespace
run: echo "name=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Deploy to ephemeral namespace
run: |
kubectl create namespace ${{ steps.namespace.outputs.name }} \
--dry-run=client -o yaml | kubectl apply -f -
kubectl label namespace ${{ steps.namespace.outputs.name }} \
app.kubernetes.io/managed-by=ci \
pr-number=${{ github.event.number }} \
--overwrite
kubectl apply -f ./manifests/ephemeral-guardrails.yaml \
-n ${{ steps.namespace.outputs.name }}
helm upgrade --install app-${{ github.event.number }} ./chart \
--namespace ${{ steps.namespace.outputs.name }} \
--set image.tag=${{ github.sha }} \
--set ingress.host=pr-${{ github.event.number }}.preview.example.com \
--wait --timeout 5m
- name: Comment PR with preview URL
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Ephemeral environment ready: https://pr-${context.issue.number}.preview.example.com`
})
teardown-ephemeral:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Authenticate to Kubernetes
uses: azure/k8s-set-context@v4
with:
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Delete ephemeral namespace
run: |
kubectl delete namespace pr-${{ github.event.number }} \
--wait=false --ignore-not-found=trueОграничения: ResourceQuota, LimitRange и очистка осиротевших namespace
Preview-кластеры по определению мультитенантны. Без guardrails один ошибочный chart может запланировать неограниченное число pod и вытеснить соседние workload. ResourceQuota ограничивает суммарный CPU, память и число pod в namespace. LimitRange задаёт дефолты для контейнеров и не даёт запросить больше, чем разрешает квота. Если CI не удалил окружение, запускайте cron или политику Kyverno, которая удаляет namespace с меткой pr-number старше заданного TTL (часто начинают с семи дней).
apiVersion: v1
kind: ResourceQuota
metadata:
name: ephemeral-quota
spec:
hard:
requests.cpu: "2"
requests.memory: 4Gi
limits.cpu: "4"
limits.memory: 8Gi
pods: "10"
---
apiVersion: v1
kind: LimitRange
metadata:
name: ephemeral-limits
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "2"
memory: 2GiОперационные практики для платформенных команд
Маршрутизируйте preview-трафик через wildcard DNS и ingress-контроллер (NGINX, Traefik или gateway service mesh), чтобы pr-N.preview.example.com указывал на Service нужного namespace без ручных DNS-заявок на каждый PR. Изолируйте данные: предпочитайте схему БД на PR, эфемерный Postgres или Testcontainers в CI вместо одной изменяемой staging-базы с перекрёстными конфликтами тестов. Повторяйте production security gates — сканирование образов, NetworkPolicy и admission-проверки — чтобы preview не стали неконтролируемым каналом для уязвимых образов. Ограничьте RBAC так, чтобы CI service account мог создавать namespace только в разрешённых preview-кластерах. Отслеживайте стоимость preview-кластера по меткам namespace и включайте выводы в FinOps-процессы, чтобы скорость разработки не раздувала облачный счёт незаметно.
Эфемерные preview логично встраиваются в единый слой деплоя — подход разобран в гайде по internal developer platform.
Когда preview надолго остаются в общем кластере, namespace-автоматизацию стоит сочетать с декларативной синхронизацией из материала про GitOps с Argo CD и Flux.
