PHP代码自动部署方案

wen PHP项目 3

本文目录导读:

PHP代码自动部署方案

  1. 简单方案
  2. 使用部署工具
  3. CI/CD 方案
  4. Docker 容器化部署
  5. 推荐方案对比
  6. 最佳实践建议
  7. 总结建议

我来为您详细介绍PHP代码自动部署的几种方案:

简单方案

Git Webhook + 简单脚本

// deploy.php (放在服务器上)
<?php
$secret = 'your-secret-key';
$headers = getallheaders();
$signature = $headers['X-Hub-Signature'] ?? '';
// 验证GitHub/GitLab签名
$payload = file_get_contents('php://input');
$hash = 'sha1=' . hash_hmac('sha1', $payload, $secret);
if ($hash !== $signature) {
    http_response_code(403);
    exit('Invalid signature');
}
// 执行部署命令
$commands = [
    'cd /var/www/project && git pull origin main',
    'composer install --no-dev --optimize-autoloader',
    'php artisan migrate --force', // 如果有Laravel
    'chown -R www-data:www-data /var/www/project',
];
foreach ($commands as $command) {
    $output = shell_exec($command . ' 2>&1');
    file_put_contents('/var/log/deploy.log', $output, FILE_APPEND);
}
echo 'Deploy completed';

简单Shell脚本

#!/bin/bash
# deploy.sh
set -e
PROJECT_ROOT="/var/www/project"
cd $PROJECT_ROOT
# 备份当前版本
cp -r $PROJECT_ROOT /backup/project_$(date +%Y%m%d_%H%M%S)
# 拉取最新代码
git pull origin main
# 更新依赖
if [ -f composer.json ]; then
    composer install --no-dev --prefer-dist --optimize-autoloader
fi
# 执行数据库迁移
if [ -f artisan ]; then
    php artisan migrate --force
fi
# 清理缓存
php artisan cache:clear
php artisan config:clear
# 设置权限
chown -R www-data:www-data $PROJECT_ROOT
chmod -R 755 storage bootstrap/cache

使用部署工具

Deployer (推荐)

// deploy.php
<?php
namespace Deployer;
require 'recipe/common.php';
// 项目配置
set('application', 'my_project');
set('repository', 'git@github.com:username/project.git');
set('git_tty', false);
set('keep_releases', 5);
// 服务器配置
host('production')
    ->hostname('your-server.com')
    ->user('deployer')
    ->set('deploy_path', '/var/www/project')
    ->set('branch', 'main');
// 上传配置
set('shared_files', ['.env']);
set('shared_dirs', ['storage', 'uploads']);
set('writable_dirs', ['storage', 'bootstrap/cache']);
// 任务定义
task('build', function () {
    run('cd {{release_path}} && composer install --no-dev --prefer-dist --optimize-autoloader');
    run('cd {{release_path}} && php artisan migrate --force');
    run('cd {{release_path}} && php artisan optimize');
});
task('deploy', [
    'deploy:info',
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:writable',
    'build',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
]);
after('deploy:failed', 'deploy:unlock');

Capistrano

# Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/composer'
require 'capistrano/file-permissions'
# config/deploy.rb
set :application, 'project'
set :repo_url, 'git@github.com:username/project.git'
set :deploy_to, '/var/www/project'
set :branch, `git rev-parse --abbrev-ref HEAD`.chomp
set :keep_releases, 5
# 服务器配置
server 'your-server.com', user: 'deployer', roles: %w{app web}
namespace :deploy do
    after :updated, :composer_install do
        on roles(:app) do
            within release_path do
                execute :composer, 'install --no-dev --prefer-dist --optimize-autoloader'
            end
        end
    end
    after :finished, :finalize do
        on roles(:app) do
            within release_path do
                execute :php, 'artisan migrate --force'
                execute :php, 'artisan cache:clear'
            end
        end
    end
end

CI/CD 方案

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy to Production
on:
  push:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
      - name: Install dependencies
        run: composer install --no-interaction
      - name: Run tests
        run: php artisan test
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/project
            git pull origin main
            composer install --no-dev --prefer-dist
            php artisan migrate --force
            php artisan optimize

GitLab CI

# .gitlab-ci.yml
stages:
  - test
  - deploy
test:
  stage: test
  script:
    - composer install --no-interaction
    - php artisan test
deploy_production:
  stage: deploy
  script:
    - apt-get update -y
    - apt-get install -y sshpass
    - sshpass -p $SERVER_PASSWORD ssh $SERVER_USER@$SERVER_HOST "
        cd /var/www/project &&
        git pull origin main &&
        composer install --no-dev --prefer-dist -o &&
        php artisan migrate --force &&
        php artisan optimize
      "
  only:
    - main

Docker 容器化部署

# Dockerfile
FROM php:8.1-fpm
# 安装扩展
RUN docker-php-ext-install pdo_mysql bcmath
# 安装Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 设置工作目录
WORKDIR /var/www/html
# 复制源码
COPY . .
# 安装依赖
RUN composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
# 设置权限
RUN chown -R www-data:www-data /var/www/html \
    && chmod -R 755 storage bootstrap/cache
# 启动命令
CMD ["php-fpm"]
# docker-compose.yml
version: '3.8'
services:
  php:
    build: .
    container_name: project-php
    restart: unless-stopped
    working_dir: /var/www/html
    volumes:
      - ./:/var/www/html
    environment:
      - APP_ENV=production
      - DB_HOST=mysql
    depends_on:
      - mysql
  nginx:
    image: nginx:alpine
    container_name: project-nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php
  mysql:
    image: mysql:8.0
    container_name: project-mysql
    restart: unless-stopped
    environment:
      - MYSQL_DATABASE=project
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
      - MYSQL_ROOT_PASSWORD=rootpassword
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
  mysql_data:

推荐方案对比

方案 优点 缺点 适合场景
Webhook+脚本 简单、轻量 有限错误处理 小型项目
Deployer 功能强大、支持回滚 学习成本 中大型项目
CI/CD 自动化程度高、可测试 配置复杂 团队协作项目
Docker 环境一致、可扩展 资源消耗 微服务架构

最佳实践建议

安全措施

// 部署前安全检查
function preDeployCheck() {
    // 检查仓库状态
    $status = shell_exec('cd /var/www/project && git status --porcelain');
    if ($status) {
        throw new Exception('工作目录不干净,请先提交或回滚');
    }
    // 检查PHP语法
    $files = shell_exec('find /var/www/project -name "*.php" -not -path "*/vendor/*"');
    foreach (explode("\n", $files) as $file) {
        if ($file) {
            $result = shell_exec("php -l $file 2>&1");
            if (strpos($result, 'No syntax errors') === false) {
                throw new Exception("语法错误: $file");
            }
        }
    }
}

回滚策略

// 回滚到上一版本
function rollback($targetRelease = null) {
    $releaseDir = '/var/www/releases';
    if (!$targetRelease) {
        // 获取上一个版本
        $releases = glob("$releaseDir/*", GLOB_ONLYDIR);
        sort($releases);
        $targetRelease = $releases[count($releases) - 2];
    }
    // 创建软链接
    shell_exec("ln -sfn $targetRelease /var/www/current");
    // 清理缓存
    shell_exec("cd /var/www/current && php artisan optimize:clear");
    // 记录日志
    file_put_contents('/var/log/deploy.log', 
        "Rollback to $targetRelease at " . date('Y-m-d H:i:s') . "\n");
}

零停机部署

#!/bin/bash
# zero-downtime-deploy.sh
FRESH_DIR="/var/www/releases/$(date +%Y%m%d%H%M%S)"
CURRENT_DIR="/var/www/current"
# 克隆新版本
git clone -b main --depth=1 /var/www/repo $FRESH_DIR
# 安装依赖
cd $FRESH_DIR
composer install --no-dev --prefer-dist --no-interaction
# 复制环境文件
cp /var/www/shared/.env $FRESH_DIR/.env
# 创建共享目录链接
ln -s /var/www/shared/storage $FRESH_DIR/storage
ln -s /var/www/shared/uploads $FRESH_DIR/uploads
# 切换软链接
ln -sfn $FRESH_DIR $CURRENT_DIR
# 清理过期版本
find /var/www/releases -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \;

监控和日志

// 部署日志记录
class DeployLogger {
    private $logFile = '/var/log/deploy.log';
    public function log($message, $type = 'INFO') {
        $timestamp = date('Y-m-d H:i:s');
        $entry = "[$timestamp] [$type] $message\n";
        file_put_contents($this->logFile, $entry, FILE_APPEND);
        // 发送通知
        $this->notify($message, $type);
    }
    private function notify($message, $type) {
        // 发送到Slack
        $slackUrl = getenv('SLACK_WEBHOOK_URL');
        if ($slackUrl) {
            // curl请求发送通知
        }
        // 发送邮件
        if (in_array($type, ['ERROR', 'ROLLBACK'])) {
            // 邮件通知管理员
        }
    }
}

总结建议

  1. 小型项目:使用 Git Webhook + 简单脚本
  2. 中型项目:使用 Deployer + 手动触发备份
  3. 大型项目:使用 GitLab CI/CD + Docker 容器化
  4. 关键应用:实现零停机部署 + 自动化回滚机制

选择方案时需要考虑:

  • 团队技术栈
  • 服务器环境
  • 部署频率
  • 失败容忍度
  • 是否需要自动化测试

无论选择哪种方案,都要确保有完善的备份和回滚机制。

抱歉,评论功能暂时关闭!