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

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

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

扫描二维码登录本站

QQ登录

只需一步,快速开始

搜索
查看: 363|回复: 0

你会写DevOps的文档吗?

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

参加活动:0

组织活动:12

发表于 2018-8-23 14:53:32 | 显示全部楼层 |阅读模式 来自- 广东广州
本帖最后由 monicazhang 于 2018-8-23 14:53 编辑
& `$ x- }* K- A* U: }/ C, O) k2 N. t4 U1 J" d2 B
每个DevOps都一个百宝箱,里面放着各种命令行脚本,可以用来自动化各式任务。但若文档不全,即便是脚本的作者,时间一久也不敢随便乱用,毕竟运维的大部分工作是管理生产环境,要是出了错,不是轻描淡写就可以蒙混过关的。写好 DevOps 的文档其实也是一门技术活儿,这里给大家分享一些组织运维脚本及其文档的经验。
* ^4 @" \# g) k% J
1.png
2 z$ l: |/ G: O- N: A; Q+ s
Fabric的任务管理与文档
' I7 O( f1 m1 D/ x  ~2 Z
在以前的文章中,我们曾经介绍过Glow使用了fabric来执行各种日常管理的任务。Fabric提供了非常好用的任务组织以及查阅任务文档的功能。

3 @2 e9 l; f! [& K& M9 B7 Y
Fabric的主文件一般命名为fabfile.py,但任务多了,都写在一个文件里显然很难维护。Fabric有一个很实用的特性,就是当fabfile.py里导入其他模块时,会自动发现里面的fabric任务。利用这个特性,可以把各种任务分类写在不同的模块中,然后在fabfile.py中统一导入。比如 Glow 的 DevOps 代码库的结构大概长这个样子:

5 u6 f. J5 n" \/ Y9 B) d
$ tree
9 x; y9 C+ j% U+ u  `5 S, b├── __init__.py
9 i# t! w6 J  t0 B├── fabfile.py
/ u8 G( }$ Y: |% @/ X) @; H├── fab_scripts1 I2 X2 n, I* O7 \! |" n- J
│   ├── __init__.py+ U& B; _7 ~  F) D1 Q" ?2 J
│   ├── monitors.py" y; R; Q& w/ a$ \! l4 f; [2 w
│   ├── mysql.py/ V9 D" B7 K9 e# S& C" R3 X+ H
│   ├── nginx.py
+ q7 }+ u! O4 z) o6 V│   ├── redis.py* W# J" N( R/ X/ x6 Q% |& M9 n
│   ├── scaling.py
9 j# m) y9 J2 R$ ?; v" }│   ├── services.py
1 {5 \0 }3 U' ?7 F' P
在fabfile.py里除了一些最核心的任务脚本外,主要就是一些import语句

6 e* [2 C. ?) s2 m0 p0 g
# fabfile.pyfrom fab_scripts import monitors  ) B. ]/ E$ W2 d5 y8 V
from fab_scripts import mysql  
  ?% |1 Q" [, vfrom fab_scripts import nginx  - x# T0 R1 H& P
from fab_scripts import redis  
5 L  f  a& `, \5 V. R( ~6 k' k5 Jfrom fab_scripts import scaling  . B7 |6 [/ Z/ R
from fab_scripts import services  

( ]$ r4 i. q$ ?
这样我们就把散落在多个文件里的任务聚集到了一起,我们可以用fab -l来列出所有可执行的任务及其描述,其中任务描述来自于对应任务的第一行docstring。例如,5 C5 K1 B1 u1 Y  e+ M5 @# J3 E

% m- j2 D' a5 o8 b& A2 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.
3 `! [1 w8 S# J$ I7 J: \
这里可以看到,将任务分写在不同的模块,模块名就起到了Namespace的作用。在显示命令列表时,在同一个Namespace下的命令被聚集到了一起,很好地起到了任务分类的作用。使用fab -d [task_name]可以显示该任务完整的docstring。规整的docstring可以让执行任务的用户清楚地理解其作用及参数用法。我们在写fabric任务的docstring时,一般分为三个部分
% d$ c& Y& e7 Z" \
# F, k9 c; Z# T& t
  • 任务的简单介绍
  • 任务的参数
  • 具体用例
    3 a* q' s* q4 f+ ?: S
最后一点由为重要,有些任务参数众多,即使读了参数说明,仍会让人有些云里雾里。但几个典型的实际用例,对于用户了解任务的用法会起到至关重要的作用。在下面的例子中,我们展示了deploy任务(代码部署)的说明文档

  o7 E3 t8 r0 s* F6 X+ ?9 ^3 `& a
$ fab -d deploy
0 a4 B1 Z5 F+ eDisplaying detailed information for task 'deploy':  n$ G2 I9 r: r" R% k8 y$ d2 G, r! g

5 d2 @; _9 ?  E* L0 \) [; G   Deploy code to targeted server_group.
& o. C0 a7 T7 B) t# C3 K2 ^% K2 W   You need put ansible vault password at ~/.ansible_vault_passwd directory,
4 e+ I  f6 [+ V1 j0 D4 W   otherwise you would be prompt to enter vault password.
1 u$ D, b* J' h6 g0 g$ R' N* h. t9 z
   Args:
, s# `" Q* o+ R3 s8 K: T2 ]       server_group: Possible values include prod, stage; h; q: L6 m" h9 h; z9 M1 s
       release_tags: A list of release tags to be pushed
2 k+ z( M  v) e3 ]" [" H$ v- O, Z7 K
   Examples:        # Deploy to stage
8 S$ n% d8 s6 M* t' k       fab deploy:stage,glow_stage_1446102452        # Deploy to production" A6 Q* S1 g8 R# u/ |
       fab deploy:prod,glow_prod_1446102452        # Deploy multiple repo at once
6 ?' M8 f/ r1 }/ j1 m. j2 B0 q       fab deploy:prod,glow_prod_1446102452,nurture_prod_1445102467
: m+ b; g) f3 r$ F1 B
动态Docstring
5 T* c$ Q/ N3 A$ y& w3 l, Z6 o
在Python中,docstring其实就是函数的__doc__的属性,所以我们可以像修改普通变量那样动态修改docstring,这给我们生成动态文档或是重用公共的文档提供了可能。例如,我们的services模块下有cycle,start,stop三个任务,分别用来重启,开始,停止我们的microservice。我们当然希望在用fab -d来查看任务的文档说明时,同时可以显示所有可用的microservice。但hard-coded现有的microservice是一个愚蠢的做法,这样我们不但需要把同一段文档复制三份,并且每次新增一个microservice时还要记得来更新文档。这里我们用Python的decorator来动态地把可用服务的信息添加到docstring中。比如cycle任务的定义是这样的:

7 ~8 n+ F6 \$ q& Y7 a3 p
@task@services_docdef cycle(*services, **kwargs):  ( w& [/ G* C5 t
   """Restart application services.# ~2 e' l; J, }) {' m, Z* b7 l9 I
- a1 G7 Q0 r2 z4 p$ u0 J
   Args:/ c0 T. }5 O7 B2 W- e% R
       services: list of services need cycle (separate by comma)
& Q' D, Z# e" L3 o& e! ]9 A; y, \# l  j. @  ^' U' T: X
   Examples:0 v9 S. Q& H6 |' ?
       fab services.cycle:glow-www,glow-forum
" c1 z3 J8 i# t" }2 e   """

- n$ m& O9 \% h6 n% L% l/ W2 L
注意到这里用了@service_doc这个decorator,它的定义如下:
) }4 a3 c9 ?: A& x  B; a
def services_doc(func):  
7 f; [- p3 d+ `( P( x/ [5 e9 c   services = get_available_services()5 s$ u# |& Q5 Y! B, v- \( \
   doc = """3 y& E. d, }/ X5 f& o8 ]% R: c
   Possible values for services:
# O# F2 ~9 k7 T, `) c8 l{}
8 I( l9 F( c& z2 H7 M% d  m   """.format(" ".join(" " * 8 + x for x in services))0 N* |% @! k* d3 C3 M5 |# D( T% c) ~8 a
   func.__doc__ += doc    return func
# @5 j& `" b! n( k2 k
我们通过get_available_services来动态取得当前环境下可用的microservice(这里我们不关心get_available_service是如何实现的),并将其添加到函数的docstring之后。这样,当我们查看cycle的方法时,所有可用的microservice也会显示出来。

% g  y' f5 ]' G) C$ w3 o0 b- c* w0 c
$ 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 q! ^  f( c4 j5 s2 D5 x5 j

: c, f$ i; h7 p9 z
动态外部文档
7 `4 s1 q- w" I( h# e
除了docstring,我们也经常需要写独立的外部文档。在Glow,这些文档绝大部分都是用Markdown来写的。例如,我们需要写一个介绍生产环境架构的文档,其中肯定会加入生产环境中有哪些服务器,每个服务器的功能描述以及它们的hostname。我们可以用手动的方式来写,但每当为生产环境添加新服务器时,我们必须记得更新这份文档。
6 o7 ?# P* a$ v+ @6 f, V
% D7 T) L& B3 B7 k! W  M
而实际情况是,我们从来不在AWS的控制台手动创建服务器,所有的服务器都是由Ansible来创建与维护的。也就是说,所有的服务器配置信息及其功能描述都已经存在于Ansible的playbook中。当我们写外部文档时,应该去引用Ansible中的信息,而不是重写手写一遍。
- Q: C' h$ P+ M$ X4 H7 @0 G3 c  `
2.png
. l( G' J# [2 V

- g7 j% A5 }; E% `
所以在我们的生产环境文档中会利用HTML注释来指定需要外部引用的部分,然后通过执行脚本将这些引用的内容填充至文档里。例如,在我们的生产环境文档中有这样一段:

- g& @) E5 A( K( p
## EC2 servers<!-- BEGIN EC2-SERVER-LIST -->  | Server group | Instance type | Count | Description           |, z& y" [  Y" A$ a& R6 P/ z0 W
|:-------------|:--------------|------:|:----------------------|% @5 {  c/ v  u9 m; T
| bastion      | t2.small      |     1 | Bastion/Jumper server |) C' q( t' A: X# `3 \
| www          | c3.large      |     4 | Web servers           |8 v$ J4 E9 w% a4 v
...<!-- END EC2-SERVER-LIST -->  

( X! j% q4 j: [% |; k/ G* f6 q* K. [2 h4 \4 d3 h2 O  k
这里<!-- BEGIN EC2-SERVER-LIST -->和<!-- END EC2-SERVER-LIST -->之间的表格就是一个外部引用,每次Ansible更新服务器配置时,会执行一个脚本,它会自动在文档中查找这对标签,并更新其中的内容。这是一个很简单的技术,但对于保持文档与实际环境同步很有帮助。

9 i& \) {8 |4 D
原创:叶剑烨
2 R2 t/ I8 N) W- D
, F, i" K. t1 c7 x" Z* _9 ^( V
' W* T% @$ ?" Q1 G" F

( }, P( l6 {7 M4 e* U' q0 m5 F" t5 W- X

本版积分规则

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

Baidu

GMT+8, 2019-8-17 21:24 , Processed in 0.210922 second(s), 29 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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