如何为PHP项目编写部署脚本?

wen PHP项目 4

本文目录导读:

如何为PHP项目编写部署脚本?

  1. 基础Shell部署脚本
  2. 零停机部署脚本(使用符号链接)
  3. Docker部署脚本
  4. Ansible自动化部署
  5. CI/CD集成示例(GitHub Actions)

为PHP项目编写部署脚本需要考虑环境一致性、安全性和可重复性,下面提供一个从基础到进阶的部署方案,涵盖Shell脚本、Ansible和Docker三种主流方式。

基础Shell部署脚本

一个完整的deploy.sh示例:

#!/bin/bash
set -e  # 任何命令失败立即退出
set -u  # 使用未定义变量时报错
# ============ 配置区域 ============
PROJECT_NAME="my-php-app"
REPO_URL="https://github.com/your/repo.git"
DEPLOY_DIR="/var/www/${PROJECT_NAME}"
BACKUP_DIR="/var/backups/${PROJECT_NAME}"
BRANCH="main"
PHP_VERSION="8.1"
COMPOSER_PATH="/usr/local/bin/composer"
# ================================
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 开始部署 ${PROJECT_NAME}..."
# 1. 创建目录结构
mkdir -p "${DEPLOY_DIR}" "${BACKUP_DIR}"
# 2. 备份当前版本(如果存在)
if [ -d "${DEPLOY_DIR}/current" ]; then
    echo "备份当前版本..."
    TIMESTAMP=$(date +'%Y%m%d_%H%M%S')
    cp -r "${DEPLOY_DIR}/current" "${BACKUP_DIR}/backup_${TIMESTAMP}"
    # 只保留最近5个备份
    ls -t "${BACKUP_DIR}" | tail -n +6 | xargs -I {} rm -rf "${BACKUP_DIR}/{}" 2>/dev/null
fi
# 3. 克隆/拉取代码
if [ -d "${DEPLOY_DIR}/current" ]; then
    echo "拉取最新代码..."
    cd "${DEPLOY_DIR}/current"
    git fetch origin
    git reset --hard "origin/${BRANCH}"
else
    echo "首次克隆代码..."
    git clone --depth=1 -b "${BRANCH}" "${REPO_URL}" "${DEPLOY_DIR}/current"
fi
# 4. 设置文件权限
echo "设置文件权限..."
find "${DEPLOY_DIR}/current" -type f -exec chmod 644 {} \;
find "${DEPLOY_DIR}/current" -type d -exec chmod 755 {} \;
# Web服务器用户(如www-data)可写目录
chmod -R 775 "${DEPLOY_DIR}/current/storage"
chmod -R 775 "${DEPLOY_DIR}/current/bootstrap/cache"
chown -R www-data:www-data "${DEPLOY_DIR}/current"
# 5. 安装Composer依赖
echo "安装PHP依赖..."
cd "${DEPLOY_DIR}/current"
${COMPOSER_PATH} install --no-dev --optimize-autoloader --no-interaction
# 6. 环境配置
echo "配置环境..."
if [ ! -f ".env" ]; then
    cp .env.example .env
    # 生成APP_KEY(适用于Laravel等框架)
    php artisan key:generate --force
fi
# 7. 数据库迁移(需要数据库连接)
# php artisan migrate --force
# 8. 清理缓存(Laravel示例)
echo "优化缓存..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
# 9. 创建符号链接(部署版本管理)
ln -sfn "${DEPLOY_DIR}/current" "${DEPLOY_DIR}/latest"
# 10. 重启PHP-FPM(如果使用)
echo "重启PHP-FPM..."
sudo systemctl reload php${PHP_VERSION}-fpm
echo "[$(date +'%Y-%m-%d %H:%M:%S')] 部署完成!"

使用方法

chmod +x deploy.sh
./deploy.sh

零停机部署脚本(使用符号链接)

#!/bin/bash
set -e
DEPLOY_DIR="/var/www/my-app"
RELEASE_DIR="${DEPLOY_DIR}/releases/$(date +'%Y%m%d%H%M%S')"
SHARED_DIR="${DEPLOY_DIR}/shared"
echo "创建新版本目录..."
mkdir -p "${RELEASE_DIR}"
echo "克隆指定版本代码..."
git clone --depth=1 -b main https://github.com/your/repo.git "${RELEASE_DIR}"
echo "共享资源链接..."
ln -nfs "${SHARED_DIR}/.env" "${RELEASE_DIR}/.env"
ln -nfs "${SHARED_DIR}/storage" "${RELEASE_DIR}/storage"
echo "安装依赖..."
cd "${RELEASE_DIR}"
composer install --no-dev --optimize-autoloader
echo "切换符号链接(零停机)..."
ln -nfs "${RELEASE_DIR}" "${DEPLOY_DIR}/current"
echo "重启服务..."
sudo systemctl reload nginx
sudo systemctl reload php8.1-fpm
echo "清理旧版本(保留最近3个)..."
cd "${DEPLOY_DIR}/releases"
ls -t | tail -n +4 | xargs -r rm -rf

目录结构

/var/www/my-app/
├── current -> releases/20231001_120000/
├── releases/
│   ├── 20231001_110000/
│   ├── 20231001_120000/
│   └── 20231002_090000/
└── shared/
    ├── .env
    └── storage/

Docker部署脚本

1 Dockerfile

FROM php:8.1-fpm-alpine
# 安装系统依赖
RUN apk add --no-cache \
    git \
    unzip \
    libpng-dev \
    libzip-dev \
    oniguruma-dev \
    && docker-php-ext-install pdo_mysql mbstring zip gd
# 安装Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 设置工作目录
WORKDIR /var/www/html
# 复制项目文件
COPY . .
# 安装PHP依赖
RUN composer install --no-dev --optimize-autoloader --no-interaction
# 权限设置
RUN chown -R www-data:www-data storage bootstrap/cache

2 docker-compose.yml

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: php-app
    restart: unless-stopped
    volumes:
      - ./storage:/var/www/html/storage
    environment:
      - DB_HOST=mysql
      - DB_DATABASE=app
      - DB_USERNAME=app
      - DB_PASSWORD=secret
    depends_on:
      - mysql
      - redis
    networks:
      - app-network
  web:
    image: nginx:alpine
    container_name: nginx-web
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - .:/var/www/html
    depends_on:
      - app
    networks:
      - app-network
  mysql:
    image: mysql:8.0
    container_name: mysql-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: secret
      MYSQL_ROOT_PASSWORD: rootsecret
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - app-network
  redis:
    image: redis:alpine
    container_name: redis-cache
    restart: unless-stopped
    networks:
      - app-network
volumes:
  mysql_data:
networks:
  app-network:
    driver: bridge

3 deploy-docker.sh

#!/bin/bash
set -e
echo "===== Docker部署开始 ====="
# 1. 拉取最新代码
git pull origin main
# 2. 构建新镜像
docker-compose build --no-cache app
# 3. 执行数据库迁移(如果有)
docker-compose run --rm app php artisan migrate --force
# 4. 启动服务(零停机滚动更新)
docker-compose up -d --scale app=3 --no-recreate web
# 5. 等待健康检查
sleep 10
docker-compose ps
echo "===== 部署完成 ====="

Ansible自动化部署

deploy-php.yml:

---
- name: 部署PHP应用
  hosts: webservers
  become: yes
  vars:
    project_name: "my-php-app"
    repo_url: "https://github.com/your/repo.git"
    deploy_dir: "/var/www/{{ project_name }}"
    branch: "main"
  tasks:
    - name: 安装必要系统包
      apt:
        name:
          - git
          - nginx
          - php8.1-fpm
          - php8.1-mysql
          - php8.1-xml
          - php8.1-mbstring
          - composer
        state: present
        update_cache: yes
    - name: 创建部署目录
      file:
        path: "{{ deploy_dir }}"
        state: directory
        owner: www-data
        group: www-data
        mode: 0755
    - name: 克隆代码
      git:
        repo: "{{ repo_url }}"
        dest: "{{ deploy_dir }}/current"
        version: "{{ branch }}"
        force: yes
    - name: 设置文件权限
      file:
        path: "{{ deploy_dir }}/current"
        state: directory
        recurse: yes
        owner: www-data
        group: www-data
    - name: 配置.env
      copy:
        src: "{{ playbook_dir }}/files/.env.production"
        dest: "{{ deploy_dir }}/current/.env"
        owner: www-data
        group: www-data
        mode: 0600
    - name: 安装Composer依赖
      composer:
        command: install
        working_dir: "{{ deploy_dir }}/current"
        no_dev: yes
        optimize_autoloader: yes
    - name: 执行数据库迁移
      command:
        cmd: php artisan migrate --force
        chdir: "{{ deploy_dir }}/current"
    - name: 优化缓存
      command:
        cmd: "{{ item }}"
        chdir: "{{ deploy_dir }}/current"
      loop:
        - php artisan config:cache
        - php artisan route:cache
        - php artisan view:cache
    - name: 配置Nginx
      template:
        src: "nginx.conf.j2"
        dest: "/etc/nginx/sites-available/{{ project_name }}"
      notify: reload nginx
    - name: 启用站点
      file:
        src: "/etc/nginx/sites-available/{{ project_name }}"
        dest: "/etc/nginx/sites-enabled/{{ project_name }}"
        state: link
    - name: 确保PHP-FPM运行
      systemd:
        name: php8.1-fpm
        state: started
        enabled: yes
  handlers:
    - name: reload nginx
      systemd:
        name: nginx
        state: reloaded

运行Ansible:

ansible-playbook -i inventory.ini deploy-php.yml

CI/CD集成示例(GitHub Actions)

.github/workflows/deploy.yml:

name: Deploy PHP Application
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.1'
        extensions: mbstring, xml, gd, zip
        tools: composer
    - name: Install dependencies
      run: composer install --no-dev --optimize-autoloader
    - name: Run tests
      run: vendor/bin/phpunit
    - name: Deploy to server
      uses: appleboy/ssh-action@v0.1.5
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USER }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          cd /var/www/my-app
          git pull origin main
          composer install --no-dev --optimize-autoloader
          php artisan migrate --force
          php artisan config:cache
          sudo systemctl reload php8.1-fpm

安全注意事项

  • 不要在脚本中硬编码数据库密码,使用环境变量或密钥管理工具
  • 限制部署用户权限(尽量不用root)
  • 备份前的版本,确保可以快速回滚

性能优化

  • 使用--optimize-autoloader生成优化的类映射
  • 预编译模板和配置文件
  • 使用OPcache

监控告警

  • 部署后在脚本中添加健康检查
  • 监控核心页面响应时间
  • 设置回滚触发条件

选择哪种方式取决于你的项目复杂度:

  • 简单项目:使用Shell脚本即可
  • 微服务架构:推荐Docker
  • 多服务器环境:使用Ansible等配置管理工具
  • 持续集成:结合CI/CD工具

建议先在测试环境验证脚本,确保回滚机制正常工作后再用于生产环境。

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