CI/CD pre monorepo: Rýchlosť, cache, selektívne testy a supply-chain bezpečnosť
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ť
| Metrika | Zlý stav | Cieľový stav |
|---|---|---|
| PR pipeline | 40+ min | < 10 min |
| Main pipeline | 60+ min | < 20 min |
| False positives | Denne | Týždenne |
| Security findings | Ignorované | Triaged do 24h |
| Rollback time | Hodiny | Minú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 |
|---|---|---|
| PR | Affected services + lint | Rýchla spätná väzba |
| Main | All affected + integration | Gatekeeping pred release |
| Tag | Full + security + publish | Release readiness |
| Nightly | Everything + slow tests | Kompletná 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 cachujeme | Kedy invalidovať |
|---|---|---|
| Dependency | node_modules, .m2, pip | lockfile change |
| Build | compiled artifacts | source change |
| Docker layers | base images | Dockerfile change |
| Test | test fixtures | test 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ť?
| Check | PR Block | Main Block | Komentár |
|---|---|---|---|
| Lint | Yes | Yes | Rychlé, jednoznačné |
| Unit tests | Yes | Yes | Základná funkčnosť |
| Integration | No (report) | Yes | Môže byť flaky |
| Coverage drop | No (report) | Yes | Trend je dôležitejší |
| Security HIGH | Yes | Yes | Kritické |
| Security MED | No (report) | No | Triage |
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ť
| Typ | Kedy | Blokovať? |
|---|---|---|
| SAST (Semgrep, CodeQL) | Každý PR | HIGH = yes |
| Dependency scan | Každý PR + nightly | Critical = yes |
| DAST | Nightly | No, triage |
| Container scan | Pred push do registry | HIGH = 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úť:
- “Build everything always” - 90% času zbytočne
- Flaky testy bez karantény - erodujú dôveru v CI
- Secrets v logoch -
set +xnestačí, použite masking - Mega-jobs - jeden job 30 min vs. 10 jobov po 3 min
- 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ť:
| Metrika | Zdroj | Target |
|---|---|---|
| Lead time | CI timestamps | < 15 min |
| Pipeline duration | CI metrics | Klesajúci trend |
| Flakiness rate | Test reruns | < 1% |
| Change failure rate | Rollback 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
- Pridajte path filters - najrýchlejšie zrýchlenie
- Zapnite dependency cache
- Nastavte
fail-fast: true - Pridajte
concurrencyscancel-in-progress - Rozdeľte mega-joby na menšie
- Zapnite SAST aspoň na HIGH findings
- Generujte SBOM
- Nastavte maskování secrets
- Pridajte timing metriky
- 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
- Architektúra ako kód: ADR, C4 diagramy a quality gates v CI - Ako dokumentovať architektonické rozhodnutia a automatizovať ich validáciu
- Zero-downtime migrácie PostgreSQL: Expand/Contract, backfill a rollback stratégie - Bezpečné databázové migrácie, ktoré môžete integrovať do vášho CI/CD
Súvisiace články
Kubernetes TLS Certifikát Rotácia: Výpadok o 3:00 Ráno
Certifikát expiroval o 3:00, služba padla. cert-manager renewal ticho zlyhal. Ukážem monitoring, testovanie rotácie a prevenciu cert-related výpadkov.
Kubernetes rollout bez výpadku DB: Ako zastaviť PostgreSQL connection storm
Reprodukovateľný lab na demonštráciu connection stormu pri K8s rolloutoch. PgBouncer, preStop hooks a jitter - praktické riešenia s benchmarkmi.
Java Profilovanie v Hardened Kubernetes: Keď Security Blokuje Tvoj Debugger
Nemôžeš pripojiť profiler k produkčnej JVM. seccomp blokuje perf_event_open, container dropol CAP_SYS_PTRACE a PodSecurityPolicy bráni privileged mode. Tu je ako profilovať aj tak.
JWT Revokovanie Stratégie: Keď Stateless Tokeny Potrebujú Stav
Používateľ kompromitovaný, treba revokovať JWT okamžite. Ale JWT sú immutable. Porovnávam allowlist, denylist a krátku expiráciu s performance benchmarkmi.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.