puppet安装配置004
http://www.chenshake.com/puppet-study-notes/
下了决心,好好学习puppet,周末专门去参加一个puppet的培训,难得朋友那么热心,组织大家一起去学习。我就提前做一下功课。
2012年10月29日:参加完两天的培训,深刻体会到puppet的强悍,当然讲Puppet的朋友,还是非常有经验,不只是puppet经验,还有讲课的经验,学习一个新东西的经验。一个最大的收获:官方的文档非常好,把puppet读完,你就差不多。
Contents [hide]
概述
Puppet 是一个客户端/服务器(C/S)架构的配置管理工具,在中央服务器上安装 puppet-server 服务器(puppet master),在需要被管理的目标服务器上安装 puppet 客户端软件(puppet client)。
如果服务器端也安装客户端,那么还可以管理本地机器。简单点说,当你把puppet装好后,你可以利用puppet的管理功能,直接用puppet安装foreman。这是我希望实现的功能。
准备
我直接使用Centos 6.3,puppet官方的yum源,目前puppet版本已经是3.01。对os的要求也很简单
- 关闭selinux
- 关闭iptables,这是为了避免各种麻烦,你可以通过打开端口,而不需要关闭iptables
- 设置host文件,由于puppet需要用FQDN,一般实验环境都是没有dns,所以通过hosts文件设置
- 设置ntp,同步时间,这个也是必须的。
- 设置源,根据你希望使用的版本,设置不同的源.我是启用了EPEL和Puppet官方的源
- node06 为master (10.1.199.6)
- node08 为client (10.1.199.8)
这些设置,大家可以参考 vpsee的puppet文档
安装
Puppet 3.01,对很多以前版本的命令已经去掉,这也让大家看文档的时候,比较混乱。目前pre-2.6的命令,在3.0以后的版本,完全无法使用。这个大家要记住。这样更换后,其实也比较清晰。
服务器端
因为我们采用源安装,所有ruby的依赖关系都是自动解决。装服务器端的时候,其实也同时把客户端装上.
yum -y install puppet-server
看看依赖的包
启动puppet
chkconfig puppet on chkconfig puppetmaster on service puppetmaster start service puppet start
Puppet Master 运行在TCP的8140端口。以前iptables的命令打开一个端口很长,很难记忆,现在发现一个好工具 lokkit。打开的端口,重启机器也是不影响,非常方便。
lokkit -p 8140:tcp
查看打开的端口
# netstat -lpnut Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:8140 0.0.0.0:* LISTEN 1476/ruby tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1454/sshd tcp 0 0 :::22 :::* LISTEN 1454/sshd udp 0 0 10.1.199.6:123 0.0.0.0:* 1462/ntpd udp 0 0 127.0.0.1:123 0.0.0.0:* 1462/ntpd udp 0 0 0.0.0.0:123 0.0.0.0:* 1462/ntpd udp 0 0 :::123 :::* 1462/ntpd
查看证书
# puppet cert list --all + "node06.chenshake.com" (SHA256) FF:54:B7:86:11:F7:EA:92:34:A4:E0:53:41: 32:5C:8F:C5:5C:DC:03:66:6C:CF:20:9E:11:DE:40:98:D1: 7E:F8 (alt names: "DNS:node06.chenshake.com", "DNS:puppet", "DNS:puppet.chenshake.com")
这个时候,已经自动把本机当成客户端,管理起来,证书已经自动签发.
客户端
单独安装一个客户端
yum install puppet
大家可以看看,比较一下客户端和服务器依赖的包.
启动服务
chkconfig puppet on service puppet start
puppet的基本默认配置,这里面的内容是不需要修改。
# cat /etc/sysconfig/puppet
# The puppetmaster server
#PUPPET_SERVER=puppet
# If you wish to specify the port to connect to do so here
#PUPPET_PORT=8140
# Where to log to. Specify syslog to send log messages to the system log.
#PUPPET_LOG=/var/log/puppet/puppet.log
# You may specify other parameters to the puppet client here
#PUPPET_EXTRA_OPTS=--waitforcert=500
上面就是Puppet的默认设置,大家可以去掉注释,进行修改。这也是为啥puppet的agent,默认就到网络找一个puppet的机器,你可以在这里修改。
/var/lib/puppet 目录,是客户端一个比较重要的目录,agent的证书就是放在这个目录下。
]# pwd /var/lib/puppet ]# ll total 24 drwxr-x--- 2 root root 4096 Oct 26 15:35 clientbucket drwxr-x--- 2 root root 4096 Oct 26 15:35 client_data drwxr-x--- 2 root root 4096 Oct 26 15:35 client_yaml drwxr-xr-x 2 root root 4096 Oct 26 15:35 lib drwxrwx--x 7 puppet root 4096 Oct 26 15:35 ssl drwxr-xr-t 3 root root 4096 Oct 26 15:35 state
Puppet agent 配置文件,是 /etc/puppet/puppet.conf , 基本编辑这个文件可以。
# pwd /etc/puppet # ll total 12 -rw-r--r-- 1 root root 2979 Oct 19 02:07 auth.conf drwxr-xr-x 2 root root 4096 Oct 19 02:07 modules -rw-r--r-- 1 root root 853 Oct 19 02:06 puppet.conf
对于puppet.conf 来说,里面分成3部分[main], [master], [agent], 外面的文档,有些是把参数添加到[main], 有些是添加到[agent], 用初学者比较困惑,到底那个是正确。对于agent来说,你就在agent里修改就可以。如果你的设置和[main]冲突,就会保留[agent]设置。所以你基本就不需要管[main]设置就可以。
对于puppet 客户端,我们需要编辑 /etc/puppet/puppet.conf, 添加一行,指定master服务器名称。
[agent] # The file in which puppetd stores a list of the classes # associated with the retrieved configuratiion. Can be loaded in # the separate ``puppet`` executable using the ``--loadclasses`` # option. # The default value is '$confdir/classes.txt'. classfile = $vardir/classes.txt # Where puppetd caches the local configuration. An # extension indicating the cache format is added automatically. # The default value is '$confdir/localconfig'. localconfig = $vardir/localconfig server = node06.chenshake.com
重启agent就可以,这个时候,你就不需要加上服务器地址,就可以连接master。
常用命令
查看puppet版本
# puppet --version 3.0.1
查看模块位置
# puppet config print modulepath /etc/puppet/modules:/usr/share/puppet/modules
查看报告
# puppet agent -t --summarize Info: Retrieving plugin Info: Caching catalog for node08.chenshake.com Info: Applying configuration version '1351737193' Finished catalog run in 0.05 seconds Changes: Events: Resources: Skipped: 6 Total: 7 Time: Filebucket: 0.00 Config retrieval: 0.18 Total: 0.18 Last run: 1351737193 Version: Config: 1351737193 Puppet: 3.0.1
证书
Client申请证书
client需要向服务器端发出请求, 让服务器对客户端进行管理. 这其实是一个证书签发的过程. 第一次运行 puppet 客户端的时候会生成一个 SSL 证书并指定发给 Puppet 服务端, 服务器端如果同意管理客户端,就会对这个证书进行签发.
puppet agent
为了详细了解注册的过程和日后排错,可以增加参数,因为配置文件里
- –no-daemonize 前台输出日志
- –verbose 输入更加详细的日志
- –debug 更加详细的日志,排错的时候使用
- –test 表示测试,就带一个–test参数就可以
puppet agent --server=node06.chenshake.com --no-daemonize --onetime --verbose --debug
我的服务器端,如果iptables的端口没打开或者iptables没有关闭,你回看到下面错误
Debug: Finishing transaction 70232051730000
Error: Could not request certificate: No route to host - connect(2)
Exiting; failed to retrieve certificate and waitforcert is disabled
如果一切正常,你回看到下面输出
Debug: Finishing transaction 69982568075580 Info: Caching certificate for ca Info: Creating a new SSL certificate request for node08.chenshake.com Info: Certificate Request fingerprint (SHA256): DC:BF:4A:B7:65:9F:8D:80:79:42:B3:1D:94:B6:D9: A7:1B:99:38:EB:49:DA:13:1E:E2:CE:56:5C:78:CC:12:53 Debug: Using cached certificate for ca Debug: Using cached certificate for ca Exiting; no certificate found and waitforcert is disabled
这个时候,你在服务器端就可以看到请求签发的证书
# puppet cert list --all "node08.chenshake.com" (SHA256) DC:BF:4A:B7:65:9F:8D:80:79:42:B3:1D:94:B6:D9:A7:1B:99:38:EB:49:DA:13:1E:E2:CE:56:5C:78:CC:12:53 + "node06.chenshake.com" (SHA256) FF:54:B7:86:11:F7:EA:92:34:A4:E0:53:41:32:5C:8F:C5:5C:DC:03:66:6C:CF:20:9E:11:DE:40:98:D1:7E:F8 (alt names: "DNS:node06.chenshake.com", "DNS:puppet", "DNS:puppet.chenshake.com")
旁边有+ 号的,表示已经签发。
签发证书很简单
puppet cert --sign node08.chenshake.com puppet cert --sign --all
签发证书。
# puppet cert --sign --all Signed certificate request for node08.chenshake.com Removing file Puppet::SSL::CertificateRequest node08.chenshake.com at '/var/lib/puppet/ssl/ca/requests/node08.chenshake.com.pem'
签发完成后,你再查看,就会发现
# puppet cert --list --all + "node06.chenshake.com" (SHA256) FF:54:B7:86:11:F7:EA:92:34:A4:E0:53:41:32:5C:8F:C5:5C:DC:03:66:6C:CF:20:9E:11:DE:40:98:D1:7E:F8 (alt names: "DNS:node06.chenshake.com", "DNS:puppet", "DNS:puppet.chenshake.com") + "node08.chenshake.com" (SHA256) A1:80:54:46:03:01:AE:6E:22:B1:39:8F:45:F2:C5:5A:F9:4E:CA:94:DA:A9:BF:85:34:E7:6E:98:07:97:B7:BC
注销证书
让证书失效,真正操作,我建议使用clean的参数,发现revoke,仅仅是让证书失效。
puppet cert revoke node08.chenshake.com Revoked certificate with serial 3
这个时候,你查看证书
# puppet cert list --all + "node06.chenshake.com" (SHA256) 9C:3E:5C:11:03:C9:AA:35:B8:DE:A2:2C:44:79:2F:F2:64:7D:19:1B:75:99:09:2E:43:C0:26:70:6A:24:30:C2 (alt names: "DNS:node06.chenshake.com", "DNS:puppet", "DNS:puppet.chenshake.com") - "node08.chenshake.com" (SHA256) CB:15:4A:55:23:1D:AD:08:5F:A6:D8:3C:D8:17:47:6E:E1:42:47:01:2D:D3:1B:55:85:18:65:6E:B2:6C:46:EA (certificate revoked)
你需要重启puppetmaster服务,才能正式生效,你可以通过客户端连接来测试,没有重启服务前,一切正常,只有重启了master服务后,你再用node08去连接,就会提示下面的错误。
Debug: Using cached certificate_revocation_list for ca Error: Failed to apply catalog: SSL_connect returned=1 errno=0 state=SSLv3 read server session ticket A: sslv3 alert certificate revoked Debug: Value of 'preferred_serialization_format' (pson) is invalid for report, using default (b64_zlib_yaml) Debug: report supports formats: b64_zlib_yaml raw yaml; using b64_zlib_yaml Error: Could not send report: SSL_connect returned=1 errno=0 state=SSLv3 read server session ticket A: sslv3 alert certificate revoked
删除证书
在master上,清除证书后,需要重启服务才能生效.
# puppet cert --clean node08.chenshake.com Revoked certificate with serial 3 Removing file Puppet::SSL::Certificate node08.chenshake.com at '/var/lib/puppet/ssl/ca/signed/node08.chenshake.com.pem' Removing file Puppet::SSL::Certificate node08.chenshake.com at '/var/lib/puppet/ssl/certs/node08.chenshake.com.pem'
重启puppetmaster服务,
/etc/init.d/puppetmaster restart
在client上
rm -f /var/lib/puppet/ssl/certs/node08.chenshake.com.pem
或者整个目录删除,这样ca的证书,也删除。
rm -rf /var/lib/puppet/ssl
这个时候,你再申请就可以
# puppet agent -t Info: Creating a new SSL certificate request for node08.chenshake.com Info: Certificate Request fingerprint (SHA256): 43:4F:C8:D7:B0:84:D8:89:F6:D9:9C:DE:D4:5B: C0:BF:F1:D6:89:6C:C0:94:7C:02:99:50:98:BA:4C:1C:52:4F Exiting; no certificate found and waitforcert is disabled
这个时候,你在master就可以正常签发。
自动签发证书
可以设置master自动签发所有的证书,我们只需要在/etc/puppet 目录下创建 autosign.conf 文件。(不需要修改 /etc/puppet/puppet.conf文件,因为我默认的autosign.conf 文件的位置没有修改)
cat > /etc/puppet/autosign.conf <<EOF *.chenshake.com EOF
这样就会对所有来自 chenshake.com 的机器的请求,都自动签名。
[root@node08 ~]# puppet agent -t Info: Creating a new SSL key for node08.chenshake.com Info: Caching certificate for ca Info: Creating a new SSL certificate request for node08.chenshake.com Info: Certificate Request fingerprint (SHA256): 50:6B:ED:AB:E4:46:49:53:3E:41:6A:DD:93:7F:5F:3F: 00:55:17:25:A0:BB:12:AF:4A:2B:89:88:5D:41:9F:86 Info: Caching certificate for node08.chenshake.com Info: Caching certificate_revocation_list for ca Info: Retrieving plugin Info: Caching catalog for node08.chenshake.com Info: Applying configuration version '1351497197' Finished catalog run in 0.04 seconds
Pre-signing 证书
就是提前在服务器端签发证书,把证书复制到客户端,这样可以避免自动签名的危险。不过很麻烦,需要你手工copy证书。创建证书的命令,和以前版本的puppet 2.6有不同,大家注意就可以。
# puppet cert generate node08.chenshake.com node08.chenshake.com has a waiting certificate request Signed certificate request for node08.chenshake.com Removing file Puppet::SSL::CertificateRequest node08.chenshake.com at '/var/lib/puppet/ssl/ca/requests/node08.chenshake.com.pem' Removing file Puppet::SSL::CertificateRequest node08.chenshake.com at '/var/lib/puppet/ssl/certificate_requests/node08.chenshake.com.pem'
客户端操作
mkdir -p /var/lib/puppet/ssl/private_keys mkdir -p /var/lib/puppet/ssl/certs scp root@10.1.199.6:/var/lib/puppet/ssl/private_keys/node08.chenshake.com.pem /var/lib/puppet/ssl/private_keys/ scp root@10.1.199.6:/var/lib/puppet/ssl/certs/node08.chenshake.com.pem /var/lib/puppet/ssl/certs/ scp root@10.1.199.6:/var/lib/puppet/ssl/certs/ca.pem /var/lib/puppet/ssl/certs/
这时候你就可以在客户端运行
# puppet agent -t Info: Caching certificate_revocation_list for ca Info: Retrieving plugin Info: Caching catalog for node08.chenshake.com Info: Applying configuration version '1351735593' Finished catalog run in 0.04 seconds
Puppet Dashboard
我其实是希望直接使用puppet来安装dashboard,不过目前阶段,我还是搞不定,这个留待日后慢慢挑战。
mysql
yum install -y mysql mysql-devel mysql-server
优化mysql设置
编辑 /etc/my.cnf, 在[mysqld]字段,增加最后一行.
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
# Allowing 32MB allows an occasional 17MB row with plenty of spare room
max_allowed_packet = 32M
启动服务
/etc/init.d/mysqld start chkconfig mysqld on
设置mysql密码,我这里使用是密码是password
mysqladmin -u root password 'password'
创建一个dashboard数据库
mysql -uroot -ppassword <<EOF CREATE DATABASE dashboard CHARACTER SET utf8; CREATE USER 'dashboard'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON dashboard.* TO 'dashboard'@'localhost'; FLUSH PRIVILEGES; EOF
Passenger+Apache+Dashboard
这是让Apache支持ruby,
yum install mod_passenger puppet-dashboard
看看包的依赖关系
配置Dashboard
编辑 /usr/share/puppet-dashboard/config/database.yml
production: database: dashboard username: dashboard password: password encoding: utf8 adapter: mysql
修改时区 /usr/share/puppet-dashboard/config/environment.rb
#config.time_zone = 'UTC' config.time_zone = 'Beijing'
初始化数据库
cd /usr/share/puppet-dashboard/ rake RAILS_ENV=production db:migrate
配置Apache
我们需要整合Passenger和apache
cat > /etc/httpd/conf.d/passenger.conf << EOF LoadModule passenger_module modules/mod_passenger.so <IfModule mod_passenger.c> PassengerRoot /usr/share/rubygems/gems/passenger-3.0.17 PassengerRuby /usr/bin/ruby PassengerHighPerformance on PassengerMaxPoolSize 12 PassengerPoolIdleTime 1500 PassengerStatThrottleRate 120 RailsAutoDetect On </IfModule> <VirtualHost *:80> ServerName node06.chenshake.com DocumentRoot "/usr/share/puppet-dashboard/public/" <Directory "/usr/share/puppet-dashboard/public/"> Options None AllowOverride AuthConfig Order allow,deny allow from all </Directory> ErrorLog /var/log/httpd/node06.chenshake.com_error.log LogLevel warn CustomLog /var/log/httpd/node06.chenshake.com_access.log combined ServerSignature On </VirtualHost> EOF
重启服务
/etc/init.d/httpd start chkconfig httpd on
打开80端口
lokit -p 80:tcp
配置puppet
让Dashboard使用Reports,现在默认agent是已经启用Report的功能,所以你就不需要设置agent,你只需要设置Server端就可以.
# puppet.conf (on puppet master) [master] reports = store, http reporturl = http://node06.chenshake.com:80/reports/upload
重启puppetmaster 服务
/etc/init.d/puppetmaster restart
这时候就可以直接用 http://ip 访问puppet Dashboard
导入报告
cd /usr/share/puppet-dashboard # rake RAILS_ENV=production reports:import (in /usr/share/puppet-dashboard) Importing 7 reports from /var/lib/puppet/reports/ in the background Importing: 100% |#########| Time: 00:00:00 7 of 7 reports queued
这时候你访问Dashboard,可以看到导入的任务.
Delayed Job Workers
这个其实我理解就是一个脚本,用来分析report的。
env RAILS_ENV=production /usr/share/puppet-dashboard/script/delayed_job -p dashboard -n 4 -m start
查看启动的job
ps -ef|grep delayed_job|grep -v grep
停止delay job
env RAILS_ENV=production /usr/share/puppet-dashboard/script/delayed_job -p dashboard -n 4 -m stop
这个时候你才能在Dashbaord里看到数据.
Foreman
目前Puppet 3.0和Foreman 1.0还有问题,官方正在解决中
这是一篇几个月前被枪毙的文章。当然,确实写太浅了一些,就是一入门。因为我对 puppet 本来了解得也不深入。这会想起了还有这篇文章,姑且放出来。高手们还是绕路吧。:-)
相信做过运维的朋友都会有这样的体会:把一个新的服务器从刚装好系统的状态配置到可以运行应用程序,是个挺麻烦的过程。就拿一个运行 nginx + php 的 web 服务器为例,可能需要部署 ssh 公钥,设置用户 sudo 权限,关闭密码登录、root 远程登录,配置 iptables 规则。然后安装所需版本的 nginx、 php 到规范的路径,不能搞错版本,以免缺失所需特性或者造成冲突,还要安装应用所需要的 php 扩展比如 gd 之类。然后是用于监控的客户端程序,比如 nagios 的 nrpe ,或者 zabbix_agent,用于日志轮转的 cronolog 等等。最后还得记得修改 ulimits 、tcp 相关的内核参数。然后还得一一验证所有的设置是否正确。如果是部署了新的东西或者更新了配置,还得写一下安装文档,这样下回安装的时候才不会遗漏什么编译 选项、软件包,或者少设置了系统参数导致故障。有人请假或则离职的时候,别人也才能接替。需要部署的还不会仅仅是 web 服务器,还有 lvs 、mysql、memcache,也许还会有 redis、sphinx 等等等等。有软件包需要升级,也得对所有用到的机器都升级一遍。更要命的是,往往需要配置的还不是一台机器,而是十几台乃至上百台。
这里 可以看出,系统配置本身是件很繁琐的事情。需要考虑到很多方面的事情,任何一个地方出了纰漏就可能导致故障或者埋下隐患。手动来做很容易出错,也很不够敏 捷高效,难以快速响应需求。为了解决这些问题,我也曾经用 bash 做过一些脚本,来实现部署和系统设置的自动化,但是受到 bash 的表达能力的限制,脚本的编写测试维护并不轻松。我们碰到的麻烦别人也会碰到。所以就有了自动化好在现在有了一个好用的工具来解决这些问题。puppet 就是这样一个功能强大的系统配置集群管理工具。puppet 分为 master 端和 agent 端,可以实现分布式分发,有强大的配置管理功能,可以实现自动化分发文件、安装软件包、执行命令、添加系统用户、设置 crontab 等等。它的配置文件是一种表达能力强的 DSL,可读性好、容易复用,且本身就可以作为很好的文档,这就免除了维护文档的负担,也避免了文档过时的问题。puppet 还使用了 ssl 来保证通讯的安全性,防止敏感的配置信息泄露。支持集群化,以实现大批量主机并行更新维护。引入的 factor 还实现了针对不同系统、不同发行版、不同环境的针对性设置。以及很多方便强大的功能。
我们先看看如何安装 puppet 。最方便的方法是使用包管理器。对于 centos 等 redhat 系发行版,可以通过 yum 来安装。先把 puppet 的官方源加入到系统中:
# cat > /etc/yum.repos.d/puppet.repo << EOF [puppet-dependencies] name=puppet dependencies baseurl=http://yum.puppetlabs.com/el/\$releasever/dependencies/\$basearch/ gpgcheck=1 gpgkey=http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs [puppet-products] name=puppet dependencies baseurl=http://yum.puppetlabs.com/el/\$releasever/products/\$basearch/ gpgcheck=1 gpgkey=http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs EOF
然后
# yum install puppet
就会自动安装好 puppet。从所列出依赖的软件包可以看出, puppet 是用 ruby 实现的。对于 debian 系发行版,也可以在 apt.puppetlabs.com 中找到相应的源和 gpg key。
puppet 分为 master 和 agent 。找两台机器或者两个虚拟机,一台作为 master,一台作为 agent ,两端都安装好后就可以开始配置了。
首先配置 master 。先在 master 端运行一下
# puppet master
初次运行会生成puppet 用户, 在 /etc/puppet 目录下生成默认配置目录结构并在 /var/lib/puppet 生成数据文件。如果提示 permisiton denided ,可以试试 chown puppet:puppet /var/lib/puppet/run 。
然后我们就可以开始写第一个配置文件了。在 /etc/puppet/manifests 目录下建一个 site.pp ,这是 puppet master 的主配置文件。输入
package { "bison": ensure=>"installed", } exec { "puppet test": command=>"/bin/touch /tmp/puppet-test", }
第一个配置会使 agent 端确保编译 php 所必须的软件包 bison 已经安装好。对于不同的系统,会使用各自的包管理器来安装。第二个配置会在 agent 端执行 /bin/touch /tmp/puppet-test 。
然后配置客户端。先编辑 /etc/hosts,加入 master 的 ip,如:
192.168.1.101 puppet
在客户端运行一下
# puppet agent --test
初次运行也同样会生成客户端的相应文件,然后就会去连接 master 端执行任务。此时会提示
warning: peer certificate won't be verified in this SSL session exiting; no certificate found and waitforcert is disabled
这表示 agent 需要认证。因为 puppet 使用了 ssl 来保证安全,并需要 agent 经过 master 认证才能够访问配置。到 master 端执行一下
# puppet cert list
会列出待认证的 agent 列表。这里可以看到 agent 的主机名。如
puppet-agent-test-01 (66:62:5C:84:B0:23:73:FB:80:7C:89:48:4C:A6:AF:53)
然后可以使用
# puppet cert sign puppet-agent-test-01
就能完成认证。如果觉得直接使用主机名不够灵活,也可以在运行 agent 时使用 --certname=认证名 来指定。在 agent 端再试一次,这回就可以看到,agent 已经开始干活了。看看 bison 工具是否安装好了,再看看 /tmp 目录下是否生成了 /tmp/puppet-test 文件。
需要注意的是,一个主机可以使用多个不同的certname,但一个certname只能被一台主机使用。如果原有的certname需要移动到另一个主机上使用,就需要在master端先 puppet cert clean "认证名" 来清除原有数据。所以,certname应当尽量保持全局唯一。
这里 agent 使用的 --test 让 agent 不以服务方式运行,只执行一次,并输出详细信息。去掉这个参数,puppet agent 就会以服务方式在后台运行,默认每 30 分钟连接一次服务器更新配置。可以用 puppet help master、puppet help agent 查看更多选项。
刚才是把所有的配置都写在了 sites.pp 文件里。在配置项增多,维护的项目增多以后,就会变得过于庞大而难以维护。所以就需要把配置分到不同的模块中去,以模块化的方式来管理配置。
puppet 的模块放在 /etc/puppet/modules 下。模块的目录结构如下图所示:
modules/ |-- test |-- files | `-- test.txt `-- manifests |-- init.pp `-- test.pp
在这个例子里,定义了一个 test 模块。其中 files 目录中用于安放该模块所需分发的文件,manifests 目录中是该模块的配置文件。其中 init.pp 是每个模块的主配置文件。内容通常为 import "*" ,来载入该模块的其他配置文件。我们在 files 目录中加入一个 test.txt 文件,并把之前 site.pp 中的内容挪到 test.pp 中,再加入分发文件的配置,定义成一个类:
class test1 { package { "bison": ensure=>"installed", } exec { "puppet test": command=>"/bin/touch /tmp/puppet-test", } file { "/tmp/test.txt": ensure => "present", source => "puppet:///modules/test/test.txt" } }
site.pp 中删除原有的配置,加入 import "test" ,把 test 模块加载进来,然后加入 include "test1" ,应用 test1 类的配置。Include语句也可以再class内使用。以在一个class中复用另一个class的配置。现在,我们在 agent 端再运行一次 puppet agent --test ,agent 还是会照常工作,但是配置已经分到模块中了。实践中,会把每个配置的项目建立一个模块,比如:nginx、php 等等。再看看 /tmp 目录,会发现 master 端的 test.txt 文件已经下载回来。puppet 已经完成了文件分发的工作。module 中的 files 通常用于分发配置文件,把软件包的配置文件集中管理。
file配置也可以用来创建目录。只要使用 ensure => "directory" 即可。如:
file { "/tmp/testdir": ensure => "directory", }
到目前为止,我们只使用了一个agent,实际环境中,会有许多台需要不同配置的 agent 。这就需要对不同的 agent 应用不同的配置。在 sites.pp 中把 include test1 替换成针对特定节点的配置:
import "test" node "puppet-agent-test-01" { include "test1" }
这里的主机名可以用 "," 分隔,指定多个主机,也可以用类似 /puppet-agent-test-.*/ 这样的正则表达式来灵活匹配。为了测试配置是否生效,我们可以修改一下之前的配置,再运行一下 agent。还可以再加入几台 agent 试试应用不同的节点的不同配置。
之前加入 agent 时,需要在 master 端手动为 agent 认证。在客户端众多,或者需要完全自动化的时候,可以配置自动签名。当然,前提是能通过别的途径比如 iptables 限制访问 master 的来源,或者是在可信的内网环境下。
在 /etc/puppet 目录中添加 autosign.conf 中输入agent 认证名的模式,如 *.test.net 。需要注意的是,这里必须使用类似泛域名通配符的方式。也就是说,* 只能出现在前面,而不允许 test.* 这样的形式。所以要应用自动签名,在规划 agent 的认证名的时候就要注意这一点。现在,我们用 puppet agent --test --certname="agent01.test.net" 试试。这回就不再需要手动认证,直接就能执行 master 分发的配置任务了。
实际应用puppet时,会把puppet的配置文件,以及要分发的软件包的配置文件都加入到svn等源代码管理中。但是我们也会需要用puppet来分发一些我们自己编译打包的软件包等二进制文件。这些二进制文件并不适合放进源代码管理中。另外,需要用puppet分发的证书、密钥等敏感信息也不适合放入。这时,使用模块的文件就不太方便。好在puppet的文件服务器也是可以配置的。建立puppet文件服务器配置文件:/etc/puppet/fileserver.conf,输入
[modules] allow * [files] path /data/puppet allow *
这就定义了一个名为files的额外文件服务挂载点,位于 /data/puppet。也放一个 test.txt 在里面,然后就可以使用
file { "/tmp/test2.txt": source => "puppet:///files/test.txt" }
来分发了。这里 puppet:// 是协议名,后面的路径 /files/test.txt 的第一部分是挂载点名称。之前使用的 modules 这个特殊的挂载点名是指向各个模块的文件。如 /modules/test/test.txt 就是test模块下files目录中的test.txt文件。而 /files/test.txt 就是 files 挂载点对应目录下的 test.txt 文件。之后,我们就可以把这些二进制文件等用别的途径部署到自定义挂载点上。
之前我们使用的file、exec、package 在puppet中都被称为资源。每一种资源都有许多参数可以设置。对于 file 资源,可以用 ensure => "link", target => "目标路径" 来建立软链接,用mode=>0644来设置文件访问权限。对于exec资源,可以用cwd参数来设置当前路径,用creates参数来设置执行命令创建的路径,可以用于防止重复执行命令。对于所有的资源类型,都有一些共有的参数,称为元参数。其中最常用的就是require参数。这个参数指定了这个资源所依赖的资源。实际应用中,不同的任务间会有依赖关系。比如安装软件包需要先创建好目标路径的目录,需要先安装好所需的依赖软件包,这就可以用require选项来实现。比如:
exec { "install sth.": cwd => "/opt/some_package", exec => "/bin/tar -xzvf /path/to/package.tar.gz", require => File["/opt/some_package"], } file { "/opt/some_package": ensure => "directory", }
任务 Exec["install sth."] 就会在任务 File["/opt/some_package"] 完成后运行。这里,对于每个类型的资源,可以用“,”分隔;如果要指定多种类型的资源,也可以写成列表形式。如:
require => [ File["/path/to/file1", "/path/to/file2"], Package["package1] ]
还有一个与require相反的参数before,可以指定该任务必须在哪些任务前完成。
puppet还提供了多种资源类型来完成不同的任务。比如可以用cron类型来管理定时任务,用host类型来设置hosts文件等。
至此,已经简单介绍了puppet 的基本功能设置,可以针对不同主机安装执行不同的所需任务。puppet 还有更多强大的功能,您可以参照 puppet 的官方文档,各取所需,在实践中学习应用。应用puppet,把原先繁琐的系统配置过程自动化了,需要部署一台新的服务器时,只需要在初始化好puppet后,执行一次 puppet agent --test,剩下的就交给它来干了。既省时省力也不会出错。机械化的操作就应当交给机器来做,这样才能把人的精力省出来做更有价值的事情。
###############################################################################
puppet 系统配置自动化解决方案
这是一篇几个月前被枪毙的文章。当然,确实写太浅了一些,就是一入门。因为我对 puppet 本来了解得也不深入。这会想起了还有这篇文章,姑且放出来。高手们还是绕路吧。:-)
相信做过运维的朋友都会有这样的体会:把一个新的服务器从刚装好系统的状态配置到可以运行应用程序,是个挺麻烦的过程。就拿一个运行 nginx + php 的 web 服务器为例,可能需要部署 ssh 公钥,设置用户 sudo 权限,关闭密码登录、root 远程登录,配置 iptables 规则。然后安装所需版本的 nginx、 php 到规范的路径,不能搞错版本,以免缺失所需特性或者造成冲突,还要安装应用所需要的 php 扩展比如 gd 之类。然后是用于监控的客户端程序,比如 nagios 的 nrpe ,或者 zabbix_agent,用于日志轮转的 cronolog 等等。最后还得记得修改 ulimits 、tcp 相关的内核参数。然后还得一一验证所有的设置是否正确。如果是部署了新的东西或者更新了配置,还得写一下安装文档,这样下回安装的时候才不会遗漏什么编译 选项、软件包,或者少设置了系统参数导致故障。有人请假或则离职的时候,别人也才能接替。需要部署的还不会仅仅是 web 服务器,还有 lvs 、mysql、memcache,也许还会有 redis、sphinx 等等等等。有软件包需要升级,也得对所有用到的机器都升级一遍。更要命的是,往往需要配置的还不是一台机器,而是十几台乃至上百台。
这里 可以看出,系统配置本身是件很繁琐的事情。需要考虑到很多方面的事情,任何一个地方出了纰漏就可能导致故障或者埋下隐患。手动来做很容易出错,也很不够敏 捷高效,难以快速响应需求。为了解决这些问题,我也曾经用 bash 做过一些脚本,来实现部署和系统设置的自动化,但是受到 bash 的表达能力的限制,脚本的编写测试维护并不轻松。我们碰到的麻烦别人也会碰到。所以就有了自动化好在现在有了一个好用的工具来解决这些问题。puppet 就是这样一个功能强大的系统配置集群管理工具。puppet 分为 master 端和 agent 端,可以实现分布式分发,有强大的配置管理功能,可以实现自动化分发文件、安装软件包、执行命令、添加系统用户、设置 crontab 等等。它的配置文件是一种表达能力强的 DSL,可读性好、容易复用,且本身就可以作为很好的文档,这就免除了维护文档的负担,也避免了文档过时的问题。puppet 还使用了 ssl 来保证通讯的安全性,防止敏感的配置信息泄露。支持集群化,以实现大批量主机并行更新维护。引入的 factor 还实现了针对不同系统、不同发行版、不同环境的针对性设置。以及很多方便强大的功能。
我们先看看如何安装 puppet 。最方便的方法是使用包管理器。对于 centos 等 redhat 系发行版,可以通过 yum 来安装。先把 puppet 的官方源加入到系统中:
# cat > /etc/yum.repos.d/puppet.repo << EOF [puppet-dependencies] name=puppet dependencies baseurl=http://yum.puppetlabs.com/el/\$releasever/dependencies/\$basearch/ gpgcheck=1 gpgkey=http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs [puppet-products] name=puppet dependencies baseurl=http://yum.puppetlabs.com/el/\$releasever/products/\$basearch/ gpgcheck=1 gpgkey=http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs EOF
然后
# yum install puppet
就会自动安装好 puppet。从所列出依赖的软件包可以看出, puppet 是用 ruby 实现的。对于 debian 系发行版,也可以在 apt.puppetlabs.com 中找到相应的源和 gpg key。
puppet 分为 master 和 agent 。找两台机器或者两个虚拟机,一台作为 master,一台作为 agent ,两端都安装好后就可以开始配置了。
首先配置 master 。先在 master 端运行一下
# puppet master
初次运行会生成puppet 用户, 在 /etc/puppet 目录下生成默认配置目录结构并在 /var/lib/puppet 生成数据文件。如果提示 permisiton denided ,可以试试 chown puppet:puppet /var/lib/puppet/run 。
然后我们就可以开始写第一个配置文件了。在 /etc/puppet/manifests 目录下建一个 site.pp ,这是 puppet master 的主配置文件。输入
package { "bison": ensure=>"installed", } exec { "puppet test": command=>"/bin/touch /tmp/puppet-test", }
第一个配置会使 agent 端确保编译 php 所必须的软件包 bison 已经安装好。对于不同的系统,会使用各自的包管理器来安装。第二个配置会在 agent 端执行 /bin/touch /tmp/puppet-test 。
然后配置客户端。先编辑 /etc/hosts,加入 master 的 ip,如:
192.168.1.101 puppet
在客户端运行一下
# puppet agent --test
初次运行也同样会生成客户端的相应文件,然后就会去连接 master 端执行任务。此时会提示
warning: peer certificate won't be verified in this SSL session exiting; no certificate found and waitforcert is disabled
这表示 agent 需要认证。因为 puppet 使用了 ssl 来保证安全,并需要 agent 经过 master 认证才能够访问配置。到 master 端执行一下
# puppet cert list
会列出待认证的 agent 列表。这里可以看到 agent 的主机名。如
puppet-agent-test-01 (66:62:5C:84:B0:23:73:FB:80:7C:89:48:4C:A6:AF:53)
然后可以使用
# puppet cert sign puppet-agent-test-01
就能完成认证。如果觉得直接使用主机名不够灵活,也可以在运行 agent 时使用 --certname=认证名 来指定。在 agent 端再试一次,这回就可以看到,agent 已经开始干活了。看看 bison 工具是否安装好了,再看看 /tmp 目录下是否生成了 /tmp/puppet-test 文件。
需要注意的是,一个主机可以使用多个不同的certname,但一个certname只能被一台主机使用。如果原有的certname需要移动到另一个主机上使用,就需要在master端先 puppet cert clean "认证名" 来清除原有数据。所以,certname应当尽量保持全局唯一。
这里 agent 使用的 --test 让 agent 不以服务方式运行,只执行一次,并输出详细信息。去掉这个参数,puppet agent 就会以服务方式在后台运行,默认每 30 分钟连接一次服务器更新配置。可以用 puppet help master、puppet help agent 查看更多选项。
刚才是把所有的配置都写在了 sites.pp 文件里。在配置项增多,维护的项目增多以后,就会变得过于庞大而难以维护。所以就需要把配置分到不同的模块中去,以模块化的方式来管理配置。
puppet 的模块放在 /etc/puppet/modules 下。模块的目录结构如下图所示:
modules/ |-- test |-- files | `-- test.txt `-- manifests |-- init.pp `-- test.pp
在这个例子里,定义了一个 test 模块。其中 files 目录中用于安放该模块所需分发的文件,manifests 目录中是该模块的配置文件。其中 init.pp 是每个模块的主配置文件。内容通常为 import "*" ,来载入该模块的其他配置文件。我们在 files 目录中加入一个 test.txt 文件,并把之前 site.pp 中的内容挪到 test.pp 中,再加入分发文件的配置,定义成一个类:
class test1 { package { "bison": ensure=>"installed", } exec { "puppet test": command=>"/bin/touch /tmp/puppet-test", } file { "/tmp/test.txt": ensure => "present", source => "puppet:///modules/test/test.txt" } }
site.pp 中删除原有的配置,加入 import "test" ,把 test 模块加载进来,然后加入 include "test1" ,应用 test1 类的配置。Include语句也可以再class内使用。以在一个class中复用另一个class的配置。现在,我们在 agent 端再运行一次 puppet agent --test ,agent 还是会照常工作,但是配置已经分到模块中了。实践中,会把每个配置的项目建立一个模块,比如:nginx、php 等等。再看看 /tmp 目录,会发现 master 端的 test.txt 文件已经下载回来。puppet 已经完成了文件分发的工作。module 中的 files 通常用于分发配置文件,把软件包的配置文件集中管理。
file配置也可以用来创建目录。只要使用 ensure => "directory" 即可。如:
file { "/tmp/testdir": ensure => "directory", }
到目前为止,我们只使用了一个agent,实际环境中,会有许多台需要不同配置的 agent 。这就需要对不同的 agent 应用不同的配置。在 sites.pp 中把 include test1 替换成针对特定节点的配置:
import "test" node "puppet-agent-test-01" { include "test1" }
这里的主机名可以用 "," 分隔,指定多个主机,也可以用类似 /puppet-agent-test-.*/ 这样的正则表达式来灵活匹配。为了测试配置是否生效,我们可以修改一下之前的配置,再运行一下 agent。还可以再加入几台 agent 试试应用不同的节点的不同配置。
之前加入 agent 时,需要在 master 端手动为 agent 认证。在客户端众多,或者需要完全自动化的时候,可以配置自动签名。当然,前提是能通过别的途径比如 iptables 限制访问 master 的来源,或者是在可信的内网环境下。
在 /etc/puppet 目录中添加 autosign.conf 中输入agent 认证名的模式,如 *.test.net 。需要注意的是,这里必须使用类似泛域名通配符的方式。也就是说,* 只能出现在前面,而不允许 test.* 这样的形式。所以要应用自动签名,在规划 agent 的认证名的时候就要注意这一点。现在,我们用 puppet agent --test --certname="agent01.test.net" 试试。这回就不再需要手动认证,直接就能执行 master 分发的配置任务了。
实际应用puppet时,会把puppet的配置文件,以及要分发的软件包的配置文件都加入到svn等源代码管理中。但是我们也会需要用puppet来分发一些我们自己编译打包的软件包等二进制文件。这些二进制文件并不适合放进源代码管理中。另外,需要用puppet分发的证书、密钥等敏感信息也不适合放入。这时,使用模块的文件就不太方便。好在puppet的文件服务器也是可以配置的。建立puppet文件服务器配置文件:/etc/puppet/fileserver.conf,输入
[modules] allow * [files] path /data/puppet allow *
这就定义了一个名为files的额外文件服务挂载点,位于 /data/puppet。也放一个 test.txt 在里面,然后就可以使用
file { "/tmp/test2.txt": source => "puppet:///files/test.txt" }
来分发了。这里 puppet:// 是协议名,后面的路径 /files/test.txt 的第一部分是挂载点名称。之前使用的 modules 这个特殊的挂载点名是指向各个模块的文件。如 /modules/test/test.txt 就是test模块下files目录中的test.txt文件。而 /files/test.txt 就是 files 挂载点对应目录下的 test.txt 文件。之后,我们就可以把这些二进制文件等用别的途径部署到自定义挂载点上。
之前我们使用的file、exec、package 在puppet中都被称为资源。每一种资源都有许多参数可以设置。对于 file 资源,可以用 ensure => "link", target => "目标路径" 来建立软链接,用mode=>0644来设置文件访问权限。对于exec资源,可以用cwd参数来设置当前路径,用creates参数来设置执行命令创建的路径,可以用于防止重复执行命令。对于所有的资源类型,都有一些共有的参数,称为元参数。其中最常用的就是require参数。这个参数指定了这个资源所依赖的资源。实际应用中,不同的任务间会有依赖关系。比如安装软件包需要先创建好目标路径的目录,需要先安装好所需的依赖软件包,这就可以用require选项来实现。比如:
exec { "install sth.": cwd => "/opt/some_package", exec => "/bin/tar -xzvf /path/to/package.tar.gz", require => File["/opt/some_package"], } file { "/opt/some_package": ensure => "directory", }
任务 Exec["install sth."] 就会在任务 File["/opt/some_package"] 完成后运行。这里,对于每个类型的资源,可以用“,”分隔;如果要指定多种类型的资源,也可以写成列表形式。如:
require => [ File["/path/to/file1", "/path/to/file2"], Package["package1] ]
还有一个与require相反的参数before,可以指定该任务必须在哪些任务前完成。
puppet还提供了多种资源类型来完成不同的任务。比如可以用cron类型来管理定时任务,用host类型来设置hosts文件等。
至此,已经简单介绍了puppet 的基本功能设置,可以针对不同主机安装执行不同的所需任务。puppet 还有更多强大的功能,您可以参照 puppet 的官方文档,各取所需,在实践中学习应用。应用puppet,把原先繁琐的系统配置过程自动化了,需要部署一台新的服务器时,只需要在初始化好puppet后,执行一次 puppet agent --test,剩下的就交给它来干了。既省时省力也不会出错。机械化的操作就应当交给机器来做,这样才能把人的精力省出来做更有价值的事情。
This entry was posted in 技术 and tagged puppet on 2012-11-02.
####################################################################################
http://www.ituring.com.cn/article/1867
http://www.ituring.com.cn/article/1868
18.1. 概述
Puppet 是一个用 Ruby 写的开源 IT 管理工具,用于数据中心自动化和服务器的管理,用户包括 Google, Twitter, 纽约证券交易所以及很多其他机构。Puppet 的主要维护者是 Puppet Labs,也就是 Puppet 项目的发起者。Puppet 可以管理从 2 台到 5 万台机器,管理员可以只有1个人或者是上百人。
Puppet 是一个用于配置和维护计算机的工具;通过使用 puppet 的简单的配置语言,你可以告诉 Puppet,你希望如何配置机器,然后 Puppet 就会按照你的指示来修改机器的配置。之后,如果你改变了需求,比如更新了软件包,增加了新用户,或是更新了配置,那么 Puppet 也会自动地随之更新你的机器。而如果这些机器本来就已经配置好了,Puppet 就什么都不会动。
总的讲,Puppet 可以借助任何已有的系统特性来完成它的工作。比如,在 Red Hat 中,它会使用yum
来进行包管理,并修改 init.d
来管理系统服务;而在 OS X 中,Puppet 则使用 dmg 来管理软件包,使用 launchd
来管理系统服务。Puppet 的一个目标就是让你或者系统本身都可以通过 Puppet 代码来明白如何进行工作,所以,必须要能够遵从各个系统的规范。
Puppet 集成了很多已有工具的传统。从开源社区的角度讲,对 Puppet 影响最大的莫过于 CF-Engine 和 ISconf 了,CF-Engine 是第一个开源通用配置管理工具,而 ISconf 使用了 make
来完成所用工作,其灵感来源于显式地处理系统中所有的依赖关系。在商业软件中,Puppet 受到了 BladeLogic 和 Opsware (两者都已经被大公司收购了)的影响,在 Puppet 项目开始时,这两种工具都在市场中获得了不小的成功,不过两者都更加关注于向大公司的管理层来推销,相反,对于直接为管理员提供一个强大的工具,没有足够的重视。Puppet 希望和这些工具解决差不多的问题,但关注的用户有很大不同。
这个简单的例子用于解释如何使用 Puppet,例子中的这段代码用来确保系统中安装了 SSH 服务,并配置正确:
class ssh {package{ ssh:ensure=> installed }
file {"/etc/ssh/sshd_config":
source =>'puppet:///modules/ssh/sshd_config',ensure=> present,require=>Package[ssh]}
service { sshd:ensure=> running,require=>[File["/etc/ssh/sshd_config"],Package[ssh]]}}
这个配置确保软件包被安装,文件就位,并且服务运行。注意,我们指定了资源之间的依赖关系,这样,我们就可以确保一定能够按照正确顺序工作。这个类 (class) 可以被关联到任何一个需要这个配置的主机。Puppet 配置的基本组件是结构化的对象,在这个例子里就是 package
, file
和service
。我们将这些对象称为资源 (resource),Puppet 配置中的任何东西,最后都会分解为一些资源,以及资源间的依赖关系。
通常,一个使用 Puppet 的机构可能拥有数十甚至上百个这样的代码段,我们称之为类 (class),我们将存储这些类的文件称为货单 (manifest
),这些被按照相关性分组,这些组称为模块 (module)。比如,你可能有个 ssh
模块,其中包含了这个 ssh
类和一些其他相关类,同时还会有诸如 mysql
,apache
, 以及 sudo
等模块。
大部分的 Puppet 交互都通过命令行或是一直运行着的 HTTP 服务进行的,不过有些东西也有图形界面,比如报告处理。Puppet Labs 还提供了一些 Puppet 的周边商业产品,这些产品更倾向于使用基于 web 的图形化界面。
Puppet 的第一个原型写于2004年夏天,从2005年2月开始成为了一个有全职投入的项目。Puppet 最初由 Luke Kanies 设计并实现,它是一位经验丰富、写过很多不足万行的小工具的管理员。本质上说,随着 Puppet 的开发,Luke 学着称为了一位程序员,从程序架构上说,这具有正反两面性。
Puppet 最初并最重要的目的就是成为一个管理员的工具,让他们的工作更轻松、更快、更高效,且更不易犯错。第一个关键性的创新就是上面提到的资源,这是 Puppet 的基本元素,它们是跨操作系统可移植的,并且抽象的资源屏蔽了实现细节,允许用户关注配置的结果,而非具体如何实现这些配置。而这些基本元素本身由 Puppet 的资源抽象层来实现。
在一台给定的主机上,Puppet 资源必须是唯一的。你只能有一个叫 "ssh" 的包,一个叫 "sshd" 的服务,也只能有一个叫 "/etc/ssh/sshd_config" 的文件。这就避免了你的配置的不同部分之间的冲突,一旦发生冲突,你会在进行配置的最初阶段发现它。我们可以通过资源的名称和类型来指定一个资源,比如 Package[ssh]
和 Service[sshd]
。你可以有一个包和一个服务同名,因为它们类型不同,但不论如何,你都不可以有两个同名的包或是同名的服务。
Puppet 的第二个重要创新是提供了一种方法,来直接指定资源之间的依赖关系。之前的各种工具的关注点都在于某个孤立的工作是否完成,而非它们之间的各种关联性;Puppet 是第一种明确地将依赖关系作为配置中的一等元素的工具,而且,必须在建模中明确指出依赖型。Puppet 会根据资源及其依赖型构建出一张图,在 Puppet 的执行过程中,每件事情都对应于这张图(称为目录, Catalog)和图的顶点与边。
Puppet 的最后一个重要组成部分是它的配置语言。这是一种声明性的语言,其重点在于配置数据,而非编程。很大程度上说,它沿袭了 Nagios 的配置格式,不过也受到了 CFEngine 和 Ruby 的很大影响。
除了功能性组件之外,在 Puppet 开发过程中,还有两个指导性原则:简单胜过一切,易用性胜于能力;先做框架,再做应用,其他人可以在 Puppet 的基础上开发它们自己的应用。可以这么理解,Puppet 框架需要一个杀手级的应用(Puppet 本身)来让自己获得广泛接受,但框架始终是关注的焦点,而非应用。当然,大部分人开始会将 Puppet 作为一个应用来使用,而非其背后的框架。
当 Puppet 的原型刚刚搭建出来时,Luke 还是一个 Perl 程序员,也有不少 shell 经验以及一些 C 的经验,主要使用 CFEngine 工作。不寻常的一点是,他拥有为简单语言写解析器的经验,曾经为一些小工具写过解析器部分,也从头重写过 CFEngine 的解析器,来让它更容易维护(这段代码从未被提交到上游项目中去,因为有些小的兼容性问题)。
对于 Puppet 的实现,选择一个动态语言是没什么争议的,因为动态语言的开发效率会更高,但是选择一个合适的语言却十分困难。最初的使用 Perl 的原型不是非常好用,所以 Luke 开始考虑其他语言。Python 曾经被考察过,但 Luke 发现这种语言和他的世界观不够匹配。在朋友的关于语言易用性的言论影响下,Luke 尝试了 Ruby,4小时之后,他就使用 Ruby 完成了一个可用的原型。在2005年,当 Puppet 成为 Luke 的全职项目时,Ruby 还是一种默默无闻的语言,这个决定显然有巨大的风险,程序员的开发效率是选择 Ruby 的决定性因素。相对于 Perl,Ruby 的最显著的特性是,非常容易构建非层次式的(non-hierarchical)类间关系,这和 Luke 所想的非常匹配,这之后被证明是决定性的(critical)。
18.2. 架构概览
本章主要讨论 Puppet 的实现架构(也就是我们用来让 Puppet 来完成我们想让它进行的工作的代码),不过,简要探讨一下应用架构(也就是程序的各个部分之间如何通信)也会很有帮助,这会让我们对实现有所感觉。
Puppet 支持两种工作模式:客户机/服务器模式,由一个中心服务器和部署到各个主机上的代理(agent)构成;或无服务器模式(serverless),由一个单独的进程完成所有工作。为了确保这些模式之间的一致性,Puppet 内部始终是对网络透明的,这样,两种模式可以使用相同的代码,只是是否走网络有所不同。每个可执行程序都可以根据需要配置使用本地或远程服务,除此之外,它们的行为都是完全一致的。注意,你也可以在进行客户机/服务器使用的情况下使用无服务器模式,只要将所有配置文件推送到每个客户端上,并让它们直接解析这些文件即可。本节将集中在客户机/服务器模式上,因为这样更易于把各个功能理解为彼此独立的组件,不过请各位明白,这些实际上在无服务器模式下也是同样的。
Puppet 架构上的一个设计决策是,认为客户端不应该直接访问到 Puppet 模块;相反,它们应该得到专为它们编译的配置。这样做有很多好处:首先,这遵从了最小权限准则,每台主机只能得到它自己需要知道的(它该如何配置自己),不会知道其他服务器如何配置。其次,你可以将编译配置的权限(需要访问中央数据存储)和实施配置的权限彼此分离。第三,当主机之后每次应用配置的时候,可以处于离线状态,无需与中央服务器保持连接,这意味着即使是服务器当即了,或者两者无法连接了(比如,移动客户端,或是客户端位于DMZ中等),也可以重复应用配置。
这样,Puppet 的工作流就比较直接了:
- Puppet 代理进程收集当前主机的信息,并交给服务器。
- 解析器利用系统信息和磁盘上存储的模块信息,编译为对这台机器的一个配置信息,并将这些配置信息返回给代理。
- 代理将配置应用到本地主机,这将影响主机的本地状态,之后,将结果状态汇报给服务器。
这样,代理拥有访问它自己的系统信息、配置、以及它自己生成的报告的权限。服务器拥有这些数据的副本,还有访问所有 Puppet 模块、后端数据库和可能用于编译配置信息的服务的权限。
这个工作流图中的组件之外,我们后面还会用到很多 Puppet 用来进行内部通信的数据类型。这些数据类型非常重要,因为这些数据中有些是决定了各种通信如何进行,有些公开类型影响到其他工具如何产生或使用这些与 Puppet 交互的数据。
最重要的数据类型包括:
- Facts: 从每台机器上收集到的系统数据,用于编译为配置。
- Manifest: 包含 Puppet 代码的文件,通常被组织为一些称为“模块 (modules)”的集合中。
- Catalog: 一张包含主机上可管理资源及其依赖关系的图。
- Report: 对于一个 Catalog,在其应用过程中产生的所有事件的集合。
在 Facts, Manifests, Catalogs, 和 Reports 之外,Puppet 还支持 files, certificates (用于鉴权)等服务类型。
18.3. 组件分析
Agent (代理)
在 Puppet 运行过程之中,第一个提到的组件就是 agent
进程。历史上,这是一个独立的称为puppedd
的可执行程序,但在 2.6 中,我们把 Puppet 变成了一个唯一的可执行程序,现在,可以使用 puppet agent
来调用它,和 Git 的使用方式很类似。这个代理本身的功能不多,主要是用于实现上面图中客户端的工作流的配置和代码。
Facter
在 agent 之后的下一个组件是一个称为 Facter 的外部工具,这是一个用于检测本机信息的非常简单的工具。这些信息包括操作系统、IP 地址、主机名等,但 Facter 非常易于扩展,很多机构在使用中会增加它们自己的插件来检测一些个性化信息。Facter 发现的信息会由 agent 发送给服务器,之后,服务器就接过了接力棒,继续工作流。
外部节点分类器
在服务端,第一个遇到的组件称为外部节点分类器(External Node Classifier),简称为 ENC。ENC 接受主机名作为输入,返回一个包含对应主机的高级配置信息的数据结构。ENC 通常是一个独立的服务或程序:可能是一个其他的开源项目,比如 Puppet Dashboard 或 Foreman,或者是继承的已有的数据存储,比如 LDAP。ENC 的目的是,确定一台主机从功能上属于那些类,以及需要用哪些参数来配置这些类。比如,一个给定的主机可能属于 debian
和 webserver
类,datacenter
参数应该设置为 atlanta
。
注意,在 Puppet 2.7 中,ENC 不是一个必选组件,用户可以在 Puppet 代码中直接指定节点的配置。大约在 Puppet 项目开始两年之后,ENC 才被添加进来,因为我们开始意识到,对节点的功能进行划分和配置节点从本质上说是不同的两件事,将它们划分到两个不同的程序中可能比扩展语言来支持两种功能要更合理。尽管不是必须,但 ENC 仍然是推荐配置,并且将来某天可能会成为必选组件(到那时,Puppet 会提供一个拥有足够必备功能的 ENC,不必担心)。
一旦服务器收到 ENC 的分类信息,或是来自 Facter (经过 agent)的系统信息后,它会将所有信息绑定到一个节点对象上,并将它送入编译器。
编译器(Compiler)
如前所述,Puppet 有一个自定义的语言,用于指定系统配置。它的编译起实际上有三个部分:一个 Yacc 风格的分析器生成器和一个定制的词法分析器;一组用于创建我们的抽象语法树(AST)的类;以及 Compiler 类,它用于处理所有这些类,以及实现编译器作为系统的一部分所提供的API的函数之间的交互。
编译器要处理的最复杂的事情是,大部分 Puppet 配置代码是在第一次被引用时才延迟加载的(减少加载时间,同时避免缺少一些实际不必要的依赖资源时产生的无关日志),这意味着不会有显式的调用来加载并分析代码。
Puppet 的解析器使用了一个使用开源的 Racc 构建的正常的 Yacc 风格的解析器生成器。可不幸的是,在 Puppet 项目开始时,没有可用的词法分析器生成器,所以只好使用了一个定制的词法分析器。
因为我们在 Puppet 中使用了 AST,所以 Puppet 语法中的每个语句都可以被求值为 Puppet AST 类的一个实例(Puppet::Parser::AST::Statement
),这些 AST 实例不会被直接执行操作,而会被放入一个语法树之中,被一起执行。当一个服务器为很多不同节点服务时,使用 AST 会带来一些性能上的收益,因为这样可以一次解析,多次编译。同时这也给了我们一个机会,来对 AST 进行一些内省(introspection),让我们得到一些额外的信息和能力,如果直接解析执行是无法得到这些的。
在 Puppet 项目开始时,可参考的 AST 的例子并不多,这部分已经经过了很多的演化,发展到现在,我们的形式看起来是比较独一无二的。我们不会直接针对整个配置生成一个单独的AST,相反,我们创建很多小的 AST,按照名字切开。比如,如下代码:
class ssh {package{ ssh:ensure=> present }}
会创建一个新的 AST,包含一个 Puppet::Parser::AST::Resource
实例,并将这个 AST 命名为 "ssh",存储在存储这个特定环境的所有类的哈希表中。(这里略过了构建类的细节,不过对于这里的讨论来说,这些是不必要的。)
给定 AST 和(来自 ENC 的)Node 对象,编译器取出 node 对象(如果存在的话)指定的类,查找并进行求值。在这个求值的过程中,编译器构建了不同不同的域的树,每个类有自己的作用域。这意味着 Puppet 是动态作用域的:如果一个 class include 了另一个类,那么里面的类就可以访问外面类的变量。这的确是一个噩梦,我们正在着手消除这个问题。
作用域树是临时数据结构,一旦编译完成就会被释放,但编译的输出也随着编译的过程逐渐完成。我们把这个输出产品称为 Catalog(目录),但它实际是一张资源和它们的关系构成的图。变量、控制结构或是函数都不会存在在 catalog 之中,catalog 是纯数据,并可以被转化为 JSON、YAML 或其他各种格式。
在编译过程中,我们会创建一些包含(containment)关系,一个类"包含(contains)"类中定义的所有资源(比如, 前面的例子中,ssh 类包含 ssh 包)。类可以包含一个定义,而这个定义本身也可以包含一个或多个定义,或其他独立的资源。一个 catalog 倾向于一个扁平的、彼此无连接的图:很多类,每个都有少数的几个层次。
这种图的一个别扭的方面是,它还包含了“依赖(dependency)”关系,比如一个服务依赖于一个包(可能因为安装了包才能创建服务),但这些依赖关系是由资源的参数指定的,而非图结构的边。我们的图类(由于历史原因,称为 SimpleGraph
)不支持在同一张图里同时有“包含”边和“依赖”边,所以,我们不得不为了不同的需求在它们之间来回转换。
事务 (Transaction)
一旦 catalog 完全构建好了(假设没有失败),就会送给 Transaction。在一个区分客户机和服务器的系统中,Transaction 运行在客户机上,如图 18.2,它通过 HTTP 协议下载 Catalog。
Puppet 的 transaction 类提供了实际进行系统修改操作的框架,而我们讨论过的其他东西都构建于其上或是进行对象的分发传递。和数据库之类的一般系统中的事务不同 Puppet transaction 的行为并不具有原子性等特征。
transaction 的工作相当直接:在图中按照各种关系指定的顺序进行遍历,并确保各种资源保持同步。正如上面提到的,它不得不将图从包含边(比如 Class[ssh]
包含 Package[ssh]
)转换为依赖边(比如 Service[ssh]
依赖于 Package[ssh]
),然后对图进行标准的拓扑排序,按顺序选择每种资源。
对于给定的资源,我们进行简单的三步操作:获取资源的当前状态,与期望的状态进行比较,进行必要的改动,以满足期望。比如,有如下代码:
file {"/etc/motd":ensure=> file,
content =>"Welcome to the machine",
mode =>644}
transaction 会检查 /etc/motd
的内容,如果和指定的状态不匹配的话,会修复不一致的地方。如果 /etc/motd 是一个目录,那么它会备份其中的文件,再删除目录,然后创建一个内容和权限都符合要求的文件。
进行操作的过程实际上是通过一个简单的 ResourceHarness
类来控制的,这个类定义了事务和资源之间的接口。这样做可以减少类之间的连接,并可以让他们互相独立地进行修改。
资源抽象层 (Resource Abstraction Layer)
事务类是 Puppet 完成工作的核心,但所有的工作实际都是由资源抽象层(RAL)来完成的,从架构上讲,这一层也是 Puppet 中最有意思的组件。
RAL 是 Puppet 中创建的第一个组件,与语言部分不同,这部分对用户能做的事情进行了清晰的定义。RAL 的工作就是定义一个资源究竟是什么,要实现一个资源需要在系统中进行什么操作,而 Puppet 语言正是用来操作由 RAL 建模的资源的。正因如此,RAL 也是系统中最重要和最难改动的组件。我们希望能修改 RAL 中的很多东西,而且也在过去的多年中进行了很多重大改进(最难的莫过于增加 Provider 了),但是在 RAL 中,还是有很多工作需要在日后慢慢修改。
在编译器子系统中,我们将资源和资源类型分别进行了建模(分别命名为 Puppet::Resource
和 Puppet::Resource::Type)。我们的目标是让这些类也成为 RAL 的核心,不过,目前这两种行为(资源和类型)被封装到了同一个类之中 —— Puppet::Type
。(这个类的命名十分糟糕,这是因为定义这个类的时间远早于我们开始使用“资源”这个名词的时间,在那时,我们在主机间进行数据通信时,是直接对内存类型进行序列化的,事到如今,想要再去重新调整命名已经非常困难了。)
当 Puppet::Type
被最早设计出来的时候,似乎把资源和类型的行为放到同一个类里是有道理的,毕竟资源是资源类型的实例。但随着时间的推移,越来越发现,资源及其类型不适合于放在一个传统的继承关系构成的模型里。比如,资源类型定义了资源可以有哪些参数,但不管资源接受哪些参数(可能全部接受)。这样,我们的 Puppet::Type
就拥有了类级别的行为——规定资源类型的行为,和实例级别的行为——规定资源实例如何行为。同时,它还负责管理注册和获取资源类型的功能,如果你需要 "user" 类型,你可以调用 Puppet::Type.type(:user)
.
这种混合的行为导致了 Puppet::Type
不太容易维护。整个类有不到 2000 行代码,但却在三个层面上工作 —— 资源、资源类型,和组员类型管理器 —— 这让它变得难以理解。这就是为什么这个模块是重构的主要目标的原因,不过它本身更多的是拼接在一起的代码,而不是面向用户的设计,所以,修正它要比直接根据功能重写更困难。
除了 Puppet::Type
之外,RAL 中还有两个重要的类,其中最有趣的一个我们称之为 Provider。在 RAL 刚刚被开发出来时,每种资源都是由参数定义和如何管理它们的代码混在一起构成的。比如,我们要定义 "content" 参数,然后提供一个方法来读取文件的内容,以及另一个用于修改内容的方法:
Puppet::Type.newtype(:file)do...
newproperty(:content)dodef retrieve
File.read(@resource[:name])enddef sync
File.open(@resource[:name],"w"){|f| f.print@resource[:content]}endendend
这是个简化的例子(比如我们内部实际使用的校验和,而非读取全部内容),但这里可以大致了解处理思路。
这样就让事情变得非常难于管理了,因为我们需要为每个资源类型管理很多不同的属性。目前 Puppet 支持超过 30 种包管理工具,这样,很难在一个 Package 资源类型中去支持所有这些管理工具了。于是,我们提供了一种资源类型和管理相应类型的资源的方法之间的一个清晰接口 —— 实际上,资源类型是指资源类型的名字和它们支持的属性。 Provider 为所有的资源类型的属性定义了 getter 和 setter 方法,以清晰直观的方式命名。例如,这是一个 provider 实现上述属性的代码示例:
Puppet::Type.newtype(:file)do
newproperty(:content)endPuppet::Type.type(:file).provide(:posix)dodef content
File.read(@resource[:name])enddef content=(str)File.open(@resource[:name],"w"){|f| f.print(str)}endend
在这个简单的例子里,似乎还多了一点代码,但这更易于理解和维护,特别是当属性的数量或是属性提供者的数量变多的时候。
本节开始处曾经提到,Transaction 并不直接改动系统,而是通过 RAL 来完成的。现在,我们可以清楚地看到,是 provider 来进行的这想具体工作。事实上,总体上讲,provider 是 Puppet 当中,唯一真正直接触及系统的部分。transaction 请求文件内容,provider 就会为它读取,transaction 要求文件的内容要被改动,provider 就去改动它。注意,尽管如此,provider 从不决定如何影响系统 —— 如何影响系统这个问题是由 Transaction 来决定的,provider 仅仅是执行任务。这种架构可以让 Transaction 能够完全控制系统,却不需要了解文件、用户和包这些具体细节,而且,这个划分可以让 Puppet 可以拥有一个完全模拟执行的模式,在这种模式下,我们可以保证系统完全不会受到任何影响。
RAL之中的另一个主要的类型负责参数本身。我们支持三种类型的参数: metaparameters, 这种参数影响所有资源类型(比如,是否在模拟模式中运行);参数,它们是不直接写入到磁盘上的一些值(比如,是否在查找文件时进入符号链接);还有属性(property),它们规范了你要修改磁盘上的资源的哪方面的内容(比如文件的内容,或者一个服务是否在运行着)。区分属性和参数的不同十分困难,但你可以这么想,属性是哪些 provider 中有 getter 和 setter 方法的,这样就易于分辨了。
报告 (Reporting)
随着 transaction 遍历整张图,并使用 RAL 来修改系统的配置,Puppet 同时也会同时生成一份报告。这份报告包含了在对系统应用修改的过程中发生的事件。这些事件也完整地饭赢了工作进行的情况:它们会在资源改变时记录时间戳,已有的值和新的值,以及所有产生的信息,以及变动成功或是失败(或者实在模拟运行模式)。
这些事件封装在 ResourceStatus
对象之中,映射到相应的资源。这样,对于一个给定的 Transaction,你可以知道其中运行的所有资源,这些改变是否成功,以及你可能希望知道的关于这些改动的元数据。
一旦 transaction 完成了,一些基本的性能参数也会被计算出来,并存储到报告中,之后发送给服务器(如果配置了服务器的话)。当报告被发送出去的时候,配置过程就完成了,agent 会回到睡眠模式,或者进程退出。
18.4. 基础架构
现在我们已经从整体上理解了 Puppet 做了什么,如何做到的,值得再花一点看看其他部分了,这些部分并没有显示出什么过人之处,但对于完成工作也是十分必要的。
Plugins
Puppet 的一个突出优点是它非常易于扩展。在 Puppet 里,至少有 12 类扩展,大部分扩展都可以被所有人使用。比如,你可以在这些方面写出你自己的扩展:
- 资源类型和自定义 provider
- 报告处理程序,比如存储报告到专用的数据库中
- Indirector,用于和已有数据存储交互
- 用于获取你的主机上的额外信息的 facts
不过,Puppet 的分布式本质意味着 agent 需要某种方式来取回并加载新的扩展。为此,在每次 Puppet 启动之前,第一件事请就是找到可用的服务器,下载所有 plugin。其中可能包括新的资源类型或 provider,新 facts,或者是新的报告处理器。
这意味着我们可以在不改动核心 Puppet 包的同时,升级大部分的 Puppet agent 功能。对于一些自定义的 Puppet 部署,这更是特别有用。
Indirector
到目前为止,你可能已经发现,Puppet 的开发历史中有一些坏名字的类,而对于大部分人来说,这一个是最无法容忍的。Indirector 是一个极具扩展性的控制反转(IoC)框架。控制反转系统允许你讲功能的开发和如何控制使用什么功能独立开。在 Puppet 的例子中,这允许我们使用很多插件,来提供非常不同的功能,比如可以通过 HTTP 访问编译器,也可以直接在进程中加载,这些可以通过一个笑得配置改变而不需要修改代码就可以实现。换句话说,按照 Wikipedia 的 “控制反转” 页面的描述,Puppet Indirector 是一个服务定位器的实现。所有从一个类到另一个类的切换都经由 Indirector,通过一个标准的类 REST 接口完成(比如,我们支持 find, search, save 以及 destroy 方法),这样,讲 Puppet 从无服务器模式切到客户机/服务器模式的操作,很大程度上说,是一个配置 agent 使用 HTTP 作为获取 catalog 的方法,而非直接访问 compiler 的问题。
因为作为一个控制反转框架,Indirector 的配置必须严格地和代码的路径分开,这个类本身非常难于理解,特别是你在 debug 为什么使用给定的代码路径时。
网络
Puppet 的原型写于 2004 年,当时的关于 RPC 的问题是 XMLRPC 与 SOAP 之争。我们选择了 XMLRPC,它工作得很好,但是有一个大部分其他方法都有的问题:不鼓励在模块间使用标准接口,而且对于获取简单的一个结果这样的操作有些过于复杂了。因为 XMLRPC 编码的需要,导致了几乎每个对象都在内存中至少出现两次,对于大文件来说代价很高,我们也为此遇到过很严重的内存问题。
从 0.25 发布开始(开始于 2008 年),我们开始了将网络通信向类 REST 模型迁移的过程,但我们没有直接修改网络,而是选择了一种更复杂的方案。我们开发了 Indirector 作为组件间通信的标准框架,然后构建了一个 REST 端点作为一个可选方案。我们用了两个 Release 来完整支持 REST,目前还没有完全完成(从使用 YAML)到使用 JSON 作为序列化方案的转换。我们进行 YAML 到 JSON 的转换有两点主要的原因:首先,Ruby 之中,处理 YAML 非常慢,而 Ruby 处理 JSON 则快很多;其次,大部分 web 应用都转向了 JSON,这让 JSON 看起来更加可移植一些。当然,对于 Puppet 的情况来说,使用 YAML 并非是为了语言间可移植性考虑的,,而且 YAML 配置对于不同版本的 Puppet 可能都经常不兼容,因为它本质上是用来序列化 Ruby 内部对象的。
我们的下一个主版本更新将完全移除 XMLRPC 的支持。
18.5. 经验教训
从实现的角度讲,我们对于 Puppet 中实现的各种解耦非常自豪:描述语言与 RAL 是完全解耦的,Transaction 无法直接访问系统,而 RAL 本身不会做任何策略判断。这些抽象和解耦让应用的开发者可以更加专注于工作流的开发,并可以获得很多关于发生了什么、为什么发生的信息。
Puppet 的可扩展性和可配置性也是它的一个主要优点,任何人都可以在 Puppet 的基础上,轻易地开发应用而无需修改其内核。我们也使用提供给用户的同样的接口来开发各种功能。
Puppet 的简单和易用性一直是它的主要优点。虽然让它跑起来还是有点困难,不过可以强出市场上的其他产品好几里地了。这些简单性是以增加了很多工程量为代价的,特别是在维护和更多的设计工作方面的工作量,但是,如果能让用户可以更加集中在他们的问题上,而非工具上的话,这些开销也是值得的。
Puppet 的可配执性是个非常好的特性,不过我们做得好像有点过了。你可以有很多方法来让 Puppet 工作起来,并且,在 Puppet 上太容易构建工作流了,这在有的时候可能让人赶到困窘。我们的一个短期目标是,减少你可以调整的 Puppet 配置,避免用户很容易地把 Puppet 调坏,而且,这样我们在升级的时候也可以考虑更少的边界情况了。
我们的改变也有些慢了。有很多重构都已经等待数年却仍然没有进行。对用户来说,短期内这意味着更稳定的系统,但却更难于维护,而且用户也更难于向社区回馈代码。
最后,我们花费了很长时间来认识到,描述我们的设计语言的最恰当的词就是简单性。我们现在已经开始在考虑简单性之外的设计目标了,我们开始采用更好的决策框架来决定是否增加或移除特性,开始通过考虑背后的深层次原因来做出决定。
18.6. Conclusion
Puppet 是一个简单的系统,也是一个复杂的系统。它由很多的部分组成,但各个部分之间的耦合非常松,每个部分从2005年至今都发生了很多变化。它是一个可以用于处理各种配置问题的框架,但作为一个应用,它非常简单易用。
在未来,我们的成功将依赖于更加坚实、更加简单的框架,并让应用在增强能力的同时,保持易用性。