终结!这可能是目前最好的 PHP 部署工具了

终结!这可能是目前最好的 PHP 部署工具了

编程那点事 随便写写 PHP 2625 字 / 5 分钟

提问:怎么把大象放进鲸鱼里?

开过一些坑,写过一些有意思的 PHP 的程序或小工具。说实话,我是真的挺喜欢 PHP 这门语言的。 她开发迅速,很快就能将突然之间产生的灵感做出来。

有了几台学生机后,我便尝试着把自己写的东西都放到上面去。凭借着 Docker,管理起来还算是“整齐有序”。 都说程序员会想尽办法偷懒,“偷懒”也一定程度上也决定了生产力。 确实是这样的。 过去的很长一段时间里,我都在寻找一个优雅的部署 PHP 程序到服务器上的解决办法。特别是部署到 Docker 容器里。

网上其实有人给出一些比较“暴力”的解决方案:强行用 PHP 写一个 Webhook,里面使用exec()来执行 Shell。从而做到拉取 Git 远程仓库等操作。 我一直是对exec() eval()这类函数十分反感的,会尽可能的避免去使用它们;究其原因就是太不安全了!因此有很多网站就会在 PHP 的配置文件中禁用这些危险的函数。

终于,在经过昨天一天的尝试踩坑,我总算解决了这个一直以来的痛点: 使用 GitHub 上的开源项目Deployer来实现 PHP 的部署。https://github.com/deployphp/deployer

先聊聊我的看法

我最先是在 v2ex 上了解到 Deployer 的。 Deployer 也是使用 PHP 编写的。其中确实也使用了exec()函数。但她是在命令行里运行的,因此……感觉还好吧……(真香 同时 Deployer 广泛支持多种主流的 PHP 框架,比如 Laravel、CodeIgniter、Symfony 等。(ThinkPHP 那垃圾国产框架压根连个 Logo 都没有,就别出来丢人现眼了 与此同时,Deployer 的部署任务还支持 Composer 依赖的安装,可以说是很舒服了。

但美中不足的是,作为 GitHub 上一个拥有 6k+ stars 的项目,Deployer 的文档质量真的是不敢恭维——在很关键的地方有错误,并且给的 example 也不是很详细。甚至还有些条目内容居然是 TODO! 瑕不掩瑜,这些小缺点并不妨碍我推荐她,不少会踩的坑我已经踩了一遍了。只要注意下就好。

那么,开始吧!

First of all

我这里用一个实际的例子来说明:将我之前写的小工具框架——Cube 部署到我腾讯云的学生机上。 其中会有用到 Docker,这是网上相关的教程所没有涉及的。

安装

有坑注意! 请使用官网文档的安装方式进行安装,切不可使用`composer`安装。
curl -LO https://deployer.org/deployer.phar
mv deployer.phar /usr/local/bin/dep
chmod +x /usr/local/bin/dep

安装完成后,命令行输入:

dep -V

来检验是否安装成功。

初始化Deployer

在你准备实现部署的文件夹中,命令行输入:

dep init

之后跟随提示进行设置。 第一步选择项目类型,因为我这是自己写的 PHP 项目,所以选择0 - Common就好了。 初始化成功后,在当前目录下会多出一个deployer.php的文件,这就是我们部署所使用的配置文件。

修改配置文件

首先,我们来大致梳理下通常一个 PHP 部署任务所要做的几件事情:

  1. 保存当前已经部署好的版本,以便之后玩脱了可以回滚。
  2. 从远程 Git 仓库拉取代码。
  3. 删掉之前的代码,将新的代码文件放到生产环境的文件夹里。
  4. 将一些文件换成生成环境下的,比如config.php文件,Laravel 框架下的.env文件等。
  5. 调用 compsoer 下载依赖包
  6. 删掉临时的文件
  7. 完成

这些任务,Deployer 都支持!让我们一步一步来配置:

设置项目名称

// Project name
set('application', 'Cube');

设置远程 Git 仓库地址

// Project repository
set('repository', 'https://github.com/wuhan005/Cube.git');

如果是私有仓库的话,还请输入用户名与密码。Deployer 也是支持公钥的,这个还请自行查阅官方文档。

共享的文件/文件夹

// Shared files/dirs between deploys 
set('shared_files', ['./core/database/Cube.db']);		//文件
set('shared_dirs', ['./public/Module']);		//文件夹

这里就是上面部署步骤中提到的第四点。 就我 Cube 这个项目而言,她是使用 SQLite 作为数据库,因此相对应的.db数据库文件应该在每次部署中被保留下来。同时,她里面是存放着我写的小工具,因此包含着小工具的文件夹Module也应该被保留。

有坑注意! 这里的文件与文件夹应填写相对路径,因为我们的网站是跑在 Docker 容器里的。填写宿主机的绝对路径就炸了。

部署的位置&文件夹链接形式

set('use_atomic_symlink', false);
set('use_relative_symlink', true);		//软链接使用相对路径
set('deploy_path', '.');		//部署目录为当前目录下

Deployer 部署后生成的目录结构是这样的:

.
├── current -> releases/11
├── deploy.php
├── releases
└── shared

其中,releases文件夹中包含了当前以及历史的版本,每个版本放在对应数字的文件夹里,方便你回滚。Deployer 默认会保存最近 5 次的部署版本,你可以在配置文件中进行设置。 而重点就是这个current文件夹,她就是你当前版本的文件所在的文件夹,即站点的根目录。之后我们也要修改 Nginx 配置中站点根目录为current文件夹。 注意,这里current文件夹是通过软链接的形式连接到releases文件夹中的当前版本。而不是采用复制的方式。 Linux 菜鸡的我还专门去查了下软链接,顺便问了下部长。

有坑注意! 这里我们需要将`use_relative_symlink`这一属性设置成`ture`,从而使用相对路径的软链接。不然在 Docker 容器无法找到文件夹。 可以使用`ls -n`来查看软链接所指向的文件夹路径。 同时`deploy_path`,即部署目录也设置成当前目录的相对路径。 值得一提的是,关于软链接使用相对路径的特性,是 Deployer 用户提了 issue 后才加上去的: https://github.com/deployphp/deployer/issues/503 因此官方的文档写的也是仓促,把设置的属性名给写错了。就很无语。

**这个时候就可以把你想要共享的文件放到shared文件夹下了。**注意要创建与项目里一样的目录结构。

设置要执行的任务

// Tasks
desc('Deplo project');
task('deploy', [
    'deploy:info',
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:writable',
    'deploy:vendors',		//composer 安装依赖包
    'deploy:clear_paths',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'success'
]);

这边其实不用做太多的改动,如果你的项目不需要 composer,或者没有composer.json文件,可以去掉 'deploy:vendors'这一步。

配置完成

到此配置文件Deploy.php就完成了,附上我完整的配置:

<?php
namespace Deployer;
require 'recipe/common.php';

// Project name
set('application', 'Cube');

// Project repository
set('repository', 'https://github.com/wuhan005/Cube.git');

// [Optional] Allocate tty for git clone. Default value is false.
set('git_tty', true); 

// Shared files/dirs between deploys 
set('shared_files', ['./core/database/Cube.db']);
set('shared_dirs', ['./public/Module']);

// Writable dirs by web server 
set('writable_dirs', []);
set('allow_anonymous_stats', false);

// Hosts
set('use_atomic_symlink', false);
set('use_relative_symlink', true);
set('deploy_path', '.');

// Tasks
desc('Deplo project');
task('deploy', [
    'deploy:info',
    'deploy:prepare',
    'deploy:lock',
    'deploy:release',
    'deploy:update_code',
    'deploy:shared',
    'deploy:writable',
    'deploy:vendors',
    'deploy:clear_paths',
    'deploy:symlink',
    'deploy:unlock',
    'cleanup',
    'success'
]);

// [Optional] If deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');

好诶!

到此为止就完成了!ヽ(✿゚▽゚)ノ好耶 命令行运行:

dep deploy

来执行部署吧!

有坑注意! 若提示没有写入权限,则使用`chmod`将文件夹权限改为`777`。

可以看到 Deployer 很整齐很有序的部署好了我们的项目。 Enjoy it!

总结一下吧

可以说是完成了一件大事啊。现在部署起来可就轻松许多了,直接上服务器一行命令搞定。如果今后能做成 Webhook 加到 GitHub 上就更棒了! 我对 Deployer 这个项目也是挺感兴趣的,虽然她的功能强大,但是我对她的实现大概有个概念;有空的时候可以去研究下她的源码。 或者说……帮她改一下文档中的错误,翻译个中文文档也行啊嘻嘻嘻。(这人又想蹭 contributors 了