GitHub Actions Scheduled Workflow ile Zamanlı Görevler
Eğer CI/CD pipeline’larınızı sadece “birisi push yaptığında çalış” mantığıyla kurduysanız, muhtemelen masanızda bekleyip elle tetiklediğiniz işler hâlâ var demektir. Nightly build’ler, veritabanı yedeklemeleri, eski log dosyalarının temizlenmesi, güvenlik taramaları… Bunların hepsi gece yarısı siz uyurken otomatik olarak çalışabilir. GitHub Actions’ın scheduled workflow özelliği tam olarak bunun için var ve doğru kullanıldığında hayatı ciddi ölçüde kolaylaştırıyor.
Scheduled Workflow Nedir ve Neden Kullanırız
GitHub Actions normalde event-driven çalışır. push, pull_request, release gibi olaylar workflow’u tetikler. Ama bazı işlerin belirli bir zaman diliminde, düzenli aralıklarla çalışması gerekir. Scheduled workflow’lar bu ihtiyacı karşılar.
Teknik olarak bakarsak, GitHub’ın cron tabanlı bir zamanlayıcısı var ve siz bu zamanlayıcıya “her gece saat 02:00’de şunu yap” diyebiliyorsunuz. Underlyingda standart Unix cron syntax’ı kullanılıyor, dolayısıyla cron’a aşina olan herkes hemen adaptasyon sağlayabilir.
Gerçek dünya kullanım senaryoları şunlar oluyor genellikle:
- Nightly build ve test: Her gece tüm test suite’ini çalıştırıp sabah işe geldiğinde raporları hazır bulmak
- Dependency güncellemeleri: Haftalık olarak npm, pip veya gem bağımlılıklarını kontrol etmek
- Veritabanı yedekleme: Üretim ortamının düzenli snapshot’larını almak
- Performans testleri: Gece saatlerinde yük testi çalıştırmak
- Güvenlik taramaları: Zafiyet taramalarını mesai saatleri dışına almak
- Stale issue/PR temizliği: Uzun süredir hareketsiz kalan issue ve PR’ları otomatik etiketlemek veya kapatmak
- Cache temizliği: Eski artifact ve cache’leri periyodik olarak temizlemek
Cron Syntax’ı ve Temel Yapı
Önce temel bir scheduled workflow nasıl görünür, oradan başlayalım:
name: Nightly Build
on:
schedule:
- cron: '0 2 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run nightly build
run: |
echo "Nightly build başlıyor: $(date)"
make build
make test
Cron syntax’ı beş alandan oluşur:
- Dakika: 0-59 arası değer
- Saat: 0-23 arası değer (UTC bazlı!)
- Ayın günü: 1-31 arası değer
- Ay: 1-12 arası değer
- Haftanın günü: 0-7 arası değer (0 ve 7 Pazar’dır)
Yıldız işareti * “her” anlamına gelir. Birkaç pratik örnek:
0 2 *– Her gece saat 02:00 UTC0 8 1-5– Hafta içi her sabah 08:00 UTC0 0 0– Her Pazar gece yarısı0 /6– Her 6 saatte bir30 1 1– Her ayın ilk günü 01:30 UTC
Önemli bir not: GitHub Actions zamanlama tamamen UTC bazında çalışır. Türkiye saati UTC+3 (yaz saatinde UTC+2) olduğundan, Türkiye saatiyle gece 02:00’de bir iş çalıştırmak istiyorsanız cron’a 0 23 * yazmanız gerekir (UTC+3 için: 23:00 UTC = 02:00 TR saati).
Ayrıca GitHub’ın resmi dokümantasyonunda şu uyarı var: Yoğun dönemlerde scheduled workflow’lar planlanandan birkaç dakika geç başlayabilir. Saniyeler içinde tetiklenmesi gereken kritik işler için scheduled workflow’a güvenmemek gerekir.
Gerçek Senaryo 1: Haftalık Dependency Audit
Projenizde npm bağımlılıklarınızın güvenlik açıklarını her Pazartesi sabahı kontrol eden ve sonuçları Slack’e gönderen bir workflow yazalım:
name: Weekly Security Audit
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
jobs:
security-audit:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run npm audit
id: audit
run: |
npm audit --json > audit-report.json || true
VULN_COUNT=$(cat audit-report.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('metadata',{}).get('vulnerabilities',{}).get('total',0))")
echo "vuln_count=$VULN_COUNT" >> $GITHUB_OUTPUT
echo "Toplam zafiyet: $VULN_COUNT"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: security-audit-report
path: audit-report.json
retention-days: 30
- name: Notify Slack
if: steps.audit.outputs.vuln_count > 0
uses: slackapi/[email protected]
with:
payload: |
{
"text": "Haftalık Güvenlik Raporu",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* reposunda *${{ steps.audit.outputs.vuln_count }}* güvenlik açığı tespit edildi.n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Raporu görüntüle>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Burada dikkat ettiğiniz bir şey var mı? workflow_dispatch ekledim. Bu sayede hem zamanlayıcı tetikleyebilir hem de siz istediğiniz an GitHub UI’dan manuel olarak çalıştırabilirsiniz. Scheduled workflow’lara her zaman workflow_dispatch eklemek iyi bir alışkanlık.
Gerçek Senaryo 2: Nightly Database Backup
Production ortamında PostgreSQL veritabanının gece düzenli yedeklerini alan ve S3’e yükleyen bir workflow:
name: Nightly Database Backup
on:
schedule:
- cron: '0 23 * * *'
workflow_dispatch:
env:
BACKUP_BUCKET: s3://my-company-backups
BACKUP_PREFIX: postgres/nightly
jobs:
backup:
runs-on: ubuntu-latest
environment: production
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1
- name: Install PostgreSQL client
run: |
sudo apt-get update -qq
sudo apt-get install -y postgresql-client-16
- name: Create backup
run: |
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_${TIMESTAMP}.sql.gz"
echo "Yedek alınıyor: $BACKUP_FILE"
PGPASSWORD=${{ secrets.DB_PASSWORD }} pg_dump
-h ${{ secrets.DB_HOST }}
-U ${{ secrets.DB_USER }}
-d ${{ secrets.DB_NAME }}
--no-password
--verbose
| gzip > $BACKUP_FILE
echo "BACKUP_FILE=$BACKUP_FILE" >> $GITHUB_ENV
echo "Yedek boyutu: $(du -sh $BACKUP_FILE | cut -f1)"
- name: Upload to S3
run: |
aws s3 cp $BACKUP_FILE
${{ env.BACKUP_BUCKET }}/${{ env.BACKUP_PREFIX }}/$BACKUP_FILE
--storage-class STANDARD_IA
echo "S3'e yüklendi: ${{ env.BACKUP_BUCKET }}/${{ env.BACKUP_PREFIX }}/$BACKUP_FILE"
- name: Cleanup old backups (30 günden eski)
run: |
CUTOFF_DATE=$(date -d "30 days ago" +%Y-%m-%d)
echo "Şu tarihten eski yedekler siliniyor: $CUTOFF_DATE"
aws s3 ls ${{ env.BACKUP_BUCKET }}/${{ env.BACKUP_PREFIX }}/
| awk -v cutoff="$CUTOFF_DATE" '$1 < cutoff {print $4}'
| while read -r file; do
echo "Siliniyor: $file"
aws s3 rm "${{ env.BACKUP_BUCKET }}/${{ env.BACKUP_PREFIX }}/$file"
done
- name: Notify on failure
if: failure()
run: |
curl -X POST ${{ secrets.PAGERDUTY_WEBHOOK }}
-H "Content-Type: application/json"
-d "{"message": "KRITIK: Nightly backup basarisiz! Run: ${{ github.run_id }}"}"
Bu workflow’da environment: production kullanımına dikkat edin. GitHub Environments özelliği sayesinde, bu workflow’un üretim ortamı secret’larına erişimi kısıtlayabilir ve isteğe bağlı onay mekanizması kurabilirsiniz.
Birden Fazla Schedule Tanımlamak
Tek bir workflow dosyasında birden fazla schedule tanımlayabilirsiniz. Örneğin, bazı işlerin gündüz daha sık, gece daha seyrek çalışmasını istiyorsanız:
name: Health Check Monitor
on:
schedule:
- cron: '*/15 8-18 * * 1-5'
- cron: '0 */2 * * *'
jobs:
health-check:
runs-on: ubuntu-latest
steps:
- name: Check API health
run: |
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://api.myapp.com/health)
if [ "$RESPONSE" != "200" ]; then
echo "HATA: API yanıt vermiyor! HTTP kodu: $RESPONSE"
exit 1
fi
echo "API sağlıklı. HTTP: $RESPONSE - $(date)"
- name: Check response time
run: |
RESPONSE_TIME=$(curl -s -o /dev/null -w "%{time_total}" https://api.myapp.com/health)
echo "Yanıt süresi: ${RESPONSE_TIME}s"
# 3 saniyeden uzun sürüyorsa uyar
if (( $(echo "$RESPONSE_TIME > 3.0" | bc -l) )); then
echo "UYARI: Yanıt süresi yüksek: ${RESPONSE_TIME}s"
exit 1
fi
Hafta içi mesai saatlerinde (08-18 UTC) her 15 dakikada bir, gece ve hafta sonları ise her 2 saatte bir çalışır.
Matrix ile Paralel Scheduled Jobs
Birden fazla servisi veya ortamı aynı anda kontrol etmek için matrix strategy ile scheduled workflow’u birleştirmek güzel sonuçlar veriyor:
name: Multi-Environment Health Check
on:
schedule:
- cron: '0 */4 * * *'
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
environment:
- name: staging
url: https://staging.myapp.com
expected_version: "2.1"
- name: production
url: https://myapp.com
expected_version: "2.0"
- name: dr-site
url: https://dr.myapp.com
expected_version: "2.0"
steps:
- name: Check ${{ matrix.environment.name }}
run: |
ENV_NAME="${{ matrix.environment.name }}"
ENV_URL="${{ matrix.environment.url }}"
EXPECTED_VER="${{ matrix.environment.expected_version }}"
echo "Kontrol ediliyor: $ENV_NAME ($ENV_URL)"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$ENV_URL/health")
APP_VERSION=$(curl -s "$ENV_URL/version" | python3 -c "import sys,json; print(json.load(sys.stdin).get('version','unknown'))" 2>/dev/null || echo "unknown")
echo "HTTP Kodu: $HTTP_CODE"
echo "Uygulama versiyonu: $APP_VERSION"
if [ "$HTTP_CODE" != "200" ]; then
echo "KRITIK: $ENV_NAME down! HTTP: $HTTP_CODE"
exit 1
fi
echo "$ENV_NAME - OK (version: $APP_VERSION)"
Scheduled Workflow’larda Dikkat Edilmesi Gereken Noktalar
Default Branch Zorunluluğu
Scheduled workflow’lar sadece default branch (genellikle main veya master) üzerindeki workflow dosyasını çalıştırır. Feature branch’inizdeki bir schedule tanımı test ortamında çalışmaz. Bu özellikle yeni bir scheduled workflow geliştirirken can sıkıcı olabilir. Test etmek için workflow_dispatch tetikleyicisini ekleyip manuel olarak çalıştırın, doğruladıktan sonra merge edin.
60 Günlük İnaktivite Kuralı
GitHub, repository’de 60 gün boyunca hiçbir aktivite olmazsa scheduled workflow’ları otomatik olarak devre dışı bırakır ve size bir email gönderir. Açık kaynak veya uzun dönemli bakım projelerinde bu durumla karşılaşabilirsiniz. GitHub, devre dışı bırakmadan önce onay email’i gönderiyor, oradan “Keep enabled” diyerek devam ettirebilirsiniz.
Çakışan Çalışmaları Önlemek
Bir scheduled job, bir önceki çalışması bitmeden tekrar tetiklenirse ne olur? Varsayılan davranış her ikisinin de çalışmasına izin vermektir. Bunu engellemek için concurrency kullanın:
name: Daily Report Generator
on:
schedule:
- cron: '0 1 * * *'
concurrency:
group: daily-report
cancel-in-progress: false
jobs:
generate-report:
runs-on: ubuntu-latest
steps:
- name: Generate daily report
run: |
echo "Günlük rapor oluşturuluyor..."
# Uzun süren rapor işlemi
sleep 3600
cancel-in-progress: false ayarı, yeni bir tetikleme olduğunda önceki çalışmanın bitmesini bekler ve yeni olanı kuyrukta tutar. true yaparsanız önceki çalışmayı iptal eder.
Minimum Çalıştırma Sıklığı
GitHub Actions’ta minimum schedule sıklığı 5 dakikada bir‘dir. /1 * yazsanız bile 5 dakikada bir çalışır. Daha sık çalıştırmanız gerekiyorsa self-hosted runner ile kendi cron altyapınızı kurmanız gerekir.
Conditional Logic ile Akıllı Scheduled Jobs
Bazı durumlarda scheduled workflow’un ne zaman tetiklendiğine göre farklı davranmasını isteyebilirsiniz. Örneğin Pazartesi sabahları tam yedek, diğer günler incremental yedek:
name: Smart Backup Strategy
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
backup_type:
description: 'Yedek tipi'
required: false
default: 'auto'
type: choice
options:
- auto
- full
- incremental
jobs:
backup:
runs-on: ubuntu-latest
steps:
- name: Determine backup type
id: backup-type
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.backup_type }}" != "auto" ]; then
BACKUP_TYPE="${{ inputs.backup_type }}"
elif [ "$(date +%u)" == "1" ]; then
# Pazartesi = tam yedek
BACKUP_TYPE="full"
else
BACKUP_TYPE="incremental"
fi
echo "backup_type=$BACKUP_TYPE" >> $GITHUB_OUTPUT
echo "Seçilen yedek tipi: $BACKUP_TYPE"
- name: Run full backup
if: steps.backup-type.outputs.backup_type == 'full'
run: |
echo "TAM YEDEK alınıyor..."
# Full backup komutu
- name: Run incremental backup
if: steps.backup-type.outputs.backup_type == 'incremental'
run: |
echo "ARTIMSAL YEDEK alınıyor..."
# Incremental backup komutu
Zamanlı Görevlerde Monitoring ve Alerting
Scheduled workflow’larınızın çalışıp çalışmadığını nasıl takip ediyorsunuz? Manuel kontrol etmek pratik değil. İki yaklaşım işe yarıyor:
Birincisi, GitHub’ın kendi notification ayarlarından “Failed workflows” için email bildirimi aktif etmek. Repository Settings > Notifications bölümünden yapabilirsiniz.
İkincisi, her başarılı çalışmada bir “heartbeat” göndermek. Healthchecks.io veya benzeri bir servisle entegre ederek eğer beklenen heartbeat gelmezse uyarı alabiliriz:
name: Nightly Data Sync
on:
schedule:
- cron: '0 3 * * *'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Ping start
run: |
curl -s "${{ secrets.HEALTHCHECK_URL }}/start" || true
- name: Run data sync
run: |
echo "Veri senkronizasyonu başlıyor..."
# Senkronizasyon işlemleri burada
- name: Ping success
if: success()
run: |
curl -s "${{ secrets.HEALTHCHECK_URL }}" || true
- name: Ping failure
if: failure()
run: |
curl -s "${{ secrets.HEALTHCHECK_URL }}/fail" || true
Bu pattern’de Healthchecks.io gibi bir servis, beklenen saatte heartbeat gelmezse size email veya SMS atar. Workflow çalıştı ama işler başarısız oldu mu? O da ayrı /fail endpoint’iyle bildiriliyor.
Self-Hosted Runner ile Scheduled Jobs
Veritabanı yedeklemeleri veya iç ağa erişim gerektiren işleri GitHub’ın hosted runner’larında çalıştırmak güvenli değil ya da mümkün olmayabiliyor. Self-hosted runner kullanırken scheduled workflow yapısı aynı kalıyor, sadece runs-on değişiyor:
name: Internal DB Maintenance
on:
schedule:
- cron: '0 4 * * 0'
jobs:
maintenance:
runs-on: self-hosted
steps:
- name: Cleanup old records
run: |
psql -h localhost -U dbadmin -d maindb -c
"DELETE FROM audit_logs WHERE created_at < NOW() - INTERVAL '90 days';"
- name: Vacuum analyze
run: |
psql -h localhost -U dbadmin -d maindb -c "VACUUM ANALYZE;"
- name: Reindex
run: |
psql -h localhost -U dbadmin -d maindb -c "REINDEX DATABASE maindb;"
Self-hosted runner’ınızın iç ağda olması sayesinde doğrudan veritabanına erişebilir, external network açmanıza gerek kalmaz.
Workflow’ları Test Etmek
Scheduled workflow geliştirirken en büyük can sıkıcı şey test sürecidir. Cron zamanını beklemek pratik değil. Şu adımları izlemek işleri kolaylaştırır:
- Her zaman
workflow_dispatchtetikleyicisi ekle, manuel test için - Geliştirme sırasında cron’u
/5 *yap (her 5 dakika), merge’den önce gerçek zamana al actaracını kullanarak workflow’ları lokalde test et
# act kurulumu (macOS)
brew install act
# Scheduled workflow'u lokalde test etme
act schedule
# Belirli bir job'ı test etme
act schedule -j backup
# Workflow_dispatch ile test
act workflow_dispatch
act her özelliği desteklemiyor ama temel adımları test etmek için yeterli.
Sonuç
Scheduled workflow’lar, GitHub Actions’ın en değer katan ama en az konuşulan özelliklerinden biri. Düzgün kurulmuş bir yapıyla gece yarısı testler çalışıyor, yedekler alınıyor, güvenlik taramaları yapılıyor ve sabah işe geldiğinizde her şey hazır.
Birkaç kritik noktayı tekrar vurgulamak istiyorum: Cron zamanlaması her zaman UTC bazlıdır, her scheduled workflow’a workflow_dispatch eklemek test ve acil durum müdahalesi için şart, uzun süren işlerde concurrency ile çakışmaları engellemek gerekiyor ve monitoring olmadan hiçbir scheduled job “çalışıyor” sayılmaz.
En önemli tavsiye şu: Mevcut crontab dosyanıza ya da Windows Task Scheduler görevlerinize bakın. Bunların kaçı gerçekte bir uygulama deposuyla ilişkili? Bunları ilgili repo’nun workflow’u haline getirmek hem versiyon kontrolü hem de ekip görünürlüğü açısından büyük fayda sağlıyor. Infrastructure as Code mantığını scheduled görevlere de taşımak, “bu iş ne zaman bozuldu ki?” sorusunun cevabını git log’dan bulmanızı mümkün kılıyor.
