2025年6月16日 星期一

如何在GitLab CI/CD Laravel 自動化資料庫更新配置?

 


GitLab CI/CD Laravel 自動化資料庫更新配置

您好!很高興能為您提供關於如何在 GitLab CI/CD 中為 Laravel 專案配置自動化資料庫更新的詳細指南,其中包含了測試、構建和部署階段,並整合了資料庫備份、錯誤通知和環境變數管理。作為一名資深 PHP 工程師,您將會發現這些步驟清晰易懂。

專案設定概述

我們將會利用您的 Laravel 專案,結合 MySQL 資料庫和 PHPUnit 進行測試。部署目標是遠端伺服器。

程式碼範例

以下是您專案中需要準備的相關程式碼和設定檔案:

1. GitLab CI/CD 設定檔:.gitlab-ci.yml

這個檔案定義了 CI/CD 流程的每個階段和任務。請將其儲存於您的專案根目錄。

YAML
stages:
  - test
  - build
  - deploy

variables:
  # Laravel 相關環境變數 (可在 GitLab CI/CD 變數中覆寫)
  APP_ENV: production
  APP_DEBUG: false
  # MySQL 服務設定
  MYSQL_DATABASE: $DB_DATABASE
  MYSQL_ROOT_PASSWORD: $DB_PASSWORD # 使用 DB_PASSWORD 作為 root 密碼以便於 CI 環境設置
  MYSQL_USER: $DB_USERNAME
  MYSQL_PASSWORD: $DB_PASSWORD

cache:
  paths:
    - vendor/

.php_template: &php_template
  image: php:8.2-fpm-alpine # 使用較輕量的 Alpine 映像
  before_script:
    - apk add --no-cache git openssh-client curl # 安裝必要的工具
    - docker-php-ext-install pdo_mysql # 安裝 MySQL 擴展
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer # 安裝 Composer
    - composer install --no-interaction --prefer-dist --optimize-autoloader

test:
  <<: *php_template
  stage: test
  services:
    - mysql:8.0 # 使用 MySQL 8.0 服務
  variables:
    MYSQL_ALLOW_EMPTY_PASSWORD: "yes" # 允許 MySQL 服務使用空密碼,方便測試環境
  script:
    - cp .env.example .env # 複製 .env.example 到 .env
    - php artisan key:generate # 生成應用程式金鑰
    - php artisan migrate:fresh --seed # 執行 Migration 並填充測試資料
    - vendor/bin/phpunit --coverage-text --colors=never # 執行 PHPUnit 測試並生成覆蓋率報告
  only:
    - main
    - merge_requests

build:
  <<: *php_template
  stage: build
  script:
    - composer install --no-dev --optimize-autoloader # 安裝生產環境依賴
    - php artisan config:cache # 快取設定
    - php artisan route:cache # 快取路由
    - php artisan view:cache # 快取視圖
    - zip -r laravel-app.zip . -x ".git/*" "vendor/*" # 壓縮應用程式程式碼
  artifacts:
    paths:
      - laravel-app.zip
  only:
    - main

deploy:
  stage: deploy
  image: alpine/git # 使用輕量級映像,包含 SSH 客戶端
  before_script:
    - apk add --no-cache openssh-client rsync # 安裝 SSH 客戶端和 rsync
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa # 寫入 SSH 私鑰
    - chmod 600 ~/.ssh/id_rsa # 設定私鑰權限
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts # 寫入已知主機
    - chmod 644 ~/.ssh/known_hosts
  script:
    # 資料庫備份
    - ssh "$SERVER_USER"@"$SERVER_HOST" "mysqldump -u '$DB_USERNAME' -p'$DB_PASSWORD' '$DB_DATABASE' > /backups/db_backup_$(date +%Y-%m-%d_%H-%M-%S).sql" || (echo "資料庫備份失敗!" && exit 1)
    # 部署應用程式
    - rsync -avz --delete --exclude 'vendor/' --exclude '.git/' --exclude '.env' laravel-app.zip "$SERVER_USER"@"$SERVER_HOST":/tmp/laravel-app.zip
    - ssh "$SERVER_USER"@"$SERVER_HOST" "unzip -o /tmp/laravel-app.zip -d /var/www/laravel-app && rm /tmp/laravel-app.zip"
    # 動態生成 .env 文件
    - |
      ssh "$SERVER_USER"@"$SERVER_HOST" << EOF
        cd /var/www/laravel-app
        echo "APP_NAME=Laravel" > .env
        echo "APP_ENV=$APP_ENV" >> .env
        echo "APP_KEY=$(php artisan key:generate --show)" >> .env
        echo "APP_DEBUG=$APP_DEBUG" >> .env
        echo "APP_URL=https://your-domain.com" >> .env
        echo "DB_CONNECTION=mysql" >> .env
        echo "DB_HOST=$DB_HOST" >> .env
        echo "DB_PORT=3306" >> .env
        echo "DB_DATABASE=$DB_DATABASE" >> .env
        echo "DB_USERNAME=$DB_USERNAME" >> .env
        echo "DB_PASSWORD=$DB_PASSWORD" >> .env
        chmod 664 .env # 設定 .env 檔案權限
        chown www-data:www-data .env # 設定 .env 檔案所有者
        php artisan optimize:clear # 清除應用程式快取
        php artisan config:cache # 重新快取設定
        php artisan route:cache # 重新快取路由
        php artisan view:cache # 重新快取視圖
      EOF
    # 執行資料庫 Migration
    - ssh "$SERVER_USER"@"$SERVER_HOST" "cd /var/www/laravel-app && php artisan migrate --force" || (echo "資料庫 Migration 失敗!" && exit 1)
    # 重啟 Nginx
    - ssh "$SERVER_USER"@"$SERVER_HOST" "sudo service nginx restart" || (echo "Nginx 重啟失敗!" && exit 1)
    # 發送 Slack 通知 (成功)
    - |
      if [ -n "$SLACK_WEBHOOK_URL" ]; then
        curl -X POST -H 'Content-type: application/json' --data '{"text":"✅ Laravel 應用程式已成功部署到 '$SERVER_HOST'!"}' $SLACK_WEBHOOK_URL
      fi
  after_script:
    # 發送 Slack 通知 (失敗)
    - |
      if [ "$CI_JOB_STATUS" == "failed" ] && [ -n "$SLACK_WEBHOOK_URL" ]; then
        curl -X POST -H 'Content-type: application/json' --data '{"text":"❌ Laravel 部署到 '$SERVER_HOST' 失敗!請檢查 GitLab CI/CD 管道日誌。"}' $SLACK_WEBHOOK_URL
      fi
  only:
    - main
  when: manual # 建議部署階段設為手動觸發,以進行更多控制

2. 範例 Migration 文件

這是一個新增 phone_number 欄位的 Migration 檔案。

PHP
// database/migrations/YYYY_MM_DD_HHMMSS_add_phone_to_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddPhoneToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('phone_number')->nullable()->after('email');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('phone_number');
        });
    }
}

3. 範例 .env.example 文件

請確保您的專案根目錄有此檔案,其中包含資料庫連線設定。

程式碼片段
APP_NAME=Laravel
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://your-domain.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel_user
DB_PASSWORD=secret

4. PHPUnit 測試範例

此測試用於驗證 users 表是否包含 phone_number 欄位。

PHP
// tests/Feature/UserTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;

class UserTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test if the user table has the phone_number column.
     *
     * @return void
     */
    public function test_user_table_has_phone_column()
    {
        $this->artisan('migrate'); // 確保在測試前執行 Migration

        $user = User::create([
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => bcrypt('password'),
            'phone_number' => '1234567890',
        ]);

        $this->assertDatabaseHas('users', [
            'email' => 'test@example.com',
            'phone_number' => '1234567890',
        ]);
    }
}

5. PHPUnit 配置範例

確保 phpunit.xml 配置正確,支援 MySQL 或 SQLite 測試環境。

XML
<phpunit>
    <php>
        <env name="DB_CONNECTION" value="mysql"/>
        <env name="DB_HOST" value="mysql"/> # 因為 CI 服務名稱為 mysql
        <env name="DB_DATABASE" value="laravel"/> # 與 CI/CD 中設定的資料庫名稱一致
        <env name="DB_USERNAME" value="laravel_user"/>
        <env name="DB_PASSWORD" value="secret"/>
    </php>
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </source>
</phpunit>

配置步驟

1. GitLab CI/CD 變數設定

前往您的 GitLab 專案:Settings > CI/CD > Variables,然後添加以下變數。請確保敏感資訊的變數(如密碼)勾選 "保護變數"

  • SSH_PRIVATE_KEY: 遠端伺服器的 SSH 私鑰。
  • SSH_KNOWN_HOSTS: 遠端伺服器的公鑰。您可以透過在終端機執行 ssh-keyscan your-server.com 來獲取。
  • SERVER_USER: 伺服器用戶名 (例如:deployer)。
  • SERVER_HOST: 伺服器 IP 或域名 (例如:your-domain.com)。
  • DB_HOST: 生產環境資料庫主機 (例如:mysqldb.your-domain.com)。
  • DB_DATABASE: 資料庫名稱 (例如:laravel)。
  • DB_USERNAME: 資料庫用戶名 (例如:laravel_user)。
  • DB_PASSWORD: 資料庫密碼 (敏感資訊,請設為保護變數)。
  • SLACK_WEBHOOK_URL: Slack 通知的 Webhook URL (可選)。

2. 伺服器環境準備

確保您的遠端伺服器已安裝以下軟體和配置:

  • PHP 8.2+
  • MySQL 8.0+
  • Nginx (配置 Nginx 指向 /var/www/laravel-app/public 作為根目錄)
  • Composer

請確保伺服器上存在資料庫用戶和資料庫,並授予適當的權限。您可以執行以下指令來完成:

Bash
mysql -u root -p
CREATE DATABASE laravel;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'secret';
GRANT ALL PRIVILEGES ON laravel.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
exit;

此外,請確保 /var/www/laravel-app 目錄存在且具有正確的權限,以便您的部署用戶可以寫入。

3. 專案準備

  • 將您的 Migration 文件、測試檔案和 .env.example 提交到 GitLab 儲存庫。
  • 確認 phpunit.xml 已根據上述範例正確配置。

程式碼功能與說明

這個 CI/CD 管道包含三個主要階段:

1. 測試階段 (test)

  • 環境模擬:使用 php:8.2-fpm-alpine 映像和 mysql:8.0 服務,模擬生產環境。
  • 依賴安裝:安裝專案所需的 Composer 依賴。
  • 資料庫操作:執行 php artisan migrate:fresh --seed 來重置資料庫並運行所有 Migration,同時填充測試資料。
  • 單元測試:運行 PHPUnit 測試,並生成程式碼覆蓋率報告。
  • 觸發條件:僅在推送到 main 分支或建立 Merge Requests 時觸發。

2. 構建階段 (build)

  • 生產環境優化:安裝 Composer 依賴時排除開發環境的依賴 (--no-dev),並優化自動載入器。
  • 應用程式快取:快取 Laravel 的設定、路由和視圖,以提升效能。
  • 打包應用程式:將 Laravel 應用程式程式碼壓縮成 laravel-app.zip 檔案,以方便部署。
  • 觸發條件:僅在推送到 main 分支時觸發。

3. 部署階段 (deploy)

  • 資料庫備份:在部署前自動備份生產環境的資料庫到遠端伺服器的 /backups 目錄。備份檔案會包含時間戳記,例如:db_backup_YYYY-MM-DD_HH-MM-SS.sql
  • 應用程式部署:使用 rsync 將壓縮好的 laravel-app.zip 傳輸到遠端伺服器,然後解壓縮到 /var/www/laravel-app
  • 動態生成 .env:在遠端伺服器上根據 GitLab CI/CD 變數動態生成 .env 檔案,確保生產環境的配置正確。
  • 執行 Migration:在遠端伺服器上執行 php artisan migrate --force 來更新資料庫結構。--force 選項在生產環境中是必要的,否則會出現提示。
  • 重啟 Nginx:重啟 Nginx 服務,以確保應用程式的新程式碼被載入。
  • Slack 通知:部署成功或失敗時,會自動發送通知到您配置的 Slack 頻道。
  • 觸發條件:僅在推送到 main 分支時觸發。
  • 建議手動觸發:為了更好地控制部署過程,建議將部署階段設定為 when: manual,以便您可以手動啟動部署。

錯誤處理

  • 如果任何一個關鍵步驟(例如:資料庫備份、Migration)失敗,管道將會中止,並且會發送 Slack 通知報告錯誤,幫助您及時發現問題。
  • 資料庫備份確保了在 Migration 失敗時,您可以回滾到先前的狀態。
  • Laravel Migration 的 down 方法也提供了回滾機制,以防需要撤銷 Migration。

執行與驗證

  1. 觸發 CI/CD 管道:將您的程式碼(包含 .gitlab-ci.yml、新的 Migration 文件、更新後的測試檔案和 .env.example)推送到 GitLab 的 main 分支。

  2. 檢查管道狀態:前往 GitLab 專案的 CI/CD > Pipelines,確認測試、構建和部署階段是否成功。

  3. 驗證資料庫更新:登錄到遠端伺服器,執行以下指令,檢查 users 表是否包含了 phone_number 欄位:

    Bash
    mysql -u laravel_user -p laravel -e "DESCRIBE users;"
    

    您應該會看到 phone_number 欄位在輸出中。

  4. 驗證應用程式運行:訪問您的應用程式 URL (例如:https://your-domain.com),確保應用程式正常運行,並且新的功能(如果有的話)可以正常使用。


進階建議

1. 多環境支持

您可以為不同的環境(例如 stagingproduction)配置不同的資料庫和部署路徑,並在 .gitlab-ci.yml 中添加條件分支。

YAML
deploy_staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.your-domain.com
  script:
    - # 針對 staging 環境的部署邏輯
    - ssh $STAGING_USER@$STAGING_HOST "cd /var/www/staging && php artisan migrate --force"
  only:
    - staging # 假設有一個 staging 分支

deploy_production:
  stage: deploy
  environment:
    name: production
    url: https://your-domain.com
  script:
    - # 針對 production 環境的部署邏輯
    - ssh $PRODUCTION_USER@$PRODUCTION_HOST "cd /var/www/production && php artisan migrate --force"
  only:
    - main # 假設 main 分支代表 production
  when: manual # 通常生產環境部署建議手動觸發

2. 回滾機制

在部署失敗時,您可以考慮執行 php artisan migrate:rollback --step=1 來回滾最後一次的 Migration。這需要更精確的錯誤處理和觸發條件。

YAML
# 部署階段的 after_script 中可以添加
# ... (前略)
  after_script:
    - |
      if [ "$CI_JOB_STATUS" == "failed" ]; then
        echo "部署失敗,嘗試回滾 Migration..."
        ssh "$SERVER_USER"@"$SERVER_HOST" "cd /var/www/laravel-app && php artisan migrate:rollback --step=1" || echo "Migration 回滾失敗!"
        # 發送 Slack 失敗通知
        if [ -n "$SLACK_WEBHOOK_URL" ]; then
          curl -X POST -H 'Content-type: application/json' --data '{"text":"❌ Laravel 部署到 '$SERVER_HOST' 失敗,且已嘗試回滾!"}' $SLACK_WEBHOOK_URL
        fi
      elif [ -n "$SLACK_WEBHOOK_URL" ]; then
        curl -X POST -H 'Content-type: application/json' --data '{"text":"✅ Laravel 應用程式已成功部署到 '$SERVER_HOST'!"}' $SLACK_WEBHOOK_URL
      fi

3. 測試覆蓋率整合

您可以整合 Codecov 或其他工具,在 CI/CD 中上傳測試覆蓋率報告,並在您的 README 中添加覆蓋率徽章。

YAML
test:
  # ... (前略)
  after_script:
    - bash <(curl -s https://codecov.io/bash) # 將覆蓋率報告上傳到 Codecov

4. 零停機部署

對於對服務可用性要求極高的專案,您可以考慮使用更進階的零停機部署策略,例如:

  • Laravel Envoyer:一個專門為 Laravel 應用程式設計的零停機部署服務。
  • Blue-Green 部署:透過維護兩個相同的生產環境(Blue 和 Green),並在部署時切換流量來實現零停機。
  • 符號連結 (Symlink) 部署:這在我們目前的部署腳本中已有初步體現,通過先將新版本部署到一個新目錄,然後更新 Nginx 的符號連結來實現。

驗證與除錯

  • 檢查管道失敗原因:當 CI/CD 管道失敗時,請務必查看 GitLab CI/CD 的日誌。日誌會提供詳細的錯誤訊息,幫助您判斷是依賴安裝問題、資料庫連線錯誤,還是 Migration 語法錯誤。
  • 本地模擬:在推送程式碼之前,您可以在本地環境使用 Docker Compose 模擬 MySQL 和 PHP 環境,並嘗試運行 .gitlab-ci.yml 中的指令,以提前發現問題。
  • 資料庫確認:部署後,務必手動登錄遠端伺服器,檢查資料庫結構和資料完整性,確保 Migration 已正確應用且沒有資料遺失。

希望這份詳盡的指南能幫助您在 GitLab CI/CD 中成功配置 Laravel 專案的自動化資料庫更新!

您是否還有其他特定場景(例如處理大表 Migration、多租戶資料庫或 FastAPI/Django 整合)的程式碼範例需要我提供呢?

沒有留言:

張貼留言

網誌存檔