开发完项目,免不了要部署上线。纯手动操做,登陆、拉代码、改配置、清缓存、各类服务重启等等一条龙下来,人生宝贵的几分钟就过去了。并且手动操做十分容易出错,遗漏部分步骤都有可能产生一些邪门问题。因此我很早就开始寻求一种能轻松部署 Laravel 项目的办法。php
laravel 的官方文档里介绍了 Envoy,以前用过,能知足大部分场景,但仍然有一些限制。直到后来看到了 deployer,大有相见恨晚之感!前端
首先说明下我的实际使用场景。nginx
本人使用 win10 系统,使用 Homestead 做为 PHP 项目的开发环境(vagrant v2.1.1, homestead v7.4.1, virtualbox v5.2.8, homestead 的 virtual box 版本为 v5.2)。laravel
本地开发能完成绝大部分开发和测试任务,但在部署到生产机以前仍然须要先部署到开发机上进行测试。线上测试与生产使用的是青云的云主机,Ubuntu16 系统。git
如下的操做都是在 homestead 虚拟机里进行操做!
cd /path/to/your/project composer require deployer/deployer --dev
我的习惯于将其做为项目依赖安装,固然也能够根据须要或我的喜爱全局安装。
vendor/bin/dep init
由于我用的是 laravel 输入项目类型 1 后回车,而后会出现一个让设置 git 仓库的,默认是对应项目的 git 远端仓库,不须要修改的话确认就能够了。github
完成上面的初始化后,项目要目录下会出现一个 deploy.php 文件,deployer 的配置就靠它了。初始的配置以下,里面显示了一些基本的配置。web
<?php namespace Deployer; require 'recipe/laravel.php'; // Project name // 项目名 set('application', 'my_project'); // Project repository // 项目仓库地址不解释 set('repository', 'git@github.com:tianyong90/xxx.git'); // [Optional] Allocate tty for git clone. Default value is false. set('git_tty', true); // Shared files/dirs between deploys // 分享文件即目录,一般也不用改,默认包含了 storage 目录 add('shared_files', []); add('shared_dirs', []); // Writable dirs by web server // 可写目录,通常不用改 add('writable_dirs', []); // Hosts // 目标主机配置,这是最基本的 host('project.com') ->set('deploy_path', '~/{{application}}'); // Tasks // 这算是个自定义任务示例 task('build', function () { run('cd {{release_path}} && build'); }); // [Optional] if deploy fails automatically unlock. // 若是部署失败,自动解除部署锁定状态,以避免影响下次执行 after('deploy:failed', 'deploy:unlock'); // Migrate database before symlink new release. // 执行数据库迁移,建议删掉,迁移虽好,但毕竟高风险,只推荐用于开发环境。 before('deploy:symlink', 'database:migrate');
默认的配置确定是不行的,目标主机啥的还不知道呢。下面直接贴上本身用到的配置,并加入了少许说明。redis
<?php namespace Deployer; require 'recipe/laravel.php'; // Project name set('application', 'xxx'); // Project repository set('repository', 'git@github.com:tianyong90/xxx.git'); // [Optional] Allocate tty for git clone. Default value is false. set('git_tty', true); // Shared files/dirs between deploys add('shared_files', []); add('shared_dirs', []); // Writable dirs by web server add('writable_dirs', []); // 保存最近五次部署,这样的话回滚最多也只能回滚到前 5 个版本 set('keep_releases', 5); // 实践证实,这样能减小一些没必要要的麻烦,如出现权限相关的问题,也可将此项设置为 true 后尝试 set('writable_use_sudo', false); // 生产用的主机 host('172.16.1.1') ->stage('production') ->user('root') ->port(22) ->set('branch', 'master') // 最新的主分支部署到生产机 ->set('deploy_path', '/data/wwwroot/xxx') ->identityFile('/home/vagrant/.ssh/id_rsa') ->forwardAgent(true) ->multiplexing(true) ->set('http_user', 'www') // 这个与 nginx 里的配置一致 ->addSshOption('UserKnownHostsFile', '/dev/null') ->addSshOption('StrictHostKeyChecking', 'no'); // 测试用的主机 host('172.16.3.2') ->stage('debug') ->user('root') ->port(22) ->set('branch', 'develop') // 通常是把 develop 分支弄到测试机测试,没问题再合并 ->set('deploy_path', '/data/wwwroot/xxx') ->identityFile('/home/vagrant/.ssh/id_rsa') ->forwardAgent(true) ->multiplexing(true) ->set('http_user', 'www') ->addSshOption('UserKnownHostsFile', '/dev/null') ->addSshOption('StrictHostKeyChecking', 'no'); // 自定义任务:重置 opcache 缓存 task('opcache_reset', function () { run('{{bin/php}} -r \'opcache_reset();\''); }); // 自定义任务:重启 php-fpm 服务 task('php-fpm:restart', function () { run('systemctl restart php-fpm.service'); }); // 自定义任务:supervisor reload task('supervisor:reload', function () { run('sudo supervisorctl reload'); }); // 自定义任务:部署成功了用 bearychat 发消息给大佬和本身 task('send_message', function () { run('{{bin/php}} {{release_path}}/artisan deployed'); }); // 自定义任务:缓存路由,recipe/laravel.php 默认的流程里没有这个,因此加上,息看须要 after('artisan:config:cache', 'artisan:route:cache'); // 执行自定义任务,注意时间点是 current 已经成功链向新部署的目录以后 after('deploy:symlink', 'php-fpm:restart'); after('deploy:symlink', 'supervisor:reload'); // 部署成功后重置 opcache 缓存 after('deploy:symlink', 'opcache_reset'); // 部署成功后调用 laravel 命令行发送通知 after('success', 'send_message'); // [Optional] if deploy fails automatically unlock. after('deploy:failed', 'deploy:unlock');
修改完成后记得先提交并将代码推送到远端仓库。而后执行以下命令进行部署:数据库
vendor/bin/dep deploy debug // 部署到测试机 vendor/bin/dep deploy production // 部署到生产机
过程当中若是提示要输入密码,则输入登陆目标主机的密码。或者想办法设置 SSH key 实现免密码登陆。缓存
默认状况下,首次部署后,.env 文件是不会自动建立的,须要本身建立并修改,同时 nginx 站点配置也须要本身动手。对于 .env 文件,存放于目标主机的 /path/to/project/shared/
目录下。
修改 .env 后记得从新缓存配置 php artisan config:cache
另外须要注意的是配置 nginx 站点时,网站根目录应该为 /path/to/project/current/public
。若是使用 supervisor 之类的,相关的目录在配置时也要注意了。
在部署的目标目录下执行 ls -la
,能够看到以下结果:
说明:
| projectname |--- @current -> releases/<num> |--- .dep |--- releases 一个文本文件,里面存着各次部署的时间、次数序号(或者说版本号)信息 |--- releases // 目录下根据配置保存近几回部署,更早的则会被自动清理 |--- 1 |--- 2 |--- . |--- . |--- <num> |--- 目录中是项目的实际代码 |--- 包括 .git, vendor, .env, storage ... |--- .env, storage 实际经过 symlink 连接到 shared 目录下对应的文件上 |--- shared |--- storage // 即 laravel 项目的 storage 文件夹 |--- .env // 即 laravel 项目的 .env
每次部署更新,会在 releases 下新建文件夹如 num,拉取对应的最新代码,安装 composer 依赖完成一些其它自定义任务,并将 storage, .env 连接到 shared 文件夹下的那两个上去,而后项目根目录下的 current 经过 syslink 连接到这个新文件夹 num 上,这算是其动做的基本原理,网站在部署过程当中能继续访问也得益于此。
.env 和 storage 下的一些未加入代码库中的内部,部署时不会自动更新,所以有些状况下须要手动处理。
正常状况下,部署过程当中 deployer 会自动完成缓存配置、清理已编译的缓存等任务。理论上咱们不须要本身再动手,但须要时也能够手动执行
// 缓存路由 vendor\bin\dep artisan:route:cache production // 缓存配置 vendor\bin\dep artisan:config:cache production // 清视图缓存 vendor\bin\dep artisan:view:clear production // 执行自定义任务,如前面提到的从新载入 supervisor vendor\bin\dep supervisor:reload production // ssh 链接到主机,hostname 也能够不输入,而后从选项里选 vendor\bin\dep ssh <hostname> // 列出其它一些可用的命令 vendor\bin\dep list
在 deploy 命令后加上 -vvv 选项能够输出详细错误信息,方便调试。
目标主机 php.ini
里的 disabled_functions
项里配置了一些被禁用的函数,若是 deployer 用到了这些函数就可能报错,修改 php.ini
解除相关函数的禁用状态就能够了。
目标主要经过 apt-get 命令或 oneinstack 一类的一键包安装的 PHP,可执行文件一般在 /usr/local/php/bin/php
,而 deployer 内使用 /usr/bin/env: php 形式调用,至关于 /usr/local/bin/php
。这就可能出错,通常是报 command -v 'php' failed
。解决办法很简单,只要加个软连接就能够了。
ln -s /usr/local/php/bin/php /usr/local/bin/php
解决办法固然是打开目录主机并检查网络状况
deployer 的 laravel 默认部署流程中,会执行 php artisan cache:clear 命令,若是你的项目里使用了 redis 驱动的队列或者一些强依赖于缓存的业务逻辑(如缓存文章阅读数按期再入库),则须要进行一些骚操做了。
好比,你能够在 config/database.php
的 redis
项中为队列连接指定其它的 database。
或者修改 deploy.php
配置默认的缓存清理任务,跳过缓存清理动做。(一般并不建议这么作,由于项目的缓存,应该是可清理的,若是部分业务确实十分依赖于缓存,则应该考虑一些缓存持久化的实现了)
// 覆盖 recipe/laravel 里默认的 artisan:cache:clear 任务,部署时不清缓存 task('artisan:cache:clear', function () { return true; });