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

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

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

扫描二维码登录本站

QQ登录

只需一步,快速开始

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

你会写DevOps的文档吗?

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

参加活动:0

组织活动:12

发表于 2018-8-23 14:53:32 | 显示全部楼层 |阅读模式 来自- 广东广州
本帖最后由 monicazhang 于 2018-8-23 14:53 编辑 / q) f7 x: s, j/ b7 [2 t
+ k: F8 v/ K% N$ f: g
每个DevOps都一个百宝箱,里面放着各种命令行脚本,可以用来自动化各式任务。但若文档不全,即便是脚本的作者,时间一久也不敢随便乱用,毕竟运维的大部分工作是管理生产环境,要是出了错,不是轻描淡写就可以蒙混过关的。写好 DevOps 的文档其实也是一门技术活儿,这里给大家分享一些组织运维脚本及其文档的经验。
* R  X% e7 H) F5 K# x: M; i
1.png

5 M1 k; V+ N! ~: @: c" k" \
Fabric的任务管理与文档
3 v9 M& f" J+ o7 a  w
在以前的文章中,我们曾经介绍过Glow使用了fabric来执行各种日常管理的任务。Fabric提供了非常好用的任务组织以及查阅任务文档的功能。
0 ?( @/ O( P- }* k  B0 X
Fabric的主文件一般命名为fabfile.py,但任务多了,都写在一个文件里显然很难维护。Fabric有一个很实用的特性,就是当fabfile.py里导入其他模块时,会自动发现里面的fabric任务。利用这个特性,可以把各种任务分类写在不同的模块中,然后在fabfile.py中统一导入。比如 Glow 的 DevOps 代码库的结构大概长这个样子:

3 p3 |# X* m2 t# D/ b% J
$ tree6 F2 x' u" d$ B4 C3 A" z: l
├── __init__.py
6 D  @! M  @" \. M9 G' M$ Q" a├── fabfile.py' Z# T' [- b2 ^' V
├── fab_scripts
" i! u+ m9 ^+ E7 S│   ├── __init__.py# v9 h6 S5 q& q$ `7 A  n$ ?' O( q
│   ├── monitors.py
9 w7 p, a+ }" m% W- t2 i1 O│   ├── mysql.py3 L/ }( h+ J$ E% m8 j  d7 l( e" i
│   ├── nginx.py
$ H5 Y: g' m9 E│   ├── redis.py5 u1 {, m* z/ E2 I6 u6 J
│   ├── scaling.py% H( I2 P1 F" L8 V! O  i
│   ├── services.py

0 j) `* t* `. |! j/ h) u
在fabfile.py里除了一些最核心的任务脚本外,主要就是一些import语句

  ~3 X' m/ R* i7 E9 n
# fabfile.pyfrom fab_scripts import monitors  
+ i4 V9 G# F7 bfrom fab_scripts import mysql  
. s" B" `! ?% ^/ Vfrom fab_scripts import nginx  # L! I, h& Y  L2 Y; r7 _1 F
from fab_scripts import redis  
- T  B* w* g) |' kfrom fab_scripts import scaling  
2 t9 o+ H- {2 U0 Q! w7 l. q& \from fab_scripts import services  

0 Y7 q$ b8 {) Q: T
这样我们就把散落在多个文件里的任务聚集到了一起,我们可以用fab -l来列出所有可执行的任务及其描述,其中任务描述来自于对应任务的第一行docstring。例如,
, ~6 S6 ?/ Y" H" ?5 p2 S4 j- t+ w2 q: N

1 c+ Q- L' N! p% L4 h$ 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.
  N' j8 A6 o% D
这里可以看到,将任务分写在不同的模块,模块名就起到了Namespace的作用。在显示命令列表时,在同一个Namespace下的命令被聚集到了一起,很好地起到了任务分类的作用。使用fab -d [task_name]可以显示该任务完整的docstring。规整的docstring可以让执行任务的用户清楚地理解其作用及参数用法。我们在写fabric任务的docstring时,一般分为三个部分: b/ l! s7 O2 V" U. m  I
. h9 X  B' B0 n+ ]% n5 H4 ^
  • 任务的简单介绍
  • 任务的参数
  • 具体用例

    , f7 T8 M: |( Q, }
最后一点由为重要,有些任务参数众多,即使读了参数说明,仍会让人有些云里雾里。但几个典型的实际用例,对于用户了解任务的用法会起到至关重要的作用。在下面的例子中,我们展示了deploy任务(代码部署)的说明文档

5 @% p; Z7 B  b
$ fab -d deploy
( Q3 {" j  |0 G8 B- s6 p# iDisplaying detailed information for task 'deploy':/ g3 ]3 c6 y* n' T+ @/ T

0 l. u; [3 {/ `3 H7 Z   Deploy code to targeted server_group.. F/ w! ?: D2 D8 d6 Q& ~2 `! q
   You need put ansible vault password at ~/.ansible_vault_passwd directory, 9 }6 f6 n0 N1 t+ U5 P1 p
   otherwise you would be prompt to enter vault password.
) q3 @' W; c/ I3 \& m( f, |$ W* O: \( T9 J
   Args:
5 s$ N2 I# ?% k5 V* X7 |5 w       server_group: Possible values include prod, stage* K0 p# g- R4 M' |  J
       release_tags: A list of release tags to be pushed 5 A0 H, Z  R& j" F6 a5 @
% U/ i; E7 i8 k- o
   Examples:        # Deploy to stage- t" t0 @) ^0 e* t9 @, m( O4 {
       fab deploy:stage,glow_stage_1446102452        # Deploy to production( @9 e: }0 u$ G/ }
       fab deploy:prod,glow_prod_1446102452        # Deploy multiple repo at once( I' J" B  T! C& t# h
       fab deploy:prod,glow_prod_1446102452,nurture_prod_1445102467
8 Z& N1 n) [- A
动态Docstring

* N  \) {8 H7 q  H
在Python中,docstring其实就是函数的__doc__的属性,所以我们可以像修改普通变量那样动态修改docstring,这给我们生成动态文档或是重用公共的文档提供了可能。例如,我们的services模块下有cycle,start,stop三个任务,分别用来重启,开始,停止我们的microservice。我们当然希望在用fab -d来查看任务的文档说明时,同时可以显示所有可用的microservice。但hard-coded现有的microservice是一个愚蠢的做法,这样我们不但需要把同一段文档复制三份,并且每次新增一个microservice时还要记得来更新文档。这里我们用Python的decorator来动态地把可用服务的信息添加到docstring中。比如cycle任务的定义是这样的:

$ }6 j0 e3 N  ]2 L6 W
@task@services_docdef cycle(*services, **kwargs):  
9 v  w' a/ ]# _* A) M& f- A   """Restart application services., b- ?7 o7 x, _
: i" y9 {7 J) ^' l
   Args:& Q  U8 \* J1 T8 W  j& k9 m+ B
       services: list of services need cycle (separate by comma)' A, y/ `! Z: W. q  m
. M4 ^. S, R- L
   Examples:1 b. z8 B. M. Y& f
       fab services.cycle:glow-www,glow-forum  v  U- g+ E* y5 l
   """

) [6 b5 G( m: L4 E0 @) _1 F
注意到这里用了@service_doc这个decorator,它的定义如下:
+ d3 [. K. o4 `3 q
def services_doc(func):  1 B9 t) U' T) j5 j* Y5 w: x
   services = get_available_services()
$ Y. N& y+ ]7 F  i$ S( U$ u6 \   doc = """
0 X, k5 t5 f! B- Q8 `& f   Possible values for services:
1 N/ U% a: B2 t+ M{}
  v* u: _3 z$ y# ]   """.format(" ".join(" " * 8 + x for x in services))1 n; j- }& s' P
   func.__doc__ += doc    return func
/ h7 U+ F! e4 t6 K/ Y
我们通过get_available_services来动态取得当前环境下可用的microservice(这里我们不关心get_available_service是如何实现的),并将其添加到函数的docstring之后。这样,当我们查看cycle的方法时,所有可用的microservice也会显示出来。
# U6 ]# U/ ]/ H" j1 [0 p

. b  h( T6 t6 o# `$ 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       ...
3 w" U4 m/ u6 Y4 V
, ?/ u. d0 E4 ~% T2 W* R
动态外部文档
) j! i( L" V6 R
除了docstring,我们也经常需要写独立的外部文档。在Glow,这些文档绝大部分都是用Markdown来写的。例如,我们需要写一个介绍生产环境架构的文档,其中肯定会加入生产环境中有哪些服务器,每个服务器的功能描述以及它们的hostname。我们可以用手动的方式来写,但每当为生产环境添加新服务器时,我们必须记得更新这份文档。

8 R7 i( w4 `8 c: b0 N) k

! r0 J$ u7 A, ^: z) Y4 c
而实际情况是,我们从来不在AWS的控制台手动创建服务器,所有的服务器都是由Ansible来创建与维护的。也就是说,所有的服务器配置信息及其功能描述都已经存在于Ansible的playbook中。当我们写外部文档时,应该去引用Ansible中的信息,而不是重写手写一遍。

" y- n7 ^3 h, R5 l
2.png

/ P* c7 |5 H9 [0 y: @" p- R/ q! M+ X& c$ I  E
所以在我们的生产环境文档中会利用HTML注释来指定需要外部引用的部分,然后通过执行脚本将这些引用的内容填充至文档里。例如,在我们的生产环境文档中有这样一段:

8 Z5 f  f7 R6 B' w
## EC2 servers<!-- BEGIN EC2-SERVER-LIST -->  | Server group | Instance type | Count | Description           |
5 H, r- v  A# ~) F/ }$ B5 _$ D; ?3 _|:-------------|:--------------|------:|:----------------------|. `$ C1 K* ?& o; a' j5 _9 f1 z
| bastion      | t2.small      |     1 | Bastion/Jumper server |% D" i) [# Y# _
| www          | c3.large      |     4 | Web servers           |. @4 Z  F9 H0 y& R% b
...<!-- END EC2-SERVER-LIST -->  

* l% W8 U0 b. @& a/ x4 @% i3 y4 c4 E4 S% h% r. f! P2 y
这里<!-- BEGIN EC2-SERVER-LIST -->和<!-- END EC2-SERVER-LIST -->之间的表格就是一个外部引用,每次Ansible更新服务器配置时,会执行一个脚本,它会自动在文档中查找这对标签,并更新其中的内容。这是一个很简单的技术,但对于保持文档与实际环境同步很有帮助。
! z/ J! N2 o2 A2 w
原创:叶剑烨
  M5 b, V5 {1 c- d1 @% Q
! K$ t2 S* |+ g" I9 J" t

- b$ |% k% K) g+ W9 }' ?7 p) g( P, t5 p3 I# Y: H  ]. }1 i

本版积分规则

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

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

Baidu

GMT+8, 2019-5-24 13:45 , Processed in 0.224256 second(s), 36 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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