原创文章,转载请务必将下面这段话置于文章开头处。 本文转发自技术世界,原文链接 http://www.jasongj.com/spark/ci_cd/
持续集成是指,及时地将最新开发的且经过测试的代码集成到主干分支中。
持续集成的优点
目前主流的代码管理工具有,Github、Gitlab等。本文所介绍的内容中,所有代码均托管于私有的 Gitlab 中。
鉴于 Jenkins 几乎是 CI 事实上的标准,本文介绍的 Spark CI CD & CD 实践均基于 Jenkins 与 Gitlab。
Spark 源码保存在 spark-src.git 库中。
由于已有部署系统支持 Git,因此可将集成后的 distribution 保存到 Gitlab 的发布库(spark-bin.git)中。
每次开发人员提交代码后,均通过 Gitlab 发起一个 Merge Requet (相当于 Gitlab 的 Pull Request)
每当有 MR 被创建,或者被更新,Gitlab 通过 Webhook 通知 Jenkins 基于该 MR 最新代码进行 build。该 build 过程包含了
Jenkins 将 build 结果通知 Gitlab,只有 Jenkins 构建成功,Gitlab 的 MR 页面才允许 Merge。否则 Gitlab 不允许 Merge
另外,还需人工进行 Code Review。只有两个以上的 Reviewer 通过,才能进行最终 Merge
所有测试与 Reivew 通过后,通过 Gitlab Merge 功能自动将代码 Fast forward Merge 到目标分支中
该流程保证了
持续交付是指,及时地将软件的新版本,交付给质量保障团队或者用户,以供评审。持续交付可看作是持续集成的下一步。它强调的是,不管怎么更新,软件都是可随时交付的。
这一阶段的评审,一般是将上文集成后的软件部署到尽可能贴近生产环境的 Staging 环境中,并使用贴近真实场景的用法(或者流量)进行测试。
持续发布的优点
这里有提供三种方案,供读者参考。推荐方案三
如下图所示,基于单分支的 Spark 持续交付方案如下
spark-src.git/dev
(即 spark-src.git 的 dev branch) 上进行spark-bin.git/dev
的 spark-${ build # }
(如图中第 2 周的 spark-72)文件夹内spark-${ build # }
(如图中的 spark-72)注:
在 Staging 环境中发现 spark-dev 的 bug 时,修复及集成和交付方案如下
spark-${ build \# }
(如图中的 spark-73)spark-${ build \# }
(如图中的 spark-73 )生产环境中发现 bug 时修复及交付方案如下
spark-${ build \# }
(如图中的 spark-73)spark-${ build \# }
(如图中的 spark-73 )如下图所示,基于两分支的 Spark 持续交付方案如下
spark-src.git
与 spark-bin.git
均包含两个分支,即 dev branch 与 prod branchspark-src.git/dev
上进行spark-src.git/dev
打包出一个 release 放进 spark-bin.git/dev
的 spark-${ build \# }
文件夹内(如图中第 2 周上方的的 spark-2 )。它包含了之前所有的提交(commit 1、2、3、4)spark-bin.git/dev
的 spark 作为 symbolic 指向 spark-${ build \# }
文件夹内(如图中第 2 周上方的的 spark-2)spark-src.git/prod
通过 fast-forward merge 将 spark-src.git/dev
一周前最后一个 commit 及之前的所有 commit 都 merge 过来(如图中第 2 周需将 commit 1 merge 过来)spark-src.git/prod
打包出一个 release 放进 spark-bin.git/prod
的 spark-${ build \# }
文件夹内(如图中第 2 周下方的的 spark-1 )spark-bin.git/prod
的 spark 作为 symbolic 指向 spark-${ build \# }
在 Staging 环境中发现了 dev 版本的 bug 时,修复及集成和交付方案如下
spark-src.git/dev
上提交一个 commit (如图中黑色的 commit 9),且 commit message 包含 bugfix 字样spark-src.git/dev
打包生成一个 release 并放进 spark-bin.git/dev
的 spark-${ build \# }
文件夹内(如图中第二周与第三周之间上方的的 spark-3 )spark-bin.git/dev
中的 spark 作为 symbolic 指向 spark-${ build \# }
在生产环境中发现了 prod 版本的 bug 时,修复及集成和交付方案如下
spark-src.git/dev
上提交一个 commit(如图中红色的 commit 9),且 commit message 包含 hotfix 字样spark-src.git/dev
打包生成 release 并 commit 到 spark-bin.git/dev
的 spark-${ build \# }
(如图中上方的 spark-3 )文件夹内。 spark 作为 symbolic 指向该 spark-${ build \# }
spark-src.git/prod
(如无冲突,则该流程全自动完成,无需人工参与。如发生冲突,通过告警系统通知开发人员手工解决冲突后提交)spark-src.git/prod
打包生成 release 并 commit 到 spark-bin.git/prod
的 spark-${ build \# }
(如图中下方的 spark-3 )文件夹内。spark作为 symbolic 指向该spark-${ build \# }
spark-src.git/dev
(包含 commit 1、2、3、4、5) 与 spark-src.git/prod
(包含 commit 1) 的 base 不一样,有发生冲突的风险。一旦发生冲突,便需人工介入spark-src.git/dev
合并 commit 到 spark-src.git/prod
时需要使用 rebase 而不能直接 fast-forward merge。而该 rebase 可能再次发生冲突spark-bin.git/dev
的 bug,即图中的 commit 1、2、3、4 后的 bug,而 bug fix commit 即 commit 9 的 base 是 commit 5,存在一定程度的不一致spark-bin.git/dev
包含了 bug fix,而最新的 spark-bin.git/prod
未包含该 bugfix (它只包含了 commit 2、3、4 而不包含 commit 5、9)。只有到第 4 周,spark-bin.git/prod
才包含该 bugfix。也即 Staging 环境中发现的 bug,需要在一周多(最多两周)才能在 prod 环境中被修复。换言之,Staging 环境中检测出的 bug,仍然会继续出现在下一个生产环境的 release 中spark-src.git/dev
与 spark-src.git/prod
中包含的 commit 数一致(因为只允许 fast-forward merge),内容也最终一致。但是 commit 顺序不一致,且各 commit 内容也可能不一致。如果维护不当,容易造成两个分支差别越来越大,不易合并如下图所示,基于多分支的 Spark 持续交付方案如下
spark-src.git/master
上进行spark-src.git/master
最新代码合并到 spark-src.git/dev
。如下图中,第 2 周将 commit 4 及之前所有 commit 合并到 spark-src.git/dev
spark-src.git/dev
打包生成 release 并提交到 spark-bin.git/dev
的 spark-${ build \# }
(如下图中第 2 周的 spark-2) 文件夹内。spark 作为 symbolic,指向该 spark-${ build \# }
spark-src.git/master
一周前最后一个 commit 合并到 spark-src.git/prod
。如第 3 周合并 commit 4 及之前的 commitspark-src.git/prod
中,因为它们是对 commit 4 进行的 bug fix。后文介绍的 bug fix 流程保证,如果对 commit 4 后发布版本有多个 bug fix,那这多个 bug fix commit 紧密相连,中间不会被正常 commit 分开spark-src.git/prod
打包生成 release 并提交到 spark-bin.git/prod
的 spark-${ build \# }
(如下图中第 2 周的 spark-2) 文件夹内。spark 作为 symbolic,指向该 spark-${ build \# }
在 Staging 环境中发现了 dev 版本的 bug 时,修复及集成和交付方案如下
spark-src.git/dev
(包含 commit 1、2、3、4) 上提交一个 commit(如图中黑色的 commit 9),且 commit message 中包含 bugfix 字样spark-src.git/dev
打包生成 release 并提交到 spark-bin.git/dev
的 spark-${ build \# }
(如图中的 spark-3) 文件夹内,spark 作为 symbolic,指向该 spark-${ build \# }
git checkout master
切换到 spark-src.git/master
,再通过 git rebase dev
将 bugfix 的 commit rebase 到 spark-src.git/master
,如果 rebase 发生冲突,通过告警通知开发人员人工介入处理冲突spark-src.git/dev
上顺序相连。因此它们被 rebase 到 spark-src.git/master
后仍然顺序相连在生产环境中发现了 prod 版本的 bug 时,修复及集成和交付方案如下
spark-src.git/prod
中提交一个 commit,且其 commit message 中包含 hotfix 字样spark-src.git/prod
打包生成 release 并提交到 spark-bin.git/prod
的 spark-${ build \# }
(如图中的 spark-3) 文件夹内,spark 作为 symbolic,指向该 spark-${ build \# }
git checkout master
切换到 spark-src.git/master
,再通过 git rebase prod
将 hotfix rebase 到 spark-src.git/master
本文介绍的实践中,不考虑多个版本(经实践检验,多个版本维护成本太高,且一般无必要),只考虑一个 prod 版本,一个 dev 版本
上文介绍的持续发布中,可将 spark-bin.git/dev
部署至需要使用最新版的环境中(不一定是 Staging 环境,可以是部分生产环境)从而实现 dev 版的部署。将 spark-bin.git/prod
部署至需要使用稳定版的 prod 环境中
本文介绍的方法中,所有 release 都放到 spark-${ build \# }
中,由 spark 这一 symbolic 选择指向具体哪个 release。因此回滚方式比较直观
spark-src.git/master
上进行,Staging 环境的 bug fix 在 spark-src.git/dev
上进行,生产环境的 hot fix 在 spark-src.git/prod
上进行,清晰明了spark-src.git/master
时使用 rebase,从而保证了 spark-src.git/dev
与 spark-src.git/master
所有 commit 的顺序与内容的一致性,进而保证了这两个 branch 的一致性spark-src.git/master
时使用 rebase,从而保证了 spark-src.git/dev
与 spark-src.git/master
所有 commit 的顺序性及内容的一致性,进而保证了这两个 branch 的一致性spark-src.git/master
发生冲突时才需人工介入spark-bin.git/dev
与 spark-bin.git/prod
将开发版本与生产版本分开,方便独立部署。而其路径统一,方便版本切换与灰度发布spark-src.git/master
提交时,须先 rebase 远程分支,而不应直接使用 merge。在本方案中,这不仅是最佳实践,还是硬性要求spark-src.git/master
。但发生冲突时,需要相应修改 spark-src.git/master
上后续 commit。如上图中,提交红色 commit 9 这一 hot fix 后,在 rebase 回 spark-src.git/master
时,如有冲突,可能需要修改 commit 2 或者 commit 3、4、5。该修改会造成本地解决完冲突后的版本与远程版本冲突,需要强制 push 回远程分支。该操作存在一定风险持续部署是指,软件通过评审后,自动部署到生产环境中
上述 Spark 持续发布实践的介绍都只到 “将 *** 提交到 spark-bin.git
“ 结束。可使用基于 git 的部署(为了性能和扩展性,一般不直接在待部署机器上使用 git pull –rebase,而是使用自研的上线方案,此处不展开)将该 release 上线到 Staging 环境或生产环境
该自动上线过程即是 Spark 持续部署的最后一环