SVN自助更新:运维利器Puppet实例讲解(一)
作者简介:刘晗昭,网名蚊子(博客),某通信业国企系统工程师,熟悉各种主流开源软件的使用,部署和组合应用,以及主流网站架构。目前关注puppet线上应用与分布式缓存清除系统。
一直想写点关于puppet的文章,但通过学习发现,puppet更多的是参数配置上的东西,写理论性的内容无非就是翻译一下官方文档,不利于新人对于puppet的理解上手。于是觉得,还是应该结合实际应用写些关于puppet使用方面的东西更贴近大众。
首先简单介绍一下puppet。puppet是一种Linux、Unix平台的集中配置管理系统,使用自有的puppet描述语言,可管理配置文件、用户、cron任务、软件包、系统服务等。puppet把这些系统实体称之为资源,其设计目标是简化对这些资源的管理以及妥善处理资源间的依赖关系。
puppet采用C/S星状的结构,所有的客户端和一个或几个服务器交互。每个puppet客户端每半小时(可以设置)连接一次服务器端,下载最新的配置文件,并且严格按照配置文件来配置服务器,保证和该配置信息同步。配置完成以后,puppet客户端可以反馈给服务器端一个消息。如果出错,也会给服务器端反馈一个消息。
情景描述
在开始介绍实现方法之前,先介绍一下我们的业务情况。我们企业有不少项目是交给外包做的,相关的项目负责人或开发者有新的或更改过的项目文件会上传到我们的SVN服务器上,再由我们的系统管理员执行SVN update更新页面。一直以来我们的做法都是,项目负责人或开发者上传了新文件之后,去找到SA,SA再登录服务器手动执行svn up。每次都要SA动手实在很麻烦,所以前些日子看到puppet这个东西的时候,觉得这就是能够将项目负责人、开发者和SA从这个工作中解放出来的利器~
这个SVN自助更新系统的实现思路如下:
一、开发平台
- apache+perl-cgi
- puppet
二、功能介绍
系统管理员/项目执行者/开发人员通过web的get对应的url,然后触发更新puppet-master中的一个文件,puppet-client在同步时间到来的时候(默认半小时,可改),判断master上对应的项目文件的md5值是否有变化,如果有变化,触发puppet配置中对应的svn更新的程序,完成此项目的更新。
三、系统架构
四、实施步骤及相关说明解释
1、puppet配置
此处puppet的安装方法我就不写了,有需要的自行去网上查找。我这边是CentOS 5.4(服务器端)和CentOS 5.5(客户端),采用yum安装。
A、puppet-master配置
a) 首先是创建autosign.conf文件,此文件用于自动验证。
*.wenzizone.cn
这样所有这个域名过来的请求会自动验证。
b) 修改puppet.conf文件。如果是通过rpm或yum安装,此文件内容可以基本保持不变
[main] logdir = /var/log/puppet rundir = /var/run/puppet ssldir = $vardir/ssl bindaddress = 192.168.192.199 [puppetd] classfile = $vardir/classes.txt localconfig = $vardir/localconfig
此文件主要是定义了一些文件路径,我在这里添加了bindaddress = 192.168.192.199这条,绑定在了内网的ip上,因为我并不想让我的puppet-master被外网访问到,这个可以因个人环境而定。如果需要看详细的puppet.conf内容可以执行puppet --genconfig。
c) 修改fileserver.conf,增加如下内容
[system] path /etc/puppet/modules/system/files allow *.wenzizone.cn allow 192.168.192.0/24 [svnup] path /etc/puppet/modules/svn/files allow *.wenzizone.cn allow 192.168.192.0/24
此文件可以创建一个文件系统,有点类似rsync的配置,后面我会继续介绍。这个意思就是所有wenzizone.cn或者192.168.192.0网段的机器可以访问/etc/puppet/modules/svn/files下的文件。
注:以上三个文件都是放至在/etc/puppet目录下的
d) puppet功能配置
首先看下puppet的目录结构,
/etc/puppet/ |-- auth.conf |-- autosign.conf |-- fileserver.conf |-- manifests | |-- huodong_web.pp | `-- site.pp |-- modules | |-- svn | | |-- files | | | |-- hd_zf_up_file | | `-- manifests | | |-- hd_zf_up.pp | | |-- init.pp | `-- system | |-- files | | |-- puppet.client.conf | `-- manifests | |-- init.pp | |-- puppet_client.pp `-- puppet.conf
根下的manifests目录放置主配置文件,modules下面是各种的模块,在这里我设置了两个模块,system和svn,其中system用来给我的puppet-client同步puppet.conf文件,svn就是我这次介绍的重点了。
这里插个题外话:puppet的模块都是遵从
| |-- svn | | |-- files | | `-- manifests | | |-- init.pp
这种结构的,包含模块名字:svn目录,files目录,manifests目录,此目录下包含init.pp文件。
下面先看svn的这个模块
files目录:用来存放触发svn更新的文件的,我们可以在此目录下touch任何一个名字的文件,更新的工作由perl-cgi来控制,我这里使用的名字是:hd_zf_up_file
manifests目录:用来存放puppet的脚本文件的,必须包含init.pp文件,通常情况下可以把脚本都写入到这个一个文件中,但为了便于管理,最好是按照功能或者项目分开存放。我这里的hd_zf_up.pp就是我为公司线上的一个项目建立的。内容如下:
1class hd_zf_up 2{ 3 file { 4 "hd_zf_up": 5 group => 'root', 6 mode => '600', 7 owner => 'root', 8 path => "/tmp/hd_zf_up_file", 9 source => "puppet:///svnup/hd_zf_up_file", 10 } 11 12 exec { 13 "svn up": 14 cwd => "/var/www/html/www.51cto.com/zf", 15 path => "/usr/bin:/usr/sbin:/bin", 16 subscribe => File["hd_zf_up"], 17 refreshonly => true, 18 } 19}
下面详细解释一下:
1行定义了一个类,类似php里的类,用于将相应的方法整合。
3行是puppet的file方法,网上的介绍file是一种资源,我这里认为当作方法比较合适,这样就可以跟php的类相似了,file后面大括号里的都是file这个方法的属性。属性用来赋值,方法用来调用。当然怎样理解因人而异。
4行给这个file方法定义了一个方便使用的别名
5行定义此文件的属组是root
6行定义此文件的访问模式是只有属主用户可读可写
7行定义文件的属主也是root
8行定义此文件在puppet-client端的放置位置及文件名
9行定义了从puppet-master的什么文件拿到那个文件。这里就用到了前fileserver.conf定义的内容,可以看到svnup是预定义的别名,它实际对应的路径是/etc/puppet/modules/svn/files,这样后面跟的文件就会到这个路径下去取。整个svn这部分的配置理解就是:puppet-client要从source定义的路径拿到hd_zf_up_file文件放到client的/tmp目录下并将文件所以权给root用户和组,访问模式是600。
12行定义了puppet的exec方法,这个方法是使puppet执行系统命令或脚本用的。
13行是定义了exec要执行的命令,在这里是要求puppet执行svn up这个命令。也就是更新项目的命令
14行定义了上面的svn up命令要在什么目录下执行
15行定义了path的变量,用于系统可以找到svn对应的命令。
16行定义了一个依存关系,我们这里和17行联合起来解释,当file方法定义里的那个文件的md5值发生变化的时候,此exec方法触发,即当上面source里面定义的文件md5值发生改变了,就执行svn up这个命令。
题外话:起先我这里并没有想到要和一个文件进行联立,但结果发现,puppet会在每次触发的时候都执行exec这个方法,这会造成网络资源的消耗,因为svn里的数据并不是每次都有更新的,所以没必要每次都执行。
到此svn up这部分的配置就完成了。接下来看看init.pp里面的内容
class svn { import "hd_zf_up.pp" }
可以看到,init.pp的内容还是很简单的,只是引入了我们定义好的pp文件。
下面来介绍system那个模块的内容,这部分内容我主要讲同步puppet-client端配置文件的这个功能,这样方便我们一次性配置好client端的puppet。
files目录下:放置的是puppet.client.conf文件,主要内容如下
[main] logdir = /var/log/puppet rundir = /var/run/puppet ssldir = $vardir/ssl [puppetd] classfile = $vardir/classes.txt localconfig = $vardir/localconfig server = app-21-199.wenzizone.cn runinterval = 300 show_diff=true
从内容上看,基本上和master上一样,主要区别是后三行,server用于指明master的名字,可以是ip可以是对应的主机名(前提是你已经有了对应的解析)。runinterval用于指定puppet-client每次执行的间隔,单位是秒,最后一行是当文件被替换的时候是否显示不一样的地方。
manifests目录下同样放置了init.pp文件,同时我还自定义了一个puppet_client.pp,内容如下:
1 class puppet_client 2 { 3 file 4 { 5 "puppet_conf": 6 path => "/etc/puppet/puppet.conf", 7 source => "puppet:///system/puppet.client.conf", 8 } 9 exec 10 { 11 "reload-puppet-client": 12 command => "/etc/init.d/puppet force-reload", 13 require => Service["puppet_client"], 14 refreshonly => true, 15 } 16 service 17 { 18 "puppet_client": 19 name => "puppet", 20 enable => true, 21 ensure => running, 22 hasrestart => true, 23 hasstatus => true, 24 subscribe =>File["puppet_conf"], 25 } 26 }
相同的内容就不重复介绍了。这里exec部分的11行并不是直接写的要执行的命令,而是定义了富有含义的名字,比较直观,然后在12行通过command定义了要执行的命令。
13行表明exec能不能执行是依赖于service这个方法的,后面详细介绍。
16行开始定义了一个新的方法service。顾名思义就是服务类的方法。
18行同样定义了一个富有含义的名称。
19行定义这个服务的名字,这里的名字必须和/etc/init.d下对应的名字一样
20行表示此服务在开机时是否启动,true是启动
21行表示是否运行此服务,running表示运行。
22行指出管理脚本是否支持restart参数,如果不支持,就用stop和start实现restart效果. 可以设置的值是true 或 false
23行指出管理脚本是否支持status参数,puppet用status参数来判断服务是否已经在运行了,如果不支持status参数,puppet利用查找运行进程列表里面是否有服务名来判断服务是否在运行. 可以设置的值是true或false
24行表示,此服务运行依赖file这个方法,同时exec又require了service,这样当file里定义的配置文件有变化及文件md5值不一样的时候,exec就执行force-reload的命令。
再来看init.pp内容
1 class system 2 { 3 import "puppet_client.pp" 4 }
此内容很简单,主要是定义了一个system的类,然后导入puppet_client.pp的内容。
到此puppet上自定义的两大功能就介绍完成了,下面看看puppet如何加载。
回到/etc/puppet/manifests目录下,主要文件就两个,site.pp和huodong_web.pp。site.pp是主文件,huodong_web.pp是我为我需要svn更新的web服务器做的。内容如下
1 node "app198-vhost-192-55" { 2 #系统初始化工具 3 include system 4 include puppet_client 5 #祝福活动页面更新 6 include svn 7 include hd_zf_up 8 }
1行定义了一个主机名,此处可以是主机名可以是ip地址
3加载system类,这个类是在system模块下的init.pp中定义的
4在system类加载后就来加载puppet_client的类,这个累是在system模块下的puppet_client.pp定义的。
6,7行同样是加载svn模块中定义好的类。
接下来看site.pp的内容
1 node default 2 { 3 import "huodong_web.pp" 4 }
这个主文件的内容也很简单,首先定义了一个默认node,然后加载了huodong_web.pp文件。
到此master上puppte部分的配置就全部完毕了。接下来看client端上的配置
B、puppet-client上的配置
a) 登录到app198-vhost-192-55这台主机上,yum安装puppet,然后在命令行执行
puppetd --server app-21-199.wenzizone.cn –test
由于我们是做的自动签名,并且在服务器上配置了puppet-client的配置文件,所以执行完过后puppet自动会以守护进程的方式启动
通过简单的两步,就已经把puppet-client上的准备工作都完成了,是不是很简单。
那么下面就是perl-cgi部分了。
C、部署svn自助更新web脚本
这里可以将脚本部署在和puppet-master同一台主机上,当然也可以考虑分布式的,那样就需要调整这个脚本。现在先以同一台机器为例。
a)安装apache,配置cgi目录
因为我们的访问量不会很大,我直接用yum安装了。所以cgi目录使用的是默认的cgi-bin目录。
b)编写对应的脚本pjupdate.cgi
#! /bin/env perl use CGI; my $query = CGI->new(); #初始化cgi实例 my $file_name = $query->param('pjname'); #将url中pjname的值赋给$file_name变量 my $up_time = `date +%s`; #获得从1970-01-01 00:00:00 UTC到现在的秒数 my $dst_dir = "/etc/puppet/modules/svn/files/"; #目标文件的目录 my $success = open FH,">".$dst_dir.$file_name; #开的目标文件,即触发svn的文件 if(!$success) { print "Content-type:text/html\n\n"; print "不能更新此项目,请联系管理员!"; #如果打开不成功进行提示,通常是权限问题,文件属主改成apache执行用户 } else { print FH"$up_time"; #更新文件内容,为了达到变更此文件md5值的效果 print "Content-type:text/html\n\n"; print "更新已经开始,请5~10分钟后查看页面"; } close(FH);
五、测试
使用方法:通过
http://test.wenzizone.cn/cgi-bin/pjupdate.cgi?pjname=hd_zf_up_file
触发更新。
可以看到,pjname的值实际上就是磁盘上我定义的那个文件的名字。这样只要使用不同的文件,再配置不同的puppet的pp文件,就能实现多个项目自助更新了。同样,因为puppet是分布式的c/s模式,所以在同一时间,也可以实现对多台服务器的更新。
到此,这个自助svn更新系统就介绍完毕了。有几点需要注意的:
- 请确认部署环境中有对应的主机名到ip地址的解析环境,本文所在环境已存在内部dns,所以全部使用主机名。
- 所有puppet主机时间要保持一致。
- 注意防火墙对puppet端口的限制,如果非必要,可以关闭防火墙。