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

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

 找回密码
 点击获取邀请码 - 立即注册

扫描二维码登录本站

QQ登录

只需一步,快速开始

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

你会写DevOps的文档吗?

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

参加活动:0

组织活动:12

发表于 2018-8-23 14:53:32 | 显示全部楼层 |阅读模式 来自- 广东广州
本帖最后由 monicazhang 于 2018-8-23 14:53 编辑
5 E* ?5 K& J) w& d) z2 Z" @- `
. r& `0 Y5 h6 u7 |8 R
每个DevOps都一个百宝箱,里面放着各种命令行脚本,可以用来自动化各式任务。但若文档不全,即便是脚本的作者,时间一久也不敢随便乱用,毕竟运维的大部分工作是管理生产环境,要是出了错,不是轻描淡写就可以蒙混过关的。写好 DevOps 的文档其实也是一门技术活儿,这里给大家分享一些组织运维脚本及其文档的经验。( z* ~& M! S- i* s; t
8 ^! \/ W1 i0 V8 l
Fabric的任务管理与文档

$ n9 V; q8 {0 ?- q& k; k
在以前的文章中,我们曾经介绍过Glow使用了fabric来执行各种日常管理的任务。Fabric提供了非常好用的任务组织以及查阅任务文档的功能。
, B% F; j( J9 T1 `/ T1 j
Fabric的主文件一般命名为fabfile.py,但任务多了,都写在一个文件里显然很难维护。Fabric有一个很实用的特性,就是当fabfile.py里导入其他模块时,会自动发现里面的fabric任务。利用这个特性,可以把各种任务分类写在不同的模块中,然后在fabfile.py中统一导入。比如 Glow 的 DevOps 代码库的结构大概长这个样子:
& J+ P) a# d$ u: y$ g: M$ x8 O& }
$ tree# l+ \3 y. N+ }2 k; s% x
├── __init__.py
! d) G9 l& y, R* b├── fabfile.py
" E$ t; B* z# E, F5 Y/ ~├── fab_scripts
" w# U# x! e' T9 H% N. k# n│   ├── __init__.py
( j( e8 V" C" P. |0 K& P$ l, C9 u│   ├── monitors.py1 f' Q! o+ U$ W
│   ├── mysql.py
# H) [0 w7 k/ F│   ├── nginx.py
/ K, F. K6 p+ E% s- h7 h7 n│   ├── redis.py
& `% B$ w0 u5 B│   ├── scaling.py- r6 i# W4 ?+ X& s% E8 j
│   ├── services.py
  D% ~0 n9 g- r) d. H8 a% A
在fabfile.py里除了一些最核心的任务脚本外,主要就是一些import语句

) k- G* a# A7 ?1 J$ p
# fabfile.pyfrom fab_scripts import monitors  
; C6 s) O1 g2 F! }$ a. B* {. ]from fab_scripts import mysql  
8 \' M- q$ J. mfrom fab_scripts import nginx  
) ^& g* u) L6 u! Mfrom fab_scripts import redis  % k2 t( l5 b$ z2 {' d- r
from fab_scripts import scaling  1 M1 I  m; N. q) f
from fab_scripts import services  

$ p* G. ^" K5 Q1 h; e4 j" d- I
这样我们就把散落在多个文件里的任务聚集到了一起,我们可以用fab -l来列出所有可执行的任务及其描述,其中任务描述来自于对应任务的第一行docstring。例如,
  ~% e" x3 R0 o

/ P1 v% G2 e. B; O$ 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.
9 {' \! n& _9 V4 B$ S
这里可以看到,将任务分写在不同的模块,模块名就起到了Namespace的作用。在显示命令列表时,在同一个Namespace下的命令被聚集到了一起,很好地起到了任务分类的作用。使用fab -d [task_name]可以显示该任务完整的docstring。规整的docstring可以让执行任务的用户清楚地理解其作用及参数用法。我们在写fabric任务的docstring时,一般分为三个部分
% ^8 u% F, k# A9 f- U

7 J* P: W% x# G% Y- o; U9 V
  • 任务的简单介绍
  • 任务的参数
  • 具体用例
    ' g- H4 @* r5 ~0 X8 G
最后一点由为重要,有些任务参数众多,即使读了参数说明,仍会让人有些云里雾里。但几个典型的实际用例,对于用户了解任务的用法会起到至关重要的作用。在下面的例子中,我们展示了deploy任务(代码部署)的说明文档
# P) o* Y. u. `0 G1 i2 S) H+ R2 z3 z
$ fab -d deploy
) d5 V* ?! W" K: p/ S4 [4 hDisplaying detailed information for task 'deploy':
# Z1 h  z! s4 p
9 Z) R3 D, u. u# C   Deploy code to targeted server_group.
* Y. V# A6 ?. O, V5 [   You need put ansible vault password at ~/.ansible_vault_passwd directory, ) }) f1 Z: Y) H7 ~4 M0 {: L
   otherwise you would be prompt to enter vault password.& I( C0 n3 _: }9 i8 I0 W, ]! k* o

3 Z' H) D9 J' u( {+ L' G/ z   Args:6 y- n! R6 F/ d4 {
       server_group: Possible values include prod, stage
- d/ D# t- L8 {! U       release_tags: A list of release tags to be pushed
' S2 u; W6 }% E- J& Q- u
0 S* h: r+ T8 X( S  H& A% ^   Examples:        # Deploy to stage
9 I$ c0 c, b. d/ k" c: C       fab deploy:stage,glow_stage_1446102452        # Deploy to production
9 [# i9 x. c2 z, Y9 u       fab deploy:prod,glow_prod_1446102452        # Deploy multiple repo at once
" V) m. h7 _" ~6 \: A       fab deploy:prod,glow_prod_1446102452,nurture_prod_1445102467

$ c! z0 e6 ^) I! S  ]# B# D
动态Docstring

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

/ {0 n# L& o/ D$ ]
@task@services_docdef cycle(*services, **kwargs):  
; v/ i1 \8 {9 n% Q; P; N   """Restart application services.( p6 O. z# ^' l5 q0 q( L+ y  ]
. E$ i$ R! a& X6 W" T
   Args:
: |' j! L& t0 K" l  a9 @       services: list of services need cycle (separate by comma)
  Z) k% S% W* P# e: U8 x9 d) |5 U
5 V* l: k* ?% @: B   Examples:
8 Z& k# @4 h# n( f7 c5 G* k       fab services.cycle:glow-www,glow-forum# v9 H4 }5 E% x  J- w6 y
   """

0 v) ?' ^, E' v1 r: M7 r* j
注意到这里用了@service_doc这个decorator,它的定义如下:

  ^1 o' X7 ~2 w
def services_doc(func):  # q$ [. j* m4 B' ?
   services = get_available_services()
  M# V7 M& S& K) c. H- h0 d4 \   doc = """
9 [( Z* M2 Q; m) e- x: \   Possible values for services:3 a( f& e5 y7 R1 R/ j! O
{}0 |: ]; k1 u; s2 b" ?3 y
   """.format(" ".join(" " * 8 + x for x in services))
3 G9 Y9 p, C" s   func.__doc__ += doc    return func
. Z1 o0 W) b8 E) O
我们通过get_available_services来动态取得当前环境下可用的microservice(这里我们不关心get_available_service是如何实现的),并将其添加到函数的docstring之后。这样,当我们查看cycle的方法时,所有可用的microservice也会显示出来。
( D. e5 o3 [2 L& m& O: O. v

% }2 l7 D" S  o- G% I, O/ L$ 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       ...
9 k, ]6 W  [6 z
3 B. `5 v& }% X0 \1 E" U
动态外部文档
1 ?( e* t. L( r, q& V4 k
除了docstring,我们也经常需要写独立的外部文档。在Glow,这些文档绝大部分都是用Markdown来写的。例如,我们需要写一个介绍生产环境架构的文档,其中肯定会加入生产环境中有哪些服务器,每个服务器的功能描述以及它们的hostname。我们可以用手动的方式来写,但每当为生产环境添加新服务器时,我们必须记得更新这份文档。
0 |- L. A6 c2 J- u2 i, y
# r3 R# N! [% m) k: ~) z0 [
而实际情况是,我们从来不在AWS的控制台手动创建服务器,所有的服务器都是由Ansible来创建与维护的。也就是说,所有的服务器配置信息及其功能描述都已经存在于Ansible的playbook中。当我们写外部文档时,应该去引用Ansible中的信息,而不是重写手写一遍。
! m5 A- g; T  b! z1 I- U1 G1 o) @

, G0 r& r) L# M* P  Z3 f# W! U6 L0 X. ]$ k: n8 K: G  S- u
所以在我们的生产环境文档中会利用HTML注释来指定需要外部引用的部分,然后通过执行脚本将这些引用的内容填充至文档里。例如,在我们的生产环境文档中有这样一段:

$ I) `* F" Y0 R, U
## EC2 servers<!-- BEGIN EC2-SERVER-LIST -->  | Server group | Instance type | Count | Description           |
7 E1 N) f6 C0 {, m|:-------------|:--------------|------:|:----------------------|
* g, ~3 F2 e  _3 M| bastion      | t2.small      |     1 | Bastion/Jumper server |% i6 t: L4 P) y, S/ a
| www          | c3.large      |     4 | Web servers           |2 p+ U+ |* x/ b" ]& D
...<!-- END EC2-SERVER-LIST -->  

0 f3 u& c7 P% @( H7 |( V! p! ^) \
% }5 }4 k7 o  m6 W) P9 z
这里<!-- BEGIN EC2-SERVER-LIST -->和<!-- END EC2-SERVER-LIST -->之间的表格就是一个外部引用,每次Ansible更新服务器配置时,会执行一个脚本,它会自动在文档中查找这对标签,并更新其中的内容。这是一个很简单的技术,但对于保持文档与实际环境同步很有帮助。
/ n9 ]5 j; G* n2 ?+ E
原创:叶剑烨

0 v8 s+ q0 G6 t/ |: v( I" E& ?' S9 W$ c- |
+ [: w- C7 W: d: ^  Q
" a2 c) b' Q7 w# o

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?点击获取邀请码 - 立即注册

x

本版积分规则

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

QQ|小黑屋|手机版|Archiver|ITIL先锋论坛五万运维人社区 ( 粤ICP备17056641号|网站地图

Baidu

GMT+8, 2018-9-21 01:05 , Processed in 0.215448 second(s), 34 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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