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

 找回密码
 立即注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

查看: 551|回复: 0

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

[复制链接]
发表于 2022-5-23 17:15:38 | 显示全部楼层 |阅读模式
本帖最后由 FYIRH 于 2022-5-23 17:24 编辑
8 G2 G* A, i4 i- \3 G" g
/ U& X( ?9 M/ o
一、CI/CD & Pipeline

* v2 {$ G8 M0 O
随着DevOps的理念在众多公司的采纳,CI/CD也渐渐落地。& w, d7 \9 }& }1 t. e. }2 a9 p: q: a4 j2 G
) }# Z2 a% E/ x1 p0 a; Z
  • CI(Continuous Integration)持续集成,是把代码变更自动集成到主干的一种实践。CI的出现解决了集成地狱的问题,让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过一系列自动化测试,比如编译、单元测试、lint、代码风格检查。
    . m' ?# T$ O, {( m
- R) }' U# U6 }% \( ^* T

7 e( Q4 l3 r9 S9 S
  • CD包括持续交付和持续部署。持续交付(Continuous Delivery)指的是团队自动地、频繁地、可预测地交付高质量软件版本的过程,可以看做持续集成的下一个阶段,强调的是无论代码怎么更新,软件都是随时可以交付的;持续部署(continuous deployment)更强调的是使用自动化测试来保证变更的正确性和稳定性,以便在测试通过后立即部署,是持续交付的更进一步。二者的区别是,持续交付需要人为介入,需要确保可以部署到生产环境时,才去进行部署。7 l% p* ?8 z0 J9 J5 a
粘贴上传202205231711424172..png
, @; S% }5 v, \6 [' X8 J9 z- ?" _
CI/CD Pipeline是软件开发过程中避免浪费的一种实践,展现了从代码提交、构建、部署、测试到发布的整个过程,为团队提供可视化和及时反馈。Pipeline推荐的实施方式是,把软件部署的过程分为不同的阶段(Stage),其中任务(Step)在每个阶段中运行。; d9 ?" ^  v# {* |2 c* e  U, n
; F. [2 ~" [9 z' o
在同一阶段,可以并行执行任务,帮助快速反馈,只有一个阶段中所有任务都通过时,下一阶段的任务才可以启动。比如图中,从git push到deploy to production的整个流程,就是一条CD Pipeline。可以利用Pipeline工具,如Jenkins、Buildkite、Bamboo,来帮助我们更方便的实施CI/CD。
# T8 \1 M& l3 a/ l) l, c6 v' {! O" q7 T) e1 {* L
" t$ y; j* F2 p  m8 Z! F2 N
粘贴上传202205231712163891..png
9 a4 M8 |2 Q1 M$ A$ y) ~* Q! C0 o4 v, j
6 K  `- a3 _+ h. k1 M
二、CI/CD Pipeline的反模式

- c! r0 I8 \$ c' s9 p
虽然有Pipeline广泛的应用,但我们却会听见开发人员抱怨糟糕的Pipeline对他们的伤害,如阻塞开发流程,影响变更的部署效率,降低交付质量。我们收集了项目上经常出现的Pipeline的八大反模式,按照出现频率排序,分别阐述这些坏味道,分析可能产生的原因、影响及解决方式,希望能够减少抱怨,让Pipeline更大程度上提升工作效率。
3 ~* r0 [% s3 I% `) C' j
, D4 K  b) ~: j, d3 O* }) s- F8 E
2.1 没有代码化+ ]! @1 q8 H: g+ T7 v$ [0 K; l

$ _5 i7 ~% d6 ~
. b6 u8 c0 Y2 q: g2 ]. a. \
反模式
- z* P. |) w' b
: C4 b( i" U  @  @. w
5 [6 L9 P+ b, t* B: D
Pipeline的定义没有完全代码化,进行版本控制,存储在代码仓库,而是在Pipeline 工具上直接输入shell脚本定义Pipeline的运行过程。% S: [: l" F' i( j( h) N

- P1 e3 b% x4 U
. U- g- Q* s, R  i& t* `; G; G
原因
0 ^" y; ?, n6 @. i& A- u
  A& \5 ^& p  `7 c9 d6 y7 p2 I

1 N! _" y6 V/ S由于早期的CI工具不支持代码化,一直能够保留到现在,没有做重构和升级。
- ]+ l* `; f5 g) c2 Y
! m# e: ~1 y6 E" d9 U! s" U3 l* q
0 m/ @3 W6 j& a+ v( m
影响1 w1 Z# n& ^6 X* F
. t* }6 n  m% q& w- H
& _) C6 i0 t) Z% W5 M" `
Pipeline的创建和管理都是通过CI工具的界面交互来的,难以维护,因此需要专门的管理员来维护,而有人工操作的部分就会出错,因此会降低Pipeline的可靠性。如果Pipeline因为一些原因丢失就没有办法很快恢复,就会影响交付速率。; s  o( a) a' W! B1 F7 g7 `
7 [1 n% q# ~7 A, Y
9 a& j; P2 I0 G& e# Z6 j
解决方案0 d2 \" |2 I9 N3 n

# Y: `! _9 E9 Q% f1 h6 t
0 n, H) s" _8 L1 q) m5 }) i
Pipeline as code这个理念已经提了很多年了,在ThoughtWorks 2016年的技术雷达里就已经采纳了,需要强调的是,用于构建、测试和部署我们应用程序或基础设施的交付Pipeline的配置,都应以代码形式展现。随着组织逐渐演变为构建微服务或微前端的去中心化自治团队,人们越来越需要以代码形式管理Pipeline这种工程实践,来保证组织内部构建和部署软件的一致性。  c8 H4 ?; R- n+ f6 g
, o( @5 A; E. [4 V" ~
通常,针对某个项目的Pipeline配置,应和项目代码放在项目的源码管理仓库中。同业务代码一样要做code review。这种需求使得业界出现了很多支持Pipeline工具,它们可以以标准的方式构建、部署服务和应用,如Jenkins、Buildkite、Bamboo。这些工具用大多有一个Pipeline的蓝图,来执行一个交付生命周期中不同阶段的任务,如构建、测试和部署,而不用关心实现细节。以代码形式来完成构建、测试和部署流水线的能力,应该成为选择CI/CD工具的评估标准之一。
% [# P: y' p" h1 r
$ {3 w1 c; G- R; `# R/ u  _
' ~  [+ j$ p! f( E  p
2.2 运行速度慢. U1 R6 i- X/ ]4 B$ T

6 t9 Y2 q4 r& I$ H5 G) o
/ W4 A5 `" O& D3 q- p
反模式: x  `2 T+ @0 ~3 U) s! o9 S+ n
' w  O" r. j% Q
" p! u! ]0 A0 J" Y" }6 y+ A
一条Pipeline的执行时间超过半小时,就属于运行速度慢的Pipeline。(这里的运行速度与交付的产品有关,在不同的项目中,运行时长的限定也有所不同)
1 A  p' A/ P# {  g) D
6 \- U, L7 Y- e7 Z9 m) [

1 I7 b8 `( q$ }' E- E; _# I原因, f9 Y; R; b1 `# ]5 e% f/ D

$ j' V' {/ t' P5 y
3 c/ l) A6 v- r: U0 Q& g
很多原因都会导致运行一次Pipeline时间很长,比如:/ x  H( H' a! x8 h" [
  • 该并行的任务没有并行执行,等待的任务拉长了执行时间;
  • 执行Pipeline的agent节点太少,或者性能不足,导致排队时间太长,效率太低;
  • 执行的任务太重,相同测试场景被不同的测试覆盖了很多次。比如同样的逻辑在不同测试中都测了一遍;
  • 没有合理利用缓存,比如每个任务里都要下载全部依赖,在构建Dockerfile时没有合理利用layer,每次都会构建一个全新的image。  C* p/ v) J0 X2 V7 M- a% z$ Y
' d. a# Y: q/ d  X: U8 T

+ i; i5 \2 M: b) @  h影响
; {* [$ c8 J# @5 H- d% z2 ~

- y& A2 S8 F# W. O0 q" H

- u/ k6 _' L  y1 Z1 R这是开发人员抱怨最多的一个反模式。敏捷开发模式需要Pipeline快速反馈结果,受这一反模式制约,在特性开发过程中,经常出现开发人员改一行代码,等半天CI的效果。如果出现一个线上事故需要修改一行代码来修复,最终需要很长的周期才能让这一更改应用在生产环境。4 V, [/ a' j! U/ p5 Q
1 E" a$ P/ f2 f2 z9 j
0 N9 w# i! z* H: Q% O
解决
5 \+ U, h5 L" m" j
; c. [/ W6 t# E+ q6 h; C) i

( B7 K" u9 L; B- C) K不同的原因导致的Pipeline速度慢,有不同的解决方法。比如针对上面的问题,我们可以去:
$ V( R1 l7 n; I- F2 i8 N; u3 |; B
  • 检查Pipeline的设计是否合理,尽可能让任务并行;
  • 对代码的各种测试深入了解,让测试尽量正交,避免过多的重复;
  • 检查代码中的依赖,合理利用好缓存。包括Docker Image、Gradle、Yarn、Rubygem的缓存,以及Dockerfile是否合理的设计,最大化的将不可变的layer集中的开始阶段;
  • 检查执行构建的节点资源是否充足,能否在任务量大时做弹性伸缩,减少等待和执行时间。
    : _* e3 h( D. j' B

, l0 L  _9 f5 d. N9 @
& Q- W5 T) q  U6 f
2.3 执行结果不稳定
% q' s7 Y5 Z4 c, X5 @! O9 c' U5 U! _& ]. J* d7 B& F' ?% g5 `1 w, Q- P
  W! e1 b# S) s( C8 [# ~' y! z: q4 ]. [
粘贴上传202205231712533710..png

- h: ]. O5 K  d( f
图3 执行多次结果不稳定

- P* S7 S' ]% ~& T4 Z: f6 N% O" I& f  V
反模式
8 [. z6 J& p% o0 `4 n# x0 u) m- N

. c! c7 z2 ^5 E& x8 {" s2 ?- D构建相同代码的Pipeline运行多次,得到结果不同。比如,基于同一代码基线,一条Pipeline构建了5次,只有最后一次通过了。. k' [& f6 R+ y2 b  I( s7 b! L

- a# [+ I) f! s% b

: I& a4 J6 ]( ^0 U( N原因7 g- r" W" y! A  ~' F" i8 D& V$ J

* w' p- o0 i  p5 `

& w% ]# r! u4 ]; z出现执行结果不稳定的原因也多种多样,比如测试用例的实现不合理,导致测试结果时过时不过;代码中使用了不可靠的依赖源,比如来自国外的依赖源,下载依赖经常超时;由或是在Pipeline运行过程中没有合理设计各个阶段,导致有些任务同时运行冲突了。
) h! g# K, n. o% e) |. K+ v- ^3 t0 W
. _8 r9 I+ V7 r+ Q- U+ I
影响
6 J" W% a6 J4 \. G+ h7 Q* N6 p; Z+ Z( Q* Y! R; r
5 C8 I7 U- Y5 M0 w! w% ]" p; A4 z
Pipeline作为代码发布的最后一道防火墙,最基本的特性是幂等性,即在一个相同的代码基线,执行Pipeline的任意任务,不管是10次、100次,得到的结果都相同。Pipeline不稳定会直接导致代码的部署速率降低。更重要的是,影响开发人员对Pipeline的信任。如果不稳定Pipeline不及时解决,慢慢这条Pipeline会失去维护,开发最后会转向手工部署。$ q- J. ?5 V3 h2 M2 {

7 ?, K9 ]0 f" N6 P" c+ e) u
7 Y+ V4 H% c1 U) s5 S! S$ j
解决
5 J' {6 @" U, y3 q# I7 P5 K8 i# U8 C" p

6 |3 v9 A1 ^( N3 e" `/ [% v/ F要构建幂等的、可靠的Pipeline,就要分析这些不稳定因素出现的原因。
  • 提升测试的稳定性,比如用mock替代不稳定的源。
  • 采用Pipeline的重试功能,或者采用稳定的镜像源,或者提前构建好基础镜像。
  • 引入Pipeline的插件保证任务不会并行执行。5 G! t3 S$ J7 h- u

& [0 w! o5 ~" ]0 z- p

( `" z6 M$ B3 @; ?2.4 滥用job处理生产环境数据
' c3 O0 @" h( ?  G: [* }" B4 X
. v' {+ D% S4 p$ q% O6 B& {7 M, x# ?

6 e1 `8 {7 r# A6 Q7 @反模式3 e: `: B; j$ w( ]1 T8 B

+ |1 W3 L8 l9 ?* y8 X

) ]2 `: W3 F. {4 x: f& O* E使用Pipeline的定时任务的特性,运行生产环境的负载。比如经常会定期做数据备份、数据迁移,数据抓取。7 x1 t1 I* }$ Y. J% f

% ~2 v3 O. q0 E- P& J

" S1 |* o3 Z- ~. E: q原因
" V- u! X. L/ E3 O: r% N( ]" W3 b& {! j7 i0 ~, ^# \
/ r! a' ]% w: F, M
由于对Pipeline的认识不够清晰,将重要的任务交由Pipeline做。Pipeline一旦有了某个生产环境的访问权限,做这些数据处理相关的任务就很方便,减少了很多人为的操作。, q3 @; c. q3 v5 B) q9 O

7 A8 z  N  i+ m' @6 g

: c; G/ A. x  [6 s影响/ r  l4 K7 D+ ?3 Q/ e2 Q! i
, l% U7 x. U" v3 r3 g, l2 V
( l7 x4 ^( a/ ~8 {
Pipeline是用来做构建、部署的工具,不能用于业务逻辑的执行。由于Pipeline是一个内部服务,他的SLO/SLI必定和生产环境不同,如果强依赖势必影响生产环境的SLO。假如某天Pipeline挂掉了,生产环境就无法得到想要的数据。另外,任务和Pipeline紧密耦合,是我们后面会讨论的另一个反模式。
7 G* ?- i+ A8 Z9 y7 R5 K% G
8 Q- ]  z( V6 s% k. ~

; _: L1 x$ L- H解决3 `8 p# p: x8 {& ~- d9 p/ i7 B

0 j" I0 M2 o! `2 _4 B( L4 m
# O# g: \6 P) w" S' E) L5 m0 z
方法用生产环境自身的工具解决这种数据问题,比如 采用AWS的lambda,定时触发数据处理任务。
2 u7 m/ C* P0 C) d6 P, P# ?" n2 ?- m* h

# C, w* Q, c: g) A% A0 j2.5 复杂难懂4 O# @* L% e7 J% v* F/ v- ]
9 u% X$ _! _+ t* P' \. ~
; n& @3 ?3 O9 k; F, I
粘贴上传202205231713372185..png
, w+ K; Q' @8 Q( F# I

0 X' A0 `; Q+ K$ Y
5 l4 e: N- N7 M3 Y
反模式
  d* g6 _% C: D+ U; F
7 L7 q/ L6 m  w* E/ J* ?

4 i4 h0 c  g! x# R. a; u9 ?Pipeline的定义包含了太多的逻辑,复杂难懂。只有在一条Pipeline运行起来才能知道这里会运行哪些步骤,会将这个版本部署到哪些环境。* }9 f* A  ]2 U4 x/ E! _
# H* y% Z" d+ M$ k$ p% T9 I
' k, S. B3 z! M, n' y
原因7 }! R/ t& @; ?( g3 K3 X
& X  S  k$ c5 ~3 ?8 J

7 \! d, w! I- _: ePipeline的代码不够整洁。有人认为Pipeline只是给CI工具提供的,就随意编写,认为能完成指定的工作就够了。
. |+ n( Y" ]3 ~1 T0 f; S
2 d/ O9 h5 O! i8 T, {
3 u4 Y  [  X  N% i, i# L8 G/ ?) p
影响9 A8 Q2 t" L0 B1 w
' I! y7 y0 T( O, Y

0 L1 s2 o% s/ K' ?; zPipeline的复杂性,会直接提升学习成本。如果想重复执行上一次构建,会花费较长时间。
6 y. G; \9 _# |+ |" C0 |4 u! h2 N  r* }# E' R2 t

( @/ r8 `1 D/ S/ \- P' X1 D解决
% j/ {2 i1 k" r* q- K, ~+ F0 g' M& i. `* r' Z, L. m

. x2 i. j' r, N" GPipeline的代码要简洁,把复杂性放在部署脚本或代码侧。通过每个阶段的的标题可以直接了解所要执行的任务。如果存在很多相同的逻辑,可以通过开发Pipeline的Plugin来简化配置。
9 Z# U- b8 I  t0 P
% J' a6 Q' u5 Z6 P$ v6 z
; D% L& U; J" ?$ O" X9 _) v' e$ q
2.6 耦合太高
- S+ w" K. C8 F4 S: q9 p
. Z$ d. X' \8 {0 L, K1 X

0 g  }1 n& V7 m4 O5 e/ t9 E2 q1 d
粘贴上传202205231714057154..png

9 Y  w6 `6 ?" F7 q! d( Y1 E  u$ N- x
' x5 ?" L, Q! K2 Y5 M
反模式
+ t( P5 E+ W5 M9 Z$ W9 s+ _) K

$ d, H  V. l/ a) e6 }8 `# F

! g, u, J, A% o' E6 \* q% F* }Pipeline跟运行它的CI工具紧密耦合,以至于无法在本地重复相同的步骤。表现可能多种多样:
  • Pipeline的定义跟构建工具紧密耦合,包含了Pipeline工具特有的参数以及CLI命令。比如在配置中使用BUILDKITE_BUILD_NUMBER,BUILDKITE_QUEUE等等。结果就是本地运行的方式或结果和Pipeline上运行的方式以及结果不一致。
  • 在Pipeline的任务中写了一大段脚本,或者直接使用命令加上一堆参数,以至于在本地想跑测试需要在Pipeline的配置中找命令并且在本地粘贴。
  • 不做环境隔离, 测试,编译,部署等都依赖于运行时环境。可能出现Pipeline 因依赖的软件/库等版本不一致而导致的不一致的情况,通常还很难排查。
    % H; D, b3 q- S; P1 I$ l( t* R# Q

; j$ F5 [- L% N) H( o; g# x

8 P* o# n  `. ^4 n影响
9 L. G7 V; m; E5 y- [" u
; \$ s# w" B  C

/ \' t: r& J( H$ |1 b因为本地不方便调试,所变更的失败概率会大大增加。如果变更用来修复一个Bug,由于不做环境隔离,会导致故障修复周期拉长。- j$ M  O7 x+ W7 ]0 l, B9 g
9 {5 m/ S. k7 j9 H, N) f; l

. J) d' N1 K. ?解决# s9 w! R0 `/ y8 A8 T

/ `+ x$ w" ~1 K- |: \. `3 A
( H. x- J( H, [1 P/ I
Pipeline的每个step都用脚本封装起来,脚本里不使用Pipeline工具特有的参数,并且保证本地运行时和Pipeline上保持一致。+ i7 `$ i* E4 c$ Y) m

9 y. q3 l' G: Y/ K  K
7 e9 H% U' a, C  c3 E  S- R
2.7 僵尸Pipeline
. o9 l: |* ]( C  G; g" M0 M6 f/ W% B) E/ P$ d6 @  K/ T7 U
$ v$ e4 S0 ^+ ?( s7 h0 ^0 R
反模式
$ x. Y$ |' S' Y' Z% T2 }; \
* m, d1 s5 ]& @. r) X/ H1 q! r
7 U7 o! _8 [+ ^, _7 t- g
一条Pipeline年久失修,很久没有执行过,而且最后一次的构建是失败的。
; i8 e' z  W& {: R7 Z4 v" U
5 B1 g. e7 d* J: O! n9 Z

; G; Q& [  a8 z( ~- F9 @6 K原因/ A- ~0 s. N; R* ]) N
5 q( r& V2 p& [3 \) s

  W1 Z# X3 C- Q& H5 W, Z这种反模式通常出现于不再活跃开发的项目上,因此很久没有执行过Pipeline。% i. Q) B1 b+ d
( g$ V5 V0 h: T$ \" z3 R
, e2 p+ x* k" `9 t+ e- a, z. r% y" r
影响; T( D7 z( ]! N  e' _/ N" u* [

* N+ q6 ?- t7 R3 Q: k
; C! ]- ^% K, N% @! t" c
Pipeline的结果反应的是一个项目的状态。由于软件产品迭代速度快,这个软件的依赖可能已经发生了巨大的变化,一旦运行,大概率会出错。假如这个项目目前出现了一个事故,需要提交代码,就得先修复项目的Pipeline,才能确保提交修复代码。
3 n; o' w. `) {) \8 c1 m- S, j$ |  \; P6 e0 W  C
& I! {( M- O9 I
解决; s$ B" M* z3 }* y
: D! t6 h4 }8 q6 U9 p

7 c4 ~7 g6 @/ X0 n$ W  z$ O$ V针对常年没有提交的Pipeline,我们建议让Pipeline周期的执行,出现问题立即修复。如Github的Dependabot,能保证项目的依赖始终是是最新的,而且能让Pipeline执行,提早发现问题。
$ u, o1 @/ p7 a4 B! ~, K- R! i3 D$ v: o2 [; i- @* ]9 l1 D. `

# w+ e. ^  Y2 L, B) n+ V* X2.8 需要人工介入. D- p  c* j. ~# f& C* q% U: ]

. @3 k, ~$ z6 x; D( w- x
/ s3 i' r1 t. ?* x( p' b; Q
反模式7 i% \+ ?- ^4 y: o% p- J" E( a
! I) M9 `9 T8 ~0 H
. L4 t7 `. v- @
通常项目上会有一个专职Ops,在项目可以发布的时候手动触发部署流程,或者需要传递很多参数,让Pipeline运行起来。
7 m# {9 Z  C1 [! q
2 C" A/ d( E. s' i
, F$ l. Z& e- a% B3 u! P
原因
# }) Y5 w5 p4 N  _/ D7 C' ]( e, [2 n# s+ R
& K* O8 f' x. ?( H
包括项目的流程繁琐,需要反复确认;DevOps成熟度不够,没有实现持续部署;或者CI的测试覆盖不够,CI通过后还要进行更多的测试才能部署。
2 O4 A6 c- z4 P. p7 a0 \4 g; }0 F0 a! [# r# S% x
' l9 D! T: x. V3 i- P
影响7 m" ^- L/ y# C
2 L5 ]* H" @9 s7 F6 Y

% f% u! ]2 j+ m7 n这些Pipeline需要专人盯着,去点某些按钮。会直接影响产品的交付速率和代码部署频率。  N# k( P6 k- [$ r  j9 M, S
& \/ n  B7 A  J: f( |) z

) `0 o" Y4 n- T& j9 c解决2 t. k! ?9 X- P

2 [, R* t- r% X& N; o
( n- E+ L. S6 G% ~
让项目的运行更加敏捷,减少Pipeline定义中的阻塞按钮,将手工测试自动化后集成到Pipeline中。' J! o/ N1 ^1 J, [" X4 y4 r2 D

7 l0 f( w, q# c2 k
最后
希望通过本篇文章,意识到项目中CI/CD Pipeline的问题,使其发挥更大的价值。(来源:Thoughtworks洞见)

* W0 ?+ M! V* ~1 v% c
5 {$ f9 W4 j: p! A# H6 u
; Q, I; G5 x0 Q: [# C+ P" u. C" s% [+ f" z& H8 ~




上一篇:【万字长文】一文看懂持续部署按需发布!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-4-1 03:05 , Processed in 0.102989 second(s), 32 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

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