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

 找回密码
 立即注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

查看: 618|回复: 0

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

[复制链接]
发表于 2022-5-23 17:15:38 | 显示全部楼层 |阅读模式
本帖最后由 FYIRH 于 2022-5-23 17:24 编辑 3 `, J5 C- z3 @0 D+ r8 `" c4 X

. `$ z6 h! q% ^! C6 M" f
一、CI/CD & Pipeline

$ q) v3 B4 m1 o
随着DevOps的理念在众多公司的采纳,CI/CD也渐渐落地。
1 H% C  K& g/ o

( Z0 {1 o  I7 f+ H8 p7 S# R
  • CI(Continuous Integration)持续集成,是把代码变更自动集成到主干的一种实践。CI的出现解决了集成地狱的问题,让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过一系列自动化测试,比如编译、单元测试、lint、代码风格检查。
    1 Z3 d+ U4 m8 A
5 t8 Q9 l* y' n& L& q  k

0 L  w6 Z* L6 P, u
  • CD包括持续交付和持续部署。持续交付(Continuous Delivery)指的是团队自动地、频繁地、可预测地交付高质量软件版本的过程,可以看做持续集成的下一个阶段,强调的是无论代码怎么更新,软件都是随时可以交付的;持续部署(continuous deployment)更强调的是使用自动化测试来保证变更的正确性和稳定性,以便在测试通过后立即部署,是持续交付的更进一步。二者的区别是,持续交付需要人为介入,需要确保可以部署到生产环境时,才去进行部署。
    # X# L3 C: U. N
粘贴上传202205231711424172..png

# e2 @* `( J3 z0 B' n) }CI/CD Pipeline是软件开发过程中避免浪费的一种实践,展现了从代码提交、构建、部署、测试到发布的整个过程,为团队提供可视化和及时反馈。Pipeline推荐的实施方式是,把软件部署的过程分为不同的阶段(Stage),其中任务(Step)在每个阶段中运行。
; t; \4 [  I; X/ j* p+ {8 G
1 R  e# B- z& T6 h& v; m
在同一阶段,可以并行执行任务,帮助快速反馈,只有一个阶段中所有任务都通过时,下一阶段的任务才可以启动。比如图中,从git push到deploy to production的整个流程,就是一条CD Pipeline。可以利用Pipeline工具,如Jenkins、Buildkite、Bamboo,来帮助我们更方便的实施CI/CD。. `! j9 V! p& Z9 Y

% B4 t. y& ?* s/ L
( P: X2 `. B( w* }( \1 ~
粘贴上传202205231712163891..png
: \) G" ~! x- E7 S) V" f& z
9 ?$ }4 H) `- b3 P0 e7 Z, ]
二、CI/CD Pipeline的反模式
1 i3 T; J) p; i- m
虽然有Pipeline广泛的应用,但我们却会听见开发人员抱怨糟糕的Pipeline对他们的伤害,如阻塞开发流程,影响变更的部署效率,降低交付质量。我们收集了项目上经常出现的Pipeline的八大反模式,按照出现频率排序,分别阐述这些坏味道,分析可能产生的原因、影响及解决方式,希望能够减少抱怨,让Pipeline更大程度上提升工作效率。
7 z' B8 b& t' I1 h/ h1 g: H" g9 W& y! |' N% v; @6 f& p+ e' U
2.1 没有代码化+ d. G% }' {4 ~- _+ i2 ~; Y
9 }: \5 l8 p; K* n7 s

* T1 C6 {( B" F  b5 i$ I, f反模式- r: t4 T( z& t% d0 ~& e

1 M* }% {; J+ R* m' i8 T# t+ h
- A$ s8 v6 L; V' U% ?+ l3 I
Pipeline的定义没有完全代码化,进行版本控制,存储在代码仓库,而是在Pipeline 工具上直接输入shell脚本定义Pipeline的运行过程。
" S  Y  x8 D/ `5 o9 k! q, f; b
: a6 T* S% w1 g5 D, [
, G. t+ t5 F2 U& d
原因1 I  N# e1 U& W2 @3 r& E- p
  e; q% |( m5 @8 S. a
" l. }$ Y5 \% m: H; I
由于早期的CI工具不支持代码化,一直能够保留到现在,没有做重构和升级。 5 N0 j; l3 C* |; q: C7 U
: _- w, _  s9 P: \% [

. }6 U) o5 Z  N% J$ m影响
  g& K2 ]+ D4 z( X8 n/ k3 a1 W2 q
0 |: [1 C9 {# U; Z7 l0 M

! |3 o6 x# y6 c5 @' ZPipeline的创建和管理都是通过CI工具的界面交互来的,难以维护,因此需要专门的管理员来维护,而有人工操作的部分就会出错,因此会降低Pipeline的可靠性。如果Pipeline因为一些原因丢失就没有办法很快恢复,就会影响交付速率。
! `7 e6 i3 K' f$ d( R) y! K- I0 a5 L5 a- b6 x  K" O

6 z: ^! o3 k# A) s+ d解决方案
9 }  k+ G* ], n& B( Q
- K% g- k/ i, }

. F$ n# T8 S/ Y4 ~/ f+ T6 RPipeline as code这个理念已经提了很多年了,在ThoughtWorks 2016年的技术雷达里就已经采纳了,需要强调的是,用于构建、测试和部署我们应用程序或基础设施的交付Pipeline的配置,都应以代码形式展现。随着组织逐渐演变为构建微服务或微前端的去中心化自治团队,人们越来越需要以代码形式管理Pipeline这种工程实践,来保证组织内部构建和部署软件的一致性。% p0 i; c% `9 h- y) q
. U# P5 R7 i* B
通常,针对某个项目的Pipeline配置,应和项目代码放在项目的源码管理仓库中。同业务代码一样要做code review。这种需求使得业界出现了很多支持Pipeline工具,它们可以以标准的方式构建、部署服务和应用,如Jenkins、Buildkite、Bamboo。这些工具用大多有一个Pipeline的蓝图,来执行一个交付生命周期中不同阶段的任务,如构建、测试和部署,而不用关心实现细节。以代码形式来完成构建、测试和部署流水线的能力,应该成为选择CI/CD工具的评估标准之一。) d/ @/ m4 F2 K  q1 e
8 l5 B5 Q1 L8 f: p" l5 m# \

* W1 o/ r" a* M: M2.2 运行速度慢8 h5 t$ w+ e6 M$ n0 \0 R9 b

  ?" X8 [  O* ~3 m2 _
5 {+ \- U4 y* O6 E, j& f% w
反模式
& {8 a* X& E. N  M; t  r) @2 Z+ m% p
( {8 K$ a7 {: i- t& _8 s

  v3 y/ l7 i! @# v' Q一条Pipeline的执行时间超过半小时,就属于运行速度慢的Pipeline。(这里的运行速度与交付的产品有关,在不同的项目中,运行时长的限定也有所不同)' G$ G# g. [# a  U3 o. w

# L3 g2 h, }' |
3 k! f% B: U& F: E" J9 Y
原因/ {1 i. V* L8 h
* P7 c  O" ~$ l- i; w0 ~: d

$ {+ |2 R* C; D; K很多原因都会导致运行一次Pipeline时间很长,比如:: |+ g. A8 J3 w; E) d* _/ K
  • 该并行的任务没有并行执行,等待的任务拉长了执行时间;
  • 执行Pipeline的agent节点太少,或者性能不足,导致排队时间太长,效率太低;
  • 执行的任务太重,相同测试场景被不同的测试覆盖了很多次。比如同样的逻辑在不同测试中都测了一遍;
  • 没有合理利用缓存,比如每个任务里都要下载全部依赖,在构建Dockerfile时没有合理利用layer,每次都会构建一个全新的image。0 i- u8 c( I9 h6 {# P

9 |4 `/ V' W2 [2 K
; \. r+ u% i2 @  M& m5 d
影响
4 `/ Z$ d# o2 H; W5 d+ y% T+ K/ ?7 h6 T
+ O$ c# k$ h6 n3 s

- D- a4 k! d9 o* S* V这是开发人员抱怨最多的一个反模式。敏捷开发模式需要Pipeline快速反馈结果,受这一反模式制约,在特性开发过程中,经常出现开发人员改一行代码,等半天CI的效果。如果出现一个线上事故需要修改一行代码来修复,最终需要很长的周期才能让这一更改应用在生产环境。
+ b) p$ v  D: W! V, ~
$ O4 E0 z5 l( g( j+ l
9 G/ x9 H" a1 a  d; ]) c: n
解决
% m, F* _2 X+ p* B
7 w* M' S0 I0 J2 F' b7 @: t

. S( M2 U5 t" C, R6 H4 k不同的原因导致的Pipeline速度慢,有不同的解决方法。比如针对上面的问题,我们可以去:
  t: w" h5 l* Y7 {0 H7 w9 y  i
  • 检查Pipeline的设计是否合理,尽可能让任务并行;
  • 对代码的各种测试深入了解,让测试尽量正交,避免过多的重复;
  • 检查代码中的依赖,合理利用好缓存。包括Docker Image、Gradle、Yarn、Rubygem的缓存,以及Dockerfile是否合理的设计,最大化的将不可变的layer集中的开始阶段;
  • 检查执行构建的节点资源是否充足,能否在任务量大时做弹性伸缩,减少等待和执行时间。
    + u# Z4 ~" v: k8 z: x% @

3 ~5 h' r# f- {9 u+ P1 b' M% Y

% r& H& C7 h0 j  x9 [4 K1 o2.3 执行结果不稳定
8 h! ?; {% V. A9 U: E5 @3 y% q' N  W4 \' M; Q3 Z1 T" C

7 I8 k! S# c  [  j) L/ M+ X
粘贴上传202205231712533710..png

$ n& w( C4 ~4 B3 w
图3 执行多次结果不稳定

0 H% v% H+ P$ J+ T4 M, d1 G; `* j! l/ i; D8 B
反模式
/ A7 x; Y) e* n: m
2 g! x& T1 |1 c0 k9 ]( `
1 l1 c7 j# N$ d; c) ^
构建相同代码的Pipeline运行多次,得到结果不同。比如,基于同一代码基线,一条Pipeline构建了5次,只有最后一次通过了。% ]! E& J+ n. e8 l& p  T
4 i1 n# _3 t. \4 z+ ^2 ?; Z# I
7 g  c1 b8 H7 N" D$ K
原因- d' P5 [5 \1 k

7 P5 b! C! |) L8 O+ ^
6 x7 q! T4 _) n7 Y3 a
出现执行结果不稳定的原因也多种多样,比如测试用例的实现不合理,导致测试结果时过时不过;代码中使用了不可靠的依赖源,比如来自国外的依赖源,下载依赖经常超时;由或是在Pipeline运行过程中没有合理设计各个阶段,导致有些任务同时运行冲突了。
: a0 N. ]( H5 C' V' {+ l; H# f7 e5 L" j, a
# P( l5 D$ v9 T0 ?0 a) ]1 n5 U3 s
影响
0 Y. v% z. U, g8 C5 ^
, [7 E8 r" B  Z7 C8 Z/ p# y$ w- x
0 n; n. j6 x( W8 l( A' b2 `, K
Pipeline作为代码发布的最后一道防火墙,最基本的特性是幂等性,即在一个相同的代码基线,执行Pipeline的任意任务,不管是10次、100次,得到的结果都相同。Pipeline不稳定会直接导致代码的部署速率降低。更重要的是,影响开发人员对Pipeline的信任。如果不稳定Pipeline不及时解决,慢慢这条Pipeline会失去维护,开发最后会转向手工部署。
) A# I) A0 }0 W7 ~* I% ?
- {7 k! O. m* c# e. i: s. U

6 D$ _5 ]" I( B解决' F: \4 f9 ?" A, @6 n& |/ U2 h
* h( S  y! j; g; F% r7 y7 F

# I9 {' g, R1 n7 W3 `1 N& u要构建幂等的、可靠的Pipeline,就要分析这些不稳定因素出现的原因。
  • 提升测试的稳定性,比如用mock替代不稳定的源。
  • 采用Pipeline的重试功能,或者采用稳定的镜像源,或者提前构建好基础镜像。
  • 引入Pipeline的插件保证任务不会并行执行。/ |& k2 t' L7 X( r+ J0 \5 N
4 \* R8 f9 Y  W# O; w
/ p* `* Q. s% |& Y* F/ s
2.4 滥用job处理生产环境数据
, g  i0 U+ f1 l/ G3 M% z4 D# L8 e9 D

' E" D6 N5 L; }3 x( b反模式/ d/ t3 @$ G+ e7 s. \

7 ]7 [) W' v6 p# r8 F

5 ^8 \; P3 y+ N! F使用Pipeline的定时任务的特性,运行生产环境的负载。比如经常会定期做数据备份、数据迁移,数据抓取。
/ b& y$ ^$ w& T9 ?# w! |, D' a9 Q$ S% F3 x0 e! M
: K" w; Y3 A) A0 u4 q0 V
原因
4 t; }$ v" \+ i1 H% i9 A
; j, T2 g9 p% B! u
) f8 N: C/ B; K/ u3 X% ~! b9 R
由于对Pipeline的认识不够清晰,将重要的任务交由Pipeline做。Pipeline一旦有了某个生产环境的访问权限,做这些数据处理相关的任务就很方便,减少了很多人为的操作。
5 Z# D4 l& p2 t2 K3 i" `8 s5 G0 v; ~  {# {, E  f2 W- L5 q

% m3 l9 [* g) Z3 p1 Y影响
1 i/ J' ~6 {$ G  P- L- w. p+ n0 e, N( N- _
* |* W( {8 n& X' J4 }2 j
Pipeline是用来做构建、部署的工具,不能用于业务逻辑的执行。由于Pipeline是一个内部服务,他的SLO/SLI必定和生产环境不同,如果强依赖势必影响生产环境的SLO。假如某天Pipeline挂掉了,生产环境就无法得到想要的数据。另外,任务和Pipeline紧密耦合,是我们后面会讨论的另一个反模式。( n4 d/ A+ `* y, k  A- l( R
' H$ B5 o; z( P

( e" Q$ w9 [$ P2 \  g3 a解决
& i3 ~# q& ~9 n& Y. h$ m. U( g1 J6 `6 j

6 I- M! w. K( x5 K' w! F方法用生产环境自身的工具解决这种数据问题,比如 采用AWS的lambda,定时触发数据处理任务。
" f5 m( |! T$ V' T) C! q! w: x* U, M6 u  t* {2 N* H

' C3 S4 B3 {9 @/ A+ |2.5 复杂难懂
" H% X7 s& z, k2 |- x5 K
; n/ s  i$ q3 E$ V0 N4 _0 b

' Y/ J" `4 i$ Y+ k; L
粘贴上传202205231713372185..png

2 H$ ?% X8 O3 l0 v; z* F4 d7 l9 W) X* a& P6 d, U4 J1 z

! z$ d+ c" U$ k: V- L反模式
* [. ~% F# c( A) r9 F3 b6 ^' _# y* v$ {; p5 C+ L: `
# [$ i+ W: ~2 \3 k
Pipeline的定义包含了太多的逻辑,复杂难懂。只有在一条Pipeline运行起来才能知道这里会运行哪些步骤,会将这个版本部署到哪些环境。& k+ ]1 x( }! Z% N( o
+ B4 H/ ~' o8 e# R8 D

/ h9 V0 z- _; a5 C) p3 a9 I原因
6 N+ G" r& \1 L& E: z2 p2 D1 c, i  f! q2 I

- g0 V# k1 u. x6 W& p7 nPipeline的代码不够整洁。有人认为Pipeline只是给CI工具提供的,就随意编写,认为能完成指定的工作就够了。
. C* {( w% j+ \+ H& i/ x" P+ C7 J7 i1 t6 Y

& W$ S) ~( o: K影响* @  Z0 ]% q4 k; Q

  y& j3 Q  ^2 I  G. ]
) S8 S6 _! \$ Q# a  G
Pipeline的复杂性,会直接提升学习成本。如果想重复执行上一次构建,会花费较长时间。
5 Y0 S$ P7 _/ G% @  g' |: {0 S3 C, S

4 c$ [6 q6 u. L解决
1 n5 I0 s; }& u0 q: q0 ]" M% l* |- C! N
' j3 B; l9 L! e. E4 u
Pipeline的代码要简洁,把复杂性放在部署脚本或代码侧。通过每个阶段的的标题可以直接了解所要执行的任务。如果存在很多相同的逻辑,可以通过开发Pipeline的Plugin来简化配置。
+ q9 x+ Y" D+ @2 t
/ s* t8 i+ r& H( }/ I3 `; i

7 L7 f8 Z1 t' I% \3 F2.6 耦合太高  Y! Z% z& o  i! M, q& R( d

6 Y  F4 }7 B# ^, R3 {- ?# e' W

0 b. [4 f( \8 F' p/ |1 e
粘贴上传202205231714057154..png
3 a- p7 U% G$ J, z! I
' E( I) w$ h1 k; p/ l- R* }

) c, R9 ]$ H2 i4 s8 w! G反模式

4 D! [; E/ F: K+ z8 b' _5 W) d4 w$ F4 q  B' T/ p/ E
3 _% X5 M! o# v0 t+ Q
Pipeline跟运行它的CI工具紧密耦合,以至于无法在本地重复相同的步骤。表现可能多种多样:
  • Pipeline的定义跟构建工具紧密耦合,包含了Pipeline工具特有的参数以及CLI命令。比如在配置中使用BUILDKITE_BUILD_NUMBER,BUILDKITE_QUEUE等等。结果就是本地运行的方式或结果和Pipeline上运行的方式以及结果不一致。
  • 在Pipeline的任务中写了一大段脚本,或者直接使用命令加上一堆参数,以至于在本地想跑测试需要在Pipeline的配置中找命令并且在本地粘贴。
  • 不做环境隔离, 测试,编译,部署等都依赖于运行时环境。可能出现Pipeline 因依赖的软件/库等版本不一致而导致的不一致的情况,通常还很难排查。$ B( J2 P7 o+ q/ u# I! P
, Y- G1 |/ h% k' e
& U0 ?$ @2 x1 g! K6 z
影响

6 e3 z8 k$ X8 @* s% R5 D" N" o6 P
* O: h( l7 O! n. ?) `, j4 ]$ |& A4 G

% o% b( A' E8 Y6 t因为本地不方便调试,所变更的失败概率会大大增加。如果变更用来修复一个Bug,由于不做环境隔离,会导致故障修复周期拉长。
" }% u- c/ ^9 G7 f9 |
& N0 A0 R& G9 b" |
9 @+ g+ t5 }/ S% S% ~4 p
解决1 K) v0 U3 v8 _% o7 K& v4 M
/ N0 ]2 u: o2 z

* m4 V) q6 e- Y! I3 f# k2 B9 ePipeline的每个step都用脚本封装起来,脚本里不使用Pipeline工具特有的参数,并且保证本地运行时和Pipeline上保持一致。: H& U: j+ C# O

3 f. E+ O; n* l- O

% `1 s! P) l, S& S7 c+ A: n2.7 僵尸Pipeline* g& y2 ]% X( T  e; s8 n

: t; ~5 a7 I6 F% V# v; P# c. t
- \: T* N  S/ W9 T
反模式
/ M8 F7 j" r) B$ C  w9 H9 ]. k, \; ?% w% [3 b1 J
# m4 o/ d6 l' f" u. k- B0 S, j0 ?
一条Pipeline年久失修,很久没有执行过,而且最后一次的构建是失败的。% b, P+ H% Y* F" C4 c% }2 \1 ?& i; P

5 ^  {' O5 Z# F" [* {. k, [

3 f4 |! Q0 y' @原因+ _5 n/ C: d8 z7 \5 I; x

' I; m- j: ~& S/ r6 `

& |% Q" \2 @& s3 e这种反模式通常出现于不再活跃开发的项目上,因此很久没有执行过Pipeline。
# E! ]9 f* d1 w5 E3 S! j, M! C) l% G' y. P7 t3 P7 C2 g3 E+ p
+ I  _- J! k# l* @" N- \- K
影响
% e8 s1 S% h% G' \' u
2 V. \" F$ w! [$ S
, \4 @2 v# ?6 C- m! L' ^
Pipeline的结果反应的是一个项目的状态。由于软件产品迭代速度快,这个软件的依赖可能已经发生了巨大的变化,一旦运行,大概率会出错。假如这个项目目前出现了一个事故,需要提交代码,就得先修复项目的Pipeline,才能确保提交修复代码。
) V! H( w+ f' {# V) Q/ m& T  b" @) w7 t* |4 _; ?

! I+ }% y0 g" Z: P* \, z$ P解决
2 ^5 O' `' _( N
' \. R; b4 k; }+ L( b$ y  ]/ @7 E

: R: X+ f* T3 B% l! j& }! W: ~! U& b针对常年没有提交的Pipeline,我们建议让Pipeline周期的执行,出现问题立即修复。如Github的Dependabot,能保证项目的依赖始终是是最新的,而且能让Pipeline执行,提早发现问题。$ z' a( Y" R' k, h5 o

' N( x/ ?9 j7 u! z( ]/ a

. u& d3 S7 ?0 V1 C& v" E# N# ]5 x2.8 需要人工介入2 `2 r$ u- T. g( a8 i2 o3 Z" ]4 H
7 F% o6 T& n4 f2 m. N+ ?3 y

# z% ?, f. X% @. U反模式8 d' C- `9 t* Y* m! j% U

, ]# f7 Y4 \" n  T1 `
& O4 B9 P. L2 ]6 I0 I
通常项目上会有一个专职Ops,在项目可以发布的时候手动触发部署流程,或者需要传递很多参数,让Pipeline运行起来。2 a# h% o. L! `! I- m# C0 Z
2 ]" `( R* N% X
& z" m$ t& u* I2 l
原因. b7 w2 g5 o, B# Y8 D8 a

2 w* y; _' L3 W' d* w

9 E: A# x5 |4 A2 d+ A包括项目的流程繁琐,需要反复确认;DevOps成熟度不够,没有实现持续部署;或者CI的测试覆盖不够,CI通过后还要进行更多的测试才能部署。
: W5 y- e) f+ H' Q- U( j2 `/ u# R2 q8 C- D) H
! w! Q/ D  z3 G1 n
影响( R$ c9 j, }0 N
$ q3 o/ \, V: {  d
7 n- c8 Y: ]* X
这些Pipeline需要专人盯着,去点某些按钮。会直接影响产品的交付速率和代码部署频率。1 ?6 r3 ]2 l  [: w0 s

5 ?( c  I4 U; \: M; H
( W0 y; |8 E) D  D- v: c
解决& p; t+ R+ e6 V! d7 p/ A

# ~7 o8 C0 [0 y9 x  F1 R% R5 w

4 H2 F# a# h  O让项目的运行更加敏捷,减少Pipeline定义中的阻塞按钮,将手工测试自动化后集成到Pipeline中。" t  W7 w; S1 V7 T7 p0 f, r& a: z
0 F. ^6 B6 E: {( G
最后
希望通过本篇文章,意识到项目中CI/CD Pipeline的问题,使其发挥更大的价值。(来源:Thoughtworks洞见)

$ e6 {% g' q5 T
/ y3 c4 W' h+ f& W* m$ o3 H6 }5 ?* N8 `

9 g3 O2 Z2 t/ h& L5 s% A, o




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

本版积分规则

参加 ITIL 4 基础和专家认证、长河ITIL实战沙盘、DevOps基础级认证、ITSS服务经理认证报名
ITIL(R) is a registered trademark of AXELOS Limited, used under permission of AXELOS Limited. The Swirl logo is a trademark of AXELOS Limited, used under permission of AXELOS Limited. All rights reserved.

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

GMT+8, 2023-6-8 12:27 , Processed in 0.143693 second(s), 30 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

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