Loading... <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">这篇博客的目的是通过分析Forge上的Puppet模块来加深一些概念的理解,同时了解一些常用用法。</span></p> <p></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">今天的例子是jfryman-nginx模块,它是原puppetlabs-nginx模块的升级版本,依赖3个Puppet公共模块:puppetlabs-apt,puppetlabs-stdlib和puppetlabs-concat。安装非常方便,puppet module install会自动为你安装所依赖的模块。</span></p> <pre class="brush:ruby;toolbar:false;"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">> puppet module install jfryman-nginx<br />/etc/puppet/modules<br />└─┬ jfryman-nginx (v0.3.0)<br /> ├── puppetlabs-apt (v2.2.2) #Puppet公共模块,提供Debain/Ubuntu下安装包管理功能<br /> ├── puppetlabs-stdlib (v4.12.0) #Puppet公共模块,提供对各种数据类型的操作函数,比如检查,转换之类<br /> └── puppetlabs-concat (v2.1.0) #Puppet公共模块,可以将分散在不同文件中的内容组合到一个目标文件中</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">模块的目录结构就不在这里累述了,tree /etc/puppet/modules/nginx命令可以显示详细的结构,需要注意的是模块的manifests目录中有以下5个文件:</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">├── manifests<br />│ ├── init.pp #模块自装载起始文件<br />│ ├── params.pp #参数类定义文件,用于给其他各类的参数变量提供默认值<br />│ ├── config.pp #配置类的定义文件<br />│ ├── package.pp #软件包安装类的定义文件<br />│ ├── service.pp #服务类的定义文件<br />│ └── ...</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">任何Forge中的模块,基本都会包含上面这几个文件。这已经成为了一种标准。其好处之一是可以清晰划分从软件包安装,配置到服务启动的各阶段功能;另一方面代码中可以利用类来定义各阶段的依赖关系,因为每个阶段通常都包含多个资源,所以用类定义依赖关系比用资源更加灵活实用。<br /></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><br /></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">下面我们看看模块的init.pp</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong>代码片段1:类定义</strong></span></p> <hr /> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">class nginx ( #nginx类定义<br />...<br /> $package_name = $::nginx::params::package_name, #用nginx::params类中的变量$package_name给nginx类的$package_name赋默认值<br /> $package_source = 'nginx', #设置$package_source默认值为'nginx'<br /> $package_flavor = undef, #设置$package_source默认值undef<br />...<br />) inherits ::nginx::params { #nginx类继承nginx::params类</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">以上是nginx的类定义。不禁要问几个问题</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em style="font-family: arial, helvetica, sans-serif;font-size: 14px"><strong>nginx类为什么要定义参数</strong></em><em style="font-family: arial, helvetica, sans-serif;font-size: 14px"></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">主要是为了使nginx类更加灵活的适应各种不同的应用场景。调用者可以通过nginx类的参数变量来传递不同的值,从而影响nginx中资源的属性和行为。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>nginx为什么要继承nginx::params类</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">在Puppet中,类继承一般只用于2个目的</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> * 重用基类中大部分代码和逻辑,只重写一小部分代码以实现新的功能</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> * 利用基类中的预定义变量值给子类的类参数赋值。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">这里是第2种情况。nginx会用到的大多数默认值都预定义在nginx::params类中。nginx继承nginx::params类来为自己的参数赋默认值。这也是最常用的赋默认值的方式。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">这里专门定义了nginx::params类是因为某些默认值很可能会随目标主机的OS或其他Facts有所差异,所以将相关逻辑隔离在nginx::params类中可以更容易管理代码。比如下面的代码片段是nginx::params类中根据Facts $::osfamily设置hash变量$_module_os_overrides</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> case $::osfamily {<br /> 'ArchLinux': {<br /> $_module_os_overrides = {<br /> 'pid' => false,<br /> 'daemon_user' => 'http',<br /> }<br /> }<br /> 'FreeBSD': {<br /> $_module_os_overrides = {<br /> 'conf_dir' => '/usr/local/etc/nginx',<br /> 'daemon_user' => 'www',<br /> 'root_group' => 'wheel',<br /> }<br /> }<br />...<br />}<span style="font-size: 14px;font-family: sans-serif"> </span></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">很容易联想到的另一个场景是为类的参数<strong>赋初值</strong>(注意不是赋默认值),它需要在<strong>声明类的时候(</strong>注意不是定义类的时候<strong>)</strong>使用resource-like声明方式声明类。比如</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> class { '::nginx::service':<br /> configtest_enable => $configtest_enable, #用变量$configtest_enable为类参数赋初值<br />....<br /> }</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>类的名字和继承有什么关系?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">答案是没有关系。确定继承关系的唯一标准是看类定义时是否使用了inherits关键字。类的名字只和其manifest文件在模块目录结构中的位置有关。比如: 详情请看</span><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">模块的目录结构</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">/etc/puppet/modules/nginx/manifests<br />├── init.pp #nginx类<br />├── package.pp #nginx::package类<br />├── package<br />│ ├── debian.pp #nginx::package::debian类<br />│ └── redhat.pp #nginx::package::redhat类<br />...</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>什么时候应该使用变量的长名字,什么时候使用短名字?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">使用短名字访问变量受scope(作用域)的限制。</span></p> <p style="text-align: center"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><img src="//cto.wang/usr/uploads/2016/07/20160703170123-43.png" title="1466508388149060.png" alt="scope-euler-diagram.png" /></span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">这张图中有4层作用域</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> a. 顶层作用域是top scope,包含Facts和Agent/Master内置变量,以及所有site.pp中节点定义以外的所有变量,表达式,资源类型等等内容</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> b. 下面一层是node scope, 也就是site.pp中节点定义所包含的所有内容<br /></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> c. 再下面是各种类的定义,在图中包括example:parent,example:four和exmaple:other.这3个类在同一层,但每个类自己是一个单独的作用域。<br /></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> d. 最下面一层是example:child,它是example:parent的子类,自己是一个作用域。</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">底层作用域中的代码可以用变量的短名字访问上层作用域的变量。比如,top scope 有个变量$var, example:child可以直接在自己的代码中用短名字$var使用它,同样方法也可以用来访问node scope和它的父类example:parent中的变量。</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px"><em><strong>注意:</strong></em>这么做的前提条件是本地作用域没有同名的变量。在上面的代码段中,虽然nginx是nginx:params的子类,但因为都定义了同名的变量$package_nam,需要用长名字$::nginx:params::package_name指代nginx:params类中的$package_name变量。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">除此之外,只要是访问其他作用域里的变量,都必须用长名字。比如访问同一个模块中的其他类的变量,或者另外一个模块里的的类变量。来看几个例子: </span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> a. example::four和example:child在一个模块中,但它既不是example:child的父类,也不是node scope或者top scope, 如果想访问其中的变量,需要这样写</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">include ::exmaple::four<br />$myvar=$::example::four::var1。</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> b. 我想访问apache模块中的apache::php类里的变量$var1。需要这样写</span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">include ::apache::php<br />$myvar=$::apache::ph::var1。</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">在实际的编码中,为了清晰,一般都会在名字左边加::,表示从顶层作用域开始唯一标识一个类或者变量。这就就如同文件路径中的绝对路径,不会造成任何混淆。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong>代码片段2:依赖关系定义</strong></span></p> <hr /> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">Class['::nginx::package'] -> Class['::nginx::config'] ~> Class['::nginx::service']<br /></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">这段代码的意思是nginx::package类必须在nginx::config类之前执行,而nginx::service类必须在nginx::config类之后执行。而且nginx::config类中<strong>任何</strong>资源的状态发生变化,比如文件内容,nginx::service类中的<strong>所有</strong>资源都会收到refresh事件。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">值得注意的几点是</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em style="font-size: 14px"><strong>为什么需要描述依赖关系?</strong></em><em style="font-size: 14px"></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">Puppet语言是声明性语言,不描述流程,所以Puppet代码执行的顺序和代码写的顺序经常是不一致的,这就是Puppet中经常提到</span><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">的 independent of evaluation-order 。当</span><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">资源或者类有执行的先后顺序时,就需要显性的描述依赖关系。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>还有什么方式可以描述依赖关系?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">类和资源都可以使用->和~>描述依赖关系(资源与资源,类与类,资源与类之间)。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">另一种方法是用元参数require/before/notify/subscribe。这种方法可以在资源声明时说明依赖关系,既可以用于资源,也适用于resource-like声明的类。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">还有一种方法是使用require函数<span style="font-family: arial, helvetica, sans-serif;font-size: 14px">(注意不是元参数require)</span>描述类之间的依赖关系。详情请见在线文档</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>为什么Class首字母大写?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">因为是引用(reference),也就是在定义和声明之外的任何场景使用类或者资源时,Class和资源类型关键字的首字母都要大写。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">常见的引用场景包括</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> a. 类或资源声明时,使用require/before/notify/subscribe来描述依赖关系,比如 </span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">file { '/etc/nginx.conf'<br />require=> Package['nginx'] #Package首字母大写<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> b. 资源声明时,引用另外一个资源的属性,这个例子中引用另外一个文件资源的mode属性</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">file { "/etc/second.conf":<br />ensure => file,<br />mode => File["/etc/first.conf"]["mode"] #File首字母大写<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> c. 资源声明后,需要设置或者修改资源的某个属性,比如</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:python;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">File['/etc/nginx.conf'] { #File首字母大写<br />content => template('nginx/sample.conf'),<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">在引用资源时,如果被引用的<span style="font-family: arial, helvetica, sans-serif;font-size: 14px">资源类型</span>是长名字时(一般是自定义资源类型),所有::分隔的命名空间的首字母都要大写,比如。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">Nginx::Resource::Location["${name}-default"] { #Nginx::Resource::Location各段首字母大写<br /> location_cfg_prepend => $location_cfg_prepend<br />}<br /></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>引用的对象是谁?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">类,资源及属性可以被引用。变量不适用。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong><em>可以在类或者资源声明前引用他们吗?</em></strong></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">答案是可以,可以在类或者资源声明前引用他们。此外,由于在同一个catalog范围的所有资源(标题)和类(名字)必须是唯一的,所以可以在代码的任何部分引用任何类和资源,不受作用域影响。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>哪些资源可以处理refresh事件</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">service, mount和exec资源可以处理refresh事件</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">service的默认行为是使用init脚本(redhat linux上,脚本在/etc/rc.d/init.d/中)重新启动服务,如果你想避免重启服务, 可以设置restart属性</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">service {"sshd":<br />restart=>"service reload sshd" #收到refresh时,运行reload而不是restart<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">mount的默认行为是重新挂载(umount再mount)</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">exec的默认行为是重新运行命令。它有两个相关的属性</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">exec { "/bin/ls ":<br />refresh => "ls -l" #refresh发生时,运行refresh属性指定的另外一个命令<br />refreshonly => true, #exec只在refresh事件发生时才运行命令<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong style="font-size: 14px"></strong></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong style="font-size: 14px">代码片段3:调用所需的类</strong><strong style="font-size: 14px"></strong></span></p> <hr /> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong style="font-size: 14px"></strong></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">class { '::nginx::service':<br /> configtest_enable => $configtest_enable,<br /> service_ensure => $service_ensure,<br /> service_restart => $service_restart,<br /> service_name => $service_name,<br /> service_flags => $service_flags,<br />}<br /></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">这段代码是用resource-like方式声明类。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">Puppet支持两种类的声明方式<br /></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> include-like方式:使用include, require, contain或者hiera_include关键字,后面跟类的名字来声明类</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> resource-like方式:像声明资源一样声明类。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em style="font-family: arial, helvetica, sans-serif;font-size: 14px"><strong>定义和声明有什么区别?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">以类举例,定义一般是说明类看起来是什么样子,含有那些资源,声明是设定类参数从而确定类中资源的属性和行为,说明类的依赖关系,然后告诉Puppet在catalog中加入这个类的一个实例。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">除了类,<strong>自定义</strong>的资源类型,函数,Facts,Provider等等也需要先定义,然后声明。</span></p> <p><em><strong><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">注意:</span></strong></em><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">变量不需要定义或者声明,直接赋值就可以了</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>inlcude-like和resource-like声明类有什么区别?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">相比inlcude-like,resource-like最大优势是可以为类赋值,这也是在类声明时为类传递参数的唯一方法。在上面的例子中,=>左边是类参数,右边是参数值。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">同时,resource-like也有一个缺点,就是只能声明一次。相比之下,include-like可以声明任意多次。在一个catalog范围内,允许把个resource-like(一次)和include-like(多次)混合使用。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>为什么resource-like声明只允许使用一次呢?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">OOP中的类的通常有下面4个特性。Puppet中的类不一样,只有前3个特性,且只支持从一个父类继承,不支持从多个父类继承。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> 抽象</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> 封装</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> 继承</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> 多态性</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">原因是Puppet中的类只是一般OOP中的的单实例类(singleton)。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">当用include-like方式声明类时,虽然声明了多次,但是在catalog中只会有一个类的实例,Puppet也只会执行这个实例一次。这就是所谓的“可以多次声明,但只应用一次”。因为include-like不传入任何参数,所以这个单实例可满足所有调用者的要求。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">resource-like声明可以传入参数,理论上讲,传入不同的参数也就创建出不同的实例。 所以</span>为了保证一个实例,resource-like声明只允许使用一次,只生成一个实例。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>在include-like声明方式中,include, require, contain和hiera_include有什么区别吗?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">include关键是最简单的声明方式,就是告诉Puppet在<span style="font-family: arial, helvetica, sans-serif;font-size: 14px">catalog中</span>生成一个类的实例。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">require关键字除了include的功能,还表明的类的依赖关系</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">hiera_include关键字是在当<span style="font-family: arial, helvetica, sans-serif;font-size: 14px">Master和Hirea集成时使用,它可以</span>通过Hirea获取类的信息并声明。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">contain用在比较特殊的场合。我们会在下面和anchor一起解释。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong>代码片段4:使用anchor</strong></span></p> <hr /> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">anchor{ 'nginx::begin':<br /> before => Class['::nginx::package'],<br /> notify => Class['::nginx::service'],<br />}<br />anchor { 'nginx::end':<br /> require => Class['::nginx::service'],<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">anchor是Puppet的内置资源类型。上面的代码声明了两个anchor资源,分别是nginx::begin和nginx::end,他们将类nginx::package,nginx::config和nginx::service夹在中间(这3个类在上面已经用->/~>设好了依赖关系),作用是强制这3个类在nginx类开始后执行,并且必须在nginx类退出前全部执行完。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">上面的代码等价于</span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">anchor{ 'nginx::begin': }<br />anchor{ 'nginx::end': }<br />Anchor['nginx::begin']->Class['::nginx::package']->Class['::nginx::config']->Class['::nginx::service']->Anchor['nginx::end']<br /></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em><strong>为什么需要用anchor呢?</strong></em></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">如果代码中只有2层类,比如类A包含类A1和类A2,A1和A2都直接包含资源,且A2依赖A1,那么执行A时Puppet会按照定义好的顺序先执行A1内的资源,再执行A2里的资源。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">当类的层次增加时,情况就不同了。比如这个例子</span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"># /etc/puppetlabs/puppet/modules/profiles/manifests/dbserver.pp<br />class profiles::dbserver {<br /> include mysql<br />}<br /># /etc/puppetlabs/puppet/modules/profiles/manifests/webserver.pp<br />class profiles::webserver {<br /> include apache<br />}<br /># /etc/puppetlabs/puppet/modules/roles/webstack.pp<br />class roles::ecommerce_app {<br /> include profiles::dbserver<br /> include profiles::webserver<br /> Class['profiles::dbserver'] -> Class['profiles::webserver']<br />}<br />#/etc/puppetpabs/puppet/manifests/site.pp<br />node 'webapp01.puppetlabs.com' {<br /> include roles::ecommerce_app<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">大多数人的第一感觉上是Puppet会先运行mysql类,然后是apache类,实际结果却不一定,经常会看到相反的顺序,这是因为默认情况下,下层的类(mysql类和apache类)之间不会继承上层类(profiles::dbserver类和profiles::webserver类)之间的依赖关系。<br /></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">为了使下层的类也能够遵循上层类的顺序执行,需要使用contain或者anchor</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">a. 使用contain</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"># /etc/puppetlabs/puppet/modules/profiles/manifests/dbserver.pp<br />class profiles::dbserver {<br /> contain mysql #把include换成contain<br />}<br /># /etc/puppetlabs/puppet/modules/profiles/manifests/webserver.pp<br />class profiles::webserver {<br /> contain apache #把include换成contain<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">b.使用anchor</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"># /etc/puppetlabs/puppet/modules/profiles/manifests/dbserver.pp<br />class profiles::dbserver {<br /> anchor{'before_mysql:'} -> class{'mysql':} -> anchor{'after_mysql':} #声明anchor并把mysql夹在中间<br />}<br /># /etc/puppetlabs/puppet/modules/profiles/manifests/webserver.pp<br />class profiles::webserver {<br /> anchor{'before_apache:'} -> class{'apache':} -> anchor{'after_apache':}#声明anchor并把apache夹在中间<br />}</span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><em style="font-size: 14px"><strong>anchor和contain有什么区别呢?</strong></em></span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">contain和anchor的效果完全相同。Puppet也同时支持这两种方法。</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">区别是contian是在Puppet Enterprise 3.2.0 (Puppet 3.4.0)之后才出现的,在此之前只能用anchor. 而且contain后面只能直接跟类的名字,不能跟resource-like类声明。</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">除此之外,还可以看到一些常用的用法</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px"><strong style="font-family: arial, helvetica, sans-serif;font-size: 14px">代码片段5:调用stdlib函数</strong></span></p> <hr /> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> validate_string($multi_accept) #检查输入字符串是否合法<br />...<br /> validate_array($proxy_set_header) #检查输入数组是否合法<br />...<br /> validate_bool($confd_purge) #检查输入bool是否合法<br />...<br /></span></pre> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">上面的代码是调用puppetlabs-stdlib模块中携带的函数做各种检查。</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">puppetlabs-stdlib是Forge上的一个公共模块,提供了很多自定义资源类型,函数,Facts,极大的方便了编程。在编程中也非常常用到。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">在使用时,需要先确认puppetlabs-stdlib已经安装在你的系统中(可以用puppet module list检查),如果没有安装,运行puppet module install puppetlabs-stdlib进行安装。</span></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">一般情况下,无需显性声明stdlib(include stdlib),可以直接调用其中功能,和使用内置的资源类型,函数,Facts没有区别。</span></p> <p></p> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px"><strong style="font-family: arial, helvetica, sans-serif;font-size: 14px">代码片段6:调用concat函数</strong></span></p> <hr /> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px"><strong style="font-family: arial, helvetica, sans-serif;font-size: 14px"></strong></span></p> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">concat { $config_file: #声明concat资源,指定目标文件,这里是$config_file指代的文件<br /> owner => $owner,<br /> group => $group,<br /> mode => $mode,<br /> notify => Class['::nginx::service'],<br />}<br /><br />concat::fragment { "${name_sanitized}-header": #声明concat::fragment资源,然后将所需内容写入目标文件。<br /> target => $config_file, #目标文件是$config_file指代的文件<br /> content => template('nginx/vhost/vhost_header.erb'), #写入的内容来自于模板'nginx/vhost/vhost_header.erb'<br /> order => '001', #说明当前内容在目标文件中的位置。这个数字越小,写入的内容越排在前面<br />}<br /><br />concat::fragment { "${name_sanitized}-footer": #声明另外一个concat::fragment资源,然后将所需内容写入目标文件。 <br /> target => $config_file, #目标文件是$config_file指代的文件<br /> content => template('nginx/vhost/vhost_footer.erb'), #写入的内容来自于模板'nginx/vhost/vhost_footer.erb'<br /> order => '699', #说明当前内容在目标文件中的位置。699大于001,所以写在vhost_header.erb写在vhost_header.erb<br />}</span></pre> <p><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">以上代码是将使用puppetlabs-concat模块将vhost_header.erb和vhost_footer.erb的结果输出的$config_file指定的文件中。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">puppetlabs-concat也是一个很常用的公共模块,它的作用是将不同的文件的内容排序后输出到一个新的目标文件中。使用puppetlabs-concat与puppetlabs-stdlib方法一致,不再累述。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong style="font-family: arial, helvetica, sans-serif;font-size: 14px">代码片段7:日志信息函数</strong></span></p> <hr /> <pre class="brush:ruby;toolbar:false"><span style="font-family: arial, helvetica, sans-serif;font-size: 14px">warning('$worker_processes must be an integer or have value "auto".') #输出一条警告信息<br /></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">除了notify资源,Puppet还有很多内置的函数可以输出不同级别的日志信息,非常方便。</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> emerg #emergency level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> crit #critical level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> alert #alert level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> err #error level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> warning #warning level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> info #information level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> debug # debug level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"> notice # notice level</span></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><strong>代码片段8:模板</strong></span></p> <hr /> <pre class="brush:ruby;toolbar:false"><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"><%- if @listen_ip.is_a?(Array) then -%> #判断listen_ip是不是一个列表(array),listen_ip是外部的变量,所以有@<br /> <%- @listen_ip.each do |ip| -%> #遍历listen_ip列表。ip代表列表中的当前项,是内部变量,所以他没有@<br /> listen <%= ip %>:<%= @listen_port %><% if @listen_options %> <%= @listen_options %><% end %>; #将内部变量ip, 外部变量listen_port 和listen_options组成一行,写入文件。<br /> <%- end -%><br /> <%- else -%> #如果listen_ip只有一个值,不是列表,就不再遍历<br /> listen <%= @listen_ip %>:<%= @listen_port %><% if @listen_options %> <%= @listen_options %><% end %>; ##将外部变量listen_ip,listen_port 和listen_options组成一行,写入文件。<br /> <%- end -%><br /><%- end -%><br /></span></pre> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif">上面是模板中的一段代码,由ERB(embedded ruby )写成,基本覆盖了比较典型的逻辑和语法。详情请参照在线文档</span></p> <p></p> <p><span style="font-size: 14px;font-family: arial, helvetica, sans-serif"></p> <p></span></p> <p></p> <p></p> 最后修改:2021 年 12 月 10 日 10 : 53 AM © 允许规范转载 赞赏 如果觉得我的文章对你有用,请随意赞赏 赞赏作者 支付宝微信