让我们来回忆下上次你是怎么发布你的代码的:

1. 先把线上的代码用ftp备份下来

2. 上传修改了的文件

3. 测试一下功能是否正常

4. 网站500了,赶紧用备份替换回去

5. 替换错了/替换漏了

6. 一台服务器发布成功

7. 登录每一台执行一遍发布操作

8. 加班搞定

9. 老板发飙

...

尤其现在的互联网行业,讲究快速迭代,小步快跑。像bug修复或者小功能的修改几乎每天都发版本,大功能的版本迭代每周也差不多会有一次。相信不少同行们像我上面说的这样发布自己的代码吧。或者可能先进一点,直接去服务器上执行一条类似git pull的命令拖下仓库中的代码,但是如果你的代码运行在集群中呢?每台机器登录一次执行一次git pull吗?如果发现代码有问题需要回滚呢?

如果你还在像我上面说的这种方式部署自己的代码的话,那么我希望你能耐心看完这篇文章,从此摆脱代码部署之痛。

其实绕了这么一圈今天是想向大家介绍一下用php写的代码发布工具:deployer。

deployer具有以下吸引人的特性:

- 快速      采用了比如并发发布、ssh通道复用、缓存可用情况下使用缓存等技术加速代码部署

- 原子部署   在新发布的版本内执行所有定义的操作,诸如下载依赖、设置文件访问权限等都不会直接影响线上,只有全部成功后,最后一步设置软链才会真正替换线上代码

- 快速回滚   由于采用了原子部署,所以回滚也只是重新设置一下软链指向

- 并发部署   集群环境下,并发在所有机器上执行相同的部署流程

- 一致性     集群环境下,只有所有机器都执行成功才算成功,一台失败则全部失败

- 内置多个框架发布模板   比如Laravel、Yii、Symfony、CodeIgniter、Zend Framework等

- 易扩展     很容易可以依据自己的项目用Common模板编写发布流程

安装:

composer global require deployer/deployer

安装完成后,切换到自己的项目目录,执行dep init,按照自己项目使用的框架选择生成的部署模板:

➜  tb dep init
Please select your project type (defaults to common):
[0] Common
[1] Laravel
[2] Symfony
[3] Yii
[4] Zend Framework
[5] CakePHP
[6] CodeIgniter
[7] Drupal
> 0

如果你的框架未使用上面列出的任何一个框架,则选择0,然后回车,就会生成通用的发布模板。

执行完这一步应该会在你的项目根目录生成一个deploy.php文件,你所需要的做的一切就是编辑这个脚本,填写一些自己的服务器和项目配置,然后定制一些task。

下面我将用一个具体的配置文件来介绍deployer的使用,配置文件如下:

<?php
namespace Deployer; use Symfony\Component\Console\Input\InputOption; require 'recipe/common.php'; option('tag', null, InputOption::VALUE_OPTIONAL, '发布的tag'); // 全局配置文件
set('ssh_type', 'native'); // 登录远程主机使用的方式,有三种:phpseclib(默认方式)、native、ext-ssh2
set('ssh_multiplexing', true); // 是否开启ssh通道复用技术(开启可以降低服务器和本地负载,并提升速度)
set('keep_releases', 10); // 报错10个之前版本,设置为-1表示一直保存历史版本
set('repository', 'git@xxxxxxx.com:loc/loc-api.git'); // 代码仓库的地址,只支持git
set('branch', 'master'); // 发布代码时候默认使用的分支
set('shared_files', []); // 共享文件列表 这里面列出的文件会被移动到项目根目录的shared目录下,并做软链
set('shared_dirs', []); // 共享目录 同上
set('writable_mode', 'chmod'); // 采用哪种方式控制可写权限,有4中:chown、chgrp、chmod、acl(默认方式)
set('writable_chmod_mode', '0755'); // 当使用chmod控制可写权限的时候,赋予的可写权限值
set('writable_dirs', []); // 可写目录 规定那些目录是需要可以被web server写入的
set('clear_path', []); // 设置在代码发布的时候需要被删除的目录
set('http_user', 'nginx'); // web server的用户,一般不用设置,deployer会自动判断
set('release_name', function () { // 设置发布版名称,这里优先使用tag作为名称,不传的话会使用日期+时间表示发布时间
if (input()->hasOption('tag')) {
return input()->getOption('tag');
}
return date('Ymd-H:i');
}); // 可以设置多个服务器,发布的时候根据设置会同步发往多个服务器
// 针对每个服务器可以单独设置参数,设置的参数会覆盖全局的参数
server('prod_1', 'xxx.xxx.xxx.xxx')
->user('root')
->password('xxxxx')
->set('deploy_path', '/var/www/tb') // 代码部署目录,注意:你的webserver,比如nginx,设置的root目录应该是/var/www/tb/current,
// 因为current是一个指向当前线上实际使用的版本的软链
->stage('prod'); // 标识该服务器类型,用于服务器分组 server('prod_2', 'xxx.xxx.xxx.xxx')
->user('root')
->password('xxxxx')
->set('deploy_path', '/var/www/tb')
->set('branch', 'master') // 指定发往这个服务器的分支,会覆盖全局设置的branch参数
->set('extra_stuff', '...') // 随意指定其他什么参数
->stage('prod'); server('beta', 'xxx.xxx.xxx.xxx')
->user('root')
->password('xxxxx')
->set('deploy_path', '/var/www/test')
->set('branch', 'beta') // 测试环境使用beta分支
->stage('beta'); // 放在beta分组 // 配置的任务
task('success', function () {
Deployer::setDefault('terminate_message', '<info>发布成功!</info>');
})->once()->setPrivate(); // 增加once调用那么这个任务将会在本地执行,而非远端服务器,并且只执行一次 desc('重启php-fpm'); // 可以给任务增加一个描述,在执行dep list的时候将能看到这个描述
task('php-fpm:restart', function () {
run('systemctl restart php-fpm.service'); // run函数定义在服务器执行的操作,通常是一个shell命令,可以有返回值,返回命令打印
}); // 聪明如你一定发现了,可以用run函数制作一些批量管理服务器的任务,比如批量重载所有的nginx配置文件、批量执行服务器上的脚本等 after('deploy:symlink', 'php-fpm:restart'); // 钩子函数,表示执行完设置软链任务之后执行php-fpm重启任务 desc('发布项目');
task('deploy', [ // 可以设置复合任务,第二个参数是这个复合任务包括的所有子任务,将会依次执行
'deploy:prepare', // 发布前准备,检查一些需要的目录是否存在,不存在将会自动创建
'deploy:lock', // 生成锁文件,避免同时在一台服务器上执行两个发布流程,造成状态混乱
'deploy:release', // 创建代码存放目录
'deploy:update_code', // 更新代码,通常是git,你也可以重写这个task,使用upload方法,采用sftp方式上传
'deploy:shared', // 处理共享文件或目录
'deploy:writable', // 设置目录可写权限
'deploy:vendors', // 根据composer配置,安装依赖
'deploy:clear_paths', // 根据设置的clear_path参数,执行删除操作
'deploy:symlink', // 设置符号连接到最新更新的代码,线上此时访问的就是本次发布的代码了
'deploy:unlock', // 删除锁文件,以便下次发布
'cleanup', // 根据keep_releases参数,清楚过老的版本,释放服务器磁盘空间
'success' // 执行成功任务,上面自己定义的,一般用来做提示
]); after('deploy:failed', 'deploy:unlock'); // 如果发布失败,则删除锁文件,以便下次重试

上面就是一个比较完整的自动化部署脚本配置了,是不是感觉到很简单? 因为大部分配置工作在你执行dep init的时候就已经帮你做了!

在接下来还需要做的一件事情就是把你要部署的服务器的ssh-key加入到你的git帐号的认证库里面,你也可以创建一个账户,只拥有仓库的git pull和git clone权限,保持最小权限原则。需要注意的是,加完key之后,首次在服务器上执行git clone可能会需要让你输入yes,所以最稳妥的办法是,去每台要部署的服务器上去执行一遍git clone,把仓库代码拖一份到其他目录。

做完上面的事情之后,所有的准备工作就算完成了。接下来就可以进行部署测试了。

首先检查下配置有没问题:

dep config:dump beta    // 打印beta环境的配置
dep config:dump prod // 打印生产环境的配置

打印出来的配置没有问题的话,接着执行发布任务:

dep deploy beta // 发布当前beta分支到beta环境
dep --tag=v1.1 deploy prod // 发布v1.1这个tag的代码到生产环境,可以增加-p选项,并发发往所有服务器

一次成功的部署应该会有类似如下输出:

➜  tb git:(master) ✗ dep --tag=v1.1 deploy prod_1
✔ Executing task deploy:prepare
✔ Executing task deploy:lock
✔ Executing task deploy:release
✔ Executing task deploy:update_code
✔ Executing task deploy:shared
✔ Executing task deploy:writable
✔ Executing task deploy:vendors
✔ Executing task deploy:clear_paths
✔ Executing task deploy:symlink
✔ Executing task php-fpm:restart
✔ Executing task deploy:unlock
✔ Executing task cleanup
✔ Executing task success
发布成功!

查看当前生产环境使用的哪个版本

dep current prod  //这里应该会输出v1.1

查看当前生产环境使用的哪个版本:

dep current prod  //这里应该会输出v1.1 

如果发布到线上之前之后发现有问题,需要回滚,只需要执行:

dep rollback prod   // 实际上只是修改软链指向,所以很快就能执行完成且基本不可能失败

再次用dep current prod应该就可以看到回滚到之前版本了

再比如之前执行出了问题,被中断,再次执行可能会提示:Deploy locked,那么只用执行:

dep deploy:unlock prod // 删除锁文件

如果线上磁盘空间吃紧了的话(一般不会),可以执行如下命令删除掉太早以前的版本:

dep cleanup

到了这里关于deployer所有你应该都掌握了。虽然第一次配置的确需要花点时间,可能半个小时也可能半天。  不过换来的却是接下来更优雅、快速、安全、易回滚的发布流程,这么想一下是不是还有点小激动呢?

如果在安装使用过程中有什么问题的话可以加群:632109190进行讨论。对php、java、运维感兴趣的同学都可以加进来,我在这等你们 :)

少年,是时候换种更优雅的方式部署你的php代码了的更多相关文章

  1. [C#] 用一种更优美的方式来替换掉又多又长的switch-case代码段

    switch-case语句是我们编码过程中常用的一种分支语句.然而正所谓成也萧何败萧何,每当我们向一个已经拥有了成百上千行的switch-case代码段中添加新的case分支的时候,我们是否有过为代码 ...

  2. PostCSS一种更优雅、更简单的书写CSS方式

    Sass团队创建了Compass大大提升CSSer的工作效率,你无需考虑各种浏览器前缀兼,只需要按官方文档的书写方式去写,会得到加上浏览器前缀的代码,如下: .row { @include displ ...

  3. [改善Java代码]集合运算时使用更优雅的方式

    在初中代数中,我们经常会求两个集合的并集.交集.差集等,在Java中也存在着此 类运算,那如何实现呢? 一提到此类集合操作,大部分的实现者都会说:对两个集合进行遍历,即可求出结果.是的,遍历可以实现并 ...

  4. Springboot — 用更优雅的方式发HTTP请求(RestTemplate详解)

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  5. Spring— 用更优雅的方式发HTTP请求(RestTemplate详解)

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  6. Springboot中RestTemplate -- 用更优雅的方式发HTTP请求

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  7. Springboot — 用更优雅的方式发HTTP请求:RestTemplate

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率. 我之前的HTTP开发是用ap ...

  8. vue自定义指令,比onerror更优雅的方式实现当图片加载失败时使用默认图,提供三种方法

    首先,来看下效果图(演示一下图片正常加载与加载失败时的效果) 在线体验地址:https://hxkj.vip/demo/vueImgOnerror/ 一.常规方法解决 我们都知道,img标签支持one ...

  9. 一种更优雅的Flutter Dialog解决方案

    前言 系统自带的Dialog实际上就是Push了一个新页面,这样存在很多好处,但是也存在一些很难解决的问题 必须传BuildContext loading弹窗一般都封装在网络框架中,多传个contex ...

随机推荐

  1. 服务器 ADO 错误:0x80004005,[DBNETLIB]

    2012-12-0310:44:06 ]ADO 错误:0x80004005,[DBNETLIB][ConnectionOpen(Connect()).]SQL Server 不存在或拒绝访问.[ 20 ...

  2. protected private public 的区别

    1.public,protected,private是Java里用来定义成员的访问权限的,另外还有一种是"default",也就是在成员前不加任何权限修饰符.如:    publi ...

  3. 谈谈java中的volatile

    内存可见性 留意复合类操作 解决num++操作的原子性问题 禁止指令重排序 总结 内存可见性 volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色.同synch ...

  4. Apache网页的缓存时间

    配置网页缓存时间概述 通过mod_expires模块配置Apache,使网页能在客户端浏览器缓存一段时间,以避免重复请求,减轻服务端工作压力. 启用mod_expires模块后,会自动生成页面头部信息 ...

  5. Linux文件权限rwx简单了解

    Ⅰ  了解Linux下的文件权限 如上图所示,ll命令详细展示当前目录下的文件或者子目录信息 红框标注的即为此文件或者目录的权限 [第一行文件10.c的权限以-开头,用来说明这是一个文件:第四行cod ...

  6. curl javaSSm框架中传入json数组的格式方法

    curl与java结合传入get.post,head请求, 比如: curl的地址: curl -l 127.0.0.1:8080/spacobj/core/do?acid=100 -H " ...

  7. Linux云自动化运维第三课

    Linux云自动化运维第三课 一.正则表达式 1.匹配符 * ###匹配0到任意字符 ? ###匹配单个字符 [[:alpha:]] ###匹配单个字母 [[:lower:]] ###匹配单个小写字母 ...

  8. NOI全国赛(2001)--食物链

    今天写了道并查集的题,看来并查集的题刷少了,,,,,用法好神奇啊!!!开三倍并查集 用i表示自己,i+n存天敌,i+2*n存可以克制de,再逻辑判断一下即可. 所以,要意识到并查集的分类处理可以开不同 ...

  9. C++枚举类型详解

    原创作品,转载请注明来源:http://www.cnblogs.com/shrimp-can/p/5171110.html  一.枚举类型的定义 enum 类型名 {枚举值表}: 类型名是变量名,指定 ...

  10. MFC画笔作用域的问题

    今天发现了程序中的一个BUG.功能是在鼠标经过图形时,对图形进行加粗重绘.默认使用白色画刷.为防止白色背景下看不清,在白色背景下改用黑色画刷.代码如下 CPen* pOldPen;if (pDC-&g ...