基于 Coding + Jenkins 实现 Laravel 项目的持续集成

前面两篇教程我们陆续介绍了基于 Github 代码仓库集成 CircleCITravis CI 实现 Laravel 项目的持续集成,今天我们继续介绍如何通过 Jenkins 实现类似的自动构建和测试。

相较于前两种持续集成系统,Jenkins 没有在 Github Marketable 上提供第三方应用,我们日常使用的话通常需要自己在独立服务器上安装并进行运维,你可以参考官方文档进行下载安装,然后创建一个 Pipeline 构建任务,最后在 Github 仓库的 Webhooks 页面添加相应的回调 URL,或者你还可以在本地项目根目录下 .git/hooks 里面定义的事件触发执行相应的 Jenkins 回调 URL(这种情况下你甚至可以在本地安装 Jenkins,比如 laradock 集成环境中就包含了 Jenkins),通过这个流程当然可以实现通用的持续集成流程,但是这一波操作对新手来说不够友好,而且比较重量级,另外,Jenkins 的插件中心在国内网络环境访问起来有些吃力,所以学院君今天的示例将基于国内的 Coding 平台提供的持续交付整体解决方案来进行演示,这套解决方案中当然包含了代码托管和持续集成的部分,而且刚好它的持续集成解决方案是基于 Jenkins 的。在该方案下,Coding 平台为我们封装了 Jenkins 的安装、维护和触发回调,我们只需要编写 Jenkins 的构建任务即可。

在 Coding 平台创建团队和用户

首先打开 Coding 首页,Coding 提供了一站式 DevOps 解决方案,从需求到开发、测试、缺陷管理、项目管理、代码托管、持续集成、部署上线都可以通过这个一站式解决方案来完成,而且这个服务对五人以下小团队免费,推荐大家试试:

你可以浏览下首页介绍和产品里面的产品列表对 Coding 平台有一个大致的了解,然后我们进入正题,如果你还没有注册过 Coding 平台,可以点击「免费体验」按钮开始创建团队和用户(已注册跳过):

输入团队名称和专属域名,然后点击「下一步」:

在上面这个页面中,输入团队管理者信息并完成注册,注册完成后,页面就会跳转到一个示例项目页面,通过这个示例项目,我们可以快速了解 Coding DevOps 解决方案的所有功能:

继续后续功能之前,先到注册邮箱收件箱中激活 Coding 账户,否则后续提交代码到仓库会提示要激活账号。

创建一个新项目

接下来,我们点击页面右上角的「+」图标为待办任务应用创建一个新项目,在创建项目页面,你还可以选择导入已有项目:

不过,目前该功能只支持 Coding 个人版和 Gitlab 项目,Github 项目还不支持,因此我们需要创建一个新项目:

创建新项目流程和 Github 差不多,都需要指定项目信息和项目地址,以及描述信息和初始化文件,所不同的时由于 Coding 更关注的是项目的整体 DevOps 和日常管理,所以多出了项目时间和项目成员选项,你可以根据需要进行设置。最后点击「新建项目」按钮,页面就跳转到新建项目的管理页面(和之前示例项目一样):

在这里,由于代码仓库还没有提交任何代码,所以可以看看在编写代码前可以做的事情,比如迭代管理、需求管理、工作管理等,代码、持续集成、部署管理则依赖开发代码提交后才能预览和管理,你还可以通过 Wiki 为项目编写文档,通过统计查看代码、测试、缺陷统计图表,最后还可以通过设置进行更多的管理功能,具体操作和使用都很简单,你可以自行去研究。

将本地代码提交到仓库

接下来,我们将之前编写的待办任务项目代码关联到 Coding 仓库,由于之前项目已经关联到 Github 仓库,所以我们拉一份新的项目代码,然后将其关联到新创建的 Coding 仓库,具体操作步骤如下:

git clone https://github.com/nonfu/todoapp todoapp2
cd todoapp2
rm -rf .git
git init
git remote add origin https://e.coding.net/laravel/todoapp.git
git add .
git commit -m 'first commit'
git push --set-upstream origin master

基本上也都是一些 Git 命令操作,首次 push 代码后,就可以在项目管理页面点击代码->代码浏览看到刚刚提交上来的代码了:

编写持续集成构建任务

有了代码之后,下面我们来编写持续集成构建任务,点击「持续集成」菜单,进入如下页面:

在这里,我们给构建任务取个名字,然后定义构建触发的时间节点以及触发的方式,一般选则在推送分支的时候自动触发,关于执行方式,对于 PHP 项目来说,推荐使用「云服务器模式」,然后通过引入 Docker 镜像构建测试环境,因为 Coding 默认的常规模式更偏向于 Java 项目,对 PHP 项目来说并不够友好。

然后我们保存进入下一步,配置集成过程,由于学院君前面提到过,Coding 是基于 Jenkins 做持续集成的,所以需要配置相应的 Pipeline 来对项目进行构建、测试、部署,如果你对 Jenkins 不太了解,可以点击页面上提供的提示链接查看,然后我们根据需要选择一个模板来编写 Jenkinsfile,比如「简易模板」:

这样,就进入了 Jenkinsfile 文件编辑页面,我们可以根据自己的构建需要对默认模板进行修改,比如我这里最终的 Jenkinsfile 版本如下:

node {
    stage("检出") {
        sh 'ci-init'
        checkout(
          [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]], userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
        )
        sh 'pwd'
    }
    docker.image('mysql:5.7').withRun('-e "MYSQL_ALLOW_EMPTY_PASSWORD=true"') { c ->
        docker.image('mysql:5.7').inside("--link ${c.id}:mysqldb") {
          /* Wait until mysql service is up */
          sh 'while ! mysqladmin ping -hmysqldb --silent; do sleep 1; done'
        }
        docker.image('circleci/php:7.1-node-browsers').inside("--link ${c.id}:mysqldb") {
          /* 以下构建步骤需要依赖 MySQL,MySQL 主机名是 mysqldb */
          stage("环境初始化") {
                  echo "环境初始化中..."
                  sh 'apt-get update'
                  sh 'apt-get install -y google-chrome-stable'
                  sh 'apt install -y mysql-client'
                  sh 'mysql -h mysqldb -u root -e "create database todoapp;"'
                  sh 'docker-php-ext-install zip'
                  sh 'docker-php-ext-install pdo_mysql'
                  echo "环境初始化完成."
          }
          stage("环境检测") {
                  echo "环境检测中..."
                  sh 'php --version'
                  sh 'composer --version'
                  sh 'npm --version'
                  sh 'mysql --version'
                  echo "环境检测完成."
          }
          stage("构建") {
                  echo "构建中..."
                  sh 'composer self-update'
                  sh 'mv .env.testing .env'
                  sh 'composer install -n --ignore-platform-reqs'
                  sh 'npm install'
                  sh 'npm run production'
                  sh 'php artisan key:generate'
                  sh 'php artisan migrate'
                  sh 'php artisan passport:install'
                  sh 'php artisan serve &'
                  echo "构建完成."
          }
          stage("测试") {       
                  echo "单元测试中..."
                  sh 'vendor/bin/phpunit'
                  sh 'php artisan dusk'
                  echo "单元测试完成."
          }
          stage("部署") {
                  echo "部署中..."
                  // sh 'envoy run deploy'
                  echo "部署完成"
          }
        }
    }
}

Coding 提供的持续集成功能有多种执行方式,具体可以参考官方文档,我这里选择的是云服务器模式下的使用预装 Docker 构建,其实也就是 Jenkins 里面通过 Docker 进行构建,该文件保存后会推送到仓库项目根目录,后续当我们推送代码到仓库时,Coding 就会根据这个 Jenkinsfile 文件对项目进行构建。

下面我们简单看下这个构建任务做了哪些工作:

  • 首先从仓库中检出对应的代码分支;
  • 我们的待办任务项目测试需要 PHP + Composer + Node + MySQL 环境,所以这里我借鉴了 CircleCI 教程里用到的两个 Docker 容器,这里略有区别,需要将 PHP 环境嵌套到 MySQL 容器中去运行,并且指定 MySQL 主机名是 mysqldb,以便在嵌套环境中通过它与 MySQL 服务器连接(这种嵌套在叫做 sidebar pattern);
  • 嵌套的第一个 docker.image(...).inside(...) 用于等待 MySQL 服务可用;
  • 嵌套到第二个 docker.image('circleci/php:7.1-node-browsers').inside(...) 中定义了正式的构建逻辑,这里面的逻辑需要 MySQL 服务可用后才能执行;
  • 多个构建步骤用不同的 stage 进行区分,每个 stage 代表一个独立的构建步骤,比如「环境初始化」中我们对数据库进行初始化并安装了相应的 PHP 扩展,然后在「环境检测」中对必须的软件进行检测;
  • 接下来,开始「构建」项目,主要是通过 Composer 安装 PHP 依赖以及通过 NPM 安装前端 JavaScript 依赖库,以及 Laravel 项目的初始化,启用 PHP 内置 Web 服务器等;
  • 再然后,进入「测试」步骤,主要是通过 phpunit 运行 HTTP 测试和 php artisan dusk 运行浏览器测试;
  • 测试通过后,如果是持续集成的话,可以将代码合并到主干,如果是持续交付或者持续部署,可以选择将项目部署到线上,这里你可以通过 Envoy 或者其他部署脚本实现代码发布工作。

执行持续集成构建任务

Jenkinsfile 编辑好保存后,Coding 会将其推送到代码仓库,此时就会触发第一个构建,在构建记录详情页中,可以看到构建的每一个步骤及相应的日志,如果构建失败,则终止后续脚本执行:

你可以在相应的日志中看到构建失败的原因,点开即可:

这里提示的是 Chrome 版本不对,需要将 Dusk 扩展包自带的 chromedriver-linux 进行升级,以便支持最新版本的 Chrome(当前是 74)。我们可以从 ChromeDriver 官网下载针对 Linux 系统的最新版本 chromedriver,将下载文件在本地解压,然后将 chromedriver 文件拷贝到待办任务项目根目录 todoapp2 下,修改 Jenkinsfile 文件测试步骤脚本如下(如果本地没有该文件,可以通过 git pull 拉取):

stage("测试") {
    echo "单元测试中..."
    sh 'vendor/bin/phpunit'
    sh 'cp -r ./chromedriver ./vendor/laravel/dusk/bin/chromedriver-linux'
    sh 'php artisan dusk'
    echo "单元测试完成."
}

即通过针对 Linux 系统最新版本的 chromedriver 覆盖系统自带的 chromedriver-linux,然后我们将上述更改提交并推送到代码仓库(以上操作也可以在构建任务中实现,但耗时较长,还是本地推送更改更快),此时 Coding 平台会自动触发新一轮的构建。这一次构建、测试成功:

这样,我们就可以将开发分支代码合并到主干了,从而完成了一次持续集成。

关于 Coding + Jenkins 实现 Laravel 项目的持续集成我们就简单介绍到这里,Coding 平台提供的这个整体 DevOps 解决方案,对小团队或者中大型公司的持续交付团队来说,都可以一试,更多功能的使用请参考官方文档并结合自己的实践去探索。

上一篇: 基于 Github + Travis CI 实现 Laravel 项目的持续集成

下一篇: 编写 JSON API —— RESTful 风格 API 设计原则与最佳实践