Jenkins持续集成搭建笔记

最近需要搭建持续集成环境,因此有了这篇笔记,记录遇到的一些坑。

Why CI

持续集成环境的好处非常多,以至于现在我几乎会给每个后台项目准备一个CI环境。

搭建好持续集成环境之后,我们的代码一旦push到master分支,CI服务器就会自动拉取最新代码,然后根据实现编写的流水线脚本完成构建、测试、部署,然后测试服务器就会立刻上线最新版本的代码。

这意味着开发过程中的部署是几乎完全自动化的,只要配置好,以后所有新代码能够立刻上线,而不需要每次更新代码之后登录服务器手动拉取代码、重启服务器。

除此以外,我们可以在CI过程中加入构建、测试脚本,这样我们可以在CI控制台中清晰地看到项目的构建是否出错(如运行时环境未安装)、测试是否失败(代码测试不通过,存在bug),甚至可以将某次CI过程中的失败和详情发送邮件给开发者,方便甩锅,啊不是,方便第一时间发现问题、解决问题。

Why Jenkins

主流的CI服务有两个,Travis和Jenkins,前者虽然和GitHub的集成比较好,但是仅面向开源项目免费,因此我选择了开源的Jenkins方案。

踩坑记录

本文并不是Jenkins持续集成的部署教程,因为这样的教程在搜索引擎、官方文档中已经有现成的文章。本文旨在记录部署过程中遇到的各种问题。

用户权限配置兼服务器权限配置(伪)最佳实践

在通过apt源安装了Jenkins之后,Jenkins是运行在jenkins用户下的。而对于大多数网站而言,他一般都是运行在www-data用户下,因此我需要进行必要的修改。

首先,我们编辑/etc/default/jenkins,修改默认的Jenkins运行用户:

# user and group to be invoked as (default to jenkins)
#JENKINS_USER=$NAME
#JENKINS_GROUP=$NAME
JENKINS_USER=www-data
JENKINS_GROUP=www-data

然后,我们需要把Jenkins操作的目录改为www-data可读写:

$ sudo chown -R www-data:www-data /var/lib/jenkins/
$ sudo chown -R www-data:www-data /var/log/jenkins/
$ sudo chown -R www-data:www-data /var/cache/jenkins/

最后即可重启Jenkins守护进程。

值得一提的是关于服务器的用户权限配置实践,我的方案是:www-data用户作为服务器应用的运行用户,www-admin作为管理用户。www-data应该是尽可能低权限的,因此它没有可用的shell,没有home目录,其HOME环境变量为/var/www。之所以其权限这么低,是因为它运行了大量服务器应用,应当避免某个应用的恶意代码(可能是bug,也可能是真的恶意代码)导致服务器安全性收到威胁。

而我们又需要一个用户对服务器进行管理,包括安装运行时环境、部署应用等,因此我们需要一个带有shell、位于sudo用户组的用户,我将它命名为www-admin

但这就产生了一个矛盾,使用该用户的话,Web应用部署的文件都是属于www-admin:www-admin的,那么www-data如何修改这些文件呢?总不能将可被写的文件都改为666(所有人可读写)权限吧。于是,这里的最佳方案是将www-admin用户的主用户组设置为www-data,这样www-admin创建的所有文件都是属于www-damin:www-data的,且默认都是rwxr-xr-x的权限,这样www-data也具有了读权限,www-admin也不必每次都chown了。而对于需要写的目录,比如某个服务器应用需要支持图片上传,那么我们将上传目录设置为用户组可写即可。

如何重置Jenkins管理员用户

Jenkins这一点非常不人道。在首次运行Jenkins时,他会要求你输入一个Key,这个Key在服务器端的一个指定目录存放,因此只有部署者可以获取并登录。登录成功之后,如果你不小心忘记了密码,就会非常尴尬。此时,你无法进入Jenkins,也找不到重置密码的渠道。唯一的解决方案是去修改Jenkins的配置文件,把“安全检查”关闭。此时,任何人都可以自由登录和修改你的服务器,你需要尽快创建新的帐号。

然而,作为匿名帐号,Jenkins不允许非用户本人更改用户信息,也就是说你没法修改admin的密码。怎么办呢?创建一个新用户temp,然后删除admin用户,然后重新创建admin用户。

怎么创建用户呢?不好意思,匿名用户没有权利增删用户。你需要进入安全设置,启用“登录保护”,同时启用“允许任何人注册”,同时启用“登录用户拥有所有权限”。然后,你会发现你需要登录,此时你注册temp用户,然后进入后台,然后删掉admin用户,然后退出登录,然后注册admin用户,然后请一定记得你的密码啊!然后登录admin用户,删掉temp用户,然后禁用“运行任何人注册”。到此为止,你的服务器才重新回到安全。

可以看出,从你开放匿名访问开始,直到你找回admin用户并关闭注册为止,你的Jenkins服务器都是完全暴露在互联网中的,假如不幸有人发现了,那么就可以轻松获得Jenkins帐号,不得不说是风险很高的事情。

事实上,我认为Jenkins完全可以让忘记密码的用户索取一个Key,同样把Key存储在服务器上即可。

正确启用Jenkins的GitHub Webhook

GitHub Webhook的意义在于将你的Jenkins的预设URL提供给GitHub,一旦你的某个仓库有人push代码,GitHub服务器就会把这次push的详细信息通过一个HTTP请求发送到你的这个URL上,Jenkins会收到并进行解析。如果这个仓库就是你的某个流水线项目(Jenkins的一种CI类型),则Jenkins会自动拉取新的代码并进入后续的CI操作。

但是,这里面需要注意两点:

  • 配置项目的时候,在Build Triggers配置里不仅要勾上GitHub hook trigger for GITScm polling,还要勾上Poll SCM,也就是说仍然要启用轮询,只是不需要设置轮询间隔。
  • 首次配置的流水线项目需要手动构建一次,即项目左边的Build Now,这样才能触发后续的Webhook。

项目名字不要乱来

Jenkins默认会在/var/lib/jenkins/workspaces/<your_project_name>目录中拉取你的项目代码,所以一定要注意项目的命名,如果你的命名包含空格,那么可能导致一些会获取PWD的构建流程接受到错误的参数。

www-data为你带来无尽烦恼

这个用户前面说到了,HOME目录在/var/www,而按照前面的配置,这个目录默认是不会给www-data写入权限的,于是,在部署的时候就会遇到很多问题,比如pip安装的时候会无法启用cache,因为/var/www/.pip目录不可写入;进程管理工具PM2更是无法正常工作,因为/var/www/.pm2无法写入。目前除了手动创建这些目录并赋予用户组写入权限以外,还没有更加优雅的解决方案。

CI无法解决部署失败的问题

CI最大的问题在于,CI的成功不能表示部署成功。这是因为,每一次CI都不能阻塞,而服务器天生就需要作为守护进程运行,这就意味着CI中的部署流程只能通过PM2这样的进程管理器完成。但是,进程管理器执行后,服务器是否正常部署、运行,却无法反馈给CI,因此会出现CI完全正常,但服务器不可访问的问题。

目前,我没有找到很完美的解决方案,只有两个可选的方案:

  • 在CI的Test流程中加入对运行时环境的检查,尽可能多地将部署的失败在CI中就体现出来
  • 对PM2等进程管理工具下文章,比如部署出错就发送邮件等,但是目前来看这些进程管理工具似乎都暂不支持直接配置部署失败的邮件通知。

总结

CI是一种一次配置,永远可用的服务,如果可能的话,任何项目都应该尽早部署,这会给项目的后续开发、部署和维护带来巨大收益,节省大量时间。现在,我已经养成了后台应用部署必用Jenkins的习惯,就像HTTPS一样,既然花不了多少时间,有什么理由不顺便做了呢?