A laptop screen showing programming code and debugging tools, ideal for tech topics.

İ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

  1. Secret yönetimi disiplinsiz: Production credentials, dev/test ile aynı secret’ta. Environment-scoped secrets kullan (production, staging, dev environment’ları).
  2. 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.
  3. 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.

🇹🇷 Türkçe🇬🇧 English🇩🇪 Deutsch🇫🇷 Français🇸🇦 العربية🇷🇺 Русский🇪🇸 Español