# Biri Github Actions mı dedi?

> GitHub Actions ile CI/CD'ye giriş: iş akışları (workflow) nasıl kurulur ve süreçler nasıl otomatikleştirilir?

- Yayın: 2024-07-23
- Güncelleme: 2026-09-07
- Kategori: Araçlar & Teknolojiler
- Etiketler: GitHub Actions
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/biri-github-actions-mi-dedi/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
GitHub Actions, bir push veya pull request gibi repository olaylarına bağlı olarak otomatik iş akışları çalıştırmanızı sağlayan bir otomasyon platformudur. Test, build ve deployment adımlarını aynı yerde, YAML dosyaları olarak tanımlarsınız; GitHub gerisini yönetir.

Bu yazıda bir PHP projesini SSH üzerinden sunucuya deploy eden, birden fazla ortamı ve IP kısıtlamasını ele alan gerçek senaryolara bakacağım. Kuralları kuru kuru geçmek yerine çalışan yapıları önce gösterip sonra üzerinden konuşmayı tercih ederim.

![GitHub Actions iş akışı (workflow) ekranı](/images/86f83-github-actions.png)

## Temel bileşenler

Bir Actions yapılandırmasının anatomisi şöyle:

- **Workflow**: `.github/workflows/` altında yaşayan YAML dosyası. Her dosya bir workflow.
- **Job**: Workflow içindeki bağımsız çalışma birimi. Varsayılan olarak paralel çalışır; [dokümanın ifadesiyle](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) "job'ları sırayla çalıştırmak için `jobs.<job_id>.needs` anahtar kelimesiyle diğer job'lara bağımlılık tanımlarsınız".
- **Step**: Job içindeki tek bir komut veya action. Sıralı çalışır; bir step başarısız olursa sonrakiler varsayılan olarak çalışmaz.
- **Action**: Marketplace'ten çekilen veya repository içinde tanımlanan yeniden kullanılabilir step. `uses:` ile çağrılır.
- **Runner**: Job'un üzerinde çalıştığı sanal makine. `ubuntu-latest` yaygın tercih; kendi self-hosted runner'ınızı da bağlayabilirsiniz.

## Tek sunucuya basit deployment

`main` branch'e her push geldiğinde sunucuya bağlanıp kodu çeken minimal bir workflow:

```yaml
name: Deploy PHP Application

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      
      - name: PHP Kurulumu
        uses: shivammathur/setup-php@v2
        with: 
          php-version: '8.0'
      
      - name: Composer paketlerinin kurulumu
        run: composer install --no-progress --no-suggest --prefer-dist

      - name: SSH Bağlantısı ve Son Kodun Alınması
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          HOST: ${{ secrets.SSH_HOST }}
          USER: ${{ secrets.SSH_USER }}
          REMOTE_PATH: ${{ secrets.REMOTE_PATH }}
        run: |
          echo "$SSH_PRIVATE_KEY" > private_key
          chmod 600 private_key
          ssh -i private_key -o StrictHostKeyChecking=no $USER@$HOST "cd $REMOTE_PATH && git pull origin main"
```

Workflow içinde kullanılan credential değerleri repository'de düz metin olarak yaşamaz; [Actions secrets](https://docs.github.com/en/actions/concepts/security/secrets) olarak saklanır. SSH anahtarı, host adresi, kullanıcı adı ve uzak dizin bilgisinin her biri için bir secret tanımlanır. Peki bu secret'lar nasıl üretilir?

Bugünden bir not: yukarıdaki `actions/checkout@v2` sürümü çok geride kaldı — [action'ın deposu bugün v7'de](https://github.com/actions/checkout). Action sürümlerini sabitlemek doğru bir alışkanlık, ama sabitlediğiniz sürümü hiç güncellememek, runner çalışma zamanı yükseltmelerinde iş akışının bir gün sessizce kırılması demek.

## SSH anahtarı ve yetkilendirme

Sunucuda bir SSH anahtar çifti oluşturun:

```bash
ssh-keygen -t rsa -b 4096 -C "yourmail@example.com"
```

Bu komut `id_rsa` (özel anahtar) ve `id_rsa.pub` (açık anahtar) üretir. Açık anahtarı sunucunun `authorized_keys` dosyasına ekleyin:

```bash
cat ~/.ssh/id_rsa.pub
```

```bash
nano ~/.ssh/authorized_keys
```

Özel anahtarın içeriği (`cat ~/.ssh/id_rsa` çıktısı) GitHub'da `SSH_PRIVATE_KEY` secret'ı olarak kaydedilir. Repository'de **Settings → Secrets and variables → Actions** altından şu dört secret'ı ekleyin:

- `SSH_PRIVATE_KEY` — sunucuda oluşturduğunuz özel anahtar
- `SSH_HOST` — sunucunun IP adresi
- `SSH_USER` — SSH bağlantısında kullanılacak kullanıcı adı
- `REMOTE_PATH` — sunucuda projenin bulunduğu dizin

Bunlar yerindeyken `main`'e yapılan her push sunucuya ulaşır.

## Farklı branch, farklı ortam

Canlı ve test ortamları genellikle ayrı sunuculara karşılık gelir. Branch koşulunu workflow içinde ele almak mümkün:

```yaml
name: Deploy PHP Application

on:
  push:
    branches:
      - main
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: SSH Bağlantısı ve Son Kodun Alınması
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
          
          if [ "${{ github.ref }}" == "refs/heads/main" ]; then
            REMOTE_USER="canli_kullanici_adi"
            REMOTE_HOST="canlı_sunucu_adresi"
            REMOTE_PATH="/home/remote/project/path"
          elif [ "${{ github.ref }}" == "refs/heads/develop" ]; then
            REMOTE_USER="test_kullanici_adi"
            REMOTE_HOST="test_sunucu_adresi"
            REMOTE_PATH="/home/remote/project/path"
          fi
          
          ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $REMOTE_USER@$REMOTE_HOST "cd $REMOTE_PATH && git pull origin ${GITHUB_REF##*/}"
```

`SSH_KNOWN_HOSTS` secret'ı için sunucunun host anahtarını şöyle alabilirsiniz:

```bash
ssh-keyscan -H your_server_domain
```

## Birden fazla sunucuya paralel deployment

Canlı ortamda birden fazla sunucu varsa döngü tercih edilebilir bir yol:

```yaml
name: Deploy PHP Application

on:
  push:
    branches:
      - main
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: SSH Bağlantısı ve Son Kodun Alınması
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
          
          if [ "${{ github.ref }}" == "refs/heads/main" ]; then
            SERVERS=("canli_sunucu1" "canli_sunucu2" "canli_sunucu3")
            REMOTE_USER="canli_kullanici_adi"
            REMOTE_PATH="/home/remote/project/path"
          elif [ "${{ github.ref }}" == "refs/heads/develop" ]; then
            SERVERS=("test_sunucu1" "test_sunucu2")
            REMOTE_USER="test_kullanici_adi"
            REMOTE_PATH="/home/remote/project/path"
          fi
          
          for SERVER in "${SERVERS[@]}"
          do
          echo "Deploying to $SERVER"
          ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no $REMOTE_USER@$SERVER "cd $REMOTE_PATH && git pull origin ${GITHUB_REF##*/}"
          done
```

Birden fazla sunucuya `SSH_KNOWN_HOSTS` için aynı anda host anahtarı toplabilirsiniz:

```bash
ssh-keyscan -H your_server1_domain your_server2_domain your_server3_domain ...
```

## Bastion host üzerinden IP kısıtlamalı sunuculara erişim

Sunucularınıza belirli IP adreslerinden erişimi kısıtladıysanız — ki kısıtlamak doğrudur — Actions runner'ının IP aralığı dinamik değişir; doğrudan bağlantı kurulamaz. Bu durumda bir bastion host üzerinden geçmek çalışan çözümdür.

Bastion host'ta SSH anahtarı oluşturup diğer sunuculara erişim yapılandırmasını `~/.ssh/config` dosyasına yazın:

```text
Host sunucu1
    HostName sunucu1_ip
    User kullanici_adi
    IdentityFile ~/.ssh/id_rsa

Host sunucu2
    HostName sunucu2_ip
    User kullanici_adi
    IdentityFile ~/.ssh/id_rsa

# Diğer sunucular için benzer yapılandırma...
```

Bu yapılandırmayla deployment workflow'u bastion üzerinden SSH ProxyJump kullanır:

```yaml
name: Deploy PHP Application

on:
  push:
    branches:
      - main
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: SSH Bağlantısı ve Son Kodun Alınması
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          BASTION_HOST: ${{ secrets.BASTION_HOST }}
          BASTION_USER: ${{ secrets.BASTION_USER }}
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          
          ssh-keyscan -H ${{ secrets.BASTION_HOST }} >> ~/.ssh/known_hosts
          
          if [ "${{ github.ref }}" == "refs/heads/main" ]; then
            SERVERS=("canli_sunucu1" "canli_sunucu2" "canli_sunucu3")
            REMOTE_USER="canli_kullanici_adi"
            REMOTE_PATH="/home/remote/project/path"
          elif [ "${{ github.ref }}" == "refs/heads/develop" ]; then
            SERVERS=("test_sunucu1" "test_sunucu2")
            REMOTE_USER="test_kullanici_adi"
            REMOTE_PATH="/home/remote/project/path"
          fi
          
          for SERVER in "${SERVERS[@]}"
          do
          echo "Deploying to $SERVER"
          ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no -J ${{ secrets.BASTION_USER }}@${{ secrets.BASTION_HOST }} $REMOTE_USER@$SERVER "cd $REMOTE_PATH && git pull origin ${GITHUB_REF##*/}"
          done
```

`-J` bayrağı SSH ProxyJump'ı etkinleştirir; runner önce bastion'a bağlanır, oradan hedef sunucuya tünel açar.

## Gözden kaçan tuzaklar

Bu yapıyı birkaç projede kurarken tekrar tekrar karşılaştığım birkaç nokta var:

**StrictHostKeyChecking=no riskini anlayın.** [OpenSSH kılavuzunun tarifiyle](https://man.openbsd.org/ssh_config#StrictHostKeyChecking) bu ayar yalnız yeni host anahtarlarını sormadan eklemiyor; anahtarı **değişmiş** sunuculara bağlanmaya da izin veriyor — man-in-the-middle vektörü tam olarak orada açılıyor, üstelik özel anahtarınızı o bağlantıya veriyorsunuz. Kılavuz `yes` için "MITM saldırılarına karşı azami koruma" diyor, `accept-new` ise ortası: yeni host'ları kabul eder ama anahtarı değişmişleri reddeder. Üretim ortamları için yukarıda gösterdiğim gibi `SSH_KNOWN_HOSTS` secret'ı ile gerçek host key doğrulaması yapmak daha güvenlidir.

**Özel anahtar dosyasını temizleyin.** İlk örnekte `private_key` dosyasını geçici olarak oluşturuyorsunuz. Step bitince runner temizlense de aynı job içinde sonraki bir step bu dosyaya erişebilir; `rm -f private_key` eklemek iyi alışkanlıktır.

**Composer çıktısını önbelleğe alın.** Her build'de `composer install` çalıştırmak runner zamanı yer. `actions/cache` ile `vendor/` dizinini `composer.lock` hash'ine göre önbellekleyebilirsiniz.

**Deployment sonrası sağlık kontrolü.** SSH komutu başarıyla döndü diye uygulama ayakta demek değildir. `git pull` ardından bir `curl` ile endpoint kontrolü eklemek erken uyarı sağlar.

GitHub Actions bu senaryo seti için yeterli ve yerinde bir araç. Workflow dosyaları repository içinde yaşadığı için deployment mantığı da kod geçmişine dahil olur — neyin ne zaman değiştiğini görmek kolaylaşır.

---

### Kaynaklar

- [GitHub Docs: Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)
- [GitHub Docs: Secrets](https://docs.github.com/en/actions/concepts/security/secrets)
- [actions/checkout](https://github.com/actions/checkout)
- [OpenSSH ssh_config(5): StrictHostKeyChecking](https://man.openbsd.org/ssh_config#StrictHostKeyChecking)
