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

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

 找回密码
 微信、QQ、手机号一键注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

艾拓先锋
搜索
查看: 201|回复: 0

你会写DevOps的文档吗?

[复制链接]
来自- 广东广州

参加活动:0

组织活动:12

发表于 2018-8-23 14:53:32 | 显示全部楼层 |阅读模式 来自- 广东广州
本帖最后由 monicazhang 于 2018-8-23 14:53 编辑 6 C1 z9 u! d& o, l- p

* c6 i* k8 j  a1 P# x9 O
每个DevOps都一个百宝箱,里面放着各种命令行脚本,可以用来自动化各式任务。但若文档不全,即便是脚本的作者,时间一久也不敢随便乱用,毕竟运维的大部分工作是管理生产环境,要是出了错,不是轻描淡写就可以蒙混过关的。写好 DevOps 的文档其实也是一门技术活儿,这里给大家分享一些组织运维脚本及其文档的经验。, l4 a8 T) x% m# j0 S9 s; j# t- z
1.png

8 D  ?( l" i! _9 k
Fabric的任务管理与文档

- h2 j5 T7 R, ?7 v5 C0 K1 p  \
在以前的文章中,我们曾经介绍过Glow使用了fabric来执行各种日常管理的任务。Fabric提供了非常好用的任务组织以及查阅任务文档的功能。

8 h8 J# g! A0 }9 C4 r- K5 x; x
Fabric的主文件一般命名为fabfile.py,但任务多了,都写在一个文件里显然很难维护。Fabric有一个很实用的特性,就是当fabfile.py里导入其他模块时,会自动发现里面的fabric任务。利用这个特性,可以把各种任务分类写在不同的模块中,然后在fabfile.py中统一导入。比如 Glow 的 DevOps 代码库的结构大概长这个样子:

4 W+ F$ ^4 W* Z' X9 u8 n: Y- }
$ tree
0 L2 M% R8 q! _8 D* x/ D├── __init__.py( A8 }1 b* r) b! R' P
├── fabfile.py
* B" `% o8 R8 q  q+ n& x├── fab_scripts
5 n& l( i. `3 A9 J│   ├── __init__.py" j0 _+ w* m3 i! {& P' W
│   ├── monitors.py
) L; {! |; q* Z9 A. x4 A: G8 j9 a│   ├── mysql.py
) p( w" ^  ?/ l0 N5 C* E│   ├── nginx.py
8 N5 L6 J; V$ |9 F: C0 i3 Z0 w6 N│   ├── redis.py
6 @' w4 N* p! A7 a- z% i& c5 j. S│   ├── scaling.py
: S% d, a+ |6 k& m" }% \│   ├── services.py

3 v3 A! [2 ~5 q6 N- k* r+ \
在fabfile.py里除了一些最核心的任务脚本外,主要就是一些import语句

  |) l. N9 }8 h2 y2 N, e
# fabfile.pyfrom fab_scripts import monitors  
4 v: Y/ L! h- {# q2 K# h! L4 b7 mfrom fab_scripts import mysql  
+ X- K/ o3 C' t  w6 ]! ?from fab_scripts import nginx  
" R2 k( b  e2 m& Q: W$ |6 @from fab_scripts import redis  0 M  C4 R. C& l( T8 O
from fab_scripts import scaling    s! ~0 E- M4 k( d9 ?
from fab_scripts import services  

' u0 C1 w- i8 V; _
这样我们就把散落在多个文件里的任务聚集到了一起,我们可以用fab -l来列出所有可执行的任务及其描述,其中任务描述来自于对应任务的第一行docstring。例如,# s! W& r9 E! W

: d5 H5 z* [* Z$ fab -lAvailable commands:   monitors.get                Get YAML definition of monitors   monitors.list               List names of all monitors   monitors.mute               Mute specific groups of monitors   monitors.mute_all           Mute all monitors globally   monitors.unmute             Unmute specific groups of monitors   monitors.unmute_all         Unmute all monitors globally   mysql.connection_list       Show number of DB connections group by host   mysql.connection_sources    Show number of DB connections group by process   nginx.turn_off_maintenance  Turn off site maintenance mode.   nginx.turn_on_maintenance   Turn on site maintenance mode. Return 503.   redis.auto_save             Update saving settings for redis instances   redis.start_slave           Set master and start replication.   redis.stop_slave            Stop replication and set its master to none.   scaling.add_servers         Launch more instances in specific server group   scaling.create_image        Create an image for provisioning new instances   scaling.get_latest_tag      Get latest tag of deployed code   services.cycle              Restart application services.   services.start              Start application services.   services.stop               Stop application services.' D; f4 a9 \# U8 N
这里可以看到,将任务分写在不同的模块,模块名就起到了Namespace的作用。在显示命令列表时,在同一个Namespace下的命令被聚集到了一起,很好地起到了任务分类的作用。使用fab -d [task_name]可以显示该任务完整的docstring。规整的docstring可以让执行任务的用户清楚地理解其作用及参数用法。我们在写fabric任务的docstring时,一般分为三个部分
, S4 D" B) v2 n
* T% r$ J) \" z9 Z9 W2 h" j5 D
  • 任务的简单介绍
  • 任务的参数
  • 具体用例
    ( e0 Z: Z' S2 _! y" r0 Z
最后一点由为重要,有些任务参数众多,即使读了参数说明,仍会让人有些云里雾里。但几个典型的实际用例,对于用户了解任务的用法会起到至关重要的作用。在下面的例子中,我们展示了deploy任务(代码部署)的说明文档

5 Z1 r1 W4 s3 z6 ]; k8 p  P3 T
$ fab -d deploy
' @5 I& h, f' aDisplaying detailed information for task 'deploy':. S3 w; `4 j: |2 y5 {2 u2 Z: }
% R, g8 L1 M0 ^/ B6 I! Z' z+ Q
   Deploy code to targeted server_group.9 p. o7 u! g1 M
   You need put ansible vault password at ~/.ansible_vault_passwd directory, / p4 G4 O6 c: B
   otherwise you would be prompt to enter vault password.0 k# D+ G4 @7 I. ^/ X: `- O; M6 ]
+ q, _6 Q+ v; [) J
   Args:
/ k# q$ w+ ~1 Y) V       server_group: Possible values include prod, stage; @+ @/ i7 a# n& w) q
       release_tags: A list of release tags to be pushed 1 ]% P% u) a- h. I
, h" [8 T" s4 g2 X$ r  G
   Examples:        # Deploy to stage# N# @' ]' U5 a: H+ S
       fab deploy:stage,glow_stage_1446102452        # Deploy to production! F4 A9 r; ?5 e1 c: @1 S+ V4 Z7 g
       fab deploy:prod,glow_prod_1446102452        # Deploy multiple repo at once
: D! D! t! U; \" r# i       fab deploy:prod,glow_prod_1446102452,nurture_prod_1445102467

! |# p; {! {) [  w
动态Docstring

. V' s  S" A. h5 H7 x
在Python中,docstring其实就是函数的__doc__的属性,所以我们可以像修改普通变量那样动态修改docstring,这给我们生成动态文档或是重用公共的文档提供了可能。例如,我们的services模块下有cycle,start,stop三个任务,分别用来重启,开始,停止我们的microservice。我们当然希望在用fab -d来查看任务的文档说明时,同时可以显示所有可用的microservice。但hard-coded现有的microservice是一个愚蠢的做法,这样我们不但需要把同一段文档复制三份,并且每次新增一个microservice时还要记得来更新文档。这里我们用Python的decorator来动态地把可用服务的信息添加到docstring中。比如cycle任务的定义是这样的:
3 o/ ~) G, m) s4 O" k
@task@services_docdef cycle(*services, **kwargs):  
# \  U5 U- }! E! p9 X. U   """Restart application services.' z2 P' h% B  I  Q# j
7 c! j9 H- U! q
   Args:' k" U/ Y! a$ d
       services: list of services need cycle (separate by comma)
' ]; _- A1 z6 P( e( F7 b; J0 @! R  D
   Examples:5 \  N1 A, j. }) e7 t4 |% a. j
       fab services.cycle:glow-www,glow-forum
1 b' v) x6 ~; J* C# `   """

/ p" j2 o- _2 v* r& N. _
注意到这里用了@service_doc这个decorator,它的定义如下:

5 W9 X) z1 e0 k. i# j
def services_doc(func):  4 w' w, A$ W  b- U2 v- C
   services = get_available_services()8 \0 |6 w& A6 r; B) {
   doc = """
7 L  t& }! t/ _) K* O/ H   Possible values for services:* D" b0 ^) n. K0 S5 R! p  }
{}
. u, [7 v( \; C" }, L$ X- y  O2 B   """.format(" ".join(" " * 8 + x for x in services))8 f9 N" A) J( Z$ z
   func.__doc__ += doc    return func
3 h8 E5 U. h- a5 T5 v
我们通过get_available_services来动态取得当前环境下可用的microservice(这里我们不关心get_available_service是如何实现的),并将其添加到函数的docstring之后。这样,当我们查看cycle的方法时,所有可用的microservice也会显示出来。

. E" f2 R: l2 q9 x  W# J0 _2 m# g
( m9 I7 w) ~( O2 j7 D1 T$ fab -d services.cycleDisplaying detailed information for task 'services.cycle':   Restart application services   Args:       services: list of services need cycle (separate by comma)   Examples:       fab services.cycle:glow-www,glow-forum   Possible values for services:       glow-www       glow-user       glow-forum       ...
+ D* n3 X$ s5 s% P' p9 V
& m3 Q. S8 t) }
动态外部文档
) k" d; s% K' F7 m0 {1 Q
除了docstring,我们也经常需要写独立的外部文档。在Glow,这些文档绝大部分都是用Markdown来写的。例如,我们需要写一个介绍生产环境架构的文档,其中肯定会加入生产环境中有哪些服务器,每个服务器的功能描述以及它们的hostname。我们可以用手动的方式来写,但每当为生产环境添加新服务器时,我们必须记得更新这份文档。

( ?; X' E' C2 |0 c; B

5 h$ _; y+ w' }- @! i
而实际情况是,我们从来不在AWS的控制台手动创建服务器,所有的服务器都是由Ansible来创建与维护的。也就是说,所有的服务器配置信息及其功能描述都已经存在于Ansible的playbook中。当我们写外部文档时,应该去引用Ansible中的信息,而不是重写手写一遍。

/ l. O1 O7 i. p
2.png
  }' j4 R$ U& f- ~

% a6 ?' g2 U# f
所以在我们的生产环境文档中会利用HTML注释来指定需要外部引用的部分,然后通过执行脚本将这些引用的内容填充至文档里。例如,在我们的生产环境文档中有这样一段:
5 M4 f  `. e% o+ M4 h/ K; b+ ^8 o1 N
## EC2 servers<!-- BEGIN EC2-SERVER-LIST -->  | Server group | Instance type | Count | Description           |& y- c9 M" O" e. ]
|:-------------|:--------------|------:|:----------------------|
7 y/ g0 k0 e; }, G% y| bastion      | t2.small      |     1 | Bastion/Jumper server |
5 T, F7 B' {0 X0 [| www          | c3.large      |     4 | Web servers           |8 V8 C: S2 ]. Z; k+ B* i
...<!-- END EC2-SERVER-LIST -->  
* L) v/ N7 ^3 x/ {9 A, E& A

0 }7 A% s+ p  t" ~& {& j8 ^9 e
这里<!-- BEGIN EC2-SERVER-LIST -->和<!-- END EC2-SERVER-LIST -->之间的表格就是一个外部引用,每次Ansible更新服务器配置时,会执行一个脚本,它会自动在文档中查找这对标签,并更新其中的内容。这是一个很简单的技术,但对于保持文档与实际环境同步很有帮助。

1 Z2 g& ^3 z7 ]8 A" ?3 T
原创:叶剑烨

+ H/ X  {, _) G
2 g% t. l. I% q* `) ]4 s/ F; J- U. _* Z& V* B- m' J

# x3 K3 G- o# i$ w# c# `

本版积分规则

选择云运维时代的王牌讲师-长河老师,助你轻松入门ITIL Foundation培训课程

QQ|小黑屋|手机版|Archiver|艾拓先锋网 ( 粤ICP备11099876号-1|网站地图

Baidu

GMT+8, 2018-11-14 13:11 , Processed in 0.258458 second(s), 33 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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