一文读懂研发效能


如何理解研发效能

研发效能的完整定义应该是:团队能够持续地为用户产生有效价值的效率,包括有效性(Effectiveness)、效率(Efficiency)和可持续性(Sustainability)三个方面。简单来说,就是能否长期、高效地交付出有价值的产品。

在互联网行业内卷加剧的情况下,如何能更好的破局,而不是一味的推崇996,研发团队的效率就显得格外重要。开发流程的顺畅是生产优质软件的关键因素,只有这样才能最大程度地释放开发者的创造性和积极性,所以提高“研发效能”,我们更多的是围绕整个软件开发的生命周期,这里就不得不说一下“DevOps”和“云原生”。

DevOps

DevOps=Developers+Operators,字面上看,是指开发团队和运维团队一体化,通过工具辅助开发完成运维的部分工作,减少成本,尽可能地为公司创造更多价值。但是我们知道,任何一个产品或者项目从最初的需求意愿到最终的上线交付,中间不仅仅是只有开发、运维两两个角色就能完成,还包含产品、测试、QA、甚至商务、销售团队的人员共同参与才能完成。 因此在人们不断的实践过程中,DevOps逐渐扩展为整个软件研发过程的管理思想和方法论,它:​
  • 是一组过程、方法与系统的统称,包含开发、测试和运维;
  • 重视软件开发人员(Dev)和IT运维技术人员(Ops)之间沟通合作的文化、运动或惯例,改善团队之间的协作关系;
  • 用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合;
  • 透过自动化“软件交付”和“架构变更”的流程,使得构建、测试、发布软件能够更加地快捷、频繁和可靠,按时交付软件产品和服务;


1.png

DevOps在实施的过程中,往往是由最了解整个系统的开发人员来负责生命周期,包括自动化测试、线上服务观测,而传统的测试团队来为测试平台、工具提供支撑,运维(SRE)团队为运维平台、工具提供支撑。 DevOps其实就是通过打通Dev和Ops来提高研发效能,结合持续集成、持续交付、持续部署来提升交付效能。为了实现DevOps,除了团队协作、文化这些软性因素,更重要的是CI/CD流水线的建设。

CI/CD

CI/CD是指持续集成(Continuous Integration,CI)、持续交付(Continuous Delivery,CD)和持续部署(Continuous Deployment,CD),持续集成的根本出发点,就是够帮助开发人员尽量早、尽量频繁地把自己的改动推送到共享的代码仓库分支上,进行代码集成,从而减少大量代码冲突造成的低效能问题。所以:

持续集成的定义就是:在团队协作中,一天内多次将所有开发人员的代码合并入同一条主干

这也就是为什么现在我们的Git工作流从之前的git-flow/gitlab-flow/github-flow慢慢演进为现在的Trunk-based(主干开发),代码入库后,剩下工作是把代码编译打包成可以发布的形式,先发布到测试环境进行测试,再发布到预发布环境进行测试,最终部署到生产环境。 ​

持续交付的目标是,对每一个进入主干分支的代码提交,构建打包成为可以发布的产品。它的定义是:一种软件工程方法,在短周期内完成软件产品,以保证软件保持在随时可以发布的状态。而持续部署,则更进一步。它把持续交付产生的产品立即自动部署给用户,定义就是:将每一个代码提交,都构建出产品直接部署给用户使用。 ​

CI/CD的理想效果是,首先开发前我们功能拆分的足够细,又因为主干开发的模式,促使我们尽可能频繁的合并到主干进行集成检测,检测通过的代码持续的发布到线上。尽可能早的集成到主干,是为了避免花费相当长的时间去解决合并冲突,在实际落地的过程中,我们也可以通过主干开发和功能分支相结合的方式,例如常见的,通过需求分解后的小功能来创建分支(feature-xxx),用来修复bug的分支(bugfix-xxx),这些分支都从主干检出,最终又合回主干,在commit信息中附带需求地址或者bug地址,和项目管理平台打通,方便我们后期管理需求周期和bug修复周期。 ​

为了实现CI/CD,我们可以把流水线与我们的代码版本库事件绑定,当不同的事件发生时触发相对应的流水线执行。
  • MR流水线:开发人员Merge请求到主干时会触发
  • Merged流水线:代码合入主干时触发
  • Tag流水线:代码打tag发版时触发


MR流水线
2.png

MR流水线被触发后,这个时候开发人员就可以并行去处理其他事情,流水线会使用我们提交的分支编译构建,通过一系列的linter检查编码规范、安全规范,来进行自动化代码审查,代码审查通过后,再依靠足够覆盖率的单测、接口测试来测试功能是否符合预期case,这些stage全部执行通过,会通知我们的代码审查人员,通常是服务owner或者文件owner,来进行人工代码审查。任何一个stage失败,都会及时通知到我们开发人员,问题修复后,重复这个流程。 ​

单元测试和接口测试可以参考《Effective Go Plus》中测试章节,这里不再赘述。但是关于规范检查前边也有提到,是通过量化指标来度量我们的规范落地,使得规范不至于最终只是“君子协定”。 可度量的指标,例如:
  • 可测性:我们更倾向于编写简单的代码,圈复杂度过高、函数和文件过长,这些一定程度上都会降低我们编写测试代码的难度,我们需要通过及时的重构来解决这些问题。
  • 重复率:代码重复率过高也代表这一定程度的耦合,这种耦合如果属于业务功能耦合,那我们就应该回本逐源,考虑需求是否合理,是否符合我们对用户交付价值的目标。
  • 安全风险:例如代码中是否硬编码了敏感信息,是否会导致程序异常终止,是否存在资源(内存、连接)泄露,是否并发安全,是否存在注入风险等等,代码安全有些情况下甚至会影响到产品和企业的生死存亡。


有了自动化的代码审查,人工审查就可以拔高维度,去关注:
  • 代码架构设计是否合理:是否能反映开发者的真实意图,结合研发效能的终极目标,提交的代码是否能产生价值以及对用户友好,如果希望审查人员能更多的帮助我们,那么详尽的背景介绍和文档也是必要的。
  • 抽象和可复制:做工程而非仅仅是做业务,这句话并不是在贬低业务系统的价值,反而我们大部分时间都是在开发业务系统,但是我们的业务系统在类似的业务场景下是否都有很强的移植性和适应性,这个值得深思。或者说我们是否都在以工程的角度开发我们的业务系统,以使的它逐步沉淀,足够抽象之后形成一套可复制的工业化技术体系。
  • 代码和命名是否能够自解释:如果代码无法清楚的解释自己时,那么代码应该变得简单。
  • 注释是否能反映决策背后的推理:而不仅仅是解释代码正在做什么,如果仅仅是解释代码正在做什么,那么参考第三条,尽量做到自解释。


代码审查还有一些其他需要我们注意的点,例如,对于审查人员来说,应该更倾向于批准MR,只要代码确实契合研发效能的目标,即使它还不够完美。审查通过或者确信开发人员会适当的处理审查者的评论时,及时评论LGTM/Approval,评审人给予LGTM表示认同。更详细的可以参考Google的《开发者CodeReview指南》。

MR流水线通过代码审查和测试把控代码质量,不加把控或者把控粒度不够,一定程度上都是在增加我们系统的风险和历史债务,但是硬性制定出来的规范,一味的追求测试高覆盖率,并不一定适合我们团队的现状,也很有可能会大大降低我们的迭代效率。相反因地制宜,经过充分商讨出来的方案更具有可执行性行,例如我们先参考业界权威的一些规范,单元测试只覆盖重点模块,重点做接口测试等等,通过演进的方式,兼顾迭代效率的同时,逐步形成适合团队不同阶段的质量把控体系。

Merged流水线
3.png

当代码审查通过之后,我们可以将代码合入到主干,由于主干开发的工作流,我们在合入主干的同时,还会有其他的代码在持续的合入,为了保障代码合入不会因为相互依赖等原因导致主干代码质量下降,还需要再走一次Merged流水线,不过相比于MR流水线,我们使用主干代码编译构建,并且只关注测试环节。测试通过后是否触发自动tag,需要考虑我们的发版和代码合入是否是同步的。

Tag流水线
4.png

Tag流水线往往和我们的发版动作是一致的,编译构建使用的代码版本也需要和打tag时的commit对应,随后在预发布环境进行上线前的回归测试,回归测试通过开始正式上线部署。这里有几个比较重要的节点:
  • 上线前确认:和代码审查类似,通过由owner来进行确认,在一些节假日或者封网期间考虑多级确认,以降低不必要的风险。
  • 灰度发布:采用逐步放量的方式,甚至可以和SLA(可用性)系统打通,观察灰度期间服务的可用性是否受影响。
  • 分批发布:这其实是滚动升级的过程,通过设置合理的滚动步长,和节点生命周期管理,来保障整个过程的平滑。


部署完成后,再进行线上回归测试,同时发布上线通知给服务的关注人员。这里之所以省去了单元测试的环节,是已经假定了我们的单元测试是通过mock掉所有依赖的方式编写的,这样我们的单元测试就可以不用担心因为资源环境变化,而需要重复编写case的情况发生。

云原生

很多人会容易把云原生(Cloud Native)做为云计算发展到一定阶段的产物,实际上从概念上来讲,它们并不属于同一个东西。云计算通常包括IAAS,SAAS,PAAS等,服务的是“资源对象”。

CNCF对云原生的定义是:


Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach. 云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式 API。


These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil. 这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。
云原生服务的对象更多的是“应用”,并不需要强依赖云计算。

云原生发展到现在也逐步演变为一系列架构、研发流程、团队文化的集合,以此支撑更快的创新速度、极致的用户体验、稳定可靠的用户服务、高效的研发效率,这点和研发效能的目标是一致的。在实际落地过程中,我们也可以尽可能的借助云原生的一些基础设置,例如:容器、微服务、Service Mesh等。

一个case

接下来我们通过一个Golang的消息微服务项目,结合GitLab、Jenkins和公有云Docker、Kubernetes来落地平民版的研发效能模型。

Docker

容器技术起源于Linux,是一种内核虚拟化技术,提供轻量级的虚拟化,以便隔离进程和资源。尽管容器技术已经出现很久,却是随着Docker的出现而变得广为人知。Docker是第一个使容器能在不同机器之间移植的系统。它不仅简化了打包应用的流程,也简化了打包应用的库和依赖,甚至整个操作系统的文件系统能被打包成一个简单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。这也是Docker的核心思想“Build once, Run anywhere”。 Docker提供了一种便捷的描述应用打包的方式,叫做Dockerfile,这是我们Golang消息服务的Dockerfile:
# Base build image
FROM golang:1.14.7-buster AS build_base

# Force the go compiler to use modules 
RUN go env -w GO111MODULE=on GOPROXY=https://goproxy.io,direct GOSUMDB=off

# This is the ‘magic’ step that will download all the dependencies that are specified in the go.mod and go.sum file.
WORKDIR /go/cache

ENV CGO_ENABLED=0

COPY ./go.mod .
COPY ./go.sum .
RUN go mod download

# This image builds the weavaite server
FROM build_base AS server_builder

# Here we copy the rest of the source code
ADD . .
# And compile the project
# use amd64 for kit syscall.Dup2 bug
RUN GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -o app ./cmd/app/

# RUN from scratch
FROM amd64/alpine:3.13.5 as prod

RUN mkdir -p /apps/webroot/ms_message/logs
WORKDIR /apps/webroot/ms_message

# COPY static config file and binfile
COPY --from=server_builder /go/cache/app /apps/webroot/ms_message/

CMD ["./app", "-e", "env.yaml"] 

Dockerfile分为三个部分:
  1. 在Golang官方镜像中,下载依赖包,借助docker的layer caching特性加速后续镜像构建。
  2. 通过中间镜像,编译构建Go可运行文件,隔离源代码在中间镜像。
  3. 初始化服务运行路径,可运行文件拷贝到alpine镜像(轻型Linux发行版),容器启动时默认启动服务。


我们基于Dockerfile构建服务镜像,并推送到镜像仓库。
# 镜像仓库
hub="dockerhub.mydomain.com"

# 组织名
organization="mybiz"

# 镜像名
image_name="ms_message"

# 镜像版本默认前缀
prefix="version-c-"

# 镜像版本version-c-ts.random
version=$prefix$(date +%s)"."$RANDOM

echo -e "\n\033[32m>>>>>> 构建本地镜像[$image_name:$version]\033[0m"
docker build . -t $image_name:$version -f ./Dockerfile
docker tag $image_name:$version $hub/$organization/$image_name:$version

echo -e "\n\033[32m>>>>>> 登录镜像仓库\033[0m"
docker login --username=myuser --password=mypass $hub

echo -e "\n\033[32m>>>>>> 推送镜像仓库\033[0m"
docker push $hub/$organization/$image_name:$version

Kubernetes

Kubernetes是一个很容易地部署和管理容器化的应用软件系统,使用Kubernetes能够方便对容器进行调度和编排。​ 对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱出来。
5.png

Pod是Kubernetes创建或部署的最小单位。一个Pod封装一个或多个容器(container)、存储资源(volume)、一个独立的网络IP以及管理控制容器运行方式的策略选项。 Kubernetes提供了Controller(控制器)来管理Pod,Controller可以创建和管理多个Pod,提供副本管理、滚动升级和自愈能力,我们使用Deployment Controller来创建无状态服务。在服务创建之前,我们先在ConfigMap中,初始化服务的配置文件:
apiVersion: v1
data:
env.yaml: |-
server:
  http:
    name: "mybiz.ms.message.http"
    addr: ":9001"
log:
  serviceName: *appName
  level: debug
kind: ConfigMap
metadata:
name: appconfig
namespace: mybiz

为了管理Kubernetes集群,我们把集群创建好之后的KubeConfig凭证存储到$HOME/.kube/config,这样就可以使用kubectl命令来进行管理。
# 使用yaml创建ConfigMap
$ kubectl create -f configmap.yaml

接下来创建一个MyBiz空间下的Deployment mybiz-ms-message,使用前边编译好的ms_message:version-c-1627276467.15923镜像创建两个Pod,每个Pod占用2U4G资源,并挂载刚才创建的ConfigMap到本地卷中。
apiVersion: apps/v1             # Kubernetes的API Version
kind: Deployment                    # Kubernetes的资源类型
metadata:
name: mybiz-ms-message    # Pod的名称
namespace: mybiz          # Pod所属的命名空间
spec:                                       
minReadySeconds: 5    # 在Pod被视为可用之前,Pod中的容器需至少运行5s
replicas: 2                               # Pod的数量,Deployment会确保一直有2个Pod运行      
selector:                                 # 表示这个Deployment会选择Label为app=mybiz-ms-message的pod
matchLabels:
  app: mybiz-ms-message
strategy:                         # 升级策略使用滚动更新,控制更新步长和比例,
rollingUpdate:          
  maxSurge: 25%
  maxUnavailable: 25%
type: RollingUpdate
template:                             # Pod的定义,用于创建Pod,也称为Pod template
metadata:
  labels:
    app: mybiz-ms-message
spec:
  containers:       
    - image: >-         # 拉取的镜像仓库的镜像地址
        dockerhub.mydomain.com/mybiz/ms_message:version-c-1627276467.15923
      lifecycle:        # 服务生命周期管理,当服务停止前,sleep 5s 保障服务升级平滑
        preStop:
          exec:
            command:
              - /bin/sh
              - '-c'
              - sleep 5
      livenessProbe:                    # 存活检测,检测tcp 9001端口是否持续正常响应
        failureThreshold: 3
        initialDelaySeconds: 10
        periodSeconds: 2
        successThreshold: 1
        tcpSocket:
          port: 9001
        timeoutSeconds: 1
      readinessProbe:                   # 就绪检查,检测9001端口的/health,是否正常响应
        failureThreshold: 3
        httpGet:
          path: /health
          port: 9001
          scheme: HTTP
        initialDelaySeconds: 10
        periodSeconds: 2
        successThreshold: 1
        timeoutSeconds: 1
      name: app
      ports:
        - containerPort: 9001   # 对外开放端口
          protocol: TCP
      resources:        # 申请容器所需的资源
        requests:
          cpu: '2'
          ephemeral-storage: 4Gi
      volumeMounts: # 日志和配置文件持久化到存储卷
        - mountPath: /apps/webroot/ms_message/logs
          name: volume-1626771282492
        - mountPath: /apps/webroot/ms_message/env.yaml
          name: volume-1627980895220
          subPath: env.yaml
  imagePullSecrets: # 使用秘钥访问镜像仓库
    - name: mysecret
  volumes:
    - emptyDir: {}
      name: volume-1626771282492
    - configMap:                    # configmap,服务配置文件 env.yaml
        defaultMode: 420
        name: appconfig
      name: volume-1627980895220

创建Deployment:
# 使用yaml创建Deployment
$ kubectl create -f deployment.yaml

# 查看deployment READY代表就绪
$ kubectl get deploy -n mybiz
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
mybiz-ms-message   2/2     2            2           29d

接下来,我们创建一个LB类型的Service,提供对外的服务发现能力:
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet
spec:
clusterIP: 10.x.x.x
externalTrafficPolicy: Local
ports:
- nodePort: 30066
  port: 80
  protocol: TCP
  targetPort: 9001
selector:
app: mybiz-ms-message
type: LoadBalancer
status:
loadBalancer:
ingress:
  - ip: 10.x.x.x

最后一步,我们使用HPA管理Pod的弹性伸缩,HPA可以配置单个和多个度量指标,配置单个度量指标时,只需要对Pod的当前度量数据求和,除以期望目标值,然后向上取整,就能得到期望的副本数。例如有一个Deployment控制有3个Pod,每个Pod的CPU使用率是70%、50%、90%,而HPA中配置的期望值是50%,计算期望副本数=(70 + 50 + 90)/50 = 4.2,向上取整得到5,即期望副本数就是5。 这里我们创建一个期望CPU或内存利用率为70%的HAP:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: scale
namespace: mybiz
spec:
maxReplicas: 4                    # 目标资源的最大副本数量
minReplicas: 2                     # 目标资源的最小副本数量
metrics:                           # 度量指标,期望CPU或内存利用率为70%
- resource:
  name: cpu
  targetAverageUtilization: 70
type: Resource
- resource:
  name: memory
  targetAverageUtilization: 70
type: Resource
scaleTargetRef:                    # 目标资源
apiVersion: apps/v1
kind: Deployment
name: mybiz-ms-message

我们整个消息服务就创建完成了,后续在镜像编译上传到镜像仓库后,就可以直接使用kubectl进行滚动升级。
# 更新镜像 触发rollingUpdate策略
$ kubectl set image deployment/mybiz-ms-message -n mybiz app=$hub/$organization/$image_name:$version

# 查看资源状态 rollingUpdate过程会一直检测只到更新成功
$ kubectl -n mybiz rollout status deployment/mybiz-ms-message
Waiting for deployment "mybiz-ms-message" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "mybiz-ms-message" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "mybiz-ms-message" rollout to finish: 1 old replicas are pending termination...
deployment "mybiz-ms-message" successfully rolled out

接下来我们把服务和前边讲到的三条流水线就行打通。

MR流水线

MR流水线和Merged流水线非常类似,只不过触发的代码分支不同,我们可以创建一个GitLab的hook。

  • [x] Merge request events



This URL will be triggered when a merge request is created/updated/merged
把MR和Merged流水线合并为一个,有必要的话,可以在流水线里区分触发的分支是否为主干,来决定是否进行编码规范检查。

创建hook

我们先在Jenkins新建Pipeline流水线,Jenkins -> New Item -> Pipeline,在流水线配置中选中Trigger builds remotely(e.g., from scripts)。
6.png

在GitLab新增一个Merge request events事件,来触发Jenkins的Trigger,URL中的jenkinsUser和jenkinsAPIToken, 我们在Jenkins系统设置中创建,token是上边的Authentication Token。
7.png

规范检测

规范检查和linter我们直接使用GolangCI-Lint,它是一系列linter的聚合器,这里介绍一些常用的linter:
  • revive golint进阶版,检查注释,函数、变量、包命名等,支持配置启用或禁用规则
  • govet 检查赋值,atomic,buildtags等
  • gosimple 检查代码是否可以简化
  • cyclop 检查方法和包的圈复杂度
  • dupl 代码重复检测
  • gosec 一揽子安全检查规则
  • errcheck 检查是否忽略err
  • forcetypeassert 使用Comma-ok,强制类型断言
  • sqlclosecheck sql.Rows和sql.Stmt是否关闭
  • bodyclose 检查 HTTP response body 是否关闭
  • funlen 检查函数长度
  • lll 检查行长度
  • gofmt goimports gofumpt 格式化检查
  • staticcheck structcheck wsl whitespace wastedassign unparam…


这些linter统一配置在.golangci.yaml,放置到我们版本库,通常来讲我们本地会安装GolangCI-Lint,和IDE进行集成,把规范问题在本地解决,但是为了防止“君子协定”,我们统一在Server端执行检查。 比较方便的是,我们通过reviewdog配置gitlab runner,这样在linter检查后,针对代码有问题的地方,会直接追加Cmment,方便我们进行修复。
8.png

这里仅仅是做演示,所以我们直接在Jenkins流水线执行检查,如果执行失败,我们直接在流水线的控制台输出信息里查看即可。
pipeline {
stages {
    stage('规范检查') {
        steps {
          checkout([$class: 'GitSCM', branches: [[name: '**']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'git@git.mydomain.com:mybiz/ms/message.git']]])
          sh "golangci-lint run"
        }
    }
}


自动化测试

由于单元测试,我们已经mock掉了所有的真实依赖,所以直接使用go tool统计单测覆盖率,生成html覆盖率报告。当然后续也可以结合gocov生产xml报告,把报告同步到sonar等代码质量管理平台。
pipeline {
stages {
    stage('单元测试') {
        steps {
            sh "mkdir -p test"
            sh "go test ./...  -coverprofile=coverage.data && go tool cover -html=coverage.data -o test/unittest.html"
        }
    }
}


执行接口测试前,我们需要把Docker镜像仓库和Kubernetes集群都创建好配套(测试/预发/生产)的环境,把kubeconfig统一放置到Jenkins Server(更合理的方式是添加到Jenkins凭据中,并设定对应的权限),发布脚本也统规到build.sh,放置到版本库中。发布完成,通过调用yapi的server test进行接口测试,根据最终的执行结果来进行断言。
# build.sh 支持对不同环境进行编译构建和发布

!/bin/bash

project_name=$1
branch=$2

hub="dockerhub.mydomain.com"

# 镜像名使用 环境-项目名
image_name=$project_name"_"$branch

# 组织名
organization="mybiz"

# 镜像版本默认前缀
prefix="version-c-"

# 镜像版本version-c-ts.random
version=$prefix$(date +%s)"."$RANDOM

echo -e "\n\033[32m>>>>>> 构建本地镜像[$image_name:$version]\033[0m"
docker build . -t $image_name:$version -f ./Dockerfile
docker tag $image_name:$version $hub/$organization/$image_name:$version

echo -e "\n\033[32m>>>>>> 登录镜像仓库\033[0m"
docker login --username=myuser --password=mypass $hub

echo -e "\n\033[32m>>>>>> 推送镜像仓库\033[0m"
docker push $hub/$organization/$image_name:$version

echo -e "\n\033[32m>>>>>> 更新镜像\033[0m"
kubectl --kubeconfig=/home/worker/.kube/config-$branch set image deployment/mybiz-ms-message -n mybiz app=$hub/$organization/$image_name:$version

echo -e "\n\033[32m>>>>>> 滚动升级\033[0m"
kubectl --kubeconfig=/home/worker/.kube/config-$branch -n mybiz rollout status deployment/mybiz-ms-message

pipeline {
stage('接口测试') {
    steps {
        sh "sh build.sh ms_message test"
        sh "curl 'https://yapi.mydomain.com/api/open/run_auto_test?id={id}&token={token}&env_7043=test&mode=html&email=false&download=false' -o test/apitest.html"
        sh "grep '全部验证通过' test/apitest.html"
    }
}


流水线执行成功后,我们把对应的测试文件归档,清理工作目录,并通知代码审核人员进行人工CR。在执行的任何环节失败,都需要及时反馈到开发人员进行处理,当然更理想的是通过updateGitlabCommitStatus同时更新GitLab Pipeline为失败状态,并禁止代码合并。
pipeline {
post {
    success {
        publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'test', reportFiles: '*test.html', reportName: 'Test', reportTitles: ''])
        mail to: 'reviewer@mydomain.com',
             subject: "流水线执行成功: ${currentBuild.fullDisplayName}",
             body: "pipeline url: ${env.BUILD_URL}"
        deleteDir()
    }
    failure {
        mail to: 'dev@mydomain.com',
             subject: "流水线执行失败: ${currentBuild.fullDisplayName}",
             body: "pipeline url: ${env.BUILD_URL}"
    }
}


Tag流水线

tag流水线也和MR流水线类似,新增GitLab hook。

  • [x] Tag push events



This URL will be triggered when a new tag is pushed to the repository
在Jenkins的Tag流水线,增加Build Triggers。
9.png

添加Pipeline script。
pipeline {
stages {
    stage('编译构建') {
        steps {
            checkout([$class: 'GitSCM', branches: [[name: '**']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[url: 'git@git.mydomain.com:mybiz/ms/message.git']]])
            sh "sh build.sh ms_message beta"
        }
    }
    stage('beta回归测试') {
        steps {
            sh "sh build.sh ms_message test"
            sh "curl 'https://yapi.mydomain.com/api/open/run_auto_test?id={id}&token={token}&env_7043=beta&mode=html&email=false&download=false' -o test/apitest.html"
            sh "grep '全部验证通过' test/apitest.html"
        }
    }
    stage('上线审核') {
        steps{
           script {
                timeout(time: 30, unit: 'MINUTES') {
                    mail bcc: '', body: "pipeline url: ${env.BUILD_URL}", subject: "CD流水线发布审核 ${currentBuild.fullDisplayName}", to: 'manager@mydomain.com'
                    input message: '是否发布(30分钟自动取消)', ok: 'ok', submitter: 'manager'
                }
            } 
        }

    }
    stage('滚动升级') {
        steps {
            sh "sh build.sh ms_message prod"
        }
    }
    stage('生产回归测试') {
        steps {
            sh "sh build.sh ms_message test"
            sh "curl 'https://yapi.mydomain.com/api/open/run_auto_test?id={id}&token={token}&env_7043=prod&mode=html&email=false&download=false' -o test/apitest.html"
            sh "grep '全部验证通过' test/apitest.html"
        }
    }
}
post {
    success {
        publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: false, reportDir: 'test', reportFiles: '*test.html', reportName: 'Test', reportTitles: ''])
        mail to: 'reviewer@mydomain.com',
             subject: "流水线执行成功: ${currentBuild.fullDisplayName}",
             body: "pipeline url: ${env.BUILD_URL}"
        deleteDir()
    }
    failure {
        mail to: 'dev@mydomain.com',
             subject: "流水线执行失败: ${currentBuild.fullDisplayName}",
             body: "pipeline url: ${env.BUILD_URL}"
    }
}


总结

DevOps和云原生本质上都是一系列方法论和最佳实践的集合,其中的CI/CD以及云原生的一些基础设施可以作为方法论落地最好的载体,通过演化和合理的度量,目的就是不断对齐我们的研发效能的终极目标,长期、高效地交付出有价值的产品。

参考链接:
  1. 架构师图谱·上篇 https://blog.xstudio.mobi/a/230.html
  2. How to do a code review https://google.github.io/eng-p ... ewer/
  3. The CL author’s guide to getting through code review https://google.github.io/eng-p ... oper/
  4. CodeReviewComments https://github.com/golang/go/w ... ments
  5. Language Guide (proto3) https://developers.google.com/ ... roto3
  6. 字节研发设施下的 Git 工作流 https://www.infoq.cn/article/9dotsvlwscznbxjpxfqe
  7. 研发效率破局之道 https://time.geekbang.org/column/article/120801
  8. How to Get Started With Containerization https://dzone.com/articles/how ... ation
  9. YAPI自动化测试 https://hellosean1025.github.i ... .html
  10. 阿里如何定义团队的研发效能?https://developer.aliyun.com/article/689242
  11. CNCF Blog https://www.cncf.io/blog/
  12. 持续演进的Cloud Native https://e.jd.com/30457466.html?ebook=1


原文链接:https://blog.xstudio.mobi/a/243.html

0 个评论

要回复文章请先登录注册