İstanbul’daki bir fintek startup, 12 microservice içeren bir monorepo ile ilerliyordu. Her commit’te 12 ayrı pipeline tetikleniyordu, her biri 8-12 dakika sürüyordu. GitHub Actions faturası aylık 800 USD’ye dayanmıştı, build’ler kuyruğa giriyor, deploy süreleri 25 dakikayı aşıyordu. Bu yazı 6 hafta süren optimizasyon çalışmasından çıkardığımız notlar.
Monorepo Tuzağı: Her Commit Tüm Servisleri Build Etmek
Başlangıç durumu: Her push’ta 12 servisin hepsi build + test + image push oluyordu. Çoğu commit aslında tek servise dokunuyor. %92 boşa harcanan compute.
Çözüm: Path-based filter ile sadece değişen servis pipeline’ı tetiklenir.
name: Service CI
on:
push:
branches: [main]
paths:
- 'services/payment/**'
- 'shared/lib/**' # shared lib değişirse de tetiklensin
pull_request:
paths:
- 'services/payment/**'
- 'shared/lib/**'
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.changes.outputs.services }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
payment:
- 'services/payment/**'
order:
- 'services/order/**'
user:
- 'services/user/**'
Optimizasyon sonrası: ortalama commit sadece 1-2 pipeline tetikliyor, %80 fatura azaldı.
Reusable Workflows: DRY Prensibi
12 servis × benzer pipeline = 12 dosya × 80 satır = 960 satır kopya kod. Reusable workflow ile tek noktada tut:
# .github/workflows/_build-and-push.yml (reusable)
on:
workflow_call:
inputs:
service-name:
type: string
required: true
service-path:
type: string
required: true
secrets:
REGISTRY_TOKEN:
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: ${{ inputs.service-path }}
push: true
tags: ghcr.io/myorg/${{ inputs.service-name }}:${{ github.sha }}
cache-from: type=gha,scope=${{ inputs.service-name }}
cache-to: type=gha,mode=max,scope=${{ inputs.service-name }}
# .github/workflows/payment-ci.yml (kullanım)
name: Payment CI
on:
push:
paths: ['services/payment/**']
jobs:
build:
uses: ./.github/workflows/_build-and-push.yml
with:
service-name: payment
service-path: services/payment
secrets:
REGISTRY_TOKEN: ${{ secrets.GHCR_TOKEN }}
12 servis × 15 satır = 180 satır. 80% azalma. Pipeline değişikliği tek dosyada.
Matrix Builds: Multi-Version Test
Bir Python servisi 3.10, 3.11, 3.12 desteklemek zorunda. Matrix ile paralel test:
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest]
fail-fast: false
steps:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pytest
3 × 2 = 6 paralel job. fail-fast: false → bir matrix job fail etse diğerleri devam eder, hepsinin sonucunu görürsün.
Self-Hosted Runner: Maliyet ve Hız
GitHub Hosted runner ücretsiz quota: ay başı 2.000 dakika (private repo). Üzeri $0.008/dakika. 12 servis × günde 30 commit × 8 dakika = 86.400 dk/ay × $0.008 = $691/ay.
Self-hosted runner: Azure VM (Standard_D4s_v5, ~$140/ay), 24/7 çalışır, sınırsız dakika. Net tasarruf $550/ay.
# Runner kurulumu (Azure VM üzerinde)
mkdir actions-runner && cd actions-runner
curl -O -L https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-linux-x64-2.319.1.tar.gz
tar xzf ./actions-runner-linux-x64-2.319.1.tar.gz
./config.sh --url https://github.com/myorg --token YOUR_TOKEN
--name "azure-runner-01" --labels "self-hosted,linux,x64,production"
sudo ./svc.sh install
sudo ./svc.sh start
Workflow’da kullanım:
jobs:
build:
runs-on: [self-hosted, linux, production]
steps: ...
Auto-scaling için: actions-runner-controller (AKS üzerinde Kubernetes operator) ile job sayısına göre runner pod scale.
OIDC Authentication: Azure’a Secret-less Auth
Eskiden Azure’a deploy için Service Principal credentials secret olarak repo’da. Modern yaklaşım: GitHub OIDC token ile Azure AD federation, secret yok.
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- run: az account show
Azure tarafında Federated Credential setup ile GitHub repository + branch + environment kombinasyonuna izin verilir. Secret rotation problemi yok.
Build Cache Stratejisi
GitHub Actions cache (actions/cache@v4) ile dependency cache. Node.js örnek:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci # 30 saniye yerine 5 saniye
Docker image build cache (BuildKit + GHA cache):
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
İlk build 8 dk, sonraki build’ler 90 saniye (sadece değişen layer).
Sahada Düşülen Üç Tuzak
- Secret yönetimi disiplinsiz: Production credentials, dev/test ile aynı secret’ta. Environment-scoped secrets kullan (production, staging, dev environment’ları).
- Workflow concurrency yok: Aynı PR’a 5 commit attıkça 5 ayrı build başlıyor, eski build’ler heba. concurrency: group/cancel-in-progress ekle.
- Status check’ler eksik: PR merge etmek için CI pass şartı yok, broken main olabilir. Branch protection rule’a required status check tanımla.
CloudSpark olarak GitHub Actions optimizasyonu, self-hosted runner kurulumu, OIDC + Azure entegrasyonu, multi-environment deployment pipeline’ları konularında danışmanlık veriyoruz.



