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?
- Dlaczego GitHub Actions do wdrażania?
- Wymagania wstępne
- Konfiguracja serwera
- Utworzenie użytkownika wdrożeniowego
- Instalacja wymaganych pakietów
- Konfiguracja Nginx
- Konfiguracja PHP-FPM
- Konfiguracja PHP
- Konfiguracja struktury katalogów
- Konfiguracja klucza SSH dla GitHub Actions
- Konfiguracja Supervisora
- Dodanie sekretów GitHub
- Utworzenie workflow GitHub Actions
- Skrypt wdrożeniowy
- Konfiguracja SSL z Let's Encrypt
- Zagadnienia bezpieczeństwa
- Rozwiązywanie problemów
- Podsumowanie
- Kod źródłowy
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:
- Wdrażanie nowej wersji podczas gdy stara wersja nadal działa
- Testowanie nowej wersji przed przekierowaniem do niej ruchu
- Stopniowe przenoszenie ruchu ze starej wersji do nowej
- 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:
- Integracja z GitHub: Bezproblemowa integracja z Twoim repozytorium kodu
- Automatyzacja workflow: Definiowanie procesów wdrażania jako kod
- Sekrety środowiskowe: Bezpieczne przechowywanie wrażliwych danych uwierzytelniających
- Budowanie macierzowe: Testowanie w wielu środowiskach jednocześnie
- Zarządzanie artefaktami: Przechowywanie i pobieranie artefaktów budowania między zadaniami
- 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:
- Repozytorium GitHub: Hostujące Twoją aplikację Laravel
- Serwer VPS: Działający na Ubuntu/Debian (w tym przewodniku użyjemy Ubuntu)
- Dostęp SSH: Do Twojego serwera VPS
- Nazwa domeny: Wskazująca na adres IP Twojego serwera
- 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 VPSSSH_USER
: Nazwa użytkownika na VPSSSH_KEY
: Prywatny klucz SSH wygenerowany powyżejSSH_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
-
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
- Sprawdź właściciela plików:
-
Błędy migracji bazy danych
- Sprawdź dane dostępowe do bazy w
.env
- Uruchom migracje ręcznie:
php artisan migrate --force
- Sprawdź dane dostępowe do bazy w
-
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
- Przetestuj konfigurację Nginx:
-
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
- Sprawdź status PHP-FPM:
-
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
- Upewnij się, że dowiązania są utworzone poprawnie:
Debugowanie wdrożenia
Aby debugować problemy z wdrożeniem:
-
Włącz szczegółowe wyświetlanie
- Dodaj
-v
do komend composera - Dodaj
--verbose
do komend artisan
- Dodaj
-
Sprawdź logi GitHub Actions
- Przejrzyj pełne logi workflow w GitHub
- Szukaj konkretnych komunikatów o błędach
-
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!