Späť na blog

CI/CD pre monorepo: Rýchlosť, cache, selektívne testy a supply-chain bezpečnosť

|
| cicd, monorepo, devops, security, kubernetes

Monorepo pipeline som sa kedysi bal, kym som si nezmeral, kde realne tecie cas. “Pipeline trvá 47 minút, ale keď to pustím s [skip ci], nikto si to nevšimne.” Toto mi povedal kolega pred 4 rokmi. O týždeň neskôr sme nasadili nefunkčný kód do produkcie - práve kvôli preskočenému CI.

Od vtedy som optimalizoval CI/CD pre tímy od 5 do 50 developerov. Najdlhší pipeline som dostal z 52 minút na 8. Tento článok je kompletný blueprint toho, čo funguje.

Moja skúsenosť: GitHub Actions, GitLab CI, Jenkins. Monorepo s 15+ službami, 200+ testami. Všetky príklady v tomto článku som reálne implementoval a optimalizoval.

Čo sa pokazí, keď monorepo rastie

Typické symptómy:

  • Pipeline trvá 40+ minút - developeri strácajú flow
  • Zbytočné buildy - zmena v README spúšťa všetky testy
  • “Works on my machine” - ale CI failuje
  • Release chaos - nikto nevie, čo ide do produkcie
  • Security theater - scany bežia, ale nikto nečíta výsledky

Ciele, ktoré musíme dosiahnuť

MetrikaZlý stavCieľový stav
PR pipeline40+ min< 10 min
Main pipeline60+ min< 20 min
False positivesDenneTýždenne
Security findingsIgnorovanéTriaged do 24h
Rollback timeHodinyMinúty

Architektúra pipeline

Základná štruktúra

stages:
  - detect      # Čo sa zmenilo?
  - build       # Builduj len zmenené
  - test        # Testuj len affected
  - security    # SAST, DAST, dependencies
  - publish     # Artefakty, images
  - deploy      # Staging, production

Pravidlá pre rôzne triggery

TriggerČo bežíPrečo
PRAffected services + lintRýchla spätná väzba
MainAll affected + integrationGatekeeping pred release
TagFull + security + publishRelease readiness
NightlyEverything + slow testsKompletná regresia

Detekcia zmien: Path filters

Najjednoduchší spôsob ako zrýchliť pipeline - nerobiť, čo netreba.

GitHub Actions

on:
  pull_request:
    paths:
      - 'services/order-service/**'
      - 'libs/common/**'
      - 'proto/**'

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      order-service: ${{ steps.filter.outputs.order-service }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            order-service:
              - 'services/order-service/**'
              - 'libs/common/**'

  build-order-service:
    needs: detect-changes
    if: needs.detect-changes.outputs.order-service == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build --workspace=order-service

GitLab CI

order-service:build:
  rules:
    - changes:
        - services/order-service/**/*
        - libs/common/**/*
        - proto/**/*

Services Map

Pre komplexnejšie závislosti vytvorte explicitnú mapu:

# .ci/services-map.yml
services:
  order-service:
    path: services/order-service
    depends_on:
      - libs/common
      - libs/database
      - proto/order.proto
    tests:
      - tests/order-service
      - tests/integration/order

  payment-service:
    path: services/payment-service
    depends_on:
      - libs/common
      - libs/payment-sdk
      - proto/payment.proto

Cache stratégia

Cache je najväčší páka pre rýchlosť. Ale má svoje nástrahy.

Typy cache

TypČo cachujemeKedy invalidovať
Dependencynode_modules, .m2, piplockfile change
Buildcompiled artifactssource change
Docker layersbase imagesDockerfile change
Testtest fixturestest data change

GitHub Actions príklad

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: |
      ~/.npm
      node_modules
    key: deps-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      deps-

- name: Cache build
  uses: actions/cache@v4
  with:
    path: dist
    key: build-${{ github.sha }}
    restore-keys: |
      build-${{ github.event.pull_request.base.sha }}
      build-

Remote cache pre build tools

Pre väčšie projekty lokálna cache nestačí. Použite remote cache:

Gradle:

// settings.gradle.kts
buildCache {
    remote<HttpBuildCache> {
        url = uri("https://cache.mycompany.com/cache/")
        isPush = System.getenv("CI") != null
    }
}

Bazel:

# .bazelrc
build --remote_cache=grpcs://cache.mycompany.com
build --remote_upload_local_results=true

Turborepo:

{
  "remoteCache": {
    "teamId": "my-team",
    "signature": true
  }
}

Paralelizácia bez explózie nákladov

Viac paralelných jobov = rýchlejšie. Ale aj drahšie. Ako nájsť balans?

Shardovanie testov

test:
  strategy:
    matrix:
      shard: [1, 2, 3, 4]
  steps:
    - run: npm test -- --shard=${{ matrix.shard }}/4

Fail fast

strategy:
  fail-fast: true  # Zastaví všetky joby pri prvom faile
  matrix:
    service: [order, payment, inventory]

Limit concurrency

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # Zruší staršie behy tej istej branch

Selektívne testovanie

Nie všetky testy musia bežať vždy.

Minimálny model

Zmena v service A → Spusti:
  - Unit testy A
  - Integration testy A
  - Contract testy A ↔ závislosti

Stredný model (dependency graph)

# Script, ktorý analyzuje import/závislosti
- name: Detect affected tests
  run: |
    ./scripts/affected-tests.sh > affected.txt

- name: Run affected tests
  run: |
    cat affected.txt | xargs npm test --

Test Impact Analysis

Pre enterprise projekty existujú nástroje ako:

  • Launchable - ML-based test selection
  • Gradle Test Retry - smart retry flaky testov
  • Jest —changedSince - built-in pre JS

Quality Gates

Čo blokovať, čo len reportovať?

CheckPR BlockMain BlockKomentár
LintYesYesRychlé, jednoznačné
Unit testsYesYesZákladná funkčnosť
IntegrationNo (report)YesMôže byť flaky
Coverage dropNo (report)YesTrend je dôležitejší
Security HIGHYesYesKritické
Security MEDNo (report)NoTriage

Praktická implementácia

- name: Check coverage
  run: |
    COVERAGE=$(npm test -- --coverage | grep "All files" | awk '{print $10}')
    THRESHOLD=80
    if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
      echo "::warning::Coverage $COVERAGE% is below $THRESHOLD%"
      # Neblokujeme, len warning
    fi

Security v pipeline (bez divadla)

Security scanning je užitočný, len ak s výsledkami niečo robíte.

Čo kedy spúšťať

TypKedyBlokovať?
SAST (Semgrep, CodeQL)Každý PRHIGH = yes
Dependency scanKaždý PR + nightlyCritical = yes
DASTNightlyNo, triage
Container scanPred push do registryHIGH = yes

SBOM (Software Bill of Materials)

SBOM je zoznam všetkých komponentov vo vašom software. Povinný pre supply-chain bezpečnosť.

- name: Generate SBOM
  uses: anchore/sbom-action@v0
  with:
    format: spdx-json
    output-file: sbom.spdx.json

- name: Upload SBOM
  uses: actions/upload-artifact@v4
  with:
    name: sbom
    path: sbom.spdx.json

SLSA (Supply-chain Levels for Software Artifacts)

SLSA definuje úrovne bezpečnosti build procesu:

  • Level 1: Dokumentovaný build
  • Level 2: Hosted build service
  • Level 3: Hardened build (čo chcete dosiahnuť)
- name: Generate SLSA provenance
  uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v1
  with:
    go-version: 1.21

Podpisovanie artefaktov

- name: Sign container image
  run: |
    cosign sign --key env://COSIGN_PRIVATE_KEY \
      ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}

- name: Verify signature
  run: |
    cosign verify --key env://COSIGN_PUBLIC_KEY \
      ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }}

Release stratégia

Semantic Versioning

- name: Determine version
  id: version
  uses: paulhatch/semantic-version@v5
  with:
    major_pattern: "BREAKING CHANGE:"
    minor_pattern: "feat:"

- name: Create release
  run: |
    gh release create v${{ steps.version.outputs.version }} \
      --generate-notes

Changelog automation

- name: Generate changelog
  uses: orhun/git-cliff-action@v2
  with:
    config: cliff.toml
    args: --latest
  env:
    OUTPUT: CHANGELOG.md

Pipeline Anti-patterns

Čomu sa vyhnúť:

  1. “Build everything always” - 90% času zbytočne
  2. Flaky testy bez karantény - erodujú dôveru v CI
  3. Secrets v logoch - set +x nestačí, použite masking
  4. Mega-jobs - jeden job 30 min vs. 10 jobov po 3 min
  5. No job artifacts - debugging nemožný

Referenčná implementácia

Minimálna pipeline (starter)

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  detect:
    runs-on: ubuntu-latest
    outputs:
      services: ${{ steps.detect.outputs.services }}
    steps:
      - uses: actions/checkout@v4
      - id: detect
        run: ./scripts/detect-changes.sh

  build-test:
    needs: detect
    strategy:
      matrix:
        service: ${{ fromJson(needs.detect.outputs.services) }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          path: node_modules
          key: deps-${{ hashFiles('package-lock.json') }}
      - run: npm ci
      - run: npm run build --workspace=${{ matrix.service }}
      - run: npm test --workspace=${{ matrix.service }}

Metriky a pozorovanie

Čo merať:

MetrikaZdrojTarget
Lead timeCI timestamps< 15 min
Pipeline durationCI metricsKlesajúci trend
Flakiness rateTest reruns< 1%
Change failure rateRollback count< 5%

Praktický dashboard

- name: Report metrics
  run: |
    curl -X POST https://metrics.mycompany.com/ci \
      -d "pipeline_duration=${{ steps.timer.outputs.duration }}" \
      -d "tests_passed=${{ steps.tests.outputs.passed }}" \
      -d "tests_failed=${{ steps.tests.outputs.failed }}"

Záver: 10 vecí na zlepšenie do zajtra

  1. Pridajte path filters - najrýchlejšie zrýchlenie
  2. Zapnite dependency cache
  3. Nastavte fail-fast: true
  4. Pridajte concurrency s cancel-in-progress
  5. Rozdeľte mega-joby na menšie
  6. Zapnite SAST aspoň na HIGH findings
  7. Generujte SBOM
  8. Nastavte maskování secrets
  9. Pridajte timing metriky
  10. Dokumentujte pipeline v README

Váš ďalší krok: Zmerajte aktuálny čas vašej pipeline. Implementujte path filters. Zmerajte znova. Uvidíte rozdiel do hodiny.

Často kladené otázky (FAQ)

Je monorepo správna voľba pre náš tím?

Monorepo funguje najlepšie, keď máte zdieľané knižnice medzi službami alebo potrebujete atomické zmeny naprieč viacerými komponentami. Ak sú vaše služby úplne nezávislé, polyrepo môže byť jednoduchšie.

Koľko stojí remote build cache?

Záleží na provideri. Self-hosted riešenie (napr. Gradle Enterprise) stojí cca 50-100k USD/rok. Cloud riešenia (Turborepo Cloud, NX Cloud) majú free tier a platíte za usage.

Ako riešiť flaky testy?

Implementujte karanténu - automaticky presuňte flaky testy do “quarantine” suite, ktorý beží nightly ale neblokuje PR. Nastavte alert na nové flaky testy a riešte ich do 48 hodín.

Čo je SLSA a potrebujem ho?

SLSA je framework pre supply-chain security. Level 3 je odporúčaný pre produkčný software. Áno, potrebujete ho - supply-chain útoky sú čoraz častejšie.


Súvisiace články

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "CI/CD pre monorepo: Rýchlosť, cache, selektívne testy a supply-chain bezpečnosť". https://www.michal-drozd.com/sk/blog/cicd-monorepo/ (Publikované 4. októbra 2025).