puppet手册使用文件和软件包

[导读]

新年开始正式上班了, sky接着去年的puppet手册系列文章进行更新,本章节

Working with Filesand Packages(使用文件和软件包),里面介绍了在Puppet

使用过程中一些方法与技巧,值得我们puppet 新手学习,本次将更新五个小节,

内容也有点多,大家要耐心看完.在此小节,我们学习在puppet中快速编辑配置

文件,augeas如何使用与原理,如何使用snippet来构建配置文件,

以及我们经常要使用到的puppet 模板,以及如何在模板中使用循环,

更多精彩不多说,进入正题…

[正文]

                                                    使用文件和软件包

“If builders built buildings the way programmers wrote programs, then the first woodpecker
that came along would destroy civilization.”
— Gerald Weinberg

在本章中我们将学习包括以下内容:

  1. 快速编辑配置文件
  2. 使用Augeas自动编辑配置文件
  3. 使用依赖关系 (注,应该是原文有误,第四章中有介绍,本章没有相关资料.请参阅第四章)
  4. 使用snippets构建配置文件
  5. 使用ERB模板
  6. 在模板中使用循环数组
  7. 从第三方仓库安装软件包
  8. 建立APT 软件包源
  9. 建立gem 源
  10. 从源文件自动构建软件包
  11. 比较软件包版本

1.快速编辑配置文件

你知道吗Pupppet 可以给配置做小手术?通常我们不想把整个配置文件交给Pupppet
管理,只希望添加些设置,特别指出的是该文件由别人管理,自己又不能覆盖它.
一个简单的方法是添加该行如果该行不存在于原配置文件中,那是多么的棒.

你可以使用exec资源去完成那样的工作,这个例子,来源于Puppet 实验室的维基.

演示了如何使用exec资源在文本文件里追加行.

怎么办呢…
1.创建/etc/puppet/manifests/utils.pp 文件,内容如下:

define append_if_no_such_line($file, $line) {
exec { "/bin/echo '$line' >> '$file'":
unless => "/bin/grep -Fx '$line' '$file'"
}
}

2.在/etc/puppet/manifests/site.pp添加这一行:

import "utils.pp"

3.现在添加这些代码:

append_if_no_such_line { "enable-ip-conntrack":
file => "/etc/modules",
line => "ip_conntrack",
}

4.运行puppet:

# puppet agent --test
info: Retrieving plugin
info: Caching catalog for cookbook.bitfieldconsulting.com
info: Applying configuration version '1303649606'
notice: /Stage[main]//Node[cookbook]/Append_if_no_such_
line[enable-ip-conntrack]/Exec[/bin/echo 'ip_conntrack' >> '/etc/
modules']/returns: executed successfully
notice: Finished catalog run in 1.22 seconds

它是如何工作的…
exec资源会追加指定的文本$line到指定的文件$file:

exec { "/bin/echo '$line' >> '$file'":

除非它已经存在该行:

unless => "/bin/grep -Fx '$line' '$file'"

append_if_no_such_line资源现在可以在你的代码里使用.在本例中,我们已经使用
它确保/etc/modules文件里有包含这一行:(指定内核模块在开机时加载)
ip_conntrack

还有更多…

你可以使用类似的定义在文本文件上执行其它的小改动.例如:
这将让你在文件里使用搜索并替换匹配字符串:

define replace_matching_line( $match, $replace ) {
exec { "/usr/bin/ruby -i -p -e 'sub(%r{$match}, "$replace")'
$name":
onlyif => "/bin/grep -E '$match' $name",
logoutput => on_failure,
}
}
replace_matching_line { "/etc/apache2/apache2.conf":
match  => "LogLevel .*",
replace => "LogLevel debug",
}

另请参阅
使用Augeas 自动修改配置文件

                                                                                  使用Augeas 自动修改配置文件

当然,有这么多关于标准,标准是件伟大的事情,有时每个应用程序看起来都有
自己巧妙不同的文件格式,并且写正则表达式去解析和修改文件,那是件烦人的业务.

幸运的是,Augeas在这里可以帮助我们.Augeas是个工具,它旨在简化使用不同的配置
文件格式,augeas是通过树形结构来配置修改相应的配置文件的.
augeas是puppet的标准资源。它可以使所需的配置变化更加智能和自动化.

可以用来管理配置文件。但这种资源的使用需要有augeas及ruby-augeas的支持.

准备…
1.创建 /etc/puppet/modules/admin/manifests/augeas.pp 文件,内容如下:

class admin::augeas {
package { [ "augeas-lenses",
"augeas-tools",
"libaugeas0",
"libaugeas-ruby1.8" ]:
ensure => "present",
}
}

2.在节点上执行类:

node cookbook {
include admin::augeas
}

3.运行Puppet:

# puppet agent --test
info: Retrieving plugin
info: Caching catalog for cookbook.bitfieldconsulting.com
info: Applying configuration version '1303657095'
notice: /Stage[main]/Admin::Augeas/Package[augeas-tools]/ensure:
ensure changed 'purged' to 'present'
notice: Finished catalog run in 21.96 seconds

怎么办呢…
1.创建/etc/puppet/modules/admin/manifests/ipforward.pp
文件,内容如下:

class admin::ipforward {
augeas { "enable-ip-forwarding":
context => "/files/etc/sysctl.conf",
changes => [
"set net.ipv4.ip_forward 1",
],
}
}

2. 在某个节点上执行这个类:

node cookbook {
include admin::augeas
include admin::ipforward
}

3.运行Pupppet:

# puppet agent --test
info: Retrieving plugin
info: Caching catalog for cookbook.bitfieldconsulting.com
info: Applying configuration version '1303729376'
notice: /Stage[main]/Admin::Ipforward/Augeas[enable-ip-
forwarding]/returns: executed successfully
notice: Finished catalog run in 3.53 seconds

4.检查设置的值是否正确的已经应用:

# sysctl -p |grep forward
net.ipv4.ip_forward = 1

它是如何工作…
1.我们声明了一个名为augeas资源名为enable-ip-forwarding:

augeas { "enable-ip-forwarding":

2.我们指定了我们要在/etc/sysctl.conf文件里要改变的内容:

context => "/files/etc/sysctl.conf",

3.传递一个数组参数,也就是我们要改变的值,(在本例中只有一个):

changes => [
"set net.ipv4.ip_forward 1",
],

通常Augeas采取这样的格式:

set <parameter> <value>

Augeas 使用了传输文件名为lenses, 以便它能够在给定的配置文件中
使用适当的格式应用这些设置.在本例中,该设置将被卧翻译成这样一行
在/etc/sysctl.conf中:

net.ipv4.ip_forward=1

还有更多…

我选择 /etc/sysctl.conf 作为例子,是因为它可以包含各种各样的内核
设置,你可能想更改这些设置各种不同目的在不同的Puppet类中.你可能需要
启用IP 转发,如本例,为一个路由器的类.但你可能还需要为负载均衡类调整net.core.somaxconn的值.

这要意味着,简单的Puppetising  /etc/sysctl.conf文件和分发它并不能工作,
因为你有可能有版本冲突,这取决于你想要修改的设置依赖.Augeas是正确的解决
方案,因为你可以定义在不同的地方修改同一个文件,他们将不会发生冲突.
Augeas 是个强大的工具,它与大多数标准的Linux配置文件兼容和你可以自己
写属于或者专有配置格式.如果你要管理这些.

使用Puppet和Augeas 更多的信息,可以查看puppet 实验室上的维基页面

http://projects.puppetlabs.com/projects/1/wiki/Puppet_Augeas


                                                           使用snippets 来构建配置文件

你怎么一口吃掉一头大象?一次一口,有时候你有个情况,你要建立一个单一的配置
文件从不同类别管理的的类中.例如:你可能有两个或者三个服务需要配置rsync模块.
因此你不可能分配一个单的rsyncd.conf.虽然你可以使用Augeas,有一个简单的方法
使用exec连接成一个单一文件.

怎么办呢…
1.创建/etc/puppet/modules/admin/manifests/rsyncdconf.pp文件,内容如下:

class admin::rsyncdconf {
file { "/etc/rsyncd.d":
ensure => directory,
}
exec { "update-rsyncd.conf":
command  => "/bin/cat /etc/rsyncd.d/*.conf > /etc/ rsyncd.conf",
refreshonly => true,
}
}

2.添加下面内容到你的代码:

class myapp::rsync {
include admin::rsyncdconf
file { "/etc/rsyncd.d/myapp.conf":
ensure => present,
source => "puppet:///modules/myapp/myapp.rsync",
require => File["/etc/rsyncd.d"],
notify => Exec["update-rsyncd.conf"],
}
}
include myapp::rsync

3.创建/etc/puppet/modules/myapp/files/myapp.rsync
文件,内容如下:

[myapp]
uid = myappuser
gid = myappuser
path = /opt/myapp/shared/data
comment = Data for myapp
list = no
read only = no
auth users = myappuser

4.运行Puppet:

# puppet agent --test
info: Retrieving plugin
info: Caching catalog for cookbook.bitfieldconsulting.com
info: Applying configuration version '1303731804'
notice: /Stage[main]/Admin::Rsyncdconf/File[/etc/rsyncd.d]/ensure:
created
notice: /Stage[main]/Myapp::Rsync/File[/etc/rsyncd.d/myapp.conf]/
ensure: defined content as '{md5}e1e57cf38bb88a7b4f2fd6eb1ea2823a'
info: /Stage[main]/Myapp::Rsync/File[/etc/rsyncd.d/myapp.conf]:
Scheduling refresh of Exec[update-rsyncd.conf]
notice: /Stage[main]/Admin::Rsyncdconf/Exec[update-rsyncd.conf]:
Triggered 'refresh' from 1 events
notice: Finished catalog run in 1.01 seconds

它是如何工作的…

admin::rsyncdconf类创建一个存放rsync配置文件目录:

file { "/etc/rsyncd.d":
ensure => directory,
}

当你创建一个配置片断(snippet)(如例子中的myapp::rsync),你所需要做的就是创建所需要的目录:

require => File["/etc/rsyncd.d"],

并通知exec 更新主要配置文件:

notify => Exec["update-rsyncd.conf"],

这样只要配置片断更新有,exec就会执行.

exec { "update-rsyncd.conf":
command=> "/bin/cat /etc/rsyncd.d/*.conf > /etc/rsyncd.conf",
refreshonly => true,
}

将合并在/etc/rsyncd.d下面的所有片断到rsyncd.conf

还有更多…

当你有个像rsync服务,需要一个单独的配置文件,那是非常有用的模式,实际上,它
也可以应用到你的Apache的conf.d的或者php-ini.d 目录.

另请参阅
使用tags

                                             使用模ERB 模板

模板是一个具有大学学历的文本文件.你使用Puppet可能需要部署一个文本文件到任何地方,

你可以使用模板来代替.在最简单的情况下,一个模板可以是一个静态文件.
更有益的是,你可以使用ERB(embedded Ruby)语法插入变量.

例如:

<%= name %>, this is a very large drink.

如果模板使用变量$name的值为Zaphod Beeblebrox,那么模板会计算结果为:

Zaphod Beeblebrox, this is a very large drink.

在不同的变量一个或者两个变量是非常有用,这是个简单的技术生成了大量的文件.
例如:虚拟主机,在本例子中,我们将使用ERB模板来生成一个Apache 虚拟主机定义.

准备…

1.如果你没有apache模块,请创建一个.

# mkdir /etc/puppet/modules/apache
# mkdir /etc/puppet/modules/apache/templates
# mkdir /etc/puppet/modules/apache/manifests

2.创建/etc/puppet/modules/apache/manifests/apache.pp文件,内容如下:

class apache {
package { "apache2-mpm-worker": ensure => installed }
service { "apache2":
enable => true,
ensure => running,
require => Package["apache2-mpm-worker"],
}
}

怎么办呢…

1.创建/etc/puppet/modules/apache/manifests/site.pp文件,内容如下:

class apache::site( $sitedomain ) {
include apache
file { "/etc/apache2/sites-available/${sitedomain}.conf":
content => template("apache/vhost.erb"),
require => Package["apache2-mpm-worker"],
}

file { "/etc/apache2/sites-enabled/${sitedomain}.conf":
ensure => symlink,
target => "/etc/apache2/sites-available/${sitedomain}.conf",
require => Package["apache2-mpm-worker"],
notify => Service["apache2"],
}
}

2.创建/etc/puppet/modules/apache/templates/vhost.erb
文件,内容如下:

<VirtualHost *:80>
ServerName <%= sitedomain %>
ServerAdmin admin@<%= sitedomain %>
DocumentRoot /var/www/<%= sitedomain %>
ErrorLog logs/<%= sitedomain %>-error_log
CustomLog logs/<%= sitedomain %>-access_log common
<Directory /var/www/<%= sitedomain %>>
Allow from all
Options +Includes +Indexes +FollowSymLinks
AllowOverride all
</Directory>
</VirtualHost>
<VirtualHost *:80>
ServerName www.<%= sitedomain %>
Redirect 301 / http://<%= sitedomain %>/
</VirtualHost>

3.添加上面到节点:

class { "apache::site": sitedomain => "keithlard.com" }

4.运行Puppet:

# puppet agent --test
info: Retrieving plugin
info: Caching catalog for cookbook.bitfieldconsulting.com
info: Applying configuration version '1304761207'
notice: /Stage[main]/Apache::Site/File[/etc/apache2/sites-
available/keithlard.com.conf]/ensure: defined content as '{md5}f2a
558c02beeaed4beb7da250821b663'
notice: /Stage[main]/Apache::Site/File[/etc/apache2/sites-enabled/
keithlard.com.conf]/ensure: created

info: /Stage[main]/Apache::Site/File[/etc/apache2/sites-enabled/
keithlard.com.conf]: Scheduling refresh of Service[apache2]
notice: /Stage[main]/Apache/Service[apache2]: Triggered 'refresh'
from 1 events
notice: Finished catalog run in 8.06 seconds

它是如何工作的…

apache::site类需要带个参数,参数名为sitedomain,sitedomain是你的虚拟主机
所要创建的域名:在本例中,keithlard.com.

然后我们使用vhost.er模板去生成一个合适的定义的Apache虚拟主机.无论在什么
时候在模板里引用$sitedomain,例如:

ServerName <%= sitedomain %>

Puppet 会将取代替为相应的值:
ServerName keithlard.com

模板系统的优点是,你可以创建多个站点,都使用相同的虚拟主机模板.

class  { "apache::site":sitedomain => "bitfieldconsulting.com" }
class  { "apache::site": sitedomain => "cribbagecorner.com" }
class  { "apache::site":sitedomain => "cornishceremonies.com" }
class  { "apache::site":sitedomain => "mosquito-mojito.com" }

如果你想对所有网站做个小的配置文件修改(例子如,修改管理员电子邮件地址),
你可以在模板里一次完成,Puppet会更新所有相应的虚拟主机.

还有更多…
你上面例子中,我们在模板里仅仅使用了一个变量,但然你可以定义放多变量.
这些也可以是facts(factre探测出来的变量):

ServerName <%= fqdn %>

或者是Ruby正则表达式:

MAILTO=<%= emails.join(',') %>

或者是你想写的任何Ruby代码:

ServerAdmin <%= sitedomain == 'coldcomfort.com' ? 'seth@coldcomfort.com' : 'flora@poste.com' %>

另请参阅
在模板里使用循环数组

                                       在模板里使用循环数组

在前面的例子子中,我们可以看到使用Ruby能在模板里插入不同的值,这些Ruby结果
是依赖于Ruby 表达式的值.你还可以使用一个循环来生成,例如:数组中的元素:
怎么办呢…
1.添加以下内容到你的代码:

$ipaddresses = [ '192.168.0.1',
'158.43.128.1',
'10.0.75.207' ]
file { "/tmp/addresslist.txt":
content => template("admin/addresslist.erb")
}

2.创建/etc/puppet/modules/admin/templates/addresslist.erb
文件,内容如下:

<% ipaddresses.each do |ip| -%>
IP address <%= ip %> is present.
<% end -%>

3.运行Puppet:

# puppet agent --test
info: Retrieving plugin
info: Caching catalog for cookbook.bitfieldconsulting.com
info: Applying configuration version '1304766335'
notice: /Stage[main]//Node[cookbook]/File[/tmp/addresslist.txt]/
ensure: defined content as '{md5}7ad1264ebdae101bb5ea0afef474b3ed'
notice: Finished catalog run in 0.64 seconds

4.检查生成的文件内容:

# cat /tmp/addresslist.txt
IP address 192.168.0.1 is present.
IP address 158.43.128.1 is present.
IP address 10.0.75.207 is present.

它是如何工作的…
1.在模板的第一行,我们引用了一个ipaddressess数组,并调用each方法:

<% ipaddresses.each do |ip| -%>

2.在Ruby中,创建一个循环数组为每个元素执行一次,每次循环时,变量ip
会被设置为当前元素的值.
3.在我们的例子中,ipaddresses数组有三个元素,因此下面的行会被执行
三次,一次每个元素:

<% ipaddresses.each do |ip| -%>

4.结果是输出如下三行:
IP address 192.168.0.1 is present.
IP address 158.43.128.1 is present.
IP address 10.0.75.207 is present.
5.最后一行结束循环.

<% end -%>

6.请注意第一和最后一行的 -%>符号,而不是我们以前所看到的%>,-的效果
是阻止换行,否则的话将产生换行符,即在我们的文件里出现无用的空白行.

还有更多…

模板也可以循环散列或者数组或者哈希.

$interfaces = [ { name => 'eth0',
ip =>'192.168.0.1' },
{ name => 'eth1',
ip =>'158.43.128.1' },
{ name => 'eth2',
ip => '10.0.75.207' } ]

<% interfaces.each do |interface| -%>
Interface <%= interface['name'] %> has the address <%= interface['ip']%>.
<% end -%>
Interface eth0 has the address 192.168.0.1.
Interface eth1 has the address 158.43.128.1.
Interface eth2 has the address 10.0.75.207.

另请参阅:
使用ERB模板

[总结]

接下来会介绍本章其它小节的内容,本节内容比较多,请仔细多看,尤其是模板使用,以及以些常用技巧.

大家也可以应用到我们运维工作中去.最后再说一句,由于个人水平有限,错误之处还请谅请!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>