mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 22:23:45 +08:00
del
This commit is contained in:
143
极客时间专栏/geek/持续交付36讲/实践案例/34 | 快速构建持续交付系统(一):需求分析.md
Normal file
143
极客时间专栏/geek/持续交付36讲/实践案例/34 | 快速构建持续交付系统(一):需求分析.md
Normal file
@@ -0,0 +1,143 @@
|
||||
<audio id="audio" title="34 | 快速构建持续交付系统(一):需求分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2d/7c/2d2c37f63358c2226840700267eb647c.mp3"></audio>
|
||||
|
||||
从今天这一篇文章开始,我们就进入这个专栏的最后一个系列:实践案例系列了。在这个系列里,我将通过4篇文章,以实际操作为主,带你快速构建一套持续交付系统。
|
||||
|
||||
当然,首先我们要做的是,一起整理一下思路,看看我们的系统具体要满足哪些实际的需求,需要具备什么功能。然后,建立需求的锚点,根据这些锚点,展开具体的搭建工作。
|
||||
|
||||
因此,在这篇文章中,我会以先介绍模拟团队和项目,再提出具体持续交付需求的思路,罗列一些要模拟的背景,并为你解说这些场景。这样做,可以帮助你在后面的三篇实践文章中找到对应的需求点,也可以让你与现在团队的持续交付体系作一番比较,找到相通之处,从而加深你对持续交付体系的理解。
|
||||
|
||||
## 模拟团队介绍
|
||||
|
||||
我在第7篇文章[《“两个披萨”团队的代码管理实际案例》](https://time.geekbang.org/column/article/11323)中,和你分享了“两个披萨”团队的代码管理实践。基本上,我们可以把一个这样的团队看作是一个微型研发团队。虽然这样规模的一个团队也可以很好地运用我们即将搭建的持续交付系统,但是因为过于理想化而缺乏了典型性。
|
||||
|
||||
所以,为了更全面地介绍持续交付系统的搭建过程,我将要模拟的团队规模扩大至3个“两个披萨”团队的大小。即,整个产品的研发,需要由这3个团队合作完成。这3个团队的分工,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b3/36/b30d7ed155a514fee323b18924d1e836.png" alt="">
|
||||
|
||||
由这样3个团队组成的中小型研发组织架构,也是目前互联网公司比较流行的。
|
||||
|
||||
## 模拟系统介绍
|
||||
|
||||
介绍完模拟团队的情况,接下来,我们需要再了解一下需要模拟的系统。对于持续交付体系来说,系统的业务逻辑并不是要解决的最重要的问题。因为不管业务逻辑如何,持续交付的过程大致都是相通的,都包括了代码管理、环境管理、集成编译管理、测试管理和发布管理这五大步骤。
|
||||
|
||||
反而,系统之间如何集成运作,以及依赖关系、交付形式,关系着这持续交付系统应该如何实现,才是更重要的内容。
|
||||
|
||||
在这里,我们要模拟的这个系统,最终表现为移动App持续交付体系的形式,需要中间件、业务后台,以及业务客户端这3个团队交付产物的协作,才算是完整:
|
||||
|
||||
- 首先,用户通过团队3交付的移动App进行系统操作;
|
||||
- 其次,移动App需要调用团队2提供的业务后台服务War,获取数据和处理业务逻辑;
|
||||
- 最后,后台服务War需要依赖团队1提供的业务中间件Jar,完成底层操作,如配置读取、缓存处理等。
|
||||
|
||||
这三个团队的依赖关系和交付产物,也决定了他们要采用不同的交付方式:
|
||||
|
||||
<li>团队1,有两类交付方式:
|
||||
<ul>
|
||||
- 第一类是,中间件服务的交付,使用传统的虚机部署,提供可部署的代码包;
|
||||
- 第二类是,中间件组件的交付,使用Jar包发布,发布到组件仓库。
|
||||
|
||||
这也是目前比较流行的移动互联网系统的架构形式,当然其中也覆盖了目前流行的容器交付。如果你现在要在一个微型研发团队搭建这样的持续交付系统,那你也可以根据这样的架构形式做适当裁剪,去除一些不需要的功能,顺利达成持续交付的目的。
|
||||
|
||||
## 主体流水线的需求
|
||||
|
||||
模拟团队对整个持续交付流水线的需求如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b9/fe/b9c7a8be8b1378bd3e1f863ab0b2d3fe.png" alt="">
|
||||
|
||||
整个过程可以大致描述为:代码合并到master后能够自动触发对应的集成编译,如编译通过则部署到对应的测试环境下,部署成功后驱动自动化测试,测试通过则分批部署到生产环境。
|
||||
|
||||
主体流水线发生的状态变更,都需要通过E-mail通知发起人。这里的发起人就是代码提交者和合并审核人。
|
||||
|
||||
这条主体流水线,看上去很简单、功能明确。但是,麻雀虽小五脏俱全。因此,各个步骤还都有一些细节实现上的要求。接下来,我们就一起看一下吧。
|
||||
|
||||
## 代码与配置管理相关的需求
|
||||
|
||||
3个模拟团队的代码分支策略均采用标准的 GitLab Flow 模型,要求是代码通过code review后才能合并入master分支;合并入master分支后,能够触发对应的集成编译。
|
||||
|
||||
同时,我们需要代码静态扫描服务,帮助我们更好地把控代码质量。这个服务的具体工作形式是:
|
||||
|
||||
>
|
||||
因为代码扫描是异步处理的,所以扫描过程将在代码编译通过之后开始。而扫描结果,则作为是否可继续流水线的依据。
|
||||
|
||||
|
||||
**这里需要注意的是,整个代码扫描过程是异步进行的,所以在没有得到扫描结果前,主体流水线将继续进行。**
|
||||
|
||||
如果主体流水线已经执行完,而代码扫描还没结束,也就是还没有得到扫描结果的话,整条流水线需要停下来等待;而如果在执行主体流水线的过程中,代码静态扫描的结果是不通过的话,那么就需要直接中断主体流水线的执行,此次交付宣告失败。
|
||||
|
||||
## 构建与集成相关的需求
|
||||
|
||||
我们对编译与集成的要求,具体可以概括为以下几点:
|
||||
|
||||
首先,**能够同时支持传统的部署包、Docker镜像,以及移动App的编译和集成**。而且能够在触发编译时自动进行适配支持,这样才能保证各个团队有新项目时无须再进行额外配置。
|
||||
|
||||
其次,**所有构建产物及构建历史,都能被有效、永久地记录和存储**。因为单从传统的编译驱动管理角度看,它以编译任务为基准,需要清除过久、过大的编译任务,从而释放更多的资源用于集成编译。但是,从持续交付的角度看,我们需要完全保留这些内容,用于版本追溯。
|
||||
|
||||
再次,**各构建产物有自己独立的版本体系,并与代码commit ID相关联**。这是非常重要的,交付产物的版本就是它的唯一标识,任何交付物都可以通过版本进行辨识和追溯。
|
||||
|
||||
最后,**构建通道必须能够支持足够的并发量**。这也就要求集成构建服务要做到高可用和可扩展,最好能做到资源弹性利用。
|
||||
|
||||
## 打包与发布相关的需求
|
||||
|
||||
要清楚打包与发布的需求,就需要先了解各个团队的部署标准和环境状况。
|
||||
|
||||
从这3个团队交付产物的角度来看,他们需要的环境,可以描述如下:
|
||||
|
||||
- 团队1,提供中间件服务。其测试服务器需要1个集群,2台虚拟机;生产环境需要2个集群,各7台虚拟机。
|
||||
- 团队2 ,提供业务后台服务。其测试服务器需要1个集群,2个Docker 实例;生产环境需要2个集群,各7个Docker 实例。
|
||||
- 团队3,交付移动App。其需要的环境就是内部测试市场。
|
||||
|
||||
整个发布体系,除了要考虑标准的War包和Docke镜像发布外,我们还要考虑Jar包组件的发布。因为团队1的Jar包对应有两类交付方式,所以对Jar包的发布,我们需要做一些特殊考虑:
|
||||
|
||||
<li>
|
||||
测试环境可以使用Snapshot版本,但是生产环境则不允许;
|
||||
</li>
|
||||
<li>
|
||||
即使测试通过,也不一定需要发布Jar包的每个版本到生产环境;
|
||||
</li>
|
||||
<li>
|
||||
Jar包是发布到对应的组件仓库,发布形式与其他几类差别(比如,War包、Docker镜像等)较大。
|
||||
</li>
|
||||
|
||||
基于以上的考虑,我们需要对Jar包的发布做特殊的系统处理。
|
||||
|
||||
另外,为了发布过程更加可控,我们需要对代码目录、进程管理、日志格式等进行统一的标准化。这部分标准化的具体内容,我将穿插在具体实现时再做详细说明。
|
||||
|
||||
## 自动化测试的需求
|
||||
|
||||
在这里,我们的自动化测试平台,选择的是TestNG,这也是业界最为流行的自动化测试平台之一。
|
||||
|
||||
对于测试,系统需要注意的是,不要有一个测试任务失败就中断交付,最好是跑完所有测试任务,并收集结果。当然,我们可以通过TestNG平台,很容易做到这一点。
|
||||
|
||||
相反,另外一点倒是我们要注意的,就是“停不下来”。比如测试脚本出现死循环。
|
||||
|
||||
除此之外,自动化测试过程中还会发生许多意想不到的事情,特别是造成了一些破坏,使得测试过程无法正常继续等情况。所以,我们需要能够处理这样的异常,比如加上超时机制,使持续交付系统能够继续正常运作。
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我通过对要模拟的团队和系统的介绍,引出了我们即将实战搭建的这套持续交付系统的需求锚点。这里,我再概括一下整个持续交付体系的需求:
|
||||
|
||||
要模拟的团队有3个,分别为中间件团队、后端业务团队和移动App团队,3个团队最终产出一个可工作的移动App。
|
||||
|
||||
而模拟团队在持续交付主体流水线的需求下,对各个主要模块还有一些具体的需求:
|
||||
|
||||
<li>
|
||||
代码与配置:需要code review,以及静态代码扫描;
|
||||
</li>
|
||||
<li>
|
||||
构建与集成:能同时支持Jar、War、Docker,以及App,版本管理可追溯,支持高并发;
|
||||
</li>
|
||||
<li>
|
||||
打包与发布:同时支持Jar、War、Docker、App的发布,以及统一的部署标准;
|
||||
</li>
|
||||
<li>
|
||||
自动化测试:通过TestNG驱动,实现全自动测试。
|
||||
</li>
|
||||
|
||||
从下一篇文章开始,我会通过开源工具和你一起解决这些需求,最终完成成这套系统的搭建。
|
||||
|
||||
## 思考题
|
||||
|
||||
在这一篇文章中,我们模拟的是一个比较完整的团队,而在实际项目中你的团队是不是更小?如果是的话,在建设持续交付体系的过程中,你会裁剪掉哪些需求呢?
|
||||
|
||||
感谢你的收听,欢迎你给我留言。
|
||||
|
||||
|
||||
372
极客时间专栏/geek/持续交付36讲/实践案例/35 | 快速构建持续交付系统(二):GitLab 解决代码管理问题.md
Normal file
372
极客时间专栏/geek/持续交付36讲/实践案例/35 | 快速构建持续交付系统(二):GitLab 解决代码管理问题.md
Normal file
@@ -0,0 +1,372 @@
|
||||
<audio id="audio" title="35 | 快速构建持续交付系统(二):GitLab 解决代码管理问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/65/b9/65674f22d9f4ec164383943af7ad2bb9.mp3"></audio>
|
||||
|
||||
在上一篇文章中,我和你一起理清了我们即将构建的持续交付系统的需求,以及要具备的具体功能。那么,从这一篇文章开始,我们就要正式进入实战阶段了。我会和你详细介绍基于开源工具,从0开始搭建一套持续交付平台的详细过程,以及整合各个持续交付工具的一些技术细节。
|
||||
|
||||
按照我在前面分享的内容,搭建一套持续交付系统的第一步,就是搭建一套代码管理平台。这里我选择的开源工具是GitLab,它是一套高仿GitHub的开源代码共享管理平台,也是目前最好的开源解决方案之一。
|
||||
|
||||
接下来,我们就从使用GitLab搭建代码管理平台开始吧,一起看看搭建GitLab平台的过程中可能遇到的问题,以及如何解决这些问题。
|
||||
|
||||
## 利用GitLab搭建代码管理平台
|
||||
|
||||
GitLab早期的设计目标是,做一个私有化的类似GitHub的Git代码托管平台。
|
||||
|
||||
我第一次接触GitLab是2013年, 当时它的架构很简单,SSH权限控制还是通过和Gitolite交互实现的,而且也只有源码安装(标准Ruby on Rails的安装方式)的方式。
|
||||
|
||||
这时,GitLab给我最深的印象是迭代速度快,每个月至少会有1个独立的release版本,这个传统也一直被保留至今。但是,随着GitLab的功能越来越丰富,架构和模块越来越多,也越来越复杂。
|
||||
|
||||
所以,现在基于代码进行部署的方式就过于复杂了, 初学者基本无从下手。
|
||||
|
||||
**因此,我建议使用官方的Docker镜像或一键安装包Omnibus安装GitLab。**
|
||||
|
||||
接下来,我就以Centos 7虚拟机为例,描述一下整个Omnibus GitLab的安装过程,以及注意事项。
|
||||
|
||||
在安装前,你需要注意的是如果使用虚拟机进行安装测试,建议虚拟机的“最大内存”配置在4 G及以上,如果小于2 G,GitLab可能会无法正常启动。
|
||||
|
||||
### 安装GitLab
|
||||
|
||||
1. 安装SSH等依赖,配置防火墙。
|
||||
|
||||
```
|
||||
sudo yum install -y curl policycoreutils-python openssh-server
|
||||
sudo systemctl enable sshd
|
||||
sudo systemctl start sshd
|
||||
sudo firewall-cmd --permanent --add-service=http
|
||||
sudo systemctl reload firewalld
|
||||
|
||||
```
|
||||
|
||||
1. 安装Postfix支持电子邮件的发送。
|
||||
|
||||
```
|
||||
sudo yum install postfix
|
||||
sudo systemctl enable postfix
|
||||
sudo systemctl start postfix
|
||||
|
||||
```
|
||||
|
||||
1. 从rpm源安装,并配置GitLab的访问域名,测试时可以将其配置为虚拟机的IP(比如192.168.0.101)。
|
||||
|
||||
```
|
||||
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash
|
||||
|
||||
sudo EXTERNAL_URL="http://192.168.0.101" yum install -y gitlab-ee
|
||||
|
||||
```
|
||||
|
||||
整个安装过程,大概需要10分钟左右。如果一切顺利,我们已经可以通过 “[http://192.168.0.101](http://192.168.0.101)” 这个地址访问GitLab了。
|
||||
|
||||
如果你在安装过程中,遇到了一些问题,相信你可以在[GitLab的官方文档](https://about.gitlab.com/installation/)中找到答案。
|
||||
|
||||
### 配置GitLab
|
||||
|
||||
安装完成之后,还要进行一些系统配置。对于Omnibus GitLab的配置,我们只需要重点关注两方面的内容:
|
||||
|
||||
<li>
|
||||
<p>使用命令行工具gitlab-ctl,管理Omnibus GitLab的一些常用命令。<br />
|
||||
比如,你想排查GitLab的运行异常,可以执行 gitlab-ctl tail 查看日志。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>配置文件/etc/gitlab/gitlab.rb,包含所有GitLab的相关配置。邮件服务器、LDAP账号验证,以及数据库缓存等配置,统一在这个配置文件中进行修改。<br />
|
||||
比如,你想要修改GitLab的外部域名时, 可以通过一条指令修改gitlab.rb文件:</p>
|
||||
</li>
|
||||
|
||||
```
|
||||
external_url 'http://newhost.com'
|
||||
|
||||
```
|
||||
|
||||
然后,执行 gitlab-ctl reconfigure重启配置GitLab即可。
|
||||
|
||||
关于GitLab更详细的配置,你可以参考[官方文档](https://docs.gitlab.com/omnibus/README.html#installation-and-configuration-using-omnibus-package)。
|
||||
|
||||
### GitLab的二次开发
|
||||
|
||||
在上一篇文章中,我们一起分析出需要为Jar包提供一个特殊的发布方式,因此我们决定利用GitLab的二次开发功能来满足这个需求。
|
||||
|
||||
对GitLab进行二次开发时,我们可以使用其官方开发环境 gdk( [https://gitlab.com/gitlab-org/gitlab-development-kit](https://gitlab.com/gitlab-org/gitlab-development-kit))。但,如果你是第一次进行GitLab二次开发的话,我还是建议你按照 [https://docs.gitlab.com/ee/install/installation.html](https://docs.gitlab.com/ee/install/installation.html%E8%BF%9B%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%9F%BA%E4%BA%8E%E6%BA%90%E7%A0%81%E7%9A%84%E5%AE%89%E8%A3%85) 进行一次基于源码的安装,这将有助于你更好地理解GitLab的整个架构。
|
||||
|
||||
为了后面更高效地解决二次开发的问题,我先和你介绍一下GitLab的几个主要模块:
|
||||
|
||||
- Unicorn,是一个Web Server,用于支持 GitLab的主体Web应用;
|
||||
- Sidekiq,队列服务,需要Redis支持,用以支持GitLab的异步任务;
|
||||
- GitLab Shell,Git SSH的权限管理模块;
|
||||
- Gitaly,Git RPC服务,用于处理GitLab发出的git操作;
|
||||
- GitLab Workhorse,基于Go语言,用于接替Unicorn处理比较大的http请求。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/59/99b692ac7f885af249a8ebf6567f3559.png" alt="" />
|
||||
|
||||
对GitLab应用层的修改,我们主要关注的是GitLab Rails和GitLab Shell这两个子系统。
|
||||
|
||||
接下来,我们一起看一个二次开发的具体实例吧。
|
||||
|
||||
### 二次开发的例子
|
||||
|
||||
二次开发,最常见的是对GitLab添加一个外部服务调用,这部分需要在app/models/project_services下面添加相关的代码。
|
||||
|
||||
我们可以参考GitLab对Microsoft Teams的支持方式:
|
||||
|
||||
1. 在app/models/project_services/microsoft_teams_service.rb下,添加一些可配置内容及其属性,这样我们就可以在GitLab的service模块页面下看到相应的配置项了。
|
||||
|
||||
```
|
||||
# frozen_string_literal: true
|
||||
|
||||
class MicrosoftTeamsService < ChatNotificationService
|
||||
def title
|
||||
'Microsoft Teams Notification'
|
||||
end
|
||||
|
||||
def description
|
||||
'Receive event notifications in Microsoft Teams'
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'microsoft_teams'
|
||||
end
|
||||
|
||||
def help
|
||||
'This service sends notifications about projects events to Microsoft Teams channels.<br />
|
||||
To set up this service:
|
||||
<ol>
|
||||
<li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li>
|
||||
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
|
||||
<li>Select events below to enable notifications.</li>
|
||||
</ol>'
|
||||
end
|
||||
|
||||
def webhook_placeholder
|
||||
'https://outlook.office.com/webhook/…'
|
||||
end
|
||||
|
||||
def event_field(event)
|
||||
end
|
||||
|
||||
def default_channel_placeholder
|
||||
end
|
||||
|
||||
def default_fields
|
||||
[
|
||||
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
|
||||
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
|
||||
{ type: 'checkbox', name: 'notify_only_default_branch' }
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify(message, opts)
|
||||
MicrosoftTeams::Notifier.new(webhook).ping(
|
||||
title: message.project_name,
|
||||
summary: message.summary,
|
||||
activity: message.activity,
|
||||
attachments: message.attachments
|
||||
)
|
||||
end
|
||||
|
||||
def custom_data(data)
|
||||
super(data).merge(markdown: true)
|
||||
end
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
1. 在lib/microsoft_teams/notifier.rb 内实现服务的具体调用逻辑。
|
||||
|
||||
```
|
||||
module MicrosoftTeams
|
||||
class Notifier
|
||||
def initialize(webhook)
|
||||
@webhook = webhook
|
||||
@header = { 'Content-type' => 'application/json' }
|
||||
end
|
||||
|
||||
def ping(options = {})
|
||||
result = false
|
||||
|
||||
begin
|
||||
response = Gitlab::HTTP.post(
|
||||
@webhook.to_str,
|
||||
headers: @header,
|
||||
allow_local_requests: true,
|
||||
body: body(options)
|
||||
)
|
||||
|
||||
result = true if response
|
||||
rescue Gitlab::HTTP::Error, StandardError => error
|
||||
Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}")
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def body(options = {})
|
||||
result = { 'sections' => [] }
|
||||
result['title'] = options[:title]
|
||||
result['summary'] = options[:summary]
|
||||
result['sections'] << MicrosoftTeams::Activity.new(options[:activity]).prepare
|
||||
|
||||
attachments = options[:attachments]
|
||||
unless attachments.blank?
|
||||
result['sections'] << {
|
||||
'title' => 'Details',
|
||||
'facts' => [{ 'name' => 'Attachments', 'value' => attachments }]
|
||||
}
|
||||
end
|
||||
|
||||
result.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
以上就是一个最简单的Service二次开发的例子。熟悉了Rails和GitLab源码后,你完全可以以此类推写出更复杂的Service。
|
||||
|
||||
### GitLab的HA方案
|
||||
|
||||
对于研发人员数量小于1000的团队,我不建议你考虑GitLab 服务多机水平扩展的方案。GitLab官方给出了一个内存对应用户数量的参照,如下:
|
||||
|
||||
>
|
||||
<p>16 GB RAM supports up to 2000 users<br />
|
||||
128 GB RAM supports up to 16000 users</p>
|
||||
|
||||
|
||||
从这个配置参照数据中,我们可以看到一台高配的虚拟机或者容器可以支持2000名研发人员的操作,而单台物理机(128 GB配置)足以供上万研发人员使用。
|
||||
|
||||
在携程,除了要支持开发人数外,还要考虑到高可用的需求,所以我们经过二次开发后做了GitLab的水平扩展。但是,即使在每天的GitLab使用高峰期,整机负载也非常低。因此,对于大部分的研发团队而言,做多机水平扩展方案的意义并不太大。
|
||||
|
||||
同时,实现GitLab的完整水平扩展方案,也并不是一件易事。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4f/bc/4f97f12fb4a0645785600c44ef12f3bc.png" alt="" />
|
||||
|
||||
我们先看一下社区版的GitLab,官方提供的HA方案的整体架构图可参考图2。从整体架构上看,PostgreSQL、Redis这两个模块的高可用,都有通用的解决方案。而GitLab在架构上最大的问题是,需要通过文件系统在本地访问仓库文件。于是,**水平扩展时,如何把本地的仓库文件当做数据资源在服务器之间进行读写就变成了一个难题。**
|
||||
|
||||
官方推荐的方案是通过NFS进行多机Git仓库共享。但这个方案在实际使用中并不可行,git本身是IO密集型应用,对于真正在性能上有水平扩展诉求的用户来说,NFS的性能很快就会成为整个系统的瓶颈。我早期在美团点评搭建持续交付体系时,曾尝试过这个方案,当达到几百个仓库的规模时,NFS就撑不住了。
|
||||
|
||||
对于水平扩展这部分内容,有一个非常不错的分享:阿里的[《我们如何为三万人的公司横向伸缩 GitLab》](https://ruby-china.org/topics/30146)。但是,实施这个方案,你需要吃透Git的底层,所以并不容易实施。
|
||||
|
||||
而携程的解决方案就比较简单了:
|
||||
|
||||
>
|
||||
我们在应用层处理这个问题,根据Git仓库的group名字做了一个简单切分,并使用ssh2对于Git访问做一次代理,保证对于不同项目的http访问,能够分配到确定的机器上。
|
||||
|
||||
|
||||
这个方案的优点是,实施起来相对简单,缺点是无法向上兼容,升级GitLab会比较麻烦。
|
||||
|
||||
当然,你还可以参考[GitLab的官方建议](https://docs.gitlab.com/ee/administration/high_availability/README.html),并结合我分享的经验完成自己的HA方案。
|
||||
|
||||
## 如何应对代码管理的需求?
|
||||
|
||||
我们先一起回忆一下,上一篇文章中,我们对代码管理平台的需求,即要求能够支持3个团队的开发工作,且具备code review和静态代码检查的功能。
|
||||
|
||||
要实现这些需求,我需要先和你介绍一下GitLab提供的几个比较重要的功能。
|
||||
|
||||
### 了解GitLab提供的功能
|
||||
|
||||
Gitlab作为开源的代码管理平台,其原生也提供了不少优秀的功能,可以直接帮助我们解决上一篇文章中的一些需求。这些功能主要包括:
|
||||
|
||||
<li>
|
||||
<p>Merge Requests<br />
|
||||
分支代码审核合并功能,关于Merge Request和分支策略。你可以回顾一下第四篇文章[《 一切的源头,代码分支策略的选择》](https://time.geekbang.org/column/article/10858)和 第七篇文章[《“两个披萨”团队的代码管理实际案例》](https://time.geekbang.org/column/article/11323)的内容。<br />
|
||||
之后就是,我们根据不同的团队性质,选择不同的分支管理策略了。<br />
|
||||
比如,在我们的这个系统中:中间件团队只有6个开发人员,且都是资深的开发人员,他们在项目的向下兼容方面也做得很好,所以整个团队选择了主干开发的分支策略,以保证最高的开发效率。<br />
|
||||
同时,后台团队和iOS团队各有20个开发人员,其中iOS团队一般是每周三下午进行发布,所以这两个团队都选择了GitLab Flow的分支策略。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>issues<br />
|
||||
可以通过列表和看板两种视图管理开发任务和Bug。在携程,我们也有一些团队是通过列表视图管理Bug,通过看板视图维护需求和开发任务。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>CI/CD<br />
|
||||
GitLab和GitLab-ci集成的一些功能,支持pipline和一些CI结果的展示。携程在打造持续交付系统时,GitLab-ci的功能还并不完善,所以也没有对此相关的功能进行调研,直接自研了CI/CD的驱动。<br />
|
||||
不过,由于GitLab-ci和GitLab天生的集成特性,目前也有不少公司使用它作为持续集成工作流。你也可尝试使用这种方法,它的配置很简单,可以直接参考官方文档。而在专栏中我会以最流行的Jenkins Pipeline来讲解这部分功能。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Integrations<br />
|
||||
Integrations包括两部分:</p>
|
||||
<ul>
|
||||
1. GitLab service,是在GitLab内部实现的,与一些缺陷管理、团队协作等工具的集成服务。
|
||||
1. Webhook,支持在GitLab触发代码push、Merge Request等事件时进行http消息推送。
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
我在下一篇文章中介绍的代码管理与Jenkins集成,就是通过Webhook以及Jenkins的GitLab plugin实现的。
|
||||
|
||||
理解了GitLab的几个重要功能后,便可以初步应对上一篇文章中的几个需求了。之后,搭建好的GitLab平台,满足代码管理的需求,我们可以通过三步实现:
|
||||
|
||||
<li>
|
||||
创建对应的代码仓库;
|
||||
</li>
|
||||
<li>
|
||||
配置Sonar静态检查;
|
||||
</li>
|
||||
<li>
|
||||
解决其他设置。
|
||||
</li>
|
||||
|
||||
接下来,我和你分享一下,每一步中的关键点,以及具体如何满足相应的代码需求。
|
||||
|
||||
### 第一步,创建对应的代码仓库
|
||||
|
||||
了解了GitLab的功能之后,我们就可以开始建立与需求相对应的Projects了。
|
||||
|
||||
因为整个项目包括了中间件服务、业务后台服务,以及业务客户端服务这三个职责,所以相应的我们就需要在GitLab上创建3个group,并分别提交3个团队的项目。
|
||||
|
||||
- 对于中间件团队,我们创建了一个名为framework/config的项目。这个项目最终会提供一个配置中心的服务,并且生成一个config-client.jar的客户端,供后台团队使用。
|
||||
- 后台服务团队的项目名为:waimai/waimai-service,产物是一个war包。
|
||||
- 移动团队创建一个React Native项目mobile/waimai-app。
|
||||
|
||||
### 第二步,配置Sonar静态检查
|
||||
|
||||
创建了三个代码仓库之后,为了后续在构建时进行代码静态检查,所以现在我们还需要做的就是配置代码静态扫描工具。而在这里,我依旧以Sonar为例进行下面详解。
|
||||
|
||||
我们在使用SonarQube服务进行静态检查时,需要注意的问题包括:
|
||||
|
||||
Sonar的搭建比较简单,从 [https://www.sonarqube.org/downloads/](https://www.sonarqube.org/downloads/) 下载Sonar的压缩包以后,在 conf/sonar.properties 中配置好数据库的连接串,然后执行bin/linux-x86-64/sonar.sh start命令。之后,我们可以再查看一下日志logs/sonar.log,当日志提示“SonarQube is up”时就可以通过http://localhost:9000访问sonar了。(如果你有不明白的问题,可以参考 [https://docs.sonarqube.org/display/SONAR/Installing+the+Server](https://docs.sonarqube.org/display/SONAR/Installing+the+Server))
|
||||
|
||||
和GitLab的扩展一般只能通过二次开发不同,Sonar通过plugin的方式就可以完成扩展。在extensions/plugins目录下面已经预置了包含 Java、Python、PHP 等语言支持,以及LDAP认证等插件。你可以通过直接安装插件的方式进行扩展。
|
||||
|
||||
插件安装完成后,我们就可以尝试在本地使用Maven命令,对中间件和后台团队的Java项目进行静态检查了,React Native项目则是通过sonar-scanner配合ESLint完成静态检查的。
|
||||
|
||||
GitLab的Merge Request需要通过触发Jenkins构建 Sonar 来驱动代码的持续静态检查,至于如何集成我会在下一篇文章中和你详细介绍。
|
||||
|
||||
关于静态检查的更多知识点,你可以再回顾一下第二十五篇文章[《代码静态检查实践》](https://time.geekbang.org/column/article/14407)。
|
||||
|
||||
### 第三步,解决其他设置
|
||||
|
||||
经过创建对应的代码仓库、配置Sonar静态检查这两步,再配合使用GitLab提供的Merge Request、Issues、CI/CD和Integration功能,代码管理平台基本上就算顺利搭建完毕了。
|
||||
|
||||
之后剩余的事情包括:
|
||||
|
||||
<li>
|
||||
为项目添加开发者及对应的角色;
|
||||
</li>
|
||||
<li>
|
||||
根据分支策略,设定保护分支,仅允许Merge Request提交;
|
||||
</li>
|
||||
<li>
|
||||
创建功能分支。
|
||||
</li>
|
||||
|
||||
至此,我们需要的代码管理平台就真的搭建好了,开发人员可以安心写代码了。
|
||||
|
||||
## 总结及实践
|
||||
|
||||
在上一篇文章中,我们已经清楚了整个持续交付体系中,代码管理平台要具备的功能,所以今天我就在此基础上,和你一起使用GitLab完成了这个代码管理平台的搭建。
|
||||
|
||||
首先,我介绍了GitLab的安装及配置过程,并通过Microsoft Teams这个具体案例,介绍了如何完成GitLab的二次开发,以应对实际业务的需求。同时,我还介绍了GitLab的高可用方案。
|
||||
|
||||
然后,我针对代码管理平台要支持3个团队的code reivew和代码静态扫描的需求,和你分享了如何使用三步实现这些需求:
|
||||
|
||||
- 第一步,创建对应的代码仓库;
|
||||
- 第二步,配置Sonar静态检查;
|
||||
- 第三步,解决其他设置。
|
||||
|
||||
完成以上工作后,我们的代码管理平台就可以正式运作了,也为我们下一篇文章要搭建的编译构建平台做好了准备。
|
||||
|
||||
最后,希望你可以按照这篇文章的内容,自己动手实际搭建一套GitLab,以及配套的Sonar服务。
|
||||
|
||||
千里之行始于足下,如果搭建过程中,遇到了什么问题,欢迎给我留言一起讨论。
|
||||
|
||||
|
||||
300
极客时间专栏/geek/持续交付36讲/实践案例/36 | 快速构建持续交付系统(三):Jenkins 解决集成打包问题.md
Normal file
300
极客时间专栏/geek/持续交付36讲/实践案例/36 | 快速构建持续交付系统(三):Jenkins 解决集成打包问题.md
Normal file
@@ -0,0 +1,300 @@
|
||||
<audio id="audio" title="36 | 快速构建持续交付系统(三):Jenkins 解决集成打包问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7d/ce/7d38a9ba32c65ab04129723e777524ce.mp3"></audio>
|
||||
|
||||
在上一篇文章中, 我和你一起利用开源代码平台GitLab和代码静态检查平台SonarQube实现了代码管理平台的需求。那么,我今天这篇文章的目的,就是和你一起动手基于Jenkins搭建集成与编译相关的系统。
|
||||
|
||||
## Jenkins的安装与配置
|
||||
|
||||
Jenkins这个开源项目,提供的是一种易于使用的持续集成系统,将开发者从繁杂的集成工作中解脱了出来,使得他们可以专注于更重要的业务逻辑实现。同时,Jenkins还能实时监控集成环境中存在的错误,提供详细的日志文件和提醒功能,并以图表的形式形象地展示项目构建的趋势和稳定性。
|
||||
|
||||
因此,在携程,我们选择Jenkins作为了代码构建平台。而为了用户体验的一致性,以及交付的标准化,携程针对Java、.net等用到的主要语言,为开发人员封装了对于Jenkins的所有操作,并在自研的持续交付平台中实现了整个持续交付的工作流。
|
||||
|
||||
而如果是第一次搭建持续交付系统,我建议你不用像携程这样进行二次开发,因为Jenkins本身就可以在持续交付的构建、测试、发布流程中发挥很大的作用,完全可以满足你的搭建需求。而且,它提供的Pipeline功能,也可以很好地驱动整个交付过程。
|
||||
|
||||
所以,在这篇文章中,我就以Jenkins为载体,和你分享如何搭建集成与编译系统。
|
||||
|
||||
### 第一步,安装Jenkins
|
||||
|
||||
为了整个持续交付体系的各个子系统之间的环境的一致性,我在这里依然以Centos 7虚拟机为例,和你分享Jenkins 2.138(最新版)的安装过程。假设,Jenkins主机的IP地址是10.1.77.79。
|
||||
|
||||
1. 安装Java环境
|
||||
|
||||
```
|
||||
yum install java-1.8.0-openjdk-devel
|
||||
|
||||
```
|
||||
|
||||
1. 更新rpm源,并安装Jenkins 2.138
|
||||
|
||||
```
|
||||
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
|
||||
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
|
||||
yum install jenkins
|
||||
|
||||
```
|
||||
|
||||
然后,我们就可以通过 “`http://10.1.77.79`” 访问Jenkins了,整个安装过程很简单。
|
||||
|
||||
当然,Jenkins还有其他的安装方式,你可以参考 [https://jenkins.io/doc/book/installing/](https://jenkins.io/doc/book/installing/)。
|
||||
|
||||
### 第二步,配置Jenkins对GitLab的访问权限
|
||||
|
||||
Jenkins安装完成之后,我们还需要初始化安装Jenkins的一些基础配置,同时配置Jenkins对GitLab的访问权限。
|
||||
|
||||
在新版的Jenkins中,第一次启动时会有一个初始化向导,引导你设置用户名、密码,并安装一些插件。
|
||||
|
||||
在这里,我推荐你勾选“安装默认插件”,用这种方式安装Pipline、 LDAP等插件。如果这个时候没能选择安装对应的插件,你也可以在安装完成后,在系统管理->插件管理页面中安装需要的插件。
|
||||
|
||||
那么如何才能使编译和GitLab、SonarQube整合在一起呢?这里,我以一个后台Java项目为例,对Jenkins做进一步的配置,以完成Jenkins和GitLab、SonarQube的整合。这些配置内容,主要包括:
|
||||
|
||||
<li>
|
||||
配置Maven;
|
||||
</li>
|
||||
<li>
|
||||
配置 Jenkins钥匙;
|
||||
</li>
|
||||
<li>
|
||||
配置GitLab公钥;
|
||||
</li>
|
||||
<li>
|
||||
配置Jenkins GitLab插件。
|
||||
</li>
|
||||
|
||||
接下来,我就逐一和你介绍这些配置内容吧。
|
||||
|
||||
1. **配置Maven**
|
||||
|
||||
进入系统管理->全局工具配置页面,安装Maven,并把名字设置为M3。如图1所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5c/e9/5c89055cf5d9eaf5886b7826785535e9.png" alt="" />
|
||||
|
||||
这样配置好Maven后,Jenkins就会在第一次使用GitLab时,自动安装Maven了。
|
||||
|
||||
1. **配置 Jenkins钥匙**
|
||||
|
||||
配置Jenkins钥匙的路径是:凭据->系统->全局凭据->添加凭据。
|
||||
|
||||
然后,将你的私钥贴入并保存。 如图2所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2c/b8/2c92b6671b222e7af043f7cab1ffeab8.png" alt="" />
|
||||
|
||||
1. **配置GitLab公钥**
|
||||
|
||||
在GitLab端, 进入 http://{Gitlab Domain}/profile/keys,贴入你的公钥并保存,如图3所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b6/a5/b6028cb08eca8a73c2bfda0caa884aa5.png" alt="" />
|
||||
|
||||
通过配置Jenkins钥匙,以及配置GitLab公钥两步,你就已经完成了Jenkins对GitLab仓库的访问权限配置。
|
||||
|
||||
1. **配置Jenkins GitLab插件**
|
||||
|
||||
Jenkins的GitLab-plugin 插件的作用是,在代码提交和Merge Request时触发编译。安装这个插件的方法是:进入Jenkins的系统管理->插件管理页面,选择 GitLab Plugin 安装。
|
||||
|
||||
Jenkins重启后,选择凭据->系统->全局凭据->添加凭据,再选择GitLab API Token。然后,将 `http://10.1.77.79/profile/personal_access_tokens`中新生成的access token贴入GitLab API Token,并保存。
|
||||
|
||||
关于GitLab-plugin插件的更详细介绍,你可以参考它的[官方文档](https://github.com/jenkinsci/gitlab-plugin)。
|
||||
|
||||
完成了这四步的必要配置之后,你就可以开始使用Jenkins Pipline构建集成与编译系统的工作流了。
|
||||
|
||||
## 使用Jenkins Pipeline构建工作流
|
||||
|
||||
在使用Jenkins搭建集成和编译系统前,我们先一起回忆一下我在[《快速构建持续交付系统(一):需求分析》](https://time.geekbang.org/column/article/40082)中提到的关于集成与编译系统的需求:
|
||||
|
||||
>
|
||||
我们需要在代码push之后,自动触发编译和集成。如果编译成功,这套系统还要能继续处理自动化测试和部署。并且,在整个过程中,这个系统要能自动地适配三种不同的代码平台和交付产物。
|
||||
|
||||
|
||||
那么,如何才能驱动整个事务的顺利完成呢?这里,我们就需要用到大名鼎鼎的Jenkins Pipeline了。
|
||||
|
||||
### Jenkins Pipeline介绍
|
||||
|
||||
Jenkins Pipeline是运行在Jenkins上的一个工作流框架,支持将原先运行在一个或多个节点的任务通过一个Groovy脚本串联起来,以实现之前单个任务难以完成的复杂工作流。并且,Jenkins Pipline支持从代码库读取脚本,践行了Pipeline as Code的理念。
|
||||
|
||||
Jenkins Pipeline大大简化了基于Jenkins的开发工作。之前很多必须基于Jenkins插件的二次开发工作,你都可以通过Jenkins Pipeline实现。
|
||||
|
||||
另外,Jenkins Pipeline大大提升了执行脚本的可视化能力。
|
||||
|
||||
接下来,我就和你分享一下如何编写Jenkins Pipeline,以及从代码编译到静态检查的完整过程。这个从代码编译到静态检查的整个过程,主要包括三大步骤:
|
||||
|
||||
- 第一步,创建Jenkins Pipeline任务;
|
||||
- 第二步,配置Merge Request的Pipeline验证;
|
||||
- 第三部,编写具体的Jenkins Pipeline脚本。
|
||||
|
||||
### 第一步,创建 Jenkins Pipeline任务
|
||||
|
||||
首先,在Jenkins中创建一个流水线任务,并配置任务触发器。详细的配置,如图4所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/91/53/91aa89343d70022880b18346e6385f53.png" alt="" />
|
||||
|
||||
然后,在GitLab端配置Webhook。配置方法为:在GitLab项目下的settings->Integrations下配置并勾选 “Merge request events”选项。
|
||||
|
||||
经过这些配置后, 每次有新的Merge Request被创建或更新,都会触发Jenkins的Pipeline,而再由自定义的Pipeline脚本完成具体任务,比如代码扫描任务。
|
||||
|
||||
### 第二步,配置Merge Request 的 Pipeline 验证
|
||||
|
||||
在驱动代码静态扫描之后,我们还要做一些工作,以保证扫描结果可以控制Merge Request的行为。
|
||||
|
||||
进入settings->Merge Request页面, 勾选“Only allow Merge Requests to be merged if the pipeline succeeds”。这个配置可以保证,在静态检查任务中,不能合并Merge Request。
|
||||
|
||||
### 第三步,编写具体的Pipeline脚本
|
||||
|
||||
然后我们再一起看一下为了实现我们之前的需求,即获取代码-编译打包-执行Sonar静态代码检查和单元测试等过程。Jenkins端的Pipeline脚本如下,同时我们需要将该脚本配置在Jenkins中。
|
||||
|
||||
```
|
||||
node {
|
||||
def mvnHome
|
||||
|
||||
#修改Merge Request的状态,并checkout代码
|
||||
stage('Preparation') { // for display purposes
|
||||
mvnHome = tool 'M3'
|
||||
updateGitlabCommitStatus name: 'build', state: 'running'
|
||||
checkout scm
|
||||
}
|
||||
|
||||
#执行Maven命令对项目编译和打包
|
||||
stage('Build') {
|
||||
echo 'Build Start'
|
||||
// Run the maven build
|
||||
sh "'${mvnHome}/bin/mvn' -Dmaven.test.skip=true clean package"
|
||||
}
|
||||
|
||||
#启动sonar检查,允许junit单元测试,获取编译产物,并更新Merge request的状态
|
||||
stage('Results') {
|
||||
// Run sonar
|
||||
sh “'${mvnHome}/bin/mvn' org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar”
|
||||
junit '**/target/surefire-reports/TEST-*.xml'
|
||||
archive 'target/*.war'
|
||||
updateGitlabCommitStatus name: 'build', state: 'success'
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在这个脚本中,一共包括了3个stage。
|
||||
|
||||
**第一个stage:**
|
||||
|
||||
从GitLab中获取当前Merge Request源分支的代码;同时,通Jenkins GitLab插件将Merge Request所在的分支的当前commit状态置为running。这个时候,我们可以在GitLab的页面上看到Merge Request的合并选项已经被限制了,如图5所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/94/c8/94ce8fb5b4446a1caf9e015e6668f8c8.png" alt="" />
|
||||
|
||||
**第二个stage:**
|
||||
|
||||
比较好理解,就是执行Maven命令对项目编译和打包。
|
||||
|
||||
**第三个stage:**
|
||||
|
||||
通过Maven调用Sonar的静态代码扫描,并在结束后更新Merge Request的commit状态,使得Merge Request允许被合并。同时将单元测试结果展现在GitLab上。
|
||||
|
||||
通过以上这三步,我们已经完整地实现了这个集成和编译系统的需求,即:在GitLab端创建Merge Request时,预先进行一次代码扫描,并保证在代码扫描期间,代码无法被合并入主干分支,只有扫描通过后,代码才能被合并。
|
||||
|
||||
当然,这个示例的Pipline的脚本还比较简单。但掌握了基本的思路之后,在这个基础上,我们还可以添加更多的改进代码,达到更多的功能。
|
||||
|
||||
比如,我们在Sonar检测之后,可以调用Sonar的API获取静态检查的详细信息;然后,调用GitLab的API,将静态检查结果通过comment的方式,展现在GitLab的Merge Request页面上,从而使整个持续集成的流程更加丰满和完整。
|
||||
|
||||
## 多语言平台构建问题
|
||||
|
||||
上面的内容,我以Java后台项目为例,详细介绍了Jenkins Pipeline的创建。 但是,在实际的工作中,整个编译平台需要支持的是多种语言。所以,我要再和你分享下多语言情况下,集成和编译系统可能会碰到的问题。
|
||||
|
||||
在这里,我将多语言栈情况下,集成与编译系统常会遇到的问题,归结为两类:
|
||||
|
||||
<li>
|
||||
多语言CI流水线的管理;
|
||||
</li>
|
||||
<li>
|
||||
Jenkins Pipeline的管理。
|
||||
</li>
|
||||
|
||||
接下来,我们就一起看看,如何解决这两个问题吧。
|
||||
|
||||
### 多语言CI流水线管理
|
||||
|
||||
关于如何进行Docker编译和移动端编译的问题,你可以先回顾一下第17篇文章[《容器镜像构建的那些事儿》](https://time.geekbang.org/column/article/13051),以及第32篇文章[《细谈移动APP的交付流水线》](https://time.geekbang.org/column/article/39706)的内容,并将相关的逻辑Pipeline化。
|
||||
|
||||
当然,对于Docker镜像和iOS App这两种不同的交付流水线,你还需要特别关注的几个点,我再带你回顾一下。
|
||||
|
||||
**第一,Docker镜像**
|
||||
|
||||
对于构建docker镜像,我们需要在静态检查之后增加一个stage,即:把Dockerfile放入代码仓库。Dockerfile包括两个部分:
|
||||
|
||||
<li>
|
||||
base镜像的定义,包括Centos系统软件的安装和Tomcat环境的创建;
|
||||
</li>
|
||||
<li>
|
||||
war包部分,将Jenkins当前工作目录下的war包复制到Docker镜像中,保证每次Docker镜像的增量就只有war包这一个构建产物,从而提高Docker镜像的编译速度。
|
||||
</li>
|
||||
|
||||
**第二,iOS App**
|
||||
|
||||
而对于iOS应用,需要在修改Build stage的逻辑中, 增加fastlane shell命令。详细步骤可以参考第32篇文章[《细谈移动APP的交付流水线》](https://time.geekbang.org/column/article/39706)的内容,我就不再此赘述了。
|
||||
|
||||
特别需要注意的是,因为iOS机器只能在OS X环境下编译,所以我们需要在Pipeline脚本的node上指定使用Jenkins的Mac Slave。
|
||||
|
||||
### Jenkins Pipeline的管理
|
||||
|
||||
原则上,对于每个项目,你都可以配置一个Jenkins Pipeline任务。但,当我们需要维护的平台越来越多,或者项目处于多分支开发的状态时,这种做法显然就不合适了,比如:
|
||||
|
||||
<li>
|
||||
每个项目组的开发人员都需要调整Jenkins的脚本,很容易造成代码被错误更改;
|
||||
</li>
|
||||
<li>
|
||||
当需要回滚代码时,无法追述构建脚本的历史版本。
|
||||
</li>
|
||||
|
||||
在专栏的第20篇文章[《Immutable!任何变更都需要发布》](https://time.geekbang.org/column/article/13535)中,我曾提到,环境中的任何变更都需要被记录、被版本化。
|
||||
|
||||
所以,在Jenkins Pipeline的过程中,更好的实践是将Pipeline的脚本文件Jenkinsfile放入Git的版本控制中。每次执行Jenkins Job前,先从Git中获取到当前仓库的Pipeline脚本。
|
||||
|
||||
这样,不仅降低了单个项目维护Jenkins job的成本,而且还标准化了不同语言平台的构建,从而使得一套Jenkins模板就可以支持各个语言栈的编译过程。
|
||||
|
||||
## 多平台构建产物管理
|
||||
|
||||
除了多语言栈的问题外,我们还会碰到的另一个问题是,构建产物的管理问题。
|
||||
|
||||
当开发语言只是Java的时候,我们管理的构建产物主要是jar包和war包,而管理方式一般就是把Nexus和Artifactory作为代码管理仓库。
|
||||
|
||||
而引入一种新的部署工具后,我们就需要额外的管理方式。比如,引入Docker镜像后,我们需要引入用于存储和分发Docker镜像的企业级Registry服务器Harbor。
|
||||
|
||||
所以,为了保证整个系统工具链的一致性,我们需要做到:
|
||||
|
||||
<li>
|
||||
产物的统一版本化,即无论是Java的war包或是.net程序的压缩包,都需要支持与上游的编译系统和下游的部署系统对接。
|
||||
</li>
|
||||
<li>
|
||||
对于同一个版本的多个构建产物,需要将它们和代码的commit ID实现有效的关联。比如,对于同一份Java代码生成的war包和Docker镜像,我们可以通过一个版本号把它们关联起来。
|
||||
</li>
|
||||
|
||||
但是,这两种做法会使得整个持续交付系统的研发复杂度更高。
|
||||
|
||||
所以,携程最终选择的方案是:标准化先行。也就是说,保证不同语言的发布有且只有一套统一的流水线,并通过在编译系统的上层再封装一层自研系统,以达到不同的物理构建产物,可以使用同一个逻辑版本号进行串联管理的目的。
|
||||
|
||||
而针对这个问题,业界普遍采用的解决方案是:用Artifactory或者Nexus对构建产物进行统一管理。Artifactory和Nexus都包括了开源OSS版和付费专业版。
|
||||
|
||||
另外,你可能在选择构建产物仓库的时候会有这样的疑惑:我到底应该选择哪个仓库呢。那么,我就再和你分享一下我之前调研得到的一些结论吧。
|
||||
|
||||
<li>
|
||||
如果你需要管理的产物只是Java相关的Maven或者Gradle,那么Nexus或者Artifactory都能工作得很好,你可以随意选择。
|
||||
</li>
|
||||
<li>
|
||||
如果你有管理多语言构建产物的需求,而又没有付费意愿的话,我建议你使用Nexus 3的OSS版本。Nexus 3的OSS版本支持10多种主流编程语言。而Artifactory的OSS版本能支持的编译工具就非常有限,只有Gradle、Ivy、Maven、SBT这四种。
|
||||
</li>
|
||||
<li>
|
||||
如果你有管理多语言构建产物的需求,而且也接受付费的话,我推荐你使用Artifactory的付费版本。Artifactory的付费版本中,包含了很多头部互联网公司的背书方案,功能相当丰富。而且,如果你所在公司的开发人员比较多的话,Artifactory按实例付费的方式也更划算。
|
||||
</li>
|
||||
|
||||
好了,到此为止,我们的集成构建系统也搭建完成了。加上我们上一篇文章中一起搭建的代码管理平台,我们已经可以跑完三分之二的持续交付过程了。
|
||||
|
||||
所以,在接下来的最后一篇文章中,我将会为你介绍关于自动化测试和发布的一些实践,这样就能完整地实现我们对持续交付系统的需求了。
|
||||
|
||||
## 总结与实践
|
||||
|
||||
通过今天这篇文章,我和你分享了如何快速安装和配置一套有效的Jenkins系统,以及如何打通Jenkins与GitLab之间的访问。这样就可以使这套基于Jenkins的集成与编译系统与我们在上一篇文章中基于GitLab搭建的代码管理平台相呼应,从而满足了在代码平台push代码时,驱动集成编译系统工作的需求。
|
||||
|
||||
当然,在今天这篇文章中,我还详细分析了Jenkins Pipeline的创建,以及与Merge Request的联动合作配置,同时提供了一个Pipeline脚本的例子,帮助你理解整个Pipeline的工作原理。这样你就可以根据自己的具体需求,搭建起适合自己的持续交付流水线了。
|
||||
|
||||
除此之外,我还提到了关于多语言平台和多平台构建产物的问题。对于这种复杂的问题,我也给出了解决问题的一些行之有效的办法。比如,使用统一逻辑版本进行产物管理等。
|
||||
|
||||
这样,通过搭建Jenkins系统,构建Pipeline流水线,以及处理好构建产物这三部曲,相信你已经可以顺利构建起一套适合自己的集成与编译系统了。
|
||||
|
||||
那么,接下来,建议你按照我今天的分析自己动手试一下,看看整个搭建过程是否顺利。如果你在这个过程中碰到了任何问题,欢迎你给我留言一起讨论。
|
||||
|
||||
|
||||
292
极客时间专栏/geek/持续交付36讲/实践案例/37 | 快速构建持续交付系统(四):Ansible 解决自动部署问题.md
Normal file
292
极客时间专栏/geek/持续交付36讲/实践案例/37 | 快速构建持续交付系统(四):Ansible 解决自动部署问题.md
Normal file
@@ -0,0 +1,292 @@
|
||||
<audio id="audio" title="37 | 快速构建持续交付系统(四):Ansible 解决自动部署问题" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/de/c9/de74cdb25a16b9ee83e15abf23f532c9.mp3"></audio>
|
||||
|
||||
今天这篇文章,已经是实践案例系列的最后一篇了。在[《快速构建持续交付系统(二):GitLab 解决配置管理问题》](https://time.geekbang.org/column/article/40169)和[《快速构建持续交付系统(三):Jenkins 解决集成打包问题》](https://time.geekbang.org/column/article/40412)这两篇文章中,我们已经分别基于GitLab搭建了代码管理平台、基于Jenkins搭建了集成与编译系统,并解决了这两个平台之间的联动、配合问题,从而满足了在代码平台 push 代码时,驱动集成编译系统工作的需求。
|
||||
|
||||
算下来,我们已经通过前面这两篇文章,跑完了整个持续交付体系三分之二的路程,剩下的就是解决如何利用开源工具搭建发布平台完成代码发布,跑完持续交付最后一公里的问题了。
|
||||
|
||||
## 利用Ansible完成部署
|
||||
|
||||
Ansible 是一个自动化运维管理工具,支持Linux/Windows跨平台的配置管理,任务分发等操作,可以帮我们大大减少在变更环境时所花费的时间。
|
||||
|
||||
与其他三大主流的配置管理工具Chef、Puppet、Salt相比,Ansible最大的特点在于“agentless”,即无需在目标机器装安装agent进程,即可通过SSH或者PowerShell对一个环境中的集群进行中心化的管理。
|
||||
|
||||
所以,这个“agentless”特性,可以大大减少我们配置管理平台的学习成本,尤其适合于第一次尝试使用此类配置管理工具。
|
||||
|
||||
另外,利用Ansible,我们可以完成虚拟机的初始化,以及Tomcat Java程序的发布更新。
|
||||
|
||||
现在,我们就先看看如何在我们的机器上安装Ansible,以及如何用它来搭建我们的代码发布平台。这里,我们再一起回顾下,我在第34篇文章[《快速构建持续交付系统(一):需求分析》](https://time.geekbang.org/column/article/40082)中提到的对发布系统的需求:
|
||||
|
||||
>
|
||||
同时支持 Jar、War、Docker的生产发布,以及统一的部署标准。
|
||||
|
||||
|
||||
对于移动App,我们只要发布到内部测试集市即可,所以只需要在编译打包之后上传至指定地址,这个操作在Jenkins Pipeline里执行就可以了,所以本篇就不累述了。
|
||||
|
||||
### **Ansible安装**
|
||||
|
||||
对于Ansible环境的准备,我推荐使用pip的方式安装。
|
||||
|
||||
```
|
||||
sudo pip install Ansible
|
||||
|
||||
```
|
||||
|
||||
安装完之后, 我们可以简单测试一下:
|
||||
|
||||
1. 提交一个Ansible的Inventory文件 hosts,该文件代表要管理的目标对象:
|
||||
|
||||
```
|
||||
$ cat hosts
|
||||
[Jenkinsservers]
|
||||
10.1.77.79
|
||||
|
||||
```
|
||||
|
||||
1. 打通本机和测试机的SSH访问:
|
||||
|
||||
```
|
||||
$ ssh-copy-id deployer@localhost
|
||||
|
||||
```
|
||||
|
||||
1. 尝试远程访问主机 10.1.77.79:
|
||||
|
||||
```
|
||||
$ Ansible -i hosts all -u deployer -a "cat /etc/hosts”
|
||||
|
||||
10.1.77.79 | SUCCESS | rc=0 >>
|
||||
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
||||
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
||||
|
||||
```
|
||||
|
||||
如果返回SUCCESS,则表示我们已经可以通过Ansible管理该主机了。
|
||||
|
||||
接下来,我们再看一下如何使用Ansible达到我们的发布目标吧。
|
||||
|
||||
### Ansible使用
|
||||
|
||||
现在,我先简单介绍下,在初次接触Ansible时,你应该掌握的两个最关键的概念:Inventory和PlayBook。
|
||||
|
||||
1. **Inventory**
|
||||
|
||||
对于被Ansible管理的机器清单,我们可以通过Inventory文件,分组管理其中一些集群的机器列表分组,并为其设置不同变量。
|
||||
|
||||
比如,我们可以通过Ansible_user ,指定不同机器的Ansible用户。
|
||||
|
||||
```
|
||||
[Jenkinsservers]
|
||||
10.1.77.79 Ansible_user=root
|
||||
10.1.77.80 Ansible_user=deployer
|
||||
[Gitlabservers]
|
||||
10.1.77.77
|
||||
|
||||
```
|
||||
|
||||
1. **PlayBook**
|
||||
|
||||
PlayBook是Ansible的脚本文件,使用YAML语言编写,包含需要远程执行的核心命令、定义任务具体内容,等等。
|
||||
|
||||
我们一起看一个Ansible官方提供的一个例子吧。
|
||||
|
||||
```
|
||||
---
|
||||
- hosts: webservers
|
||||
remote_user: root
|
||||
|
||||
tasks:
|
||||
- name: ensure apache is at the latest version
|
||||
yum:
|
||||
name: httpd
|
||||
state: latest
|
||||
- name: write the apache config file
|
||||
template:
|
||||
src: /srv/httpd.j2
|
||||
dest: /etc/httpd.conf
|
||||
- hosts: databases
|
||||
remote_user: root
|
||||
|
||||
tasks:
|
||||
- name: ensure postgresql is at the latest version
|
||||
yum:
|
||||
name: postgresql
|
||||
state: latest
|
||||
- name: ensure that postgresql is started
|
||||
service:
|
||||
name: postgresql
|
||||
state: started
|
||||
|
||||
```
|
||||
|
||||
这段代码的最主要功能是,使用yum完成了Apache服务器和PostgreSQL的安装。其中,包含了编写Ansible PlayBook的三个常用模块。
|
||||
|
||||
<li>
|
||||
yum 调用目标机器上的包管理工具完成软件安装 。Ansible对于不同的Linux操作系统包管理进行了封装,在CentOS上相当于yum, 在Ubuntu上相当于APT。
|
||||
</li>
|
||||
<li>
|
||||
Template 远程文件渲染,可以把本地机器的文件模板渲染后放到远程主机上。
|
||||
</li>
|
||||
<li>
|
||||
Service 服务管理,同样封装了不同Linux操作系统实际执行的Service命令。
|
||||
</li>
|
||||
|
||||
**通常情况下,我们用脚本的方式使用Ansible,只要使用好Inventory和PlayBook这两个组件就可以了,即:使用PlayBook编写Ansible脚本,然后用Inventory维护好需要管理的机器列表**。这样,就能解决90%以上使用Ansible的需求。
|
||||
|
||||
但如果你有一些更复杂的需求,比如通过代码调用Ansible,可能还要用到API组件。感兴趣的话,你可以参考Ansible的官方文档。
|
||||
|
||||
### 使用Ansible进行Java应用部署
|
||||
|
||||
我先来整理下,针对Java后端服务部署的需求:
|
||||
|
||||
>
|
||||
完成Ansible的PlayBook后,在Jenkins Pipeline中调用相关的脚本,从而完成Java Tomcat应用的发布。
|
||||
|
||||
|
||||
**首先,在目标机器上安装Tomcat,并初始化。**
|
||||
|
||||
我们可以通过编写Ansible PlayBook完成这个操作。一个最简单的Tomcat初始化脚本只要十几行代码,但是如果我们要对Tomcat进行更复杂的配置,比如修改Tomcat的CATALINA_OPTS参数,工作量就相当大了,而且还容易出错。
|
||||
|
||||
在这种情况下,一个更简单的做法是,使用开源第三方的PlayBook的复用文件roles。你可以访问[https://galaxy.Ansible.com](https://galaxy.ansible.com) ,这里有数千个第三方的roles可供使用。
|
||||
|
||||
在GitHub上搜索一下Ansible-Tomcat,并下载,就可以很方便地使用了。
|
||||
|
||||
这里,我和你一起看一个具体roles的例子:
|
||||
|
||||
```
|
||||
---
|
||||
- hosts: Tomcat_server
|
||||
roles:
|
||||
- { role: Ansible-Tomcat }
|
||||
|
||||
```
|
||||
|
||||
你只需要这简单的三行代码,就可以完成Tomcat的安装,以及服务注册。与此同时,你只要添加Tomcat_default_catalina_opts参数,就可以修改CATALINA_OPTS了。
|
||||
|
||||
这样一来,Java应用所需要的Web容器就部署好了。
|
||||
|
||||
**然后,部署具体的业务代码**。
|
||||
|
||||
这个过程就是指,把编译完后的War包推送到目标机器上的指定目录下,供Tomcat加载。
|
||||
|
||||
完成这个需求,我们只需要通过Ansible的SCP模块把War包从Jenkins推送到目标机器上即可。
|
||||
|
||||
具体的命令如下:
|
||||
|
||||
```
|
||||
- name: Copy a war file to the remote machine
|
||||
copy:
|
||||
src: /tmp/waimai-service.war
|
||||
dest: /opt/Tomcat/webapps/waimai-service.war
|
||||
|
||||
```
|
||||
|
||||
但是,这样编写Ansible的方式会有一个问题,就是把Ansible的发布过程和Jenkins的编译耦合了起来。
|
||||
|
||||
而在上一篇文章[《快速构建持续交付系统(三):Jenkins 解决集成打包问题》](https://time.geekbang.org/column/article/40412)中,我提到,要在编译之后,把构建产物统一上传到Nexus或者Artifactory之类的构建产物仓库中。
|
||||
|
||||
所以,**此时更好的做法是直接在部署本地从仓库下载War包**。这样,之后我们有独立部署或者回滚的需求时,也可以通过在Ansible的脚本中选择版本实现。当然,此处你仍旧可以使用Ansible的SCP模块复制War包,只不过是换成了在部署机上执行而已。
|
||||
|
||||
**最后,重启 Tomcat 服务,整个应用的部署过程就完成了**。
|
||||
|
||||
## Ansible Tower 简介
|
||||
|
||||
通过上面的几个步骤,我们已经使用Ansible脚本简单实现了Tomcat War包分发的过程。
|
||||
|
||||
这样的持续交付工作流,虽然可以工作,但依然存在两个问题。
|
||||
|
||||
<li>
|
||||
<p>用户体验问题。<br />
|
||||
我们一起回顾下第21篇文章[《发布系统一定要注意用户体验》](https://time.geekbang.org/column/article/13552)中的相关内容,用户体验对发布系统来说是相当重要的。<br />
|
||||
在上面使用Ansible进行部署Java应用的方案中,我们采用的Jenkins Pipeline和Ansible命令行直接集成的方式,就所有的信息都集中到了Jenkins的console log下面,从而缺少了对发布状态、异常日志的直观展示,整个发布体验很糟糕。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>统一管理问题。<br />
|
||||
Ansible缺乏集中式管理,需要在每个Jenkins节点上进行Ansible的初始化,增加了管理成本。</p>
|
||||
</li>
|
||||
|
||||
而这两个问题,我们都可以通过Ansible Tower解决。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/88/b5/88fc43236173194403445fe383a42bb5.png" alt="" />
|
||||
|
||||
Ansible Tower是Ansible的中心化管理节点,既提供了Web页面以达到可视化能力,也提供了Rest API以达到调用Ansible的PlayBook的目的。
|
||||
|
||||
如图1所示为Ansible Tower的Dashboard页面。我们可以看到,这个页面提供了整个Ansible集群发布的趋势图,以及每次发布在每台被部署机器上的详细结果。
|
||||
|
||||
## 灰度发布的处理
|
||||
|
||||
通过上面的内容,我们已经可以通过合理使用Ansible,顺利地部署一个Java应用了,而且还可以通过Ansible Tower监控整个发布过程。而对于灰度发布过程的处理,你只需要在Jenkins Pipeline中编写相应的脚本,控制具体的发布过程就可以了。
|
||||
|
||||
比如,通过Inventory定义灰度分批策略,再利用Pipeline驱动PlayBook,就是一个典型的灰度发布的处理过程。其实,这只是将原子化的单机操作批量化了而已。
|
||||
|
||||
当然,这个过程中我们还需要考虑其他一些问题。而对于这些问题如何解决,你就可以参考发布及监控系列的六篇文章(即,第19篇至第24篇)了。
|
||||
|
||||
至此,标准的Java应用的发布就已经大功告成了。接下来,我再和你说说其他产物(Jar包、Docker镜像)的发布方式。
|
||||
|
||||
## Jar包的发布
|
||||
|
||||
**Jar包的发布本身就比较简单,执行一条Maven命令(即,mvn deploy)就可以完成。但,Jar包发布的关键在于,如何通过工具提升Jar包发布的质量。**
|
||||
|
||||
在不引入任何工具和流程辅助时,我们在携程尝试过让开发人员自行通过“mvn deploy”进行发布。但结果可想而知,造成了很多问题。诸如,大量低质量的代码进入仓库;release版本的Jar包被多次覆盖;版本管理混乱,Bug难以排查等等。
|
||||
|
||||
后来,我们初次收紧了发布权限,也就是只把“mvn deploy”的权限下放给每个团队的技术经理(tech leader)。这种方案,虽然在一定程度上解决了Jar包质量的问题,但同时也降低了发布效率。这里发布效率的降低,主要体现在两个方面:
|
||||
|
||||
- 一方面,每次发布都需要经过技术经理,增加了他的工作负担;
|
||||
- 另一方面,“mvn deploy”权限需要由SCM人员手工完成,增加了发布的沟通成本,降低了整个团队的开发效率。
|
||||
|
||||
再后来,为了解决这些问题,我们在GitLab上进行了二次开发,即:允许开发人员自主选择某个pom module的Jar包进行发布,并记录下每次的Jar包发布的记录。
|
||||
|
||||
在Jar包发布的第一步,我们使用Maven Enforcer插件进行构建检测,以保证所有进入仓库的Jar包是合规的。这部分内容,你可以参考第15篇文章[《构建检测,无规矩不成方圆》](https://time.geekbang.org/column/article/12772)。
|
||||
|
||||
如果你不想通过在GitLab上进行二次开发控制Jar包发布的话,简单的做法是,通过Jenkins任务,参数化创建一个Jar包发布的job。让用户在每次发布前填入所需的代码仓库和module名,并在job的逻辑中保证Jar包编译时已经通过了Enforcer检查。
|
||||
|
||||
这样,我们就可以顺利解决掉Jar包发布的问题了。
|
||||
|
||||
## 使用Spinnaker处理Docker
|
||||
|
||||
现在,我们再来看一下如何选择开源的Docker交付平台。
|
||||
|
||||
在携程,我们第一版的Docker发布流程,是基于自研发布工具Tars和mesos framework集成实现的。这个方案成型于2016年底,那时容器编排平台的局面还是Mesos、Swarm,,以及Kubernetes的三方大战,三方各有优势和支持者。
|
||||
|
||||
时至今日,Kubernetes基本已经一统容器编排平台。为了更多地获取开源红利,携程也在向Kubernetes的全面迁移中。
|
||||
|
||||
目前,携程对接Kubernetes的方案是,使用StatefulSet管理Pod,并且保持实例的IP不会因为发布而产生变化,而负载均衡器依然使用之前的SLB中间件,并未使用Kubernetes天然支持的Ingress。这和我在第23篇文章[《业务及系统机构对发布的影响》](https://time.geekbang.org/column/article/14050)中提到的markdown、markup机制有关系,你可以再回顾一下这篇文章的内容。
|
||||
|
||||
但,如果今天让我再重新实现一次的话,我更推荐使用Kubernetes原生方案作为Docker编排平台的第一方案,这样更简单有效。如果你还没有在持续交付平台中支持Kubernetes的话,我的建议是:直接考虑搭建持续交付平台Spinnaker。
|
||||
|
||||
Spinnaker 是 Netflix 的开源项目,致力于解除持续交付平台和云平台之间的耦合。这个持续交付平台的优点,主要包括:
|
||||
|
||||
<li>
|
||||
发布支持多个云平台,比如AWS EC2、Microsoft Azure、Kubernetes等。如果你未来有在多数据中心使用混合云的打算,Spinnaker可以给你提供很多帮助。
|
||||
</li>
|
||||
<li>
|
||||
支持集成多个持续集成平台,包括Jenkins、Travis CI等。
|
||||
</li>
|
||||
<li>
|
||||
Netflix 是金丝雀发布的早期实践者,Spinnaker中已经天然集成了蓝绿发布和金丝雀发布这两种发布策略,减少了开发发布系统的工作量。 在此,你可以回顾一下我在第19篇文章[《发布是持续交付的最后一公里](https://time.geekbang.org/column/article/13380)》中,和你分享的蓝绿发布和金丝雀发布。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1b/d4/1b7002b9c0c85bb87075a3a7531ea2d4.png" alt="" />
|
||||
|
||||
虽然,我并未在携程的生产环境中使用过Spinnaker,但由处于持续交付领域领头羊地位的Netflix出品,并且在国内也已经有了小红书的成功案例,Spinnaker还是值得信任的。你可以放心大胆的用到自己的持续交付体系中。
|
||||
|
||||
好了,现在我们已经一起完成了发布平台的搭建。至此,整个持续交付体系,从代码管理到集成编译再到程序发布上线的完整过程,就算是顺利完成了。
|
||||
|
||||
## 总结与实践
|
||||
|
||||
在今天这篇文章中,我主要基于Ansible系统的能力,和你分享了搭建一套部署系统的过程。在搭建过程中,你最需要关注的两部分内容是:
|
||||
|
||||
<li>
|
||||
利用Inventory做好部署目标的管理;
|
||||
</li>
|
||||
<li>
|
||||
利用PlayBook编写部署过程的具体逻辑。
|
||||
</li>
|
||||
|
||||
同时,我还介绍了Ansible Tower这样一个可视化工具,可以帮助你更好地管理整个部署过程。
|
||||
|
||||
另外,对于Jar包的发布,以及Docker的处理,我也结合着携程的经验,和你分享了一些方法和建议,希望可以帮到你。
|
||||
|
||||
至此,我们要搭建的整个持续交付系统,也算是顺利完成了。
|
||||
|
||||
同样地,最后我还是建议你动手去搭建一套发布系统,看看是否能够顺利地完成这个过程。如果你在这个过程中碰到了任何问题,欢迎你给我留言一起讨论。<br />
|
||||
|
||||
Reference in New Issue
Block a user