请选择 进入手机版 | 继续访问电脑版

ITIL,DevOps,ITSS,ITSM,IT运维管理-ITIL先锋论坛

 找回密码
 立即注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

查看: 196|回复: 0

持续集成和交付流水线的8个反模式

[复制链接]
发表于 2022-5-23 17:15:38 | 显示全部楼层 |阅读模式
本帖最后由 FYIRH 于 2022-5-23 17:24 编辑 " Q( r  R' F: N) N
- ?' s: D* g% `$ W& H$ F
一、CI/CD & Pipeline

/ x- f8 D6 j2 G3 d: Q2 \2 J0 h
随着DevOps的理念在众多公司的采纳,CI/CD也渐渐落地。
% F+ T7 m' `# R) B) O( @5 R7 V

( j$ A; M2 S6 I
  • CI(Continuous Integration)持续集成,是把代码变更自动集成到主干的一种实践。CI的出现解决了集成地狱的问题,让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过一系列自动化测试,比如编译、单元测试、lint、代码风格检查。
    * l7 T4 F. r/ ^& _( i! m, R% Q

" R+ U4 X& u0 m

/ P- x2 _0 }: e) E* g; A( _( ^) |! B
  • CD包括持续交付和持续部署。持续交付(Continuous Delivery)指的是团队自动地、频繁地、可预测地交付高质量软件版本的过程,可以看做持续集成的下一个阶段,强调的是无论代码怎么更新,软件都是随时可以交付的;持续部署(continuous deployment)更强调的是使用自动化测试来保证变更的正确性和稳定性,以便在测试通过后立即部署,是持续交付的更进一步。二者的区别是,持续交付需要人为介入,需要确保可以部署到生产环境时,才去进行部署。# {" j5 A0 e( L3 \* ^8 m4 L
粘贴上传202205231711424172..png
8 b7 j1 V9 k3 `, X
CI/CD Pipeline是软件开发过程中避免浪费的一种实践,展现了从代码提交、构建、部署、测试到发布的整个过程,为团队提供可视化和及时反馈。Pipeline推荐的实施方式是,把软件部署的过程分为不同的阶段(Stage),其中任务(Step)在每个阶段中运行。
  w/ Q: n1 E% F2 |7 J& I9 f
- K2 t: j+ |- m0 A# f, j
在同一阶段,可以并行执行任务,帮助快速反馈,只有一个阶段中所有任务都通过时,下一阶段的任务才可以启动。比如图中,从git push到deploy to production的整个流程,就是一条CD Pipeline。可以利用Pipeline工具,如Jenkins、Buildkite、Bamboo,来帮助我们更方便的实施CI/CD。
6 ]; g5 n2 M# d0 U2 L( ^/ A! C' X* @# u3 J
9 A# b$ \6 j+ f
粘贴上传202205231712163891..png
# ]( J. w- N" {/ S: V4 L8 m
! e7 L; o) e; Y, ~
二、CI/CD Pipeline的反模式

! }1 h5 P' Z# {, ~9 K5 L
虽然有Pipeline广泛的应用,但我们却会听见开发人员抱怨糟糕的Pipeline对他们的伤害,如阻塞开发流程,影响变更的部署效率,降低交付质量。我们收集了项目上经常出现的Pipeline的八大反模式,按照出现频率排序,分别阐述这些坏味道,分析可能产生的原因、影响及解决方式,希望能够减少抱怨,让Pipeline更大程度上提升工作效率。
' _5 S/ }/ X$ B3 I7 v
# U+ u0 {9 \$ Y" N+ k
2.1 没有代码化" a; i4 i: }7 W8 r1 o  D  k7 {

! b& |" J/ s0 _* f" g9 s
* j2 q. d; a5 X9 q- G, v
反模式
+ e9 d) b( j# [- l7 p2 G4 d* Y+ F4 C5 l% U( l; O9 N4 b8 u; c! S

% o) V) l4 s9 LPipeline的定义没有完全代码化,进行版本控制,存储在代码仓库,而是在Pipeline 工具上直接输入shell脚本定义Pipeline的运行过程。! m/ N# ^& O" U- B  a, ~
/ |/ m" I" [9 g  h" a2 l* y
8 ~% m2 l' ^2 h0 Z- _3 F  G9 i
原因, v7 h. w& X3 m0 f' S7 C

, P- ?0 s. F3 q# ~
% G. B( `) @. N4 |
由于早期的CI工具不支持代码化,一直能够保留到现在,没有做重构和升级。 ) r, o) l- z2 [+ o7 J

* R, @( B% H7 T; V9 j
0 E( \! H" \0 b4 z/ a1 `9 T
影响  \# W# ^/ d/ q0 g* r/ ^
( a- Y9 M# i. u/ r+ \* w9 H  b8 u

: _0 A6 p$ N: S; T/ t9 f0 @, gPipeline的创建和管理都是通过CI工具的界面交互来的,难以维护,因此需要专门的管理员来维护,而有人工操作的部分就会出错,因此会降低Pipeline的可靠性。如果Pipeline因为一些原因丢失就没有办法很快恢复,就会影响交付速率。
( J0 [( Z: X4 r7 l' ]4 I$ H- c
7 }9 H4 W, @- `1 k
- ~  g# N& k4 {: p0 s# }7 g# s
解决方案1 h6 J+ m; ?* C1 P, x

4 [) o2 C/ l$ d# I

- ~% m- @: X8 I) k& bPipeline as code这个理念已经提了很多年了,在ThoughtWorks 2016年的技术雷达里就已经采纳了,需要强调的是,用于构建、测试和部署我们应用程序或基础设施的交付Pipeline的配置,都应以代码形式展现。随着组织逐渐演变为构建微服务或微前端的去中心化自治团队,人们越来越需要以代码形式管理Pipeline这种工程实践,来保证组织内部构建和部署软件的一致性。; B6 X: G1 H+ B. Y! r2 Q+ g

" |! c, ?6 w5 S+ V& w5 M7 Y* o! U. d+ E
通常,针对某个项目的Pipeline配置,应和项目代码放在项目的源码管理仓库中。同业务代码一样要做code review。这种需求使得业界出现了很多支持Pipeline工具,它们可以以标准的方式构建、部署服务和应用,如Jenkins、Buildkite、Bamboo。这些工具用大多有一个Pipeline的蓝图,来执行一个交付生命周期中不同阶段的任务,如构建、测试和部署,而不用关心实现细节。以代码形式来完成构建、测试和部署流水线的能力,应该成为选择CI/CD工具的评估标准之一。
7 G; W. g3 [( J2 |! r- M' o. V) g$ P/ A

! R# r6 b- r0 b, U0 I/ P5 g2.2 运行速度慢$ x9 x; L0 L" ?# C5 m% `6 X

6 |. Y9 f6 I% f0 y& h
8 n( i" e9 y3 H' t; V
反模式/ Z; L8 L. \: e
5 p$ }. R4 O, M
1 G" i- R5 R# x0 C& O- Q
一条Pipeline的执行时间超过半小时,就属于运行速度慢的Pipeline。(这里的运行速度与交付的产品有关,在不同的项目中,运行时长的限定也有所不同)& k/ d) j, q6 O; M. n

8 x2 c7 g8 C3 R* c

) I5 |/ R+ E& y* L5 ^+ {原因
3 C7 x, y3 h- n# ^  C
/ O1 o1 c- C& u' ^# z! M

% |6 u7 J* }, a0 o; V很多原因都会导致运行一次Pipeline时间很长,比如:
/ N! M- t: W* Q- O' \  H
  • 该并行的任务没有并行执行,等待的任务拉长了执行时间;
  • 执行Pipeline的agent节点太少,或者性能不足,导致排队时间太长,效率太低;
  • 执行的任务太重,相同测试场景被不同的测试覆盖了很多次。比如同样的逻辑在不同测试中都测了一遍;
  • 没有合理利用缓存,比如每个任务里都要下载全部依赖,在构建Dockerfile时没有合理利用layer,每次都会构建一个全新的image。. i4 I( N7 M  I4 ?# @

4 v- P5 |) n. i$ U3 `) t& q- N

+ V/ w1 j- d+ O/ I3 n影响
" l6 c0 h3 ^: B% l8 w" Q2 B# R
7 E+ v/ [  p# f+ w  p% P: y/ }
1 ?+ V, d1 c7 [
这是开发人员抱怨最多的一个反模式。敏捷开发模式需要Pipeline快速反馈结果,受这一反模式制约,在特性开发过程中,经常出现开发人员改一行代码,等半天CI的效果。如果出现一个线上事故需要修改一行代码来修复,最终需要很长的周期才能让这一更改应用在生产环境。
, V4 V* s4 h7 P$ H/ H' h; ]) U0 d/ V' X# W& X0 [* V, F

% G  O( `6 ^2 Q) t5 T解决
: I5 E# x. B$ E% g: k" h1 X1 g
+ G' Z0 f2 C4 r. b, ~. R- d; z
6 o* [# B, N9 C5 c0 y, r. t/ t6 V
不同的原因导致的Pipeline速度慢,有不同的解决方法。比如针对上面的问题,我们可以去:: B: V  K5 T4 C- O0 I
  • 检查Pipeline的设计是否合理,尽可能让任务并行;
  • 对代码的各种测试深入了解,让测试尽量正交,避免过多的重复;
  • 检查代码中的依赖,合理利用好缓存。包括Docker Image、Gradle、Yarn、Rubygem的缓存,以及Dockerfile是否合理的设计,最大化的将不可变的layer集中的开始阶段;
  • 检查执行构建的节点资源是否充足,能否在任务量大时做弹性伸缩,减少等待和执行时间。
    - M* y1 W* F) G8 M

: I2 i7 ?/ [/ ?( o$ H% z

0 [: R/ y4 M: Y, v* W2.3 执行结果不稳定
  f, o( H/ M- x( d  ^) ]/ Q$ P3 }! ]

& T+ m3 l% t8 v
粘贴上传202205231712533710..png

8 c+ ]! Y# w: T+ J; N) W, _
图3 执行多次结果不稳定
4 ~* Z. }9 D% K$ J, U, r
! ~" m3 H- r' |* o( s8 ?8 l( E
反模式
2 E8 P% X# i$ L# ]' g! |6 [7 R7 x
' X' l. N8 o; |2 p

6 ?7 U, w7 z  A& K9 M. _! W构建相同代码的Pipeline运行多次,得到结果不同。比如,基于同一代码基线,一条Pipeline构建了5次,只有最后一次通过了。
# R+ ~0 i" V1 P5 s- i) R1 y4 f+ q* Q2 L9 H' s& K, o1 [$ k0 @% J8 o
4 u9 n5 E* a3 R
原因
) s8 D; d3 E7 ^$ u. s( ~4 Q
$ A6 h) V6 E8 f( O

2 y% Q9 J! A8 C) J& ~出现执行结果不稳定的原因也多种多样,比如测试用例的实现不合理,导致测试结果时过时不过;代码中使用了不可靠的依赖源,比如来自国外的依赖源,下载依赖经常超时;由或是在Pipeline运行过程中没有合理设计各个阶段,导致有些任务同时运行冲突了。
9 o) o1 X2 J+ \0 }+ d2 m; }  ]" t$ Z5 e8 _% _; x6 d
7 B6 w9 f, F7 _
影响
# e7 @6 P/ [0 Q* P# Q$ ?* x+ ~' N/ e5 k* a: R9 ^
1 z8 D( u' ]0 g' V+ t& k3 n, u
Pipeline作为代码发布的最后一道防火墙,最基本的特性是幂等性,即在一个相同的代码基线,执行Pipeline的任意任务,不管是10次、100次,得到的结果都相同。Pipeline不稳定会直接导致代码的部署速率降低。更重要的是,影响开发人员对Pipeline的信任。如果不稳定Pipeline不及时解决,慢慢这条Pipeline会失去维护,开发最后会转向手工部署。
: n" ?+ |% i( d, G$ Z
* ^% ~" Y+ ~6 K9 v# z

7 B. @# C5 |3 h4 i* P7 w5 J解决
! V6 ]+ L; H6 E; A2 q  P8 F3 i1 ?5 L# @; b1 @

: C% i/ u6 K# B3 [" C/ K  J1 k要构建幂等的、可靠的Pipeline,就要分析这些不稳定因素出现的原因。
  • 提升测试的稳定性,比如用mock替代不稳定的源。
  • 采用Pipeline的重试功能,或者采用稳定的镜像源,或者提前构建好基础镜像。
  • 引入Pipeline的插件保证任务不会并行执行。  Y/ t4 @( r4 z$ [3 I" F+ i+ e

3 n% e$ w' Z! X$ S+ n6 S
- ?% B2 L) X: i8 ]7 e2 w
2.4 滥用job处理生产环境数据
. ~7 M7 \; Z$ F* O9 F% Q' }: N$ n5 y0 n; g2 X* x7 c8 V1 H+ |$ D3 ^

; w' Z: L- L% _反模式# w6 ?$ p$ X# j% l

5 M- Y) ?& M- ~

% j' K# [# B) c9 @4 [0 I0 e3 U使用Pipeline的定时任务的特性,运行生产环境的负载。比如经常会定期做数据备份、数据迁移,数据抓取。6 t# D( ~0 _7 S( M* P+ c: T9 `: ^; y7 V
8 l0 T+ O2 I  E/ N

5 w3 ~- i$ g# g/ u- o: i原因8 z" `; R; A4 C# Y) c5 _0 g! ]

/ H/ e# M2 a6 R( K0 {# M
( _' ]  l! a: Z& _( @! S% ?
由于对Pipeline的认识不够清晰,将重要的任务交由Pipeline做。Pipeline一旦有了某个生产环境的访问权限,做这些数据处理相关的任务就很方便,减少了很多人为的操作。
' c1 W( a$ p" V' f% }% b/ T" m& C% L, I8 U# A! \1 l; e* }1 a

+ U7 L0 C2 q7 ~; J! B2 P! y  K- T1 n影响) p  f8 m3 ]3 i2 T4 D

, ^5 i2 M$ A& `4 k7 I6 c1 \* s

3 K4 Q% n" s2 O! q5 O3 F2 |/ EPipeline是用来做构建、部署的工具,不能用于业务逻辑的执行。由于Pipeline是一个内部服务,他的SLO/SLI必定和生产环境不同,如果强依赖势必影响生产环境的SLO。假如某天Pipeline挂掉了,生产环境就无法得到想要的数据。另外,任务和Pipeline紧密耦合,是我们后面会讨论的另一个反模式。
6 ~& c. C) s1 e/ M! S0 b' D5 K
1 E( J3 |" F8 K7 [5 E% b
6 _1 Y+ L  i3 u1 u
解决
; c2 q, C, q: D) e) m$ r1 n- ]) @3 e) j  _3 S

* H: `6 I  P, d方法用生产环境自身的工具解决这种数据问题,比如 采用AWS的lambda,定时触发数据处理任务。2 H7 O0 K2 p0 z* W! Z; p  J
4 f. k5 c" J" C, m

/ N1 D2 |; F0 C' B+ H- K2.5 复杂难懂: Q( q  I5 y1 V' ]  Y3 ^

& e# N3 n% X/ L: L3 f$ l

, \- l; r) N# w  a: \( i
粘贴上传202205231713372185..png
- H3 i/ s( u& u$ M' e

( @+ m3 N' o  A1 V+ ^
( p2 {# Z0 ], `0 ?& r# V2 p0 l1 T
反模式+ s% N8 ^8 F+ W. a

2 ]) l0 V" k$ z
, M' [/ X% z( ]3 B5 O6 m
Pipeline的定义包含了太多的逻辑,复杂难懂。只有在一条Pipeline运行起来才能知道这里会运行哪些步骤,会将这个版本部署到哪些环境。
/ J' e( b( t$ @
* Y/ ^) t% u/ ~* X) ?  U
4 H$ q9 V6 H8 |+ Q8 K) z' q
原因, G( x3 k2 Q' y5 E0 [2 Q

- \7 T4 d6 q; K" R& C

5 R* p6 T1 e4 [: C! ?Pipeline的代码不够整洁。有人认为Pipeline只是给CI工具提供的,就随意编写,认为能完成指定的工作就够了。
1 i+ m: j9 ~) r; h$ j: G7 q, s5 J, b9 A$ K- Y7 _# J
) x" W) I2 Z1 H  L* q4 v& ~2 x
影响
1 m: ?( J% ~! w5 b$ w' W) P+ g3 W$ T5 f

6 J' W3 }' f9 A4 D! k4 @- _. pPipeline的复杂性,会直接提升学习成本。如果想重复执行上一次构建,会花费较长时间。
9 ?1 G, m: C5 @- V& N) ^2 `0 v- w0 M3 M( B
6 t+ o- X9 s# j: ?. D& Z
解决5 C1 G  L) m! o7 A& z* T
, o1 [$ E: }) Z5 A
4 V8 i4 F4 `+ c4 J, f4 q% m
Pipeline的代码要简洁,把复杂性放在部署脚本或代码侧。通过每个阶段的的标题可以直接了解所要执行的任务。如果存在很多相同的逻辑,可以通过开发Pipeline的Plugin来简化配置。$ M: g3 z  |3 f
5 v9 i& ?$ j4 K% `

  h* D; z+ k- I6 }9 E2.6 耦合太高
0 @- ^2 P) f0 g4 e2 d& c6 y' b, ]3 C  S# `- E
% ?9 I5 @. _, c, {% X! K
粘贴上传202205231714057154..png
# q, X7 D5 [: D& g% v  T

+ T/ ?# ?1 i' j% H

2 V: n0 b, S1 u" t反模式
" C$ e3 z* L  j0 _2 y

% @7 U5 P! O) x
) s9 r) F: n8 u2 s$ p* v
Pipeline跟运行它的CI工具紧密耦合,以至于无法在本地重复相同的步骤。表现可能多种多样:
  • Pipeline的定义跟构建工具紧密耦合,包含了Pipeline工具特有的参数以及CLI命令。比如在配置中使用BUILDKITE_BUILD_NUMBER,BUILDKITE_QUEUE等等。结果就是本地运行的方式或结果和Pipeline上运行的方式以及结果不一致。
  • 在Pipeline的任务中写了一大段脚本,或者直接使用命令加上一堆参数,以至于在本地想跑测试需要在Pipeline的配置中找命令并且在本地粘贴。
  • 不做环境隔离, 测试,编译,部署等都依赖于运行时环境。可能出现Pipeline 因依赖的软件/库等版本不一致而导致的不一致的情况,通常还很难排查。; C* [4 S+ R( v# [; k
7 w; @. [- O$ K3 [( G: Z

9 \$ Z; N6 V! i5 [: d影响

6 C3 @6 M. K9 C4 D7 |! Y' K4 X, t* L( S* G" Y9 ~

/ {5 y: ]$ R; G+ z因为本地不方便调试,所变更的失败概率会大大增加。如果变更用来修复一个Bug,由于不做环境隔离,会导致故障修复周期拉长。
" O5 E; e. E6 j: I/ s! Z3 M  ]8 ^5 `& ]9 K, L& R
, l/ _% o3 [; v% m' X2 V
解决
7 R7 _; _  f  H* y# Q7 h8 h
8 e: w/ Q7 `- h; E/ ]1 v$ l

2 @8 i8 o( r2 n& H8 T: JPipeline的每个step都用脚本封装起来,脚本里不使用Pipeline工具特有的参数,并且保证本地运行时和Pipeline上保持一致。* W3 {% @# a/ o3 O. x

9 m% L  j) A4 U& B9 \1 ~- [

% N; {/ k8 y% d& r+ g2.7 僵尸Pipeline  j0 B& M) j# X
. U, W+ I$ [5 t% X* ^

. G4 [' _- A( @! N5 Y0 C" w4 d反模式
4 U  C* L- {/ n0 H( L' B6 W+ }; E* b8 X% n' p, B- B. `6 p

5 h5 ]6 d! @1 |& L8 b0 ~* j一条Pipeline年久失修,很久没有执行过,而且最后一次的构建是失败的。& ]# K' f, c) c) U# N( H6 _

- r) x- w0 h6 \/ D
8 u: J: @- v- _3 v3 \
原因
5 V. J  H" R) W4 w# o- o% J! J) j) S" b0 m0 j# y

: W3 Q+ m' Z* L0 U8 j; \- w: ]这种反模式通常出现于不再活跃开发的项目上,因此很久没有执行过Pipeline。
( y  _& z# I6 b: r3 I: v0 A
% J( ~" J2 \* |0 t) f

5 L, p" O/ w7 v: b1 z: M影响5 o+ Q' a- D+ Q8 X% N$ B6 K/ G
6 ]8 }, [5 \8 p

- Z. g) ^" S1 J, M' XPipeline的结果反应的是一个项目的状态。由于软件产品迭代速度快,这个软件的依赖可能已经发生了巨大的变化,一旦运行,大概率会出错。假如这个项目目前出现了一个事故,需要提交代码,就得先修复项目的Pipeline,才能确保提交修复代码。+ G/ L* T2 Q: H: K% O7 ~
" ?% B$ z" E1 [

7 C* b9 q& |' |1 _! G8 V, @* |解决
5 o! f3 k1 i( p/ e" a1 O$ b+ q1 h- y  j
7 ?5 t% f' e7 S. v7 K
' C1 J1 F' a- z/ z
针对常年没有提交的Pipeline,我们建议让Pipeline周期的执行,出现问题立即修复。如Github的Dependabot,能保证项目的依赖始终是是最新的,而且能让Pipeline执行,提早发现问题。3 I2 o9 u# B$ ^% }
, t1 c7 m6 L/ q. F

  K8 j; f; e, F1 M2.8 需要人工介入
8 {$ r0 {$ h" E9 G0 t, y' J: A2 I
- _- M8 `9 x: E
反模式( y8 G; Z- c4 j- t# P" x: K5 [* k4 X
( C! \% L# V9 t; T9 p1 X' \

2 J+ T+ B+ y6 v# X; S通常项目上会有一个专职Ops,在项目可以发布的时候手动触发部署流程,或者需要传递很多参数,让Pipeline运行起来。6 q8 r  C' k3 t% m. S
" y# ]$ `% R2 L, Y; X: ?
/ s& x/ `2 ^9 A: R3 O  g8 F
原因- z* H; l" F+ j
9 Z2 h) m3 g4 Z! N8 L

2 q7 U: ]% \6 P: S包括项目的流程繁琐,需要反复确认;DevOps成熟度不够,没有实现持续部署;或者CI的测试覆盖不够,CI通过后还要进行更多的测试才能部署。
+ X0 F! z; D  i2 {
1 X* Z; M: P  y( @( K- Y8 \* z
9 X& j  v+ m9 {3 ^
影响
$ N% C; Q8 W" L3 d  g' w
, V( m. J) j4 i! s+ S

/ Q9 \0 d& W7 c. ]4 B) @这些Pipeline需要专人盯着,去点某些按钮。会直接影响产品的交付速率和代码部署频率。% D" v0 i5 v6 q7 E# C

6 [/ P0 n. j8 k5 m6 h
) E" Z6 [0 J. v& ]3 {
解决, b' B1 Z. s" ?1 U
" H- ]. r& i1 _0 s1 c1 ]8 s

7 ~4 E8 j# L8 p- f让项目的运行更加敏捷,减少Pipeline定义中的阻塞按钮,将手工测试自动化后集成到Pipeline中。
2 }0 \; n) f* N6 Y) R6 g
6 ^) i- N9 \, p2 r# A* n; e% a  d
最后
希望通过本篇文章,意识到项目中CI/CD Pipeline的问题,使其发挥更大的价值。(来源:Thoughtworks洞见)

4 v4 K; O6 `1 K! i; M
5 p/ F- O4 W" ]/ v3 Z6 V7 W5 R( O; k. J, V% a8 w- p5 l! F$ K# G
# G" h  {! w3 ]2 M' H




上一篇:【万字长文】一文看懂持续部署按需发布!DevOps部署和发布方法大全
下一篇:了解 DevOps,必读这十本书!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

参加 ITIL 4 基础和专家认证、长河ITIL实战沙盘、DevOps基础级认证、ITSS服务经理认证报名

QQ|ITIL ( 粤ICP备11099876号 )|appname

GMT+8, 2022-8-10 07:10 , Processed in 0.102265 second(s), 32 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表