部署 Laravel 到 1Panel 面板,并集成 Git 自动部署
近期要给客户转移 Laravel 网站,打算使用 1Panel 面板部署,要求 git push 之后自动化部署。
1. 创建网站
1Panel 面板搭建在 Docker 之上,每一项服务对应单独的容器,比如 Laravel 网站需要 Web 服务、PHP FPM 服务 和 MySQL 服务,对应 3 个 Docker 容器。
在创建网站之前,先创建一个 PHP 运行环境,其实就是构建一个 Docker 镜像,构建时可选用合适的扩展,比如 pod_mysql 和 opcache。然后用该环境创建网站。
2. 设置 1000:1000 用户
服务器上建议用非 root 用户管理。如果还没有普通用户,请添加一个。系统上第一个新用户的 uid/gid 都是 1000:1000。
之前通过 1Panel 创建的 PHP 运行环境,构建时设置了 1000:1000 来执行 php-fpm,这是相关的 Dockerfile 片段:
# 1Panel 用于构建 PHP 运行环境的 Dockerfile
# /opt/1panel/resource/apps/remote/php8/8.2.10/build/php/Dockerfile
# php image's www-data user uid & gid are 82, change them to 1000 (primary user)
RUN apk --no-cache add shadow && usermod -u 1000 www-data && groupmod -g 1000 www-data
我们将使用这个普通用户来操作,这样在容器内外统一使用 1000:1000 来管理和运行网站,可以避免权限上的麻烦。注意用户名是什么不重要,只要 uid/gid 一致即可。
别忘了给这个用户添加 sudo 支持,以便执行一些需要 root 权限的操作,比如 Docker 相关的操作。
3. 初始化网站
首先,用 git clone 导入网站代码:
- git clone 需要提前生成并添加部署密钥,就是拥有 repo 的只读权限的 ssh key
- git clone 到当前目录,需要提前清空当前目录
$ git clone git@codeup.aliyun.com:example/laravel-1panel.git .
然后,安装 Composer 依赖,1Panel 的 PHP FPM 容器中已经包含了 Composer,我们可以直接使用。但在此之前,先设置几个变量简化后续操作:
- 请替换 DOMAIN 为你的网站域名
- 请替换 CONTAINER 为你的 PHP FPM 容器名,这是 1Panel 随机生成的,用
docker ps
查看 - PHP FPM 容器内部默认 root 用户,用
-u/--user
切换到 1000:1000 用户,不然 vendor 等等都是 root 的了
DOMAIN="laravel-1panel.domain-4-testing.com"
CONTAINER="1Panel-php8-mQgd"
CONTAINER_WORKDIR="/www/sites/${DOMAIN}/index"
DOCKER="sudo docker exec -it -w $CONTAINER_WORKDIR -u 1000:1000 $CONTAINER"
COMPOSER="$DOCKER composer"
ARTISAN="$DOCKER php artisan"
此时再安装 Composer 依赖,就可以直接使用 $COMPOSER
了:
$ $COMPOSER install --no-dev --optimize-autoloader
用 $ARTISAN
来执行 Artisan 命令,比如:
$ $ARTISAN key:generate
$ $ARTISAN migrate
由于系统默认没有 node/npm 环境,可以使用 Docker 来构建前端资源:
$ sudo docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app -u 1000:1000 node bash -c "npm ci && npm run build"
此时网站就能访问了。别忘记调整运行目录和伪静态规则,这跟 BT 宝塔面板的设置是一样的。
4. 运行队列
1Panel 提供了 Supervisor 集成,这和 BT 面板是一样的。我们直接在 PHP FPM 容器来跑队列,这破坏了一个容器一个服务的原则,但这是最简单有效的方法:
- 请注意替换容器内工作路径、容器名称
- Supervisor 支持设置「进程数量」来控制队列的并发数,比如 3 个进程,就能同时处理 3 个任务
- 同样用 1000:1000 用户来执行,而不是默认的 root 用户,保持权限一致
docker exec -w /www/sites/laravel-1panel.domain-4-testing.com/index -u 1000:1000 1Panel-php8-mQgd php artisan queue:work
5. 运行计划任务
1Panel 也提供了 Cron 计划任务,和队列一样,我们也复用 PHP FPM 容器:
- 请注意替换容器内工作路径、容器名称
- 设置每分钟执行一次
- 同样用 1000:1000 用户来执行,而不是默认的 root 用户,保持权限一致
docker exec -w /www/sites/laravel-1panel.domain-4-testing.com/index -u 1000:1000 1Panel-php8-mQgd php artisan schedule:run
6. 设置 Git 自动部署
我们的项目使用 Git 管理,托管在阿里云 Codeup 上,用配套的流水线实现 git push 后自动部署,流水线部署脚本:
- 注意替换域名和容器名称
- 依旧用 1000:1000 用户 git pull 拉取代码,保持权限一致
# NOTE 注意替换域名和容器名称
DOMAIN="laravel-1panel.domain-4-testing.com"
CONTAINER="1Panel-php8-mQgd"
HOST_WORKDIR="/opt/1panel/apps/openresty/openresty/www/sites/${DOMAIN}/index"
set -x # 打印执行的命令
set -e # 出错后立即退出
cd $HOST_WORKDIR
su -l "$(id -nu 1000)" -c "cd $PWD && git pull"
DOMAIN=$DOMAIN CONTAINER=$CONTAINER bash ./deploy.sh
部署需要执行的步骤较多,整理到单独的脚本中,放到项目中,方便复用:
- 部署前后开启/关闭维护模式
- 给 php-fpm 发送 USER2 信号,重载 php-fpm,之所以要重载 php-fpm 是跟 Forge 学的,我不清楚原因,但起码没有副作用
- 由于队列常驻内存,代码更新之后,需要用
$ARTISAN queue:restart
重启队列,不然队列还在跑旧代码 - 计划任务不用管,因为每分钟都会从头执行一次,自然会跑新代码
#!/usr/bin/env bash
set -e # 出错后立即退出
if [ -z "$DOMAIN" ] || [ -z "$CONTAINER" ]; then
echo "DOMAIN or CONTAINER is not set, exiting"
exit 1
fi
HOST_WORKDIR="/opt/1panel/apps/openresty/openresty/www/sites/${DOMAIN}/index"
CONTAINER_WORKDIR="/www/sites/${DOMAIN}/index"
DOCKER="docker exec -w $CONTAINER_WORKDIR -u 1000:1000 $CONTAINER"
COMPOSER="$DOCKER composer"
ARTISAN="$DOCKER php artisan"
cd "$HOST_WORKDIR"
# 开启维护模式
$ARTISAN down
# 安装 Composer 依赖
$COMPOSER install --no-dev --optimize-autoloader
# 迁移数据库
$ARTISAN migrate --force
# 构建前端
docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app -u 1000:1000 node:latest sh -c "npm ci && npm run build"
# Reload php-fpm, 来源 https://github.com/docker-library/php/issues/399
# 注意容器内 php-fpm 父进程的 PID 为 1,是属于 root 的,所以需要 root 权限,不能用 -u 1000:1000
docker exec -w "$CONTAINER_WORKDIR" "$CONTAINER" kill -USR2 1
# 重启队列
$ARTISAN queue:restart
# 关闭维护模式
$ARTISAN up
试用了一下 1Panel 为什么没有 nginx ?
我上次测试发现:1Panel 的进程守护对 Laravel 兼容性太友好(无法重启进程、停止进程等操作),就目前而言不推荐生存环境。并且我还发现他的计划任务调度资源占用比较高(不排除我测试环境问题)