Построение мультиязычного CI/CD пайплайна для монорепозитория с Go и PHP-сервисами в Kubernetes
Введение: вызовы монорепозитория
Монорепозиторий — популярная архитектурная стратегия, когда несколько сервисов на разных языках программирования живут в одном Git-репозитории. Компании вроде Google, Meta и Uber используют monorepo-подход уже много лет. Однако вместе с удобством управления зависимостями и единым code review приходят серьёзные CI/CD-проблемы: как не пересобирать все сервисы при изменении одного? Как организовать параллельные пайплайны для Go и PHP? Как управлять деплоем в Kubernetes без дублирования конфигураций?
В этой статье мы разберём практическую архитектуру мультиязычного CI/CD пайплайна для монорепозитория, где сосуществуют микросервисы на Go и PHP, деплоящиеся в Kubernetes-кластер. Целевая аудитория — DevOps-инженеры и техлиды, которые уже столкнулись с болью «зелёный пайплайн на 40 минут при изменении одной строки».
Структура монорепозитория: организация директорий
Правильная структура директорий — фундамент управляемого monorepo. Разделяйте сервисы по языкам и доменам, выносите общие компоненты в отдельные директории.
monorepo/
├── services/
│ ├── go/
│ │ ├── auth-service/
│ │ │ ├── cmd/
│ │ │ ├── internal/
│ │ │ ├── Dockerfile
│ │ │ └── go.mod
│ │ └── payment-service/
│ │ ├── cmd/
│ │ ├── internal/
│ │ ├── Dockerfile
│ │ └── go.mod
│ └── php/
│ ├── api-gateway/
│ │ ├── src/
│ │ ├── Dockerfile
│ │ └── composer.json
│ └── billing-service/
│ ├── src/
│ ├── Dockerfile
│ └── composer.json
├── infra/
│ ├── helm/
│ │ ├── auth-service/
│ │ ├── payment-service/
│ │ ├── api-gateway/
│ │ └── billing-service/
│ └── kustomize/
│ ├── base/
│ └── overlays/
│ ├── staging/
│ └── production/
├── .github/
│ └── workflows/
├── scripts/
│ └── detect-changes.sh
└── Makefile
Ключевой принцип: каждый сервис самодостаточен — содержит собственный Dockerfile, конфигурацию зависимостей и тесты. Общие библиотеки выносятся в директорию libs/ с явными версиями, чтобы изменение общей библиотеки триггерило пересборку только зависящих от неё сервисов.
Определение изменённых сервисов: path filtering
Path filtering — ключевая техника для CI/CD монорепозитория. Цель — запускать пайплайн только для тех сервисов, файлы которых изменились в коммите или pull request.
Path filtering в GitHub Actions
GitHub Actions поддерживает нативный paths фильтр на уровне воркфлоу, но для динамического определения изменений лучше использовать dorny/paths-filter:
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
auth-service: ${{ steps.filter.outputs.auth-service }}
payment-service: ${{ steps.filter.outputs.payment-service }}
api-gateway: ${{ steps.filter.outputs.api-gateway }}
billing-service: ${{ steps.filter.outputs.billing-service }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
auth-service:
- 'services/go/auth-service/**'
- 'libs/go-common/**'
payment-service:
- 'services/go/payment-service/**'
- 'libs/go-common/**'
api-gateway:
- 'services/php/api-gateway/**'
- 'libs/php-shared/**'
billing-service:
- 'services/php/billing-service/**'
- 'libs/php-shared/**'
build-auth-service:
needs: detect-changes
if: needs.detect-changes.outputs.auth-service == 'true'
uses: ./.github/workflows/build-go-service.yml
with:
service: auth-service
path: services/go/auth-service
Path filtering в GitLab CI
В GitLab CI используется директива changes в секции rules:
build-auth-service:
stage: build
rules:
- changes:
- services/go/auth-service/**/*
- libs/go-common/**/*
script:
- docker build -t $CI_REGISTRY_IMAGE/auth-service:$CI_COMMIT_SHORT_SHA
services/go/auth-service/
build-api-gateway:
stage: build
rules:
- changes:
- services/php/api-gateway/**/*
- libs/php-shared/**/*
script:
- docker build -t $CI_REGISTRY_IMAGE/api-gateway:$CI_COMMIT_SHORT_SHA
services/php/api-gateway/
Независимые Docker-сборки: Go и PHP
Multistage Dockerfile для Go сервисов
Для Go-сервисов оптимальная стратегия — multistage build с финальным distroless образом. Это минимизирует размер образа и attack surface:
# services/go/auth-service/Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-w -s -X main.version=${VERSION}" \
-o auth-service ./cmd/server
FROM gcr.io/distroless/static-debian12 AS production
COPY --from=builder /app/auth-service /auth-service
USER nonroot:nonroot
ENTRYPOINT ["/auth-service"]
Distroless образы не содержат shell, package manager и других инструментов, что делает их значительно более безопасными для production-окружений.
Dockerfile для PHP сервисов (FPM + Nginx)
PHP-сервисы требуют двух контейнеров: PHP-FPM для обработки запросов и Nginx как reverse proxy. Используем multistage build с оптимизацией Composer:
# services/php/api-gateway/Dockerfile
FROM composer:2.7 AS composer-deps
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-scripts \
--prefer-dist \
--optimize-autoloader
FROM php:8.3-fpm-alpine AS production
RUN apk add --no-cache \
redis \
libpq-dev \
&& docker-php-ext-install pdo pgsql opcache
COPY --from=composer-deps /app/vendor /var/www/html/vendor
COPY src/ /var/www/html/src/
COPY config/ /var/www/html/config/
COPY php.ini /usr/local/etc/php/conf.d/custom.ini
FROM nginx:1.25-alpine AS nginx
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=production /var/www/html/public /var/www/html/public
Управление версиями образов: тегирование и семантическое версионирование
Правильная стратегия тегирования Docker-образов критична для трассируемости деплоев. Рекомендуем использовать несколько тегов одновременно:
- Git SHA — уникальный идентификатор каждого коммита:
auth-service:a1b2c3d - Ветка + SHA — для изоляции feature-веток:
auth-service:feature-oauth-a1b2c3d - Семантическая версия — для релизов:
auth-service:v1.4.2 - latest — только для main/master ветки в staging
#!/bin/bash
# scripts/tag-image.sh
SERVICE=$1
GIT_SHA=$(git rev-parse --short HEAD)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
REGISTRY="registry.company.com"
BASE_TAG="$REGISTRY/$SERVICE"
# Всегда тегируем по SHA
docker tag $BASE_TAG:build $BASE_TAG:$GIT_SHA
# Для main — дополнительно тегируем latest и семантическую версию
if [ "$BRANCH" = "main" ]; then
VERSION=$(cat services/$SERVICE/VERSION)
docker tag $BASE_TAG:build $BASE_TAG:$VERSION
docker tag $BASE_TAG:build $BASE_TAG:latest
fi
docker push $BASE_TAG --all-tags
Параллельные пайплайны: матричные джобы и зависимости
GitHub Actions поддерживает матричные стратегии, позволяющие параллельно собирать несколько сервисов одного типа. Это значительно сокращает общее время CI/CD пайплайна.
build-go-services:
needs: detect-changes
strategy:
matrix:
service:
- name: auth-service
changed: ${{ needs.detect-changes.outputs.auth-service }}
- name: payment-service
changed: ${{ needs.detect-changes.outputs.payment-service }}
fail-fast: false
runs-on: ubuntu-latest
if: matrix.service.changed == 'true'
steps:
- uses: actions/checkout@v4
- name: Build Go service
run: |
docker build \
--build-arg VERSION=${{ github.sha }} \
-t $REGISTRY/${{ matrix.service.name }}:${{ github.sha }} \
services/go/${{ matrix.service.name }}/
- name: Push image
run: docker push $REGISTRY/${{ matrix.service.name }}:${{ github.sha }}
deploy:
needs: [build-go-services, build-php-services, security-scan]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to Kubernetes
run: ./scripts/deploy.sh
Используйте fail-fast: false в матрице, чтобы проблема одного сервиса не блокировала сборку остальных. Это особенно важно при большом количестве независимых микросервисов.
Деплой в Kubernetes: Helm, Kustomize и namespace-стратегии
Helm-чарты для мультисервисного деплоя
Для каждого сервиса создаётся отдельный Helm-чарт с общим шаблоном values.yaml. Переопределяемые значения — образ и тег — передаются через CI/CD:
# infra/helm/auth-service/values.yaml
image:
repository: registry.company.com/auth-service
tag: latest
pullPolicy: IfNotPresent
replicaCount: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
# scripts/deploy.sh
#!/bin/bash
SERVICE=$1
ENVIRONMENT=$2
IMAGE_TAG=$3
helm upgrade --install $SERVICE \
infra/helm/$SERVICE \
--namespace $ENVIRONMENT \
--create-namespace \
--set image.tag=$IMAGE_TAG \
--set environment=$ENVIRONMENT \
--values infra/helm/$SERVICE/values-$ENVIRONMENT.yaml \
--wait \
--timeout 5m0s
Namespace-стратегия
Рекомендуемая стратегия namespace в Kubernetes для монорепозитория:
staging— все сервисы staging-окруженияproduction— production-окружение с политиками NetworkPolicyfeature-{branch-name}— временные namespace для feature-веток (удаляются после merge)
Kustomize удобен для управления различиями между окружениями без дублирования YAML-конфигураций. Базовые манифесты лежат в infra/kustomize/base/, а overlay-директории содержат только переопределения для конкретного окружения.
Общие шаги пайплайна: линтинг, тестирование и сканирование образов
Линтинг и тесты
Для Go используем golangci-lint с конфигурацией в .golangci.yml, для PHP — phpstan и phpcs. Тесты запускаются параллельно со сборкой образов, что экономит время пайплайна.
Сканирование образов с Trivy
Интеграция Trivy в CI/CD пайплайн — обязательный шаг для production-ready монорепозитория. Сканирование запускается сразу после сборки образа, до деплоя:
security-scan:
needs: [build-go-services, build-php-services]
runs-on: ubuntu-latest
strategy:
matrix:
service: [auth-service, payment-service, api-gateway, billing-service]
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: registry.company.com/${{ matrix.service }}:${{ github.sha }}
format: sarif
output: trivy-results-${{ matrix.service }}.sarif
severity: CRITICAL,HIGH
exit-code: 1
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results-${{ matrix.service }}.sarif
Управление секретами в мультисервисном пайплайне
Управление секретами — одна из самых чувствительных задач в CI/CD монорепозитория. Разные сервисы требуют разных секретов, при этом важно избежать утечки секретов одного сервиса в другой.
Рекомендуемый подход — External Secrets Operator в связке с HashiCorp Vault или AWS Secrets Manager. Каждый сервис получает доступ только к своему пространству секретов через Kubernetes ServiceAccount и RBAC:
# infra/helm/auth-service/templates/external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: auth-service-secrets
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: auth-service-secrets
data:
- secretKey: JWT_SECRET
remoteRef:
key: services/auth-service
property: jwt_secret
- secretKey: DB_PASSWORD
remoteRef:
key: services/auth-service
property: db_password
Для CI/CD используйте GitHub Actions secrets на уровне environment, разделяя staging и production. Никогда не храните секреты в репозитории, даже зашифрованные — только ссылки на внешние хранилища.
Мониторинг пайплайна и метрики времени деплоя
Без измерений невозможно оптимизировать. Ключевые метрики для CI/CD монорепозитория:
- Pipeline duration — общее время от коммита до деплоя (цель: менее 10 минут)
- Build cache hit rate — процент использования кеша Docker layers (цель: более 70%)
- Deployment frequency — сколько деплоев в день/неделю
- Change failure rate — процент деплоев, вызвавших инциденты
- Mean time to recovery (MTTR) — среднее время восстановления после сбоя
Для визуализации метрик GitHub Actions интегрируется с Datadog или Grafana через плагины. В GitLab CI встроены аналитика пайплайнов и отчёты о времени выполнения джобов. Для Kubernetes деплоев настройте отслеживание через Argo Rollouts с автоматическим rollback при ухудшении golden signals.
Пример сбора метрик через GitHub Actions step summary:
- name: Report deployment metrics
run: |
DEPLOY_DURATION=$(($(date +%s) - $START_TIME))
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "| Service | Image Tag | Duration |" >> $GITHUB_STEP_SUMMARY
echo "|---------|-----------|----------|" >> $GITHUB_STEP_SUMMARY
echo "| $SERVICE | $IMAGE_TAG | ${DEPLOY_DURATION}s |" >> $GITHUB_STEP_SUMMARY
# Отправка метрики в Datadog
curl -X POST "https://api.datadoghq.com/api/v1/series" \
-H "DD-API-KEY: ${{ secrets.DATADOG_API_KEY }}" \
-d "{\"series\": [{\"metric\": \"cicd.deploy.duration\",
\"points\": [[$START_TIME, $DEPLOY_DURATION]],
\"tags\": [\"service:$SERVICE\", \"env:production\"]}]}"
Заключение и рекомендации
Построение мультиязычного CI/CD пайплайна для монорепозитория — нетривиальная задача, требующая системного подхода. Подведём ключевые рекомендации:
- Инвестируйте в path filtering с первого дня. Это фундаментальный механизм, без которого CI/CD монорепозитория превращается в узкое горлышко.
- Стандартизируйте Dockerfile-шаблоны для Go (distroless) и PHP (FPM+Nginx) — это упрощает поддержку и онбординг новых разработчиков.
- Используйте матричные джобы для параллельной сборки и тестирования однотипных сервисов — это может сократить время пайплайна в 3-5 раз.
- Разделяйте секреты по сервисам через External Secrets Operator и принцип минимальных привилегий.
- Измеряйте метрики DORA — deployment frequency, lead time, MTTR и change failure rate — они покажут реальную эффективность вашего пайплайна.
- Автоматизируйте откат через Helm rollback или Argo Rollouts с canary-стратегией для минимизации рисков при деплое.
Хороший CI/CD пайплайн для монорепозитория — это не просто автоматизация сборки. Это система быстрой обратной связи, которая позволяет команде доставлять изменения уверенно и часто, независимо от количества языков и сервисов в репозитории.
Описанная архитектура масштабируется от 5 до 50+ сервисов без принципиальных изменений. Начните с малого — настройте path filtering и параллельные сборки, затем добавляйте сканирование безопасности и метрики. Итеративный подход здесь работает так же хорошо, как и в разработке продукта.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →