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

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

 找回密码
 立即注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

查看: 286|回复: 0

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

[复制链接]
发表于 2022-5-23 17:15:38 | 显示全部楼层 |阅读模式
本帖最后由 FYIRH 于 2022-5-23 17:24 编辑
4 T. h( u/ J/ X% N; \- g
9 _: N& z1 s) ^. _" ~
一、CI/CD & Pipeline

. b, z6 ]$ K2 E/ h7 u
随着DevOps的理念在众多公司的采纳,CI/CD也渐渐落地。
( G3 L. `8 W0 J5 S7 Y

1 N3 r+ I- n$ a5 Q; ~
  • CI(Continuous Integration)持续集成,是把代码变更自动集成到主干的一种实践。CI的出现解决了集成地狱的问题,让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过一系列自动化测试,比如编译、单元测试、lint、代码风格检查。9 f; u# j6 O0 m  T' T) l
/ |3 [; _# Z$ |; \
* H! U8 k! ]0 v0 {% D" z
  • CD包括持续交付和持续部署。持续交付(Continuous Delivery)指的是团队自动地、频繁地、可预测地交付高质量软件版本的过程,可以看做持续集成的下一个阶段,强调的是无论代码怎么更新,软件都是随时可以交付的;持续部署(continuous deployment)更强调的是使用自动化测试来保证变更的正确性和稳定性,以便在测试通过后立即部署,是持续交付的更进一步。二者的区别是,持续交付需要人为介入,需要确保可以部署到生产环境时,才去进行部署。* u& L; }$ N1 c$ H  Q2 s4 G/ N
粘贴上传202205231711424172..png
/ e8 v+ C" i4 \4 w2 B
CI/CD Pipeline是软件开发过程中避免浪费的一种实践,展现了从代码提交、构建、部署、测试到发布的整个过程,为团队提供可视化和及时反馈。Pipeline推荐的实施方式是,把软件部署的过程分为不同的阶段(Stage),其中任务(Step)在每个阶段中运行。/ x3 _" a- {" |( Q9 {/ ^

! }3 h& f7 _  x; d在同一阶段,可以并行执行任务,帮助快速反馈,只有一个阶段中所有任务都通过时,下一阶段的任务才可以启动。比如图中,从git push到deploy to production的整个流程,就是一条CD Pipeline。可以利用Pipeline工具,如Jenkins、Buildkite、Bamboo,来帮助我们更方便的实施CI/CD。
6 Y/ J# N$ a2 T) R* _: J0 h" x5 y) h, j" ~
* c% b  H' d+ h5 Q
粘贴上传202205231712163891..png

: g# V) k( p" P( ?8 {. U8 E" k7 S2 g) r* a0 P5 u
二、CI/CD Pipeline的反模式
1 R5 f) h3 L" I
虽然有Pipeline广泛的应用,但我们却会听见开发人员抱怨糟糕的Pipeline对他们的伤害,如阻塞开发流程,影响变更的部署效率,降低交付质量。我们收集了项目上经常出现的Pipeline的八大反模式,按照出现频率排序,分别阐述这些坏味道,分析可能产生的原因、影响及解决方式,希望能够减少抱怨,让Pipeline更大程度上提升工作效率。
9 w4 V5 I( z) O" H: r5 B% v
: [$ s7 V9 e* m* i0 W% g
2.1 没有代码化
- u! R+ S2 ]& N! d8 P  r  |& G
) `  d* y0 a: u& A2 d" i
反模式
6 T% M" Q9 p* B' R- V' E; g. v8 s
9 i' _! {0 k* K5 \* O9 \! v
0 g3 I# G8 f0 A; d9 O3 T4 T. I: h
Pipeline的定义没有完全代码化,进行版本控制,存储在代码仓库,而是在Pipeline 工具上直接输入shell脚本定义Pipeline的运行过程。$ ]* W/ X7 |; V$ e7 m6 R1 M) t
3 |' f" J; v5 y; I) W. t) z! j$ {

/ c) o0 B0 K% m& E5 M原因
8 Q; P. n3 D2 Z  C% {, y2 ~( \; x* Y, }" [. O, D& L
& L  t: q8 d$ _  Y, P. ^2 t# b% h8 }7 q
由于早期的CI工具不支持代码化,一直能够保留到现在,没有做重构和升级。
6 G% C7 H% L: o; D6 R  n( ~( b! Z$ v- J

2 E" ^8 O& p8 J! u$ @4 y# @6 P/ t/ |影响% A! v# i1 D8 F: y: J- n
* i0 i4 g7 ~& I' ?: J+ J0 h

  R% ~8 B' \0 V& E: dPipeline的创建和管理都是通过CI工具的界面交互来的,难以维护,因此需要专门的管理员来维护,而有人工操作的部分就会出错,因此会降低Pipeline的可靠性。如果Pipeline因为一些原因丢失就没有办法很快恢复,就会影响交付速率。4 n4 _) g! I% r. B6 `) F8 J

. b6 w3 [% l/ x# T. R
! r) G- ]9 M8 T" e# n6 E3 F4 s- [! J% _
解决方案9 V7 b, c2 @, j3 W
; y0 O5 m5 Y  r& r

9 p3 @' I9 A# ~1 B7 W6 \% g/ r  mPipeline as code这个理念已经提了很多年了,在ThoughtWorks 2016年的技术雷达里就已经采纳了,需要强调的是,用于构建、测试和部署我们应用程序或基础设施的交付Pipeline的配置,都应以代码形式展现。随着组织逐渐演变为构建微服务或微前端的去中心化自治团队,人们越来越需要以代码形式管理Pipeline这种工程实践,来保证组织内部构建和部署软件的一致性。
4 h( ]" u! ^- o1 F4 e. a
* D7 _: v+ R4 S; x! v- b
通常,针对某个项目的Pipeline配置,应和项目代码放在项目的源码管理仓库中。同业务代码一样要做code review。这种需求使得业界出现了很多支持Pipeline工具,它们可以以标准的方式构建、部署服务和应用,如Jenkins、Buildkite、Bamboo。这些工具用大多有一个Pipeline的蓝图,来执行一个交付生命周期中不同阶段的任务,如构建、测试和部署,而不用关心实现细节。以代码形式来完成构建、测试和部署流水线的能力,应该成为选择CI/CD工具的评估标准之一。
: b, ?) l4 R( H. P& G7 A0 F  U* B( ^8 k+ ]  x  ]+ o
0 l) T/ E; ?6 k2 a
2.2 运行速度慢
6 t* r8 B) D- x: P. F! n2 D6 R& ~/ ^6 y) R9 H' V2 s: j9 F

) A4 g0 b; B" O- W3 f  O; U反模式
6 p' P: Q9 N4 _1 f2 H- g' y! P7 ?/ g
5 @: W- D$ ]( y0 s
一条Pipeline的执行时间超过半小时,就属于运行速度慢的Pipeline。(这里的运行速度与交付的产品有关,在不同的项目中,运行时长的限定也有所不同)! e& b8 D3 T% ?5 H2 @! ~5 L
; e9 }. I/ W/ q& x
% |$ L' o: z7 E5 B1 _, B8 t- G- L
原因
2 Y9 {# d/ Y1 B- `5 m% ~- v3 Y$ o, V. S3 B6 g3 ]# n1 Z! c
" D5 _- J3 n. w# e! F
很多原因都会导致运行一次Pipeline时间很长,比如:& Y3 [" G. M  ?% Y! T" T
  • 该并行的任务没有并行执行,等待的任务拉长了执行时间;
  • 执行Pipeline的agent节点太少,或者性能不足,导致排队时间太长,效率太低;
  • 执行的任务太重,相同测试场景被不同的测试覆盖了很多次。比如同样的逻辑在不同测试中都测了一遍;
  • 没有合理利用缓存,比如每个任务里都要下载全部依赖,在构建Dockerfile时没有合理利用layer,每次都会构建一个全新的image。
    % M4 M4 b  \5 Z: |) {% E

% i! h6 j% I% e) p- {

5 S( {, M: d  A8 D4 f影响
- \7 I  _0 I! z- d2 y; d& X

; w1 g. X4 I& K5 Y% R+ H
7 ]3 P8 W" u+ c- q5 r
这是开发人员抱怨最多的一个反模式。敏捷开发模式需要Pipeline快速反馈结果,受这一反模式制约,在特性开发过程中,经常出现开发人员改一行代码,等半天CI的效果。如果出现一个线上事故需要修改一行代码来修复,最终需要很长的周期才能让这一更改应用在生产环境。8 T6 ^  W- A  A& {6 I
: s+ ~3 Z" [  ], |
9 V0 ]% A$ Z, E' S* s
解决5 z% p. E2 a) R  g

* p0 v6 @; M  j
0 O+ i3 U* g5 x0 R, Y  Q
不同的原因导致的Pipeline速度慢,有不同的解决方法。比如针对上面的问题,我们可以去:
( y7 ]  N& g4 P
  • 检查Pipeline的设计是否合理,尽可能让任务并行;
  • 对代码的各种测试深入了解,让测试尽量正交,避免过多的重复;
  • 检查代码中的依赖,合理利用好缓存。包括Docker Image、Gradle、Yarn、Rubygem的缓存,以及Dockerfile是否合理的设计,最大化的将不可变的layer集中的开始阶段;
  • 检查执行构建的节点资源是否充足,能否在任务量大时做弹性伸缩,减少等待和执行时间。+ ?7 |3 ], z( A( r
7 o. r1 V3 m& u" }2 C+ e4 t% ]7 i; {

' v  Q8 _) m+ v) [) }! k2.3 执行结果不稳定5 Z, ]" \7 D; k: l& S' Q) R
5 w* V. S* B! C

# J0 d( f3 R5 ?( t1 w; Q# x; x" a
粘贴上传202205231712533710..png

& ]2 ]$ v9 E% Y' p
图3 执行多次结果不稳定
( ?1 \1 Z! ?. r" O+ R

+ h! B% M/ ]7 Y- O, m5 x# E2 R
反模式1 H  I4 s- ~% z! S/ H7 Y
7 U- h  j7 R* F
3 K  ^* V! ]5 _5 M2 u/ @4 G
构建相同代码的Pipeline运行多次,得到结果不同。比如,基于同一代码基线,一条Pipeline构建了5次,只有最后一次通过了。3 ^( A) F) r* `
9 D2 a8 h$ c: x+ H9 ^

6 y" |% B2 B8 L, X5 Z' ?原因8 F& J" h9 L7 \2 i- @
/ N: q( P' g$ G" }  l

" U! o( m# X: j6 z( i出现执行结果不稳定的原因也多种多样,比如测试用例的实现不合理,导致测试结果时过时不过;代码中使用了不可靠的依赖源,比如来自国外的依赖源,下载依赖经常超时;由或是在Pipeline运行过程中没有合理设计各个阶段,导致有些任务同时运行冲突了。
1 ^& n+ M5 r* b) F  B) b. o, X8 h. M1 L

4 i- e% `/ p  S, i% X5 s8 _' O影响
. N) Q9 D1 Y) h5 U, `0 I' T$ u* j( t8 G# U
9 }3 D; f. N  H# x3 l3 {
Pipeline作为代码发布的最后一道防火墙,最基本的特性是幂等性,即在一个相同的代码基线,执行Pipeline的任意任务,不管是10次、100次,得到的结果都相同。Pipeline不稳定会直接导致代码的部署速率降低。更重要的是,影响开发人员对Pipeline的信任。如果不稳定Pipeline不及时解决,慢慢这条Pipeline会失去维护,开发最后会转向手工部署。
. o+ k3 P2 v5 d9 ?" @  w1 F+ }* G& T( E% g
* e8 d! O: Q& x$ j  W
解决) y- {- X# a2 j" {5 j5 T2 W$ X

1 Q! e! k# `* \( H" i* p

3 a8 V/ c1 k' j( K要构建幂等的、可靠的Pipeline,就要分析这些不稳定因素出现的原因。
  • 提升测试的稳定性,比如用mock替代不稳定的源。
  • 采用Pipeline的重试功能,或者采用稳定的镜像源,或者提前构建好基础镜像。
  • 引入Pipeline的插件保证任务不会并行执行。1 q9 C5 x& M$ l+ u# i3 H$ c

/ M) N" a3 l# Y* E; P
5 O1 L, W# w  I/ w1 b
2.4 滥用job处理生产环境数据! y, q3 h9 {" L, |7 U; W2 R

( C- m( i4 b' q
  M" A( W# K- I- {; e
反模式
$ j# M2 T. o& e* R- Z& m# ~! J) B
- u! z2 d1 u# f9 U  r4 m

8 X: D3 Q2 i; I4 ~8 ?6 \使用Pipeline的定时任务的特性,运行生产环境的负载。比如经常会定期做数据备份、数据迁移,数据抓取。
5 X# r& x5 V$ p- `! h' N% I0 @
3 D+ k2 d# @# s) q/ |% [8 w0 [  v

8 [! d7 ?4 |* r8 x5 r: r; s0 C原因- Y$ c8 y' w+ p/ ]9 e3 a

# s) q. s1 _4 q* j1 n, v% {
8 i6 K" N+ r7 M; D9 _$ y
由于对Pipeline的认识不够清晰,将重要的任务交由Pipeline做。Pipeline一旦有了某个生产环境的访问权限,做这些数据处理相关的任务就很方便,减少了很多人为的操作。3 t9 D! T- v# t9 n* @

( @9 A/ m/ X* c& `; A& Z: _
  g/ y- k5 {% u4 f. g
影响9 v4 M( L( Q5 n5 c6 _7 y) I9 G& d
, F( F$ `; k- E: v
& E$ T) m' f5 E; T3 ]9 [
Pipeline是用来做构建、部署的工具,不能用于业务逻辑的执行。由于Pipeline是一个内部服务,他的SLO/SLI必定和生产环境不同,如果强依赖势必影响生产环境的SLO。假如某天Pipeline挂掉了,生产环境就无法得到想要的数据。另外,任务和Pipeline紧密耦合,是我们后面会讨论的另一个反模式。3 }: \/ L- ~8 i# K
. x# q- ], u" B: Y

: f% W( R. U7 a2 o' h/ K( E" b" x解决5 r/ ]0 e7 z# S3 U0 T- m

; f8 j# h4 y% G" M3 x$ m0 \' r2 q* _3 i

/ s; j" I) g  h* }: t8 p方法用生产环境自身的工具解决这种数据问题,比如 采用AWS的lambda,定时触发数据处理任务。: K" r1 `! G# T; \/ D$ b( q9 Q

2 B. r$ Y/ {0 ~3 P$ \) n1 \! a

7 M% {' K; v% U7 S2.5 复杂难懂' {- h$ ?( O' l8 z8 F

% B2 S5 S3 A# j, Q2 {+ _( w
. z* E; e, x8 `. J, Y% u
粘贴上传202205231713372185..png

+ j7 A+ Z" S+ `$ z4 L3 y3 E; F9 j* ]2 N
6 {# ?& P( _+ i4 k" k5 Q
反模式
( w; F9 \6 U7 Y( g% `: f; O# E9 L% `
: \( S# M4 D4 C% q
Pipeline的定义包含了太多的逻辑,复杂难懂。只有在一条Pipeline运行起来才能知道这里会运行哪些步骤,会将这个版本部署到哪些环境。
3 L  v1 P% Q+ p6 u$ j0 b  L
- @! \+ c# b0 `6 W4 {( i( G
0 E3 Q- K- v2 P5 p- g
原因4 s  j" K7 s+ I: T3 ~

( F5 c0 t7 Q9 h9 H
0 L4 I7 M, X1 y1 j- q4 L, ^5 E/ o
Pipeline的代码不够整洁。有人认为Pipeline只是给CI工具提供的,就随意编写,认为能完成指定的工作就够了。
% i2 n7 o, a2 O
* B& ]' w- p4 d. ?. E

6 T& G- ^. Y0 z, |影响) d3 P6 ?) ?3 y, x7 [: \; H
9 k; _+ F. k6 ^- n4 I
1 ]: W( k5 E% p2 L
Pipeline的复杂性,会直接提升学习成本。如果想重复执行上一次构建,会花费较长时间。
* {/ K# E- l" g( T, ~- I
/ k7 Q; X2 T! \, C% \7 Y( p# D
0 ?% j4 d! N) e& g; n. P- I
解决
  C0 w& A% X. v' N) Z; i
0 W( {/ g, Y- H  e# Q) `0 k

" ~; q: }0 D) b4 kPipeline的代码要简洁,把复杂性放在部署脚本或代码侧。通过每个阶段的的标题可以直接了解所要执行的任务。如果存在很多相同的逻辑,可以通过开发Pipeline的Plugin来简化配置。+ q5 k+ s: z# T; H# O3 t& d! E

- w' O" a! [1 v6 E5 f+ ]4 h3 }8 {
: z4 `5 d$ o/ A) g
2.6 耦合太高
# @! {5 r8 e8 u/ U+ m) @0 d
# q1 {7 Y! Z* U( K: E. H

$ K; B6 O5 [3 }
粘贴上传202205231714057154..png
5 [2 q0 D3 O( B1 j9 N( N
7 A, l3 ]1 s% \" \/ k# x6 @" w

1 b- \- S" s& Y! k0 y反模式

# H+ M) T4 c5 S$ S% ~$ ], B# z* l7 ~( |1 ~/ ?$ k
6 L6 O. i* e+ P& m- I( o
Pipeline跟运行它的CI工具紧密耦合,以至于无法在本地重复相同的步骤。表现可能多种多样:
  • Pipeline的定义跟构建工具紧密耦合,包含了Pipeline工具特有的参数以及CLI命令。比如在配置中使用BUILDKITE_BUILD_NUMBER,BUILDKITE_QUEUE等等。结果就是本地运行的方式或结果和Pipeline上运行的方式以及结果不一致。
  • 在Pipeline的任务中写了一大段脚本,或者直接使用命令加上一堆参数,以至于在本地想跑测试需要在Pipeline的配置中找命令并且在本地粘贴。
  • 不做环境隔离, 测试,编译,部署等都依赖于运行时环境。可能出现Pipeline 因依赖的软件/库等版本不一致而导致的不一致的情况,通常还很难排查。
    ( M4 z* Q- x+ T; T& s
% N, C9 u5 b! l( X) r! e* e/ x# t% a
: u' a1 |5 @7 s4 C! N
影响

% b& {( R! y3 m7 \6 Q. r8 f4 {- T/ ]# i5 X6 s- P

0 ^! l+ o$ W8 j3 A因为本地不方便调试,所变更的失败概率会大大增加。如果变更用来修复一个Bug,由于不做环境隔离,会导致故障修复周期拉长。
: t9 D" Z0 `- [! M+ {) r0 n6 n1 u# Y+ G; f8 h7 J

7 f: }" B; F5 a, o4 {解决
9 O* E0 ]( k0 k! y1 P: r$ D! A# N! D# `

/ o$ S" i4 ]1 O! x5 E' g& sPipeline的每个step都用脚本封装起来,脚本里不使用Pipeline工具特有的参数,并且保证本地运行时和Pipeline上保持一致。
4 L, m9 I7 u+ F. y4 W9 x# v+ ?: Z; ~  r- }
  [9 B* Y" x/ a" P, L7 j) s
2.7 僵尸Pipeline
. n/ W. f2 T: ^# n0 v; ~
9 m, }  v; {! J1 R! {" V. U; ^

* j# j( k0 a+ v# }# ?4 E3 U! o- W反模式
2 J2 j6 Q, U0 a; w; D! X7 H; G' M) m5 k$ T
& |" Y( D7 U2 J+ C; s
一条Pipeline年久失修,很久没有执行过,而且最后一次的构建是失败的。
: O5 P# O. Y; x5 A2 @* L+ H2 e; S- `3 Q; U- g' @
) {8 x  @) @! N5 L2 ?
原因
; G! T* f! Q: X" L5 U, p; C
5 k9 n: P% @$ D. }

8 ]) Y+ R  x8 Z& K这种反模式通常出现于不再活跃开发的项目上,因此很久没有执行过Pipeline。, s* ^' @6 v% y* ?: _8 a
& d' E- ~8 e9 l( ?; g+ \( \; |

; E  l3 N$ s9 J* o$ c影响+ j* d: M9 S8 t8 e5 j

" e/ Z7 w! x( A/ t" [

" f) ^. D0 `& VPipeline的结果反应的是一个项目的状态。由于软件产品迭代速度快,这个软件的依赖可能已经发生了巨大的变化,一旦运行,大概率会出错。假如这个项目目前出现了一个事故,需要提交代码,就得先修复项目的Pipeline,才能确保提交修复代码。8 {. n. Q: x. F+ X7 _

6 N, D$ Z+ S/ v0 D( u. }( Z
8 O5 r0 b- I7 p4 N( k0 ^
解决2 Z2 J( F3 t% K4 i
& T0 K* E' j- [9 I
/ w1 k% ~  L( {1 n1 S
针对常年没有提交的Pipeline,我们建议让Pipeline周期的执行,出现问题立即修复。如Github的Dependabot,能保证项目的依赖始终是是最新的,而且能让Pipeline执行,提早发现问题。
. z$ ^, {3 h2 g6 I6 t7 L  U
/ C& f9 J6 Z# c6 F& X9 h5 Q! z2 b4 k
" E3 |1 C. \! t
2.8 需要人工介入% I0 [/ O) A4 j8 V  p: x. j1 r

# @; p, X. l" z' d1 L* c, s

- G: A* ~% R9 c$ j+ z4 t% A+ {" Q反模式8 c3 B4 `/ N& r6 |* w0 g" `, r

7 V& M" d) f% ?

8 J; l  m" e$ C通常项目上会有一个专职Ops,在项目可以发布的时候手动触发部署流程,或者需要传递很多参数,让Pipeline运行起来。
* o; T5 C" y+ \4 L
# z0 l3 Y9 j( j1 o' d9 ^; z, ~1 N

  _& }. Z0 @' b! s$ L原因2 Q/ A$ l) E1 }3 |" A% |5 m5 p3 l
5 D& {7 Z0 F% o
" w6 J7 f* P; I8 E3 e" u
包括项目的流程繁琐,需要反复确认;DevOps成熟度不够,没有实现持续部署;或者CI的测试覆盖不够,CI通过后还要进行更多的测试才能部署。
) ]( P+ \% q/ ~" d2 j" _0 d
7 M0 e# q5 j& G1 q8 R3 r
  `8 C1 U% h% L1 B
影响
6 o, _" X- G0 g% W8 `' Y
( E; M! j' M+ @* s" i( J' U8 b2 j# t

5 c6 n" i! a. p( ^) j$ i  k这些Pipeline需要专人盯着,去点某些按钮。会直接影响产品的交付速率和代码部署频率。
4 J- b& ]- `& m9 ~$ c1 V' z$ J  f# i& T0 w: `3 f
8 s, E9 D- F5 [2 F" D
解决
7 q) T5 w3 k9 E+ C6 T
5 I% t) P& F8 J" n

! w+ f" U) z8 d0 u让项目的运行更加敏捷,减少Pipeline定义中的阻塞按钮,将手工测试自动化后集成到Pipeline中。
) q+ H! L; \; ^7 N' A3 }3 ?3 k! s- }! l/ r4 e! n1 d0 `% A
最后
希望通过本篇文章,意识到项目中CI/CD Pipeline的问题,使其发挥更大的价值。(来源:Thoughtworks洞见)
" ~# P; Q( ^3 X( U0 Z: o

$ G5 O! S$ C3 b- N, n9 J) D' e9 J. W6 x9 i# _
6 ^4 n* m1 N7 N" \3 v5 E




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

本版积分规则

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

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

GMT+8, 2022-9-26 19:57 , Processed in 0.103215 second(s), 32 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

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