持续集成 (continuous integration),就是在敏捷开发中经常提到的 CI。
每一次代码提交更新都要通过 CI 中的自动化测试,这样可以 尽早发现现有的 bug。其目的在于让 产品快速迭代的同时,尽可能保持高质量。
以我们做 Rails 的开发为例,为例保证项目的质量,我们都会写一定的自动化测试 (例如 RSpec、minitest)。在多人协作的项目中,我们会基于当前的开发 (develop) 分支,开一个新的分支进行开发,在代码合并到 develop 分支前,跑一遍测试,通过了才能合并。如果没有 CI,这样的一个操作都是开发人员手动在本地操作的,这样也会带来一些潜在的问题。首先由于测试都是由手动触发并且在本地运行,所以每次开发人员都必须要记住去跑测试,并且这个操作有时也会是很耗时的。其次每一个人本地的环境不一定是一致的,同样的测试在某一个开发人员本地通过了,在另一个人的 PC 或者线上并不一定可以通过。在引入了 CI 之后,每一次提交都会在 CI 服务器上运行测试,这样不仅将之前需要手动触发的操作自动化,同时也保证了测试是在统一的环境 (CI 服务器) 下运行的。
CI 保证了交付的质量和效率,给开发团队提供了极大的帮助。不过现有的 CI 服务价格通常都十分昂贵 (以 RubyChina 使用的 Travis CI 为例,对于开源项目是免费的,但是私有项目的最低价格高达$69/month)。所以很多团队会选择开源的持续集成工具搭建自己的 CI 服务,其中最出名的就是Jenkins了。社区里 Jenkins 相关的中文资料不多,我最近正好在做相关的工作,所以就整理成了这篇博客。
在这里首先介绍下我使用服务器的一些基本信息,我使用的是 digitalocean 的$10/month 的 VPS(单核 CPU、1GB 内存、30GB SSD,最开始时候用的是$5,但是每次使用 jenkins 进行 build 时都会因为内存不够而进程崩溃),使用的操作系统是 Ubuntu 14.04.5 x64,Jenkins 的版本是 2.19.4。你也可以选择在本地操作系统直接搭建或者使用 Vagrant 搭建。
在 Ubuntu 上安装 jenkins
$ wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
$ sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
$ sudo apt-get update
$ sudo apt-get install jenkins
这几段命令执行完成之后 jenkins 成功安装在你的机器上并且运行在 8080 端口上了,同时你系统中也新建了一个名为jenkins
的用户。
这时你可以在浏览器中访问你服务器的 8080 端口,页面会提示你系统的初始密码存储在/var/lib/jenkins/secrets/initialAdminPassword
这个文件里。从里面获取密码粘贴到界面的输入框后就完成了认证。认证之后会让你给你两个安装 plugin 的选项,我有些选择恐惧症,所以就选择了安装推荐的 plugin。
安装完成后会让你填写用户名、密码之类的基本信息,以后从浏览器登录 jenkins 后台时需要用到。填写完成后就进入了 jenkins 的 dashboard。
由于之后会使用 jenkins 用户安装 Ruby,需要 root 权限。所以在这里我们给予该用户 root 权限。
# 把jenkins用户加入sudo用户组
$ adduser jenkins sudo
# 设置密码(也可以选择在visudo设置NOPASSWD让用户请求sudo权限时不需要输入密码)
$ passwd jenkins
首先安装 Ruby 相关的一些依赖
$ sudo su - jenkins
$ sudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6 libreadline6-dev zlib1g zlib1g-dev
在这里我使用了rvm
去管理 CI 服务器上的 Ruby 版本。
安装 rvm
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash
将 rvm 加入 shell profile 中,在.bashrc 文件下加入下面这行
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"
安装项目中的 Ruby 版本 (我的是 2.3.1)
$ rvm install 2.3.1
$ rvm use 2.3.1
# 安装bunlder
$ gem install bundler
安装一些 gem 相关的依赖,因为很多时候我们的 rails 应用需要 JavaScript runtime,所以在这里我也安装了 nodejs
$ sudo apt-get install libcurl3-dev libpq-dev nodejs
在我的服务器上我使用 PostgreSQL 作为数据库
# 安装postgres
$ sudo apt-get install postgresql postgresql-contrib
# 设置postgres password,这里为了演示我直接设置成'password'
$ sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'password';"
接着配置好 database.yml 文件,在这里我直接在 jenkins 用户 home 目录下创建。
# ~/ci_database.yml
default: &default
host: localhost
adapter: postgresql
encoding: unicode
pool: 5
test:
<<: *default
database: jenkins_test
username: postgres
password: password
由于我们的 repo 大多数都是用 Git 进行管理的,所以我们也需要在 server 上安装 git
$ sudo apt-get install git
生成 jenkins 用户的 ssh key
$ ssh-keygen
这里由于我的项目的 repo 在 GitHub 上,所以之后我就以集成 GitHub 为例。
在 shell 中打印出 ssh 公钥,拷贝到你的 Github 用户下
$ cat ~/.ssh/id_ras.pub
建立 ssh 和 Github 的连接
$ ssh -T [email protected]
首先进入你的 jenkins dashboard,点击页面左上方的New Item
,在新的页面中输入你的 CI job 的名称,然后选择Freestyle project
之后点击 ok 进入下一步。
下一个页面中是对你项目的一些配置。选择 GitHub project 后填写你项目在 GitHub 中的 url。
接着在Source Code Management
中选择Git
,在Repository URL
填写 repo 的 url,由于我们之前配置好了 ssh,所以直接填写 ssh url(git@github:username/repo.git)。
之后选择需要构建的分支,在Branch Specifier
我填写了空,代表所有分支都要进行构建。
接着在Build
中选择Add build step
-> Execute shell
,接着在其中添加你的 build 脚本。
下面贴出我的 build 脚本,大家可以针对自己需求自己定制。
#!/bin/bash -x # 指定执行本段脚本的shell为bash,默认情况会使用sh
source ~/.bashrc # 读取rvm
bundle install
cp ~/my_database.yml ./config/database.yml # 将之前写好的数据库配置yml文件拷贝到当前项目中
RAILS_ENV=test bundle exec rake db:setup # 初始化数据库
RAILS_ENV=test bundle exec rake test # 运行测试,我使用的是minitest
这时候基本的 Jenkins job 就配置好了,在 dashboard 中你可以自己点击 build 手动触发。每一次 build 都会从 Git repo 中拉取代码,运行你的 build 脚本。只有当脚本中所有的流程都通过并且成功之后,这个 job 的状态才会是成功的。
不过这对我们来说还是不够的,现在只能通过手动触发 build,还并不能在我们每次 commit 并且 push 到 Git repo 后自动完成构建。所以我们需要将我们当前配置好的 Jenkins job 和 Git repo 进行集成。
我们在 dashboard 中选中我们的 Project,点击Configuration
再次进入配置界面。
在Build Triggers
配置触发构建的条件。这里我勾选了Build when a change is pushed to GitHub
,这样会在每次 commit push 到 GitHub 上之后进行构建。这也是我们在项目中的通常做法。不过光在 Jenkins 中配置了是不够的,我们需要 GitHub 在每次收到 push 后通过 webhook 告知 Jenkins 可以开始进行构建。
在 GitHub 中进入你 project 的 repository,选择Settings
-> Webhooks
-> Add webhook
接着填写你的 Jenkins hooks url,这段 url 就是 jenkins server 的 url 加上/github-webhook
。例:http://my-jenkins-server/github-webhook/ 。
在Which events would you like to trigger this webhook?
中选择 Just the push event
。
配置好后点击Add webhook
。
Note: 这里需要你的 jenkins 安装了 GitHub plugin,如果你在安装 jenkins 时选择了安装推荐的 plugin,是会给你默认装上的。
完成后再进行 commit 和 push,你会发现在 push 之后会自动触发 jenkins 的 build 环节。
使用 CI 服务集成到 Github 时,通常有三种状态:
如果想显示这些状态,就需要 Jenkins 将构建的状态进行同步。
这里将 Jenkins 的 build 时的一些状态同步到 GitHub 上,Jenkins 向 GitHub 发送请求去同步这些状态,所以需要 GitHub 的 access_token。
进入你 GitHub 账号的 Settings 页面中,选中Developer settings
-> Personal access tokens
-> Generate access token
。在Select scopes
中选择 token 可以操作的 scope,选中repo
和admin:repo_hook
,之后点击Generate token
。token 生成后复制到你的剪贴板上,我们在 Jenkins 的配置中会用到它。
进入 Jenkins 的 dashboard,点击左侧的Manage Jenkins
-> Configure System
,找到GitHub
这栏,点击 Add GitHub Server,之后在 Credentials 那栏中点击Add
。在 Kind 中选择Secret text
,将之前复制的 access token 粘贴到 Secret 那栏,在 ID 中可以选择给这条 credential 命名。最后点击Add
就完成了,然后在 Credentials 那栏选中你刚刚添加的 credential。点击屏幕下方的Save
保存配置修改。
回到 dashboard,进入 job 的 Configuration 界面,在Build
中选择Add build step
-> Set build status to "pending" on GitHub commit
。添加后拖动这个 build step 到之前添加的Execute shell
之前。这样才能保证在 build 开始前就把 commit status 设置成 pending。
之后在Post-build Actions
中选择Add post-build action
-> Set GitHub commit status
。在Status result
那栏中选择One of default messages and statuses
。配置好后点击Save
保存。这样配置完成之后,你在 GitHub 中每一次 commit 的状态都会随着 CI 的状态而改变。
持续集成给现在的开发工作带来了很大的便利,将构建操作自动化,可以显著帮助开发者尽早发现现有系统中的问题。完成稳定而又高效的迭代。Jenkins 作为一个开源的持续集成软件,可以让我们可以自定义搭建一个免费的持续服务。
Jenkins 中也提供了各种各样强大的插件(例如 RVM 插件,可以让 RVM 安装并且使用当前 project 中指定的 Ruby 版本),现在的 Jenkins 自身也支持持续部署、持续交付的功能。个人认为 Jenkins 对于开发者还是有相当大的学习使用价值的。我在这里也仅仅是一个 Getting Started 的介绍,有不正确的地方希望大家指正,也希望有感兴趣的朋友以后能在社区里一起交流相关的问题,大家共同进步。
Jenkins CI for Rails 4, RSpec, Cucumber, Selenium
Jenkins CI on Ubuntu