снизить трение поставки через стандартизированную внутреннюю платформу

Создание Internal Developer Platform: от разрозненных CI/CD-скриптов к унифицированному деплою

13 минут

Когда у каждой команды свой стиль пайплайнов, поставка замедляется, а платформенные риски растут. В статье — как построить IDP со слоем абстракции деплоя, каталогом сервисов, policy gate и централизованными секретами.

Почему фрагментированный CI/CD со временем замедляет поставку

Большинство команд начинают с практичных скриптов под конкретную задачу: Jenkins job в одном сервисе, GitHub Actions в другом, кастомный shell-путь для деплоя, который поддерживают два человека. Проблема не в одном инструменте, а в том, что каждая команда развивает собственные соглашения по окружениям, секретам, approvals и rollback. Со временем это создаёт скрытую вариативность деплоя, силосы знаний и policy gaps, которые особенно болезненны во время инцидентов. Разработчики тратят время на различия платформы вместо продуктовой разработки.

IDP как слой абстракции, а не замена всего CI/CD

Internal Developer Platform (IDP) должен оборачивать существующие системы поставки, а не требовать big-bang миграции. Платформа даёт единый интерфейс деплоя и маршрутизирует запрос в нужный workflow, кластер и approval chain. Разработчик формулирует намерение, а платформа разрешает операционные детали. Это снижает когнитивную нагрузку и позволяет платформенной команде централизованно применять стандарты.

Команда деплоя для разработчика
# Unified command used by developers
idp deploy api-service --version v2.3.1 --env staging

# Platform resolves behind the scenes:
# 1) environment -> cluster + namespace
# 2) service metadata from catalog
# 3) secret references from vault path
# 4) workflow trigger with validated inputs
# 5) status updates to platform dashboard

Каталог сервисов: контракт между командами и платформой

Работающему IDP нужно каноническое описание сервиса. Каталог должен включать ownership, репозиторий, цели деплоя, обязательные секреты, runtime-ограничения и ожидания по надёжности. Это контракт: команды описывают намерение и требования сервиса, а платформа исполняет их единообразно. Держите каталог в Git, ревьювьте каждое изменение и версионируйте его как код приложения.

Запись в каталоге сервисов
apiVersion: idp.angri-tech.org/v1
kind: Service
metadata:
  name: api-gateway
  owner: platform-team
spec:
  repository:
    url: https://github.com/angri-tech/api-gateway
    branch: main
  deployment:
    targets:
      - name: staging
        cluster: eks-staging-us-east-1
        namespace: api-gateway-staging
        autoDeploy: true
      - name: production
        cluster: eks-prod-us-east-1
        namespace: api-gateway-prod
        approvalRequired: true
        approvers:
          - platform-team-leads
  secrets:
    - path: secret/api-gateway/database
      required: true
  resources:
    cpu:
      request: 500m
      limit: 2000m
    memory:
      request: 512Mi
      limit: 2Gi
  slo:
    availability: 99.9

Policy gate и централизованные секреты — обязательная часть

Без enforcement платформенные стандарты становятся рекомендациями, которые обходят под дедлайном. Добавьте policy gate, который валидирует запрос на деплой до выполнения: актуальность security scan, обязательные resource limits, SLO для production и совместимость зависимостей. Параллельно централизуйте секреты в Vault или cloud secret manager и инжектите их в runtime. Разработчики должны ссылаться на секреты как зависимости, а не работать с сырыми значениями.

Схема проверки политик
func EvaluateDeployment(ctx context.Context, req DeploymentRequest) PolicyResult {
    var result PolicyResult

    scanStatus, err := getLatestScanStatus(ctx, req.ServiceName, req.Version)
    if err != nil || scanStatus.AgeHours > 24 {
        result.Errors = append(result.Errors, PolicyViolation{
            Policy:      "security-scan-required",
            Resource:    req.ServiceName,
            Message:     "No recent security scan found",
            Remediation: "Run: idp security-scan <service>",
        })
    }

    if req.ServiceCatalog.Spec.Resources.CPU.Request == "" {
        result.Errors = append(result.Errors, PolicyViolation{
            Policy:      "cost-tag-required",
            Resource:    req.ServiceName,
            Message:     "No CPU resource requests defined",
            Remediation: "Add resources.cpu.request to service-catalog.yaml",
        })
    }

    if req.Environment == "production" && req.ServiceCatalog.Spec.SLO.Availability == 0 {
        result.Errors = append(result.Errors, PolicyViolation{
            Policy:      "slo-required-production",
            Resource:    req.ServiceName,
            Message:     "Production services must define an SLO",
            Remediation: "Add slo.availability to service-catalog.yaml",
        })
    }

    result.Passed = len(result.Errors) == 0
    return result
}

Опорный workflow: деплой через IDP в GitHub Actions

Workflow должен быть коротким и предсказуемым: валидация входных параметров, аутентификация в кластер, получение runtime-секретов, деплой с immutable image tag и отчёт статуса обратно в platform API. Делайте этот flow переиспользуемым между сервисами, чтобы команды не дублировали логику деплоя в каждом репозитории.

GitHub Actions workflow деплоя
name: IDP Service Deployment
on:
  workflow_dispatch:
    inputs:
      service:
        required: true
      version:
        required: true
      environment:
        required: true
      cluster:
        required: true
      namespace:
        required: true
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Validate inputs
        run: |
          test -n "${{ inputs.service }}"
          test -n "${{ inputs.version }}"

      - name: Authenticate to Kubernetes
        uses: azure/k8s-set-context@v1
        with:
          kubeconfig: ${{ secrets.KUBECONFIG }}
          context: ${{ inputs.cluster }}

      - name: Fetch secrets from Vault
        uses: hashicorp/vault-action@v2
        with:
          method: kubernetes
          url: https://vault.internal.angri-tech.org
          secrets: |
            secret/data/${{ inputs.service }}/${{ inputs.environment }} DATABASE_URL

      - name: Deploy with Helm
        run: |
          helm upgrade --install "${{ inputs.service }}" ./charts/${{ inputs.service }} \
            --namespace "${{ inputs.namespace }}" \
            --set image.tag="${{ inputs.version }}" \
            --wait --timeout 5m

Оперируйте платформой как внутренним продуктом

Относитесь к platform engineering как к продуктовой разработке для внутренних пользователей. Измеряйте adoption и трение метриками: deployment frequency, lead time, policy failure rate, время на платформенный toil. Стройте paved road, который проще, чем обход платформы. Обнаруживайте drift между каталогом и runtime-состоянием, но избегайте неожиданных auto-remediation в production без явного ownership. Начните с одного сервиса или команды, докажите сокращение трения и масштабируйте паттерны итеративно. Цель не в идеальной платформе за квартал, а в том, чтобы каждый следующий деплой был безопаснее и быстрее предыдущего.

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

Когда API платформы стабилизирован, декларативное управление выкатом проще внедрять через GitOps с Argo CD и Flux.