Tüm yazılar

blog

GitHub Actions ile kendi VPS’ine dağıtım: dört yaklaşım

Yayın tarihi

GitHub Actions’tan kendi sunucuna dağıtmanın her yolu CI’a bir tür sır verir. İşe yarar karşılaştırma şu: CI hesabın ya da tek bir workflow ele geçirilirse, o sır birine neyi yaptırır?

Yaklaşım 1: workflow’dan SSH

Workflow bir özel anahtar tutar, sunucuya bağlanır ve dağıtımı çalıştırır. Kurması en kolay olanı; kısıtlanmamış bir anahtarla da etki alanı en geniş olanı: anahtarı ele geçiren bir kabuk elde eder.

İki şey onu çok daha güvenli yapar. Sunucunun host anahtarını, cevap veren her şeye güvenmek yerine bir secret’ta sabitle ve anahtarı sunucuda tam olarak tek bir komut çalıştırabilecek şekilde kısıtla.

Workflow adımı

- name: Deploy over SSH
  env:
    SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
    KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
  run: |
    install -m 700 -d ~/.ssh
    echo "$SSH_KEY" > ~/.ssh/deploy && chmod 600 ~/.ssh/deploy
    echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
    ssh -T -i ~/.ssh/deploy deploy@203.0.113.10

DEPLOY_KNOWN_HOSTS değerini zaten güvendiğin bir oturumdan doldur ve sunucuda ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub çıktısıyla karşılaştır. Workflow içinde ssh-keyscan çalıştırmak, o gün cevap veren her şeye güvenmek demektir.

Anahtarı sunucuda kısıtla

Dağıtım kullanıcısının ~/.ssh/authorized_keys dosyasında zorunlu bir komut ve restrict, anahtarı tek amaçlı bir tetikleyiciye çevirir. İstemci hangi komutu isterse istesin sunucu script’i çalıştırır; port yönlendirme, agent yönlendirme ve terminal reddedilir.

Script’i istemcinin gönderdiği şeyden bağımsız tut. İstenen komut ona SSH_ORIGINAL_COMMAND olarak ulaşır; bir gün okuyacaksan sıkı doğrula.

command="/opt/app/deploy.sh",restrict ssh-ed25519 AAAAC3Nz... github-actions

Bu satırla, sızan bir anahtar bir dağıtım başlatabilir, başka hiçbir şey yapamaz. Bu satır olmadan aynı anahtar bir giriş hakkıdır.

Yaklaşım 2: self-hosted runner

GitHub’ın runner’ını sunucuya kurarsın ve workflow doğrudan orada, Docker’a ve yerel dosya sistemine erişimle çalışır. Hiçbir SSH anahtarı sunucudan çıkmaz ve derlemeler yerel önbelleği kullanabilir.

Bedeli, workflow kodunun artık üretim makinende, runner hangi kullanıcıysa o kullanıcı olarak çalışması. GitHub self-hosted runner’ları yalnızca özel depolar için öneriyor; çünkü herkese açık bir depoda bir fork’tan gelen pull request kendi workflow’unu senin makinende çalıştırmayı deneyebilir. Docker daemon’ıyla konuşabilen bir runner da, yine, root’tur.

Yaklaşım 3: registry’ye gönder, sonra webhook çağır

CI imajı derleyip bir registry’ye gönderir. Sonra sunucundaki küçük bir servise tek bir HTTP isteği yollar; o servis yeni imajı kendi salt okunur kimlik bilgisiyle çeker ve uygulamayı yeniden başlatır. CI hiçbir zaman SSH erişimi tutmaz, sunucu da sabit bir eylem kümesi dışında hiçbir şey kabul etmez.

Bedava bir geçiş değil: registry’ne gönderebilen kişi sırada neyin çalışacağına karar verir. Ama sızan bir webhook sırrı, birinin giriş yapmasına değil, zaten orada olanı yeniden dağıtmasına izin verir.

Derle, GHCR’a gönder, sonra sunucudan çekmesini iste

permissions:
  contents: read
  packages: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/example/app:latest
      - name: Ask the server to pull
        env:
          DEPLOY_URL: ${{ secrets.DEPLOY_URL }}
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          curl --fail --silent --show-error -X POST "$DEPLOY_URL" \
            -H "Authorization: Bearer $DEPLOY_TOKEN"

Yaklaşım 4: derleme çıktısını rsync’le

Statik bir sitede yeniden başlatılacak bir şey yok: CI’da derle ve dosyaları web sunucusunun sunduğu dizine kopyala. SSH üzerinden rsync yalnızca değişeni gönderir.

Yaklaşım 1’deki anahtar kaygısı burada da geçerli, benzer bir çözüm de var: rsync, zorunlu komut olarak kullanıldığında anahtarı tek bir dizine hapseden rrsync adlı bir yardımcıyla gelir. Kurulum yolu dağıtımdan dağıtıma değişir. Süreci ya da veritabanı olan her şeyde dosya kopyalamak dağıtımın yalnızca yarısıdır; yeniden başlatma yarısı seni yaklaşım 1’e geri götürür.

authorized_keys: bu anahtar yalnızca /var/www/site dizinine ulaşabilir

command="rrsync /var/www/site",restrict ssh-ed25519 AAAAC3Nz... github-actions

Yaklaşım yaklaşım hangi sır sızıyor

  1. 01Kısıtsız SSH anahtarı: sunucuda bir kabuk. Zorunlu komutla: tek bir dağıtım eylemi.
  2. 02Self-hosted runner: hiçbir şeyin sızmasına gerek yok — ele geçirilmiş workflow kodu zaten sunucuda çalışıyor.
  3. 03Registry artı webhook: yeniden dağıtma yetkisi, artı registry token’ının izin verdikleri. Asıl ödül registry’ye gönderme yetkisi.
  4. 04rrsync ile sınırlanmış rsync: tek bir sitenin dosyalarına yazma yetkisi.

Kimlik doğrulamasız bir dağıtım adresi bir uzaktan kumandadır

Webhook’u karşılayan şey isteğin kimliğini doğrulamak zorunda. Açık bir dağıtım adresi herkesin istediği an yeniden başlatma tetiklemesine izin verir; parametre olarak etiket ya da dal kabul eden bir adres ise neyin çalışacağını seçmelerine.

Sırrı sorgu dizesinde değil, bir başlıkta gönder — sorgu dizeleri erişim loglarına, proxy loglarına ve kabuk geçmişine düşer. Daha iyisi, GitHub’ın kendi webhook’larını X-Hub-Signature-256 ile imzaladığı gibi istek gövdesini HMAC ile imzala ve imzayı sunucuda sabit zamanlı karşılaştır.

Yaklaşım 3’ün MihraOps sürümü

MihraOps yaklaşım 3’ü isteğe bağlı bir Agent ile uygular: sunucunda mihraops-agent adlı bir konteyner. Her uygulama /hooks/<name> adresinde kendi tetikleyicisini alır; bir bearer token ya da GitHub tarzı HMAC imzasıyla doğrulanır ve doğrulamayı geçemeyen bir istek hiçbir şeyi değiştirmez.

Dağıtım webhook’ları Pro’nun parçası. Workflow adımı ve token kurulumu doküman sayfasında.

Sırada