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

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

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

扫描二维码登录本站

QQ登录

只需一步,快速开始

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

你会写DevOps的文档吗?

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

参加活动:0

组织活动:12

发表于 2018-8-23 14:53:32 | 显示全部楼层 |阅读模式 来自- 广东广州
本帖最后由 monicazhang 于 2018-8-23 14:53 编辑
. Z2 D: ?& |- F9 a2 q* o; S4 i6 l; g- ~: m5 f
每个DevOps都一个百宝箱,里面放着各种命令行脚本,可以用来自动化各式任务。但若文档不全,即便是脚本的作者,时间一久也不敢随便乱用,毕竟运维的大部分工作是管理生产环境,要是出了错,不是轻描淡写就可以蒙混过关的。写好 DevOps 的文档其实也是一门技术活儿,这里给大家分享一些组织运维脚本及其文档的经验。1 n7 i! ^/ d) z; Y2 M+ l5 ~. Y
1.png

; [( W4 b2 z" i4 y8 |! V4 ^
Fabric的任务管理与文档

0 ?4 l4 X" ]( H" H# C; _
在以前的文章中,我们曾经介绍过Glow使用了fabric来执行各种日常管理的任务。Fabric提供了非常好用的任务组织以及查阅任务文档的功能。
+ r7 t+ B# _+ Y- _6 ?) X
Fabric的主文件一般命名为fabfile.py,但任务多了,都写在一个文件里显然很难维护。Fabric有一个很实用的特性,就是当fabfile.py里导入其他模块时,会自动发现里面的fabric任务。利用这个特性,可以把各种任务分类写在不同的模块中,然后在fabfile.py中统一导入。比如 Glow 的 DevOps 代码库的结构大概长这个样子:
1 H; _. E8 [- b/ J1 a8 z6 M$ u9 X
$ tree
9 C- Z1 S/ x, W! p2 ^1 X├── __init__.py: T( d7 ?3 a6 G4 M
├── fabfile.py
1 {1 U9 R  o0 M* ^* Q/ b├── fab_scripts. |+ n* e( X8 d
│   ├── __init__.py8 o, U9 j: q  a3 u( V5 d; L& K+ @1 h
│   ├── monitors.py' ^; M# Z5 R9 K
│   ├── mysql.py. \: R% Z5 i* ?+ t$ f5 n
│   ├── nginx.py
& Z' L) i8 [* ]% ?# ~& b/ b│   ├── redis.py
# S& N; Y! K0 R0 {* a│   ├── scaling.py
7 U( R* k- [- }) k1 K  i7 }│   ├── services.py

& |* {6 I, U: T; g; T+ U% e
在fabfile.py里除了一些最核心的任务脚本外,主要就是一些import语句
5 M1 }1 B3 ]+ \8 z1 i3 U
# fabfile.pyfrom fab_scripts import monitors  
+ @7 B. D8 i3 Nfrom fab_scripts import mysql  - b7 G2 y" v8 f1 d, [
from fab_scripts import nginx  
. A1 [& W, q9 i) h3 D2 k; pfrom fab_scripts import redis  7 `2 j$ J; z5 }- e+ B5 t
from fab_scripts import scaling  1 l8 L1 q$ [; o8 u7 {4 c9 O
from fab_scripts import services  
* W; p  K4 y2 f, i5 {- w3 ^
这样我们就把散落在多个文件里的任务聚集到了一起,我们可以用fab -l来列出所有可执行的任务及其描述,其中任务描述来自于对应任务的第一行docstring。例如,1 s( B: |. r8 b5 C: c8 b# t* p/ P
( n2 G7 y8 j( b: v. p
$ 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.5 d' a5 E% J; l! F" ^7 p, M  N
这里可以看到,将任务分写在不同的模块,模块名就起到了Namespace的作用。在显示命令列表时,在同一个Namespace下的命令被聚集到了一起,很好地起到了任务分类的作用。使用fab -d [task_name]可以显示该任务完整的docstring。规整的docstring可以让执行任务的用户清楚地理解其作用及参数用法。我们在写fabric任务的docstring时,一般分为三个部分
; C; j& w5 u1 Y! B% w4 G8 d
) o2 {9 b$ C$ b% R
  • 任务的简单介绍
  • 任务的参数
  • 具体用例

    / @2 }: ^! T- t7 ~
最后一点由为重要,有些任务参数众多,即使读了参数说明,仍会让人有些云里雾里。但几个典型的实际用例,对于用户了解任务的用法会起到至关重要的作用。在下面的例子中,我们展示了deploy任务(代码部署)的说明文档
7 ^6 p, x2 n0 ], r$ e
$ fab -d deploy  c" |* j/ [  n6 z* @$ \
Displaying detailed information for task 'deploy':9 a8 @6 D( g( E! Y
% C4 d0 L7 }0 G5 K+ O3 o
   Deploy code to targeted server_group.% Q1 t, e" k0 K- T
   You need put ansible vault password at ~/.ansible_vault_passwd directory,
) ?9 {1 ~( W. k, n5 s   otherwise you would be prompt to enter vault password.- t5 w6 i1 d% R9 o# H3 b2 w

. w8 H; q: ^9 W5 B) n5 r! s( J1 V   Args:6 b0 k4 U/ C& N, `: |% f0 _
       server_group: Possible values include prod, stage
& b; W  X/ S9 {+ ~7 E" r* Z       release_tags: A list of release tags to be pushed
( d/ S) k7 y8 v) c: s/ v% z
* p) T& D" L+ W# c" I   Examples:        # Deploy to stage  ?- F9 i+ e! b+ U4 G
       fab deploy:stage,glow_stage_1446102452        # Deploy to production
7 ~, m5 G* ^9 M; i* i6 |3 O       fab deploy:prod,glow_prod_1446102452        # Deploy multiple repo at once; u% l5 B1 s; T4 o
       fab deploy:prod,glow_prod_1446102452,nurture_prod_1445102467
" o3 f  p9 O$ c. x' E/ r1 B$ k) y
动态Docstring
6 m$ ?# [/ E/ Y# g' t0 X- m) z- G
在Python中,docstring其实就是函数的__doc__的属性,所以我们可以像修改普通变量那样动态修改docstring,这给我们生成动态文档或是重用公共的文档提供了可能。例如,我们的services模块下有cycle,start,stop三个任务,分别用来重启,开始,停止我们的microservice。我们当然希望在用fab -d来查看任务的文档说明时,同时可以显示所有可用的microservice。但hard-coded现有的microservice是一个愚蠢的做法,这样我们不但需要把同一段文档复制三份,并且每次新增一个microservice时还要记得来更新文档。这里我们用Python的decorator来动态地把可用服务的信息添加到docstring中。比如cycle任务的定义是这样的:
/ }& l- }  _5 T1 K+ y" u& l8 B" Z
@task@services_docdef cycle(*services, **kwargs):  % ^- L4 O' j; T: I2 B
   """Restart application services.
$ N5 M& F/ J% S( @
8 F2 }# x, R# I6 c5 T   Args:% F$ o  J2 v3 S, S- T4 Q* B/ p
       services: list of services need cycle (separate by comma)$ ]* a. [  `  J! d, x

  n/ N' Y5 ?/ Y8 p6 c7 p   Examples:" b$ I( v' ]. b
       fab services.cycle:glow-www,glow-forum
* `$ [: K- Y) j  a6 I( i   """
& Y+ C# I+ q2 R
注意到这里用了@service_doc这个decorator,它的定义如下:
2 R0 @; C5 I/ t* ^- {* R
def services_doc(func):  
5 A+ c( G$ a, f9 C' |   services = get_available_services()4 O, E* W" E1 O% l  Z: t
   doc = """" E% k8 }: L, C
   Possible values for services:# l5 B' b5 N" i  A; W
{}
) Z% o+ P5 Y$ g$ E9 f$ B   """.format(" ".join(" " * 8 + x for x in services))' }- y0 J! S; n/ ^% O5 b4 C4 {, H
   func.__doc__ += doc    return func
' m% Z! W7 J4 w! J7 ^% V
我们通过get_available_services来动态取得当前环境下可用的microservice(这里我们不关心get_available_service是如何实现的),并将其添加到函数的docstring之后。这样,当我们查看cycle的方法时,所有可用的microservice也会显示出来。
* {) P# i5 e( \2 u5 _6 e1 s
/ L+ D2 s( ?2 y8 c+ L: K
$ 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       ...
) n' {* |% |9 [
  [* J& H4 q( r" {* e
动态外部文档
2 ^1 R5 P" K: j8 A3 |. |
除了docstring,我们也经常需要写独立的外部文档。在Glow,这些文档绝大部分都是用Markdown来写的。例如,我们需要写一个介绍生产环境架构的文档,其中肯定会加入生产环境中有哪些服务器,每个服务器的功能描述以及它们的hostname。我们可以用手动的方式来写,但每当为生产环境添加新服务器时,我们必须记得更新这份文档。
$ s* U; h* f# @. u! f

4 G3 w4 ?0 {: i4 u
而实际情况是,我们从来不在AWS的控制台手动创建服务器,所有的服务器都是由Ansible来创建与维护的。也就是说,所有的服务器配置信息及其功能描述都已经存在于Ansible的playbook中。当我们写外部文档时,应该去引用Ansible中的信息,而不是重写手写一遍。

7 U& G4 ~+ c; S6 e7 ^% U
2.png

9 g/ D% z7 C' W( o: g. e! r
; K  u: O1 r9 w$ b) M6 H$ v
所以在我们的生产环境文档中会利用HTML注释来指定需要外部引用的部分,然后通过执行脚本将这些引用的内容填充至文档里。例如,在我们的生产环境文档中有这样一段:
( E  e! M1 M& n8 R
## EC2 servers<!-- BEGIN EC2-SERVER-LIST -->  | Server group | Instance type | Count | Description           |% Q7 _% j$ E7 I2 |
|:-------------|:--------------|------:|:----------------------|
: k4 d' t# ^8 y: \3 F( y7 s| bastion      | t2.small      |     1 | Bastion/Jumper server |
: F, z/ c/ ^. \* D2 I% ~. Z| www          | c3.large      |     4 | Web servers           |
: a5 r4 D) ]/ x% W" ^...<!-- END EC2-SERVER-LIST -->  

9 J: r5 R; I- @) w9 N2 v3 v
' S1 J) z3 I) j! _3 j( s
这里<!-- BEGIN EC2-SERVER-LIST -->和<!-- END EC2-SERVER-LIST -->之间的表格就是一个外部引用,每次Ansible更新服务器配置时,会执行一个脚本,它会自动在文档中查找这对标签,并更新其中的内容。这是一个很简单的技术,但对于保持文档与实际环境同步很有帮助。
# ]1 @) j0 m, N
原创:叶剑烨
5 M, P; J6 ^9 F; c' O7 b* p

! ]" M& ?8 O' Q# E# L
. K+ Q* ^" C$ O6 l' s
4 v3 m0 l0 j1 S- V, |/ Z5 ^* U

本版积分规则

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

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

Baidu

GMT+8, 2019-1-23 22:24 , Processed in 0.201760 second(s), 31 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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