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

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

 找回密码
 立即注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

查看: 95|回复: 0

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

[复制链接]
发表于 2022-5-23 17:15:38 | 显示全部楼层 |阅读模式
本帖最后由 FYIRH 于 2022-5-23 17:24 编辑 2 q; {" l$ q2 i5 ~3 m' V% h
9 D3 o3 y) R# ~9 n# O% t2 {
一、CI/CD & Pipeline
; R6 n' K- Y6 s& L
随着ITILxf.com" target="_blank" class="relatedlink">DevOps的理念在众多公司的采纳,CI/CD也渐渐落地。
4 ]+ V! q  p0 h1 |1 P( U7 ~2 B
( P9 o0 a7 g, `' s- P
  • CI(Continuous Integration)持续集成,是把代码变更自动集成到主干的一种实践。CI的出现解决了集成地狱的问题,让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过一系列自动化测试,比如编译、单元测试、lint、代码风格检查。
      Y2 |3 g; F1 C1 U5 a

+ G8 [  e4 i+ L

/ o6 n, q- ?- }: y
  • CD包括持续交付和持续部署。持续交付(Continuous Delivery)指的是团队自动地、频繁地、可预测地交付高质量软件版本的过程,可以看做持续集成的下一个阶段,强调的是无论代码怎么更新,软件都是随时可以交付的;持续部署(continuous deployment)更强调的是使用自动化测试来保证变更的正确性和稳定性,以便在测试通过后立即部署,是持续交付的更进一步。二者的区别是,持续交付需要人为介入,需要确保可以部署到生产环境时,才去进行部署。7 [" W& I# c5 e
粘贴上传202205231711424172..png
! Q+ V" E. a/ t' _: p8 g  j0 B
CI/CD Pipeline是软件开发过程中避免浪费的一种实践,展现了从代码提交、构建、部署、测试到发布的整个过程,为团队提供可视化和及时反馈。Pipeline推荐的实施方式是,把软件部署的过程分为不同的阶段(Stage),其中任务(Step)在每个阶段中运行。9 {' V: N! }, o2 d; d* a( A
$ f& ~! d4 Y# }& N4 Q5 `
在同一阶段,可以并行执行任务,帮助快速反馈,只有一个阶段中所有任务都通过时,下一阶段的任务才可以启动。比如图中,从git push到deploy to production的整个流程,就是一条CD Pipeline。可以利用Pipeline工具,如Jenkins、Buildkite、Bamboo,来帮助我们更方便的实施CI/CD。
) c- O- l" E. I. Q; p& [7 ?
+ ^9 S& c; j9 y9 x" z4 z

5 W) \/ q1 q6 w3 f
粘贴上传202205231712163891..png
7 k+ E1 v/ D  r8 ^+ \: M3 v+ U6 P

) @: D) G1 X& I5 W8 q$ S
二、CI/CD Pipeline的反模式

. j- c! l+ U: d
虽然有Pipeline广泛的应用,但我们却会听见开发人员抱怨糟糕的Pipeline对他们的伤害,如阻塞开发流程,影响变更的部署效率,降低交付质量。我们收集了项目上经常出现的Pipeline的八大反模式,按照出现频率排序,分别阐述这些坏味道,分析可能产生的原因、影响及解决方式,希望能够减少抱怨,让Pipeline更大程度上提升工作效率。! ^4 `& e8 R3 N( k: H2 `0 ~
$ K' T- G( d3 _" k; ^# T
2.1 没有代码化
2 _# `+ J& a7 s% g0 w. y' `" K
# I0 A4 s, b1 T* c8 A; Q8 {
9 ]. I) w+ B4 B" u
反模式1 Y/ m: T# b1 D

2 h* K- Y1 R2 G7 b

( ]& c* m; {; ^5 s6 H6 n+ vPipeline的定义没有完全代码化,进行版本控制,存储在代码仓库,而是在Pipeline 工具上直接输入shell脚本定义Pipeline的运行过程。
8 _" J4 c2 t( e
; W- I. l3 C* J% b4 u
& W0 t5 x! X! {- k9 D
原因
' I9 b" A1 n& o# f+ ]# x% y& ?$ ]
8 C) y+ {3 a6 q$ ^; N

2 O) w7 t5 i! N+ I3 a& _" V由于早期的CI工具不支持代码化,一直能够保留到现在,没有做重构和升级。 % o0 r3 F. d% ?: B" J. P
2 K# X9 {4 p& d
4 h" Q" E! r9 t9 O
影响1 X2 }( f, H! p" v) u  e0 z  U9 m7 Z( l

! _4 M; h. D% D# [9 x& j2 j& X' J
! W1 c' c* R# G% B
Pipeline的创建和管理都是通过CI工具的界面交互来的,难以维护,因此需要专门的管理员来维护,而有人工操作的部分就会出错,因此会降低Pipeline的可靠性。如果Pipeline因为一些原因丢失就没有办法很快恢复,就会影响交付速率。
! ]% |# P& Z3 v) p) W( ~
7 \( j) F6 y  D5 `; ^3 `

, t' x5 q! ]9 Y- Y& Q% l$ p解决方案" Y+ v. k3 A5 H/ Z

) o6 f7 K% E! ^- s6 A8 ]% C+ r

. G( C* A& v( P. mPipeline as code这个理念已经提了很多年了,在ThoughtWorks 2016年的技术雷达里就已经采纳了,需要强调的是,用于构建、测试和部署我们应用程序或基础设施的交付Pipeline的配置,都应以代码形式展现。随着组织逐渐演变为构建微服务或微前端的去中心化自治团队,人们越来越需要以代码形式管理Pipeline这种工程实践,来保证组织内部构建和部署软件的一致性。+ T6 }& M6 A% j; s& C
2 J! [% M$ _1 B& X) e* ]) P. \
通常,针对某个项目的Pipeline配置,应和项目代码放在项目的源码管理仓库中。同业务代码一样要做code review。这种需求使得业界出现了很多支持Pipeline工具,它们可以以标准的方式构建、部署服务和应用,如Jenkins、Buildkite、Bamboo。这些工具用大多有一个Pipeline的蓝图,来执行一个交付生命周期中不同阶段的任务,如构建、测试和部署,而不用关心实现细节。以代码形式来完成构建、测试和部署流水线的能力,应该成为选择CI/CD工具的评估标准之一。' u8 T0 V/ G/ F, L2 k
$ j1 e! A5 j/ x+ U# c
' T: O8 L* ?. P3 u1 W
2.2 运行速度慢3 s7 z  M# B6 H1 N

9 D6 ~/ r- g4 n- T1 W4 G
! e% _3 Q1 v* u$ W1 V
反模式
. ]; e- J) |* D9 \- o) l
. x8 r# h" }, G  d
1 d& h3 u  @1 p* F+ N
一条Pipeline的执行时间超过半小时,就属于运行速度慢的Pipeline。(这里的运行速度与交付的产品有关,在不同的项目中,运行时长的限定也有所不同)
& ~- k* r; s% M8 m+ k( a0 c
1 m( d6 o! S8 B2 z$ d
" j" V, M  T4 u+ a& k. @. g
原因2 ]; I, ]2 H5 ~! W

1 q4 t. |+ s# I0 L4 S' L

- ?0 B3 y5 {# a很多原因都会导致运行一次Pipeline时间很长,比如:
) o+ B5 f+ E2 u' J8 [2 ]
  • 该并行的任务没有并行执行,等待的任务拉长了执行时间;
  • 执行Pipeline的agent节点太少,或者性能不足,导致排队时间太长,效率太低;
  • 执行的任务太重,相同测试场景被不同的测试覆盖了很多次。比如同样的逻辑在不同测试中都测了一遍;
  • 没有合理利用缓存,比如每个任务里都要下载全部依赖,在构建Dockerfile时没有合理利用layer,每次都会构建一个全新的image。1 Y- P: N2 {' o7 |0 J# F0 R
- G( E7 i4 Q& J4 S# Y. S1 D
8 K3 \3 p9 ~, E* ^7 A3 Q9 h; S3 E4 g: r/ o
影响

% o8 w/ u6 U8 v3 z, n) e2 K9 D- |- ]" p* T6 P8 ]

7 R& |6 L( q$ D  ~$ Y; g这是开发人员抱怨最多的一个反模式。敏捷开发模式需要Pipeline快速反馈结果,受这一反模式制约,在特性开发过程中,经常出现开发人员改一行代码,等半天CI的效果。如果出现一个线上事故需要修改一行代码来修复,最终需要很长的周期才能让这一更改应用在生产环境。7 @9 g, `1 ~- ]  m" [& w& ?0 ]

% B/ \( i; A; T4 ~% [: ~, k
. o- P& i+ D/ Z9 _' e+ l
解决
: q+ @; ?, }* Y, r! I% e, p  V$ E3 l3 x1 V

7 K# R( `4 _- Q/ A, S不同的原因导致的Pipeline速度慢,有不同的解决方法。比如针对上面的问题,我们可以去:- P: K1 L  g# `* t  Q8 A
  • 检查Pipeline的设计是否合理,尽可能让任务并行;
  • 对代码的各种测试深入了解,让测试尽量正交,避免过多的重复;
  • 检查代码中的依赖,合理利用好缓存。包括Docker Image、Gradle、Yarn、Rubygem的缓存,以及Dockerfile是否合理的设计,最大化的将不可变的layer集中的开始阶段;
  • 检查执行构建的节点资源是否充足,能否在任务量大时做弹性伸缩,减少等待和执行时间。
    / {5 ^+ Q/ F' ]2 p: u* a% }8 j

/ J0 X) n* l- b% x' l

0 f9 h. y, ~( S* o% s, Y3 o2 C8 v: E$ u2.3 执行结果不稳定1 d, v  x0 D7 Z0 i1 ]* c

$ e2 I6 c1 b5 D* Z/ |! z5 O% N) G

  i$ q# R5 b1 A5 G+ n( ]& I7 ~2 Z
粘贴上传202205231712533710..png
! I0 S7 Y; z0 V& L, Y
图3 执行多次结果不稳定
% n; \9 \+ f1 q3 U

( ]0 F9 c6 M) Q6 \; W% T
反模式
. K# P" M1 l1 x
8 V. Z2 D5 M- c4 l
3 a: m" }! g, q) s
构建相同代码的Pipeline运行多次,得到结果不同。比如,基于同一代码基线,一条Pipeline构建了5次,只有最后一次通过了。
, s  m5 u1 }4 I4 S/ F" x. B+ i/ ]
, q4 b3 b  _0 ^

7 p! k( i5 Y) M1 |% B0 \$ H  ^4 E原因9 {: T: L4 E) ?! y# x9 _# x

; v2 ?9 v* B5 \& ?" B% H

* m) E5 Z" K( b, m& b出现执行结果不稳定的原因也多种多样,比如测试用例的实现不合理,导致测试结果时过时不过;代码中使用了不可靠的依赖源,比如来自国外的依赖源,下载依赖经常超时;由或是在Pipeline运行过程中没有合理设计各个阶段,导致有些任务同时运行冲突了。
( I: l5 S% b+ w; m4 }6 }" D1 O; ?: ~; B) [% e) t# e
8 k$ ^( H8 T. u- T3 O
影响" {* t+ F+ ^2 a5 n' M, Y. @* Y9 ~

3 t. D$ ~0 v8 ?% ?4 C- o  C
* N' \& M$ h* k: A5 G$ k
Pipeline作为代码发布的最后一道防火墙,最基本的特性是幂等性,即在一个相同的代码基线,执行Pipeline的任意任务,不管是10次、100次,得到的结果都相同。Pipeline不稳定会直接导致代码的部署速率降低。更重要的是,影响开发人员对Pipeline的信任。如果不稳定Pipeline不及时解决,慢慢这条Pipeline会失去维护,开发最后会转向手工部署。. b- a( `, h3 B) `: ]* ]. w

- G$ S! K, \" L( w0 K
% T1 `$ \& m3 {6 \
解决
. k. H. M2 Q1 r) k; o4 G/ \! R
5 `  I: z4 x7 q) ?: O& k4 g8 C: w

) h2 F3 k) v9 M, e要构建幂等的、可靠的Pipeline,就要分析这些不稳定因素出现的原因。
  • 提升测试的稳定性,比如用mock替代不稳定的源。
  • 采用Pipeline的重试功能,或者采用稳定的镜像源,或者提前构建好基础镜像。
  • 引入Pipeline的插件保证任务不会并行执行。9 ~2 u1 S4 f. o7 C# l6 D/ O

3 \' U7 g  a0 @" ~, Q8 Y3 J
. L5 U( n; u+ N* m# h2 s
2.4 滥用job处理生产环境数据
: o2 a4 F6 L/ z' g2 p* u0 ?! S/ V3 D; u
0 h- {' r7 ^! \) T
反模式
8 B3 K, b7 d6 _7 ?" R1 D/ p
6 p- _+ g* O  U3 k: K/ f. d

( K- |$ r  k: L: w& D9 i% t+ l使用Pipeline的定时任务的特性,运行生产环境的负载。比如经常会定期做数据备份、数据迁移,数据抓取。
! m' o! e: D/ q+ D- ?
# G% [3 n+ Z& J, U& d0 j2 E7 g+ Z! ^

6 u; ?5 a' C8 X% q. T- r原因
6 g  z+ F: V3 g9 Z) c) R+ ]4 w/ @- H+ `" o: e! d) f# y
4 f) \) _+ J: m: l2 y; t  G
由于对Pipeline的认识不够清晰,将重要的任务交由Pipeline做。Pipeline一旦有了某个生产环境的访问权限,做这些数据处理相关的任务就很方便,减少了很多人为的操作。9 P' v5 u& J7 x) c, G- g

9 z) l5 A/ J8 F8 L6 z
( P+ Q5 l' T% N/ M+ [
影响
5 b2 k- T/ d; B$ Q: |! b' {; G
% R3 Y" U; R- D0 g6 `2 B) {. ?
) z+ j. I; G& J/ A, q1 f
Pipeline是用来做构建、部署的工具,不能用于业务逻辑的执行。由于Pipeline是一个内部服务,他的SLO/SLI必定和生产环境不同,如果强依赖势必影响生产环境的SLO。假如某天Pipeline挂掉了,生产环境就无法得到想要的数据。另外,任务和Pipeline紧密耦合,是我们后面会讨论的另一个反模式。
0 B, Z& R) O0 M( d4 y
0 `% I. R/ ~% p$ j
/ v7 C! R: x. o% T% b5 a
解决& s. _- r+ `% ^: [: A& J- T
; ^1 h' z  X5 R0 h
( `% v# C6 N4 \* s8 t) g
方法用生产环境自身的工具解决这种数据问题,比如 采用AWS的lambda,定时触发数据处理任务。
1 v! l. t7 p( L! a, Q7 u8 j# Q6 ^
* @8 Z* J: A7 ^  M" D7 e, F  Z

" x# \. f0 v7 Q/ i& [1 V2.5 复杂难懂
) a2 d* e" ~, k* r) \
4 D9 E. @5 g5 w* Q; d8 U4 Z
9 a; u( ~; V  b# t
粘贴上传202205231713372185..png
2 p- B- k2 N& `# l9 m7 J
+ s/ |8 P( O! p9 n) q2 L$ r; A

* ^1 g# C  Y1 H1 H) E3 D- @反模式
# x) }9 c) I2 w: v% P
& K- J+ q! s2 q! W

1 M0 j& ^8 c. o) ^Pipeline的定义包含了太多的逻辑,复杂难懂。只有在一条Pipeline运行起来才能知道这里会运行哪些步骤,会将这个版本部署到哪些环境。
: Y- Y) N+ ^9 [- x& ?. [' `5 e5 {- R4 M) W/ H8 \2 r; f2 U

3 n/ `; F/ y; ^原因9 }$ K2 b, P% k; |  l
& d  q- ]3 R9 w" Y3 o& s% D

& f, {1 X6 M: M$ C# KPipeline的代码不够整洁。有人认为Pipeline只是给CI工具提供的,就随意编写,认为能完成指定的工作就够了。/ ^/ E* V, }/ D3 b3 t

: {9 x: j- f& q7 Y, P0 C0 Z6 K

3 ]4 B. J, ?# K/ Y8 g+ R影响
0 c' E1 a" \( r+ f2 U
! L3 |$ v% f. I8 i. S7 ~& B+ `
% @1 h3 O6 m4 L: u1 }
Pipeline的复杂性,会直接提升学习成本。如果想重复执行上一次构建,会花费较长时间。
6 u7 \$ F4 M4 t( B' Q3 b8 Q! d! E# a! q
/ s) ?# e/ D% j0 g$ K
解决  ^& }9 ?3 ~' q: ^$ y' U) m% {* r
$ C+ F- T0 @; J0 p% u

! w; w9 l( z- g6 u) dPipeline的代码要简洁,把复杂性放在部署脚本或代码侧。通过每个阶段的的标题可以直接了解所要执行的任务。如果存在很多相同的逻辑,可以通过开发Pipeline的Plugin来简化配置。5 ~( p+ D, g8 M
4 W1 j8 r! `8 O. \3 }& P* j% p4 O
1 R4 u& k6 l+ o. i
2.6 耦合太高
: x& q+ \# j9 E1 w4 G! m8 v- c/ N5 A8 H
; n: u/ f) r/ h) s
粘贴上传202205231714057154..png
+ [2 G1 D3 I1 J

4 `) L- r8 s; u, ]+ i2 p. S1 f
  b) x6 O7 ~; B6 A4 l
反模式
0 `$ ]+ J# |3 B3 y

" f- |7 a% Z; O9 ^5 y# j2 k
% |% W# p  ~! M4 f+ {9 L$ U1 V9 h
Pipeline跟运行它的CI工具紧密耦合,以至于无法在本地重复相同的步骤。表现可能多种多样:
  • Pipeline的定义跟构建工具紧密耦合,包含了Pipeline工具特有的参数以及CLI命令。比如在配置中使用BUILDKITE_BUILD_NUMBER,BUILDKITE_QUEUE等等。结果就是本地运行的方式或结果和Pipeline上运行的方式以及结果不一致。
  • 在Pipeline的任务中写了一大段脚本,或者直接使用命令加上一堆参数,以至于在本地想跑测试需要在Pipeline的配置中找命令并且在本地粘贴。
  • 不做环境隔离, 测试,编译,部署等都依赖于运行时环境。可能出现Pipeline 因依赖的软件/库等版本不一致而导致的不一致的情况,通常还很难排查。
    ! D# O' K6 \' Q0 ~$ N
# T0 A6 Y; S6 z: U. [

" h5 O3 L8 O4 q1 K影响

/ W2 f8 @# N. A6 O7 U4 y; y; c4 [
7 r& v. L3 Z# c4 ~
' J, C& e& h, ^) E5 ^0 u: s+ H
因为本地不方便调试,所变更的失败概率会大大增加。如果变更用来修复一个Bug,由于不做环境隔离,会导致故障修复周期拉长。5 u! X0 l$ L" v8 L3 s

* ~/ q3 _2 f& S) d
9 y) l! G* P9 H; i
解决
) f; ^1 Z  Q7 P0 M& I8 {# U
4 _  Q, \; H9 L
/ ?6 }. ~8 a- c5 A5 w9 V
Pipeline的每个step都用脚本封装起来,脚本里不使用Pipeline工具特有的参数,并且保证本地运行时和Pipeline上保持一致。# j; A, \9 k+ h4 K9 l3 ?" w' [( Y

2 o& O* e* h- |  Q

" R  O: y0 V  _9 `+ [' P* A" {; g5 z2.7 僵尸Pipeline* P6 r, R) H! R

3 p2 g5 I$ U( H; M9 T
' H/ w: B: ]- q. _( B9 s( u. F
反模式0 b" g& [- ^# ~' J9 g

1 B- h: q+ x3 d$ q
# Q: g" v$ u7 X; h
一条Pipeline年久失修,很久没有执行过,而且最后一次的构建是失败的。$ I, i. q2 t$ W
. I# L2 j' ]. J4 L

0 O& b) a0 X$ G原因. Q: s' Y( \- E0 X/ r
5 ~% m+ d9 u0 a, ^% O" s

* d" h, b0 F9 \# e" @; h+ K这种反模式通常出现于不再活跃开发的项目上,因此很久没有执行过Pipeline。
+ F8 Z  Y) q9 k6 Z, `7 L1 T$ {' H+ H

# j2 Q" z: }+ H8 {+ a( L2 b0 p影响  X# O' x, `  K

  i* F  x1 \4 Q" z! k( B' L% g

8 r& t; `% E$ VPipeline的结果反应的是一个项目的状态。由于软件产品迭代速度快,这个软件的依赖可能已经发生了巨大的变化,一旦运行,大概率会出错。假如这个项目目前出现了一个事故,需要提交代码,就得先修复项目的Pipeline,才能确保提交修复代码。# }4 G: r1 R% @
2 y+ R5 K3 ^  j- ^4 x, Q& g) U( X9 E

6 [( t/ }  R7 g1 n! a% I' h: i. F解决" t! K$ ], }; T! c

9 t: @7 I- q3 J* q6 I* V

6 o2 J) P9 f) o1 d' d, i- _针对常年没有提交的Pipeline,我们建议让Pipeline周期的执行,出现问题立即修复。如Github的Dependabot,能保证项目的依赖始终是是最新的,而且能让Pipeline执行,提早发现问题。
: N6 Z9 Y9 q, L8 V; u* U
% O  ^# g& G" _5 |* W  f

9 m9 r; P2 F/ r6 {1 ?2.8 需要人工介入
  s; m# h1 ]7 }) S0 g/ D% Q" X7 ], ~9 w
) j% x7 n: p+ F4 ^# t" ]# P
反模式3 Y/ S, o2 a$ Q! ~+ e) s9 Q

9 z! d* g2 P1 ^
' I  D3 E6 m3 ?4 J3 i
通常项目上会有一个专职Ops,在项目可以发布的时候手动触发部署流程,或者需要传递很多参数,让Pipeline运行起来。8 S" T/ p+ ?6 n/ k1 [) f4 W: K
9 G1 E9 a% j- X
5 m8 ~: s, D* v5 k
原因9 j- ^8 t1 w' u. j6 i

0 y+ }# t- m9 n# Q# _! G: J) I

3 ~; l) ]- T/ M  ~& g/ U6 A包括项目的流程繁琐,需要反复确认;DevOps成熟度不够,没有实现持续部署;或者CI的测试覆盖不够,CI通过后还要进行更多的测试才能部署。. f( b# u& B. i2 f- Y2 [; b

/ D9 P9 c# f2 _8 U6 f' @3 b8 R+ t6 k

  W' H3 I4 l5 l: k" V( W影响, O# m5 \. x; F) j
5 D9 x4 V6 Q- \) }* g' W
6 w/ S- w% |1 r* j$ u
这些Pipeline需要专人盯着,去点某些按钮。会直接影响产品的交付速率和代码部署频率。" A6 H/ D# P: l' p/ b
1 b& l7 a8 K7 b! o$ N! P2 \% w
0 }  f' _- h% ]3 v( N
解决
* C& W! y3 d4 @) e' a5 R8 S" g: ?( y3 X: ^8 T% ?- Y+ ]
& t) j( ^$ W, B. w( y0 z3 r: W
让项目的运行更加敏捷,减少Pipeline定义中的阻塞按钮,将手工测试自动化后集成到Pipeline中。7 @, c3 N7 g  S$ S2 Q+ G: P  i
# @# S& Z6 J. e
最后
希望通过本篇文章,意识到项目中CI/CD Pipeline的问题,使其发挥更大的价值。(来源:Thoughtworks洞见)

1 J0 w' p- K) O" M1 Q( |% n, q# t) ]7 u& b0 H3 `4 L0 u" n: ?: t+ h

) |8 A3 p- K0 u3 ~5 a9 X
1 ^% G; j4 p" k  a* K




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

本版积分规则

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

QQ|ITIL先锋论坛 ( 粤ICP备11099876号 )|appname

Baidu

GMT+8, 2022-6-28 10:25 , Processed in 0.102883 second(s), 32 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

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