deployer 实战经验分享

开发完项目,免不了要部署上线。纯手动操作,登录、拉代码、改配置、清缓存、各种服务重启等等一条龙下来,人生宝贵的几分钟就过去了。而且手动操作十分容易出错,遗漏部分步骤都有可能产生一些邪门问题。所以我很早就开始寻求一种能轻松部署 Laravel 项目的办法。

laravel 的官方文档里介绍了 Envoy,之前用过,能满足大部分场景,但仍然有一些限制。直到后来看到了 deployer,大有相见恨晚之感!

deployer 的优势

  1. 真正解放双手,一条命令完成部署。

  2. 进行部署的过程中,项目仍然能够正常访问。部署成功完成后才切到新的版本。

  3. 能十分方便地进行回滚。

  4. 丰富任务钩子和预置任务可灵活的组合完成各种任务,比如执行前端依赖的安装、构建等。

  5. 其它骚姿势等你发掘……

使用 deployer 的前提条件

  • 本地机器(也就是你执行 dep 命令时所在的机器)能够 SSH 连接到目标机器(代码要部署到的机器,不管是在线的云主机还是局域网中的虚拟机)

  • 有登录目标机器并调整一些设置的权限,或者能让负责人协助调整。(使用过程中可能遇到问题需要调整一些设置,后面会提)

  • 目标主机有拉取项目仓库的权限。(这个应该都有吧,不然玩个毛?)

  • 足够大胆、足够细心、足够有耐性…… :smile:

deployer 的使用

首先说明下个人实际使用场景。

本人使用 win10 系统,使用 Homestead 作为 PHP 项目的开发环境(vagrant v2.1.1, homestead v7.4.1, virtualbox v5.2.8, homestead 的 virtual box 版本为 v5.2)。

本地开发能完成绝大部分开发和测试任务,但在部署到生产机之前仍然需要先部署到开发机上进行测试。线上测试与生产使用的是青云的云主机,Ubuntu16 系统。

以下的操作都是在 homestead 虚拟机里进行操作!

  1. 安装
cd /path/to/your/project

composer require deployer/deployer --dev

个人习惯于将其作为项目依赖安装,当然也可以根据需要或个人喜好全局安装。

  1. 初始化 deployer 配置文件
vendor/bin/dep init

因为我用的是 laravel 输入项目类型 1 后回车,然后会出现一个让设置 git 仓库的,默认是对应项目的 git 远端仓库,不需要修改的话确认就可以了。

deploy_init

完成上面的初始化后,项目要目录下会出现一个 deploy.php 文件,deployer 的配置就靠它了。初始的配置如下,里面显示了一些基本的配置。

<?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');
  1. 修改配置

默认的配置肯定是不行的,目标主机啥的还不知道呢。下面直接贴上自己用到的配置,并加入了少量说明。

<?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');
  1. 代码修改完成后运行部署

修改完成后记得先提交并将代码推送到远端仓库。然后执行如下命令进行部署:

vendor/bin/dep deploy debug // 部署到测试机

vendor/bin/dep deploy production // 部署到生产机

过程中如果提示要输入密码,则输入登录目标主机的密码。或者想办法设置 SSH key 实现免密码登录。

  1. 首次部署后设置 .env,并配置 nginx 站点

默认情况下,首次部署后,.env 文件是不会自动创建的,需要自己创建并修改,同时 nginx 站点配置也需要自己动手。对于 .env 文件,存放于目标主机的 /path/to/project/shared/ 目录下。

修改 .env 后记得重新缓存配置 php artisan config:cache

另外需要注意的是配置 nginx 站点时,网站根目录应该为 /path/to/project/current/public。如果使用 supervisor 之类的,相关的目录在配置时也要注意了。

部署后目录的结构及相关说明

在部署的目标目录下执行 ls -la,可以看到如下结果:

deploy_init

说明:

| 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 选项可以输出详细错误信息,方便调试。

  1. 由于部分 php 函数被禁用而报错

目标主机 php.ini 里的 disabled_functions 项里配置了一些被禁用的函数,如果 deployer 用到了这些函数就可能报错,修改 php.ini 解除相关函数的禁用状态就可以了。

  1. php 执行文件位置引起的错误

目标主要通过 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
  1. 目录主机不在线或者网络连接问题

解决办法当然是打开目录主机并检查网络情况

  1. 关于缓存清理

deployer 的 laravel 默认部署流程中,会执行 php artisan cache:clear 命令,如果你的项目里使用了 redis 驱动的队列或者一些强依赖于缓存的业务逻辑(如缓存文章阅读数定期再入库),则需要进行一些骚操作了。

比如,你可以在 config/database.phpredis 项中为队列链接指定其它的 database。

或者修改 deploy.php 配置默认的缓存清理任务,跳过缓存清理动作。(通常并不建议这么做,因为项目的缓存,应该是可清理的,如果部分业务确实十分依赖于缓存,则应该考虑一些缓存持久化的实现了)

// 覆盖 recipe/laravel 里默认的 artisan:cache:clear 任务,部署时不清缓存
task('artisan:cache:clear', function () {
    return true;
});

原文地址

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 5年前 加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 65

马上愉快的玩耍起来 :smile:

5年前 评论
luphp_安轮粉丝

田教授威武!!!! :+1: :+1: :+1:

5年前 评论
Artisan

一直用这个,很好用

5年前 评论

@Artisan 节省不少生命。

5年前 评论

deployer 感觉支持的框架比较少啊, 比如 Yaf ?

5年前 评论
ThinkQ

很好啊,一直用这个。

5年前 评论
nff93

如果遇到例如.env文件需要增改配置项的情况还是需要手动?

5年前 评论

@nff93 不然它把本地开发用的 .env 弄上去?那不得死翘翘了?

5年前 评论

@nff93 多个配置文件啊,线上可以通过 .env.release .env.beta 这种

5年前 评论

@nff93 @HACK21 说的对,如果知道用多个 .env 的话,可以自己折腾,deployer 里是自己自己定义 shared_files/ shared_dirs 之类的。本文里只是非常基础的个人使用经验,连里面的自定义任务都与个人项目有关,并不是什么最佳实践……

5年前 评论

@田勇 如果不用 root 用户会遇到很多权限问题,如果强迫症要使用普通用户,该怎么配置?

5年前 评论

@Nick 不用 root,权限问题根据提示手动设置。相关的错误提示一般都十分清楚了。

5年前 评论

长见识了,之前从没用过,需要不断去学习

5年前 评论
nff93

@HACK21 @田勇 嗷,其实我在说比如已经部署上去了,但是版本迭代中.env文件需要增加配置或者修改配置(以及类似的其他操作)还是需要人工来?或者有啥更好的实践么?

5年前 评论

@nff93 自动化部署只是减少一部分常规操作,并不存在完全自动部署,部分操作还是要手工的

5年前 评论

配合Git 的hooks 可以很好的实现自动化

5年前 评论

Jenkins + deployer + ansible 简直爽歪歪

5年前 评论

很详细~ :+1:

5年前 评论

好像挺不错啊,现在用docker已经很方便了,但是感觉加上这个还能进一步提升效率,有空试一试 :smiley: :+1:

5年前 评论

这篇真的写的超级好~~~~
超级谢谢田大大,每次折腾deploy都翻过来读几遍,每次都能get新东西

5年前 评论

@aen233 deployer 是个好东西,写的这些能让大家避开些坑也算相当欣慰了。 :smile:

5年前 评论

今天试着想用deplyer配置Supervisor的时候,发现delpoyer不能用在docker的容器中。
比如我之前的环境是nginx、php-fpm、mysql、redis在不同的容器中,而部署代码是部署在主机(用主机中的php),而代码运行都应该是在php-fpm那个容器中,执行 php artisan horizon:terminate 这句也应该是在容器中,然而deployer只能是ssh连到主机。。。连不到容器。。。蓝后,就不会了。。。。
去github找,好像遇到同样的问题也还挂着- -

5年前 评论

我发现一个问题,我按照个配置进行部署项目时,项目目录的所有者都是root,然后再上传文件时就会报 Impossible to create the root directory 错误,请问像这样情况应该如何解决?我尝试了将部署文件用户更改为 www-data,然后又会报没有权限删除一些文件的错误。

4年前 评论

@景哥哥 建议看下超哥的这一篇,里面对于权限部分补充得很详细了。我这个写的虽然早,但权限部分没太写到。
博客:又一篇 Deployer 的使用攻略

4年前 评论
zhengwhizz

求助:我部署成功后,发现current也指向了最新的,但是使用的php文件依然是之前版本的,比如我修改了Controller某个方法,但是方法执行后依然是旧逻辑,得人为删除前一版本的文件夹后才使用最新版本,这一现象仅是php文件有问题,有人说是nginx对php默认有1分钟缓存,但是我等一晚上依然没变,下面是我的脚本:

add('shared_files', []);
add('shared_dirs', []);

// Writable dirs by web server
set('writable_dirs', []);
add('copy_dirs', ['vendor']);

host('111.111.111.188')
    ->user('root') // 使用 root 账号登录
    ->identityFile('~/.ssh/id_rsa') // 指定登录密钥文件路径
    ->become('www-data') // 以 www-data 身份执行命令
    ->set('deploy_path', '/var/www/111'); // 指定部署目录

// Tasks

desc('Upload .env file');
task('env:upload', function () {
    // 将本地的 .env 文件上传到代码目录的 .env
    upload('.env', '{{release_path}}/.env');
    upload('resources', '{{release_path}}');
    upload('laravel-echo-server.json', '{{release_path}}/laravel-echo-server.json');
});

// 定义一个后置钩子,在 deploy:shared 之后执行 env:upload 任务
after('deploy:shared', 'env:upload');

desc('NPM');
task('deploy:npm', function () {
    run('cd {{release_path}} && npm install && npm run prod', ['timeout' => 600]);
});

// 定义一个后置钩子,在 deploy:vendors 之后执行 deploy:yarn 任务
after('deploy:vendors', 'deploy:npm');

task('build', function () {
    run('cd {{release_path}} && build');
});
desc('Restart Horizon');
task('horizon:terminate', function () {
    run('{{bin/php}} {{release_path}}/artisan horizon:terminate');
});

// 在 deploy:symlink 任务之后执行 horizon:terminate 任务
after('deploy:symlink', 'horizon:terminate');
after('artisan:config:cache', 'artisan:route:cache');
// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');
// 在 deploy:vendors 之前调用 deploy:copy_dirs
before('deploy:vendors', 'deploy:copy_dirs');
// Migrate database before symlink new release.
before('deploy:symlink', 'artisan:migrate');

有人说软链接有时会导致 opcache 刷新缓存失败,会是这个原因吗? 有办法不删除上一版本的前提下刷新 opcache 缓存吗?

4年前 评论
止语 4年前

@zhengwhizz 极有可能就是你说的 opcache 缓存问题。你可以在 after('deploy:symlink') 钩子里重置 opcache 缓存。

4年前 评论
qietugou 4年前
zhengwhizz

@田勇 好的,谢谢

4年前 评论

如果部署html代码可以吗?

4年前 评论

@Wen369367988 当然可以。phpdeployer 是“用 php 写的 deployer 但并不是只用来 deploy php 项目的 deployer(虽然大部分应用场景是这样)。

4年前 评论
Wen369367988 4年前

current -> releases/n
是不是最新版本改成了:
release-> releases/n

4年前 评论

@soen 应该没有吧。最近的几个版本发版说明都没有提到这个。而且按理这个也不会随便改的。

4年前 评论

@田勇

file我刚试的,我也不太清楚为何不是current二十release。
还有个问题请教一下,我本地win10,运行dep deploy时,提示'ssh' is not recognized as an internal or external command,该怎么解决

4年前 评论
Wen369367988 4年前
田勇 (楼主) 4年前
soen (作者) 4年前

请问是否支持svn部署呢?

4年前 评论

@小月月是我我是小月月 不知道你说的 SVN 部署具体指哪样的场景。但 deployer 的本质就是 SSH 连接远程主机执行一系列相关任务,这些任务可以自己定义,不限于 git 和 composer。也不限于 Laravel 项目,具体的根据自己实际业务环境来定吧。

4年前 评论

@田勇 嗯嗯 我没说明白 我的意思是支持svn的仓库地址吗 我看你写的是git的仓库地址

4年前 评论

@小月月是我我是小月月 肯定是可以的,但具体如何弄我就不知道了。七八年没碰过 SVN 了。 :joy:

4年前 评论

@田勇 我前两天试了下svn仓库 没走通 我以为只是支持git 的 可能是我配置的问题了 我有时间我再看看 谢谢啦 :+1:

4年前 评论

请教一下 我部署的时候出现了这个错误 是什么问题呢 ? file

4年前 评论

@小月月是我我是小月月 你要是非用 SVN 的话,就得自己写部署任务,而不是继承那些预设的部署流程。那些预设基本都是针对 PHP 项目,用 composer 安装依赖的。

4年前 评论

那我还是用 Capistrano 吧 谢谢啦 :ok_hand:

4年前 评论

@田勇
请问下 ,你上面的实例是 用 root 账号 部署的把,怎么 去 ->become('www-data') // 以 www-data 身份执行命令 ,我自己 用 root 可以部署 , www-data 部署 就报错。这个需要怎么调?

4年前 评论

@gyp719 可以看一下这篇,权限相关的写的很详细。博客:又一篇 Deployer 的使用攻略

4年前 评论

大佬,一般需要在task里加上composer install吧,比如

task('composer:install', function () {
    run('composer install');
});
3年前 评论

@beaplat-61f 不用啊,大部分内置的预设部署流程都已经包含 git 拉取代码以及 composer 安装这些过程了。

3年前 评论

@作者你好,请教下,deploy 部署后 uploads 文件里的图片都没了,怎么不更新 或者不覆盖这个目录啊?

3年前 评论

@chris_zqw 不清楚你的 uploads 目录是怎么组织的。但应该是位于 public 目录下用来保存前端自主上传内容的吧?这类文件夹里的内容显然是不适合放进仓库里的,你应该在 shared 目录里建一个目录用来保存这些,然后部署之后用软链的方式链到指定位置,或者改成直接上传到七牛等云存储,这样就用不着 uploads 文件夹了。

3年前 评论
巴啦啦小仙女 3年前

请问一下, 能不能使用密码的形式进行部署呢

3年前 评论

deploy:unlock 锁定 运行 dep deploy:unlock 没法解锁 怎么搞呢?

3年前 评论

@chris_zqw 用 deploy:unlock 命令。或者进入服务器网站部署的目录下,手动删除 .dep/deploy.lock 文件。

3年前 评论

是教程旧了吗?

file

3年前 评论
Wen369367988 3年前

->forwardAgent(true) ->multiplexing(true)

这两个具体的作用是?

3年前 评论

github actions 可以免去手动执行dep命令了,楼主有研究过吗,我跑通了,但是有几个问题。

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!