🚀 Wdrażanie bez przestojów z GitHub Actions: Kompletny przewodnik

Dziś zrobimy kolejny krok i zaimplementujemy solidną strategię wdrażania bez przestojów (Zero Downtime Deployment, ZDD) przy użyciu GitHub Actions. To podejście zapewnia, że Twoja aplikacja pozostaje dostępna dla użytkowników nawet podczas wdrożeń.

📋 Spis treści

Czym jest wdrażanie bez przestojów?

Wdrażanie bez przestojów (Zero Downtime Deployment, ZDD) to strategia wdrażania, która zapewnia, że Twoja aplikacja pozostaje dostępna dla użytkowników przez cały proces wdrażania. W przeciwieństwie do tradycyjnych wdrożeń, gdzie aplikacja jest całkowicie niedostępna podczas aktualizacji, ZDD utrzymuje ciągłość usługi poprzez:

  1. Wdrażanie nowej wersji podczas gdy stara wersja nadal działa
  2. Testowanie nowej wersji przed przekierowaniem do niej ruchu
  3. Stopniowe przenoszenie ruchu ze starej wersji do nowej
  4. Utrzymywanie starej wersji jako kopii zapasowej w przypadku problemów

To podejście minimalizuje ryzyko przerwania usługi i zapewnia płynne doświadczenie dla Twoich użytkowników.

Dlaczego GitHub Actions do wdrażania?

GitHub Actions oferuje kilka zalet przy implementacji ZDD:

  1. Integracja z GitHub: Bezproblemowa integracja z Twoim repozytorium kodu
  2. Automatyzacja workflow: Definiowanie procesów wdrażania jako kod
  3. Sekrety środowiskowe: Bezpieczne przechowywanie wrażliwych danych uwierzytelniających
  4. Budowanie macierzowe: Testowanie w wielu środowiskach jednocześnie
  5. Zarządzanie artefaktami: Przechowywanie i pobieranie artefaktów budowania między zadaniami
  6. Akcje społeczności: Wykorzystanie gotowych akcji do typowych zadań wdrażania

Wymagania wstępne

Przed implementacją ZDD z GitHub Actions, upewnij się, że masz:

  1. Repozytorium GitHub: Hostujące Twoją aplikację Laravel
  2. Serwer VPS: Działający na Ubuntu/Debian (w tym przewodniku użyjemy Ubuntu)
  3. Dostęp SSH: Do Twojego serwera VPS
  4. Nazwa domeny: Wskazująca na adres IP Twojego serwera
  5. Certyfikat SSL: Dla bezpiecznych połączeń HTTPS (zalecany Let's Encrypt)

Konfiguracja serwera

1. Utworzenie użytkownika wdrożeniowego

Najpierw utwórz dedykowanego użytkownika do wdrożeń:

# Utwórz nowego użytkownika
sudo adduser deployer
sudo usermod -aG www-data deployer

# Dodaj uprawnienia dla użytkownika deployer
sudo echo "deployer ALL=(ALL) NOPASSWD:/usr/bin/chmod, /usr/bin/chown" | sudo tee /etc/sudoers.d/deployer
sudo echo "deployer ALL=(ALL) NOPASSWD:/usr/bin/systemctl restart php8.4-fpm" | sudo tee /etc/sudoers.d/deployer
sudo echo "deployer ALL=(ALL) NOPASSWD:/usr/bin/systemctl restart nginx" | sudo tee /etc/sudoers.d/deployer
sudo echo "deployer ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx" | sudo tee /etc/sudoers.d/deployer

2. Instalacja wymaganych pakietów

2.1 Instalacja niezbędnych pakietów oprogramowania:

# Aktualizacja systemu
apt update
apt install nginx php-fpm mariadb-server ufw fail2ban acl supervisor
sudo apt install php8.4-cli php8.4-common php8.4-curl php8.4-xml php8.4-mbstring php8.4-zip php8.4-mysql php8.4-gd php8.4-intl php8.4-bcmath php8.4-redis php8.4-imagick php8.4-pgsql php8.4-sqlite3 php8.4-tokenizer php8.4-dom php8.4-fileinfo php8.4-iconv php8.4-simplexml php8.4-opcache
sudo systemctl restart php8.4-fpm

2.2 Instalacja Node + PM2 dla SSR (opcjonalnie)

# Pobierz i zainstaluj nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash

# bez restartowania powłoki
\. "$HOME/.nvm/nvm.sh"

# Pobierz i zainstaluj Node.js:
nvm install 22

# Zainstaluj PM2
npm install -g pm2

# Skonfiguruj PM2
pm2 startup
pm2 save --force

3. Konfiguracja Nginx

Utwórz nowy plik konfiguracyjny Nginx:

sudo nano /etc/nginx/sites-available/laravel

Dodaj następującą konfigurację:

server {
    listen 80;
    listen [::]:80;
    server_name your-domain.com;
    root /home/deployer/laravel/current/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()";
    server_tokens off;

    index index.php;
    charset utf-8;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    client_max_body_size 100M;

    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
        log_not_found off;
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;

        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_read_timeout 300;

        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
        access_log off;
        log_not_found off;
    }

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
    gzip_min_length 1024;
    gzip_buffers 16 8k;
    gzip_disable "MSIE [1-6]\.";
}

Włącz stronę:

sudo ln -s /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

4. Konfiguracja PHP-FPM

Zaktualizuj konfigurację PHP-FPM:

sudo nano /etc/php/8.4/fpm/pool.d/www.conf

Dodaj następującą konfigurację:

[www]
user = deployer
group = deployer

listen = /var/run/php/php8.4-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500

pm.process_idle_timeout = 10s
request_terminate_timeout = 30s
request_slowlog_timeout = 5s

slowlog = /var/log/php-fpm/slow.log

php_admin_value[error_log] = /var/log/php-fpm/error.log
php_admin_flag[log_errors] = on

php_admin_value[memory_limit] = 256M
php_admin_value[disable_functions] = "exec,passthru,shell_exec,system"
php_admin_value[open_basedir] = "/home/deployer/laravel/current/:/tmp/:/var/lib/php/sessions/"

5. Konfiguracja PHP

Zaktualizuj konfigurację PHP:

sudo nano /etc/php/8.4/fpm/php.ini

Dodaj następującą konfigurację:

[PHP]
expose_php = Off
max_execution_time = 30
max_input_time = 60
memory_limit = 256M
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php8.4-fpm.log

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.enable_cli=0
opcache.jit_buffer_size=256M
opcache.jit=1235

realpath_cache_size=4096K
realpath_cache_ttl=600

session.gc_probability=1
session.gc_divisor=100
session.gc_maxlifetime=1440

6. Konfiguracja struktury katalogów

Utwórz strukturę katalogów dla wdrożeń:

# Utwórz strukturę z odpowiednimi uprawnieniami
sudo mkdir -p /home/deployer/laravel/{releases,shared}
sudo chown -R deployer:www-data /home/deployer/laravel
sudo chmod -R 2775 /home/deployer/laravel

# Konfiguracja folderów współdzielonych
sudo mkdir -p /home/deployer/laravel/shared/storage/{app,framework,logs}
sudo mkdir -p /home/deployer/laravel/shared/storage/framework/{cache,sessions,views}
sudo chmod -R 775 /home/deployer/laravel/shared
sudo chmod -R 775 /home/deployer/laravel/shared/storage
sudo chown -R deployer:www-data /home/deployer/laravel/shared/storage

# Ustaw ACL dla przyszłych plików
sudo setfacl -Rdm g:www-data:rwx /home/deployer/laravel

7. Konfiguracja klucza SSH dla GitHub Actions (jako użytkownik deployer)

# Utwórz katalog SSH
mkdir -p ~/.ssh
chmod 700 ~/.ssh

# Wygeneruj klucz SSH
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy"

# Dodaj klucz publiczny do authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

# Wyświetl klucz prywatny
cat ~/.ssh/id_rsa

8. Konfiguracja Supervisora

# Utwórz plik konfiguracyjny Supervisora
sudo nano /etc/supervisor/conf.d/laravel.con
[program:laravel-worker]
command=/usr/bin/php /home/deployer/laravel/current/artisan queue:work --timeout=3600 --tries=3 --sleep=3 --stop-when-empty
autostart=true
autorestart=true
user=deployer
numprocs=1
stdout_logfile=/home/deployer/laravel/shared/storage/logs/laravel-worker.log
stderr_logfile=/home/deployer/laravel/shared/storage/logs/laravel-worker.log
# Zaktualizuj główny plik konfiguracyjny Supervisora
sudo nano /etc/supervisor/supervisord.conf
; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock   ; (the path to the socket file)
chmod=0700                       ; sockef file mode (default 0700)

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

[include]
files = /etc/supervisor/conf.d/*.con
# Uruchom Supervisora
sudo supervisorctl start all

# Użyteczne polecenia
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart all
sudo supervisorctl status

9. Dodanie sekretów GitHub

Dodaj następujące sekrety do swojego repozytorium GitHub:

  • SSH_HOST: Adres IP lub domena Twojego serwera VPS
  • SSH_USER: Nazwa użytkownika na VPS
  • SSH_KEY: Prywatny klucz SSH wygenerowany powyżej
  • SSH_PORT: Port SSH (domyślnie 22)

Dodaj zmienną dla pliku .env produkcyjnego

  • ENV_FILE: Zawartość Twojego pliku .env

10. Utworzenie workflow GitHub Actions

Utwórz nowy plik .github/workflows/workflow.yml w swoim repozytorium:

name: Wdrażanie bez przestojów

on:
  push:
    branches:
      - main

jobs:
  test:
    name: 🧪 Testy i Lint
    runs-on: ubuntu-latest
    steps:
      - name: Pobierz kod
        uses: actions/checkout@v4

      - name: Konfiguracja PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: mbstring, dom, fileinfo, sqlite3, zip, gd, intl, redis, imagick
          coverage: xdebug
          tools: composer:v2

      - name: Konfiguracja Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - name: Kopiuj .env.testing
        run: cp .env.testing .env

      - name: Pobierz katalog cache Composera
        id: composer-cache
        run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

      - name: Cache zależności Composera
        uses: actions/cache@v4
        with:
          path: |
            vendor
            ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      - name: Instalacja zależności Composera
        run: composer install --prefer-dist --no-progress

      - name: Instalacja zależności NPM
        run: npm ci

      - name: Budowanie zasobów dla testów
        run: npm run build

      - name: Generowanie konfiguracji Ziggy dla testów
        run: php artisan ziggy:generate

      - name: Uruchomienie sprawdzeń jakości kodu
        run: |
          composer larastan
          composer pint
          npm run format
          npm run types
          npm run lint

      - name: Uruchomienie testów (z Pest)
        env:
          DB_CONNECTION: sqlite
          DB_DATABASE: ':memory:'
          SESSION_DRIVER: array
        run: ./vendor/bin/pest

  build:
    name: 🏗️ Budowanie wydania
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Pobierz kod
        uses: actions/checkout@v4

      - name: Utwórz plik .env z zmiennych GitHub
        run: |
          echo "${{ vars.ENV_FILE }}" > .env

      - name: Konfiguracja PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: mbstring, dom, fileinfo, sqlite3, zip, gd, intl, redis, imagick
          tools: composer:v2

      - name: Instalacja zależności Composera
        run: composer install --optimize-autoloader --no-dev --prefer-dist --no-interaction --no-progress

      - name: Generowanie konfiguracji Ziggy
        run: php artisan ziggy:generate

      - name: Konfiguracja Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - name: Instalacja zależności NPM
        run: npm ci

      - name: Instalacja dotenv-cli
        run: npm install -g dotenv-cli

      - name: Budowanie zasobów i SSR
        run: dotenv -e .env -- npm run build:ssr

      - name: Tworzenie archiwum wydania
        run: |
          mkdir release
          shopt -s extglob
          cp -r !(release|.git|tests|node_modules|release.tar.gz) release/
          tar -czf release.tar.gz -C release .
          rm -rf release

      - name: Przesłanie artefaktu wydania
        uses: actions/upload-artifact@v4
        with:
          name: release
          path: release.tar.gz

  deploy:
    name: 🚀 Wdrożenie na serwer
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Pobierz kod
        uses: actions/checkout@v4

      - name: Konfiguracja klucza SSH
        uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.SSH_KEY }}

      - name: Konfiguracja known_hosts
        run: |
          mkdir -p ~/.ssh
          ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

      - name: Pobierz artefakt wydania
        uses: actions/download-artifact@v4
        with:
          name: release
          path: .

      - name: Utwórz plik .env z zmiennych GitHub
        run: |
          echo "${{ vars.ENV_FILE }}" > .env

      - name: Prześlij wydanie na serwer
        run: |
          scp -vvv -P ${{ secrets.SSH_PORT }} release.tar.gz ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/home/${{ secrets.SSH_USER }}/laravel/

      - name: Prześlij plik .env do katalogu współdzielonego
        run: |
          scp -P ${{ secrets.SSH_PORT }} .env ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/home/${{ secrets.SSH_USER }}/laravel/shared/.env

      - name: Uruchom skrypt wdrożeniowy na serwerze
        run: |
          ssh -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} 'bash -s' < ./deploy.sh

11. Skrypt wdrożeniowy (deploy.sh)

#!/bin/bash

set -e
set -o pipefail

APP_USER="deployer"
APP_GROUP="www-data"
APP_BASE="/home/$APP_USER/laravel"
RELEASES_DIR="$APP_BASE/releases"
SHARED_DIR="$APP_BASE/shared"
CURRENT_LINK="$APP_BASE/current"
NOW=$(date +%Y-%m-%d-%H%M%S)-$(openssl rand -hex 3)
RELEASE_DIR="$RELEASES_DIR/$NOW"
ARCHIVE_NAME="release.tar.gz"

# ... reszta skryptu wdrożeniowego ...

12. Konfiguracja SSL z Let's Encrypt

Zainstaluj i skonfiguruj SSL:

# Zainstaluj Certbot
sudo apt install -y certbot python3-certbot-nginx

# Uzyskaj certyfikat SSL
sudo certbot --nginx -d twoja-domena.com

# Skonfiguruj automatyczne odnowienie
sudo systemctl status certbot.timer

Zagadnienia bezpieczeństwa

1. Zarządzanie kluczami SSH

Przechowuj swoje klucze SSH bezpiecznie w sekretach GitHub:

# Wygeneruj klucz SSH
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy"

# Dodaj do sekretów GitHub
# SSH_HOST: Adres IP serwera
# SSH_USER: deployer
# SSH_KEY: Zawartość klucza prywatnego

2. Zmienne środowiskowe

Przechowuj wrażliwe informacje w sekretach GitHub:

# Dodaj do sekretów GitHub
# ENV_FILE: Zawartość Twojego pliku .env

3. Uprawnienia plików

Zapewnij odpowiednie uprawnienia plików:

chmod -R 775 /home/deployer/laravel/current/storage
chmod -R 775 /home/deployer/laravel/current/bootstrap/cache

4. Konfiguracja zapory

Skonfiguruj zaporę serwera:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Rozwiązywanie problemów

Typowe problemy i rozwiązania

  1. Błędy odmowy dostępu

    • Sprawdź właściciela plików: sudo chown -R deployer:www-data /home/deployer/laravel
    • Sprawdź uprawnienia plików: sudo chmod -R 775 /home/deployer/laravel
  2. Błędy migracji bazy danych

    • Sprawdź dane dostępowe do bazy w .env
    • Uruchom migracje ręcznie: php artisan migrate --force
  3. Problemy z konfiguracją Nginx

    • Przetestuj konfigurację Nginx: sudo nginx -t
    • Sprawdź logi Nginx: sudo tail -f /home/deployer/laravel/shared/storage/logs/nginx_error.log
  4. Problemy z PHP-FPM

    • Sprawdź status PHP-FPM: sudo systemctl status php8.4-fpm
    • Sprawdź logi PHP-FPM: sudo tail -f /home/deployer/laravel/shared/storage/logs/php-fpm-error.log
  5. Problemy z dowiązaniami symbolicznymi

    • Upewnij się, że dowiązania są utworzone poprawnie: ls -la /home/deployer/laravel/current
    • Utwórz dowiązania ręcznie w razie potrzeby

Debugowanie wdrożenia

Aby debugować problemy z wdrożeniem:

  1. Włącz szczegółowe wyświetlanie

    • Dodaj -v do komend composera
    • Dodaj --verbose do komend artisan
  2. Sprawdź logi GitHub Actions

    • Przejrzyj pełne logi workflow w GitHub
    • Szukaj konkretnych komunikatów o błędach
  3. Połącz się przez SSH z serwerem podczas wdrożenia

    • Dodaj krok do workflow, aby utrzymać połączenie otwarte
    • Sprawdź stan serwera podczas wdrożenia

Kod źródłowy

Pełny kod z tego artykułu znajdziesz na GitHub. Możesz go sklonować i użyć jako punkt wyjścia dla własnych projektów.


Śledź mnie na LinkedIn, aby otrzymywać więcej wskazówek o Laravel i DevOps!

Czy chciałbyś dowiedzieć się więcej o jakimś konkretnym aspekcie wdrażania bez przestojów? Daj znać w komentarzach poniżej!

Komentarze (0)
Zostaw komentarz

© 2025 Wszelkie prawa zastrzeżone.