Puppet - 编码风格
在 Puppet 中,编码风格定义了在尝试将机器配置上的基础设施转换为代码时需要遵循的所有标准。Puppet 使用资源工作并执行其所有定义的任务。
Puppet 的语言定义有助于以结构化的方式指定所有资源,这对于管理任何需要管理的目标机器都是必需的。Puppet 使用 Ruby 作为其编码语言,它具有多种内置功能,只需在代码端进行简单配置即可轻松完成工作。
基本单元
Puppet 使用多种基本编码风格,易于理解和管理。以下是一些列表。
资源
在 Puppet 中,资源被称为基本建模单元,用于管理或修改任何目标系统。资源涵盖系统的所有方面,例如文件、服务和包。 Puppet 具有内置功能,允许用户或开发人员开发自定义资源,这有助于管理机器的任何特定单元
在 Puppet 中,所有资源都通过使用"define"或"classes"聚合在一起。这些聚合功能有助于组织模块。以下是一个示例资源,它由多种类型、一个标题和一个属性列表组成,Puppet 可以使用这些属性支持多个属性。Puppet 中的每个资源都有自己的默认值,可以在需要时覆盖。
文件的示例 Puppet 资源
在以下命令中,我们尝试为特定文件指定权限。
file { '/etc/passwd': owner => superuser, group => superuser, mode => 644, }
无论何时在任何机器上执行上述命令,它都会验证系统中的 passwd 文件是否按描述进行配置。冒号之前的文件是资源的标题,可以在 Puppet 配置的其他部分中将其称为资源。
除了标题之外还指定本地名称
file { 'sshdconfig': name => $operaSystem ? { solaris => '/usr/local/etc/ssh/sshd_config', default => '/etc/ssh/sshd_config', }, owner => superuser, group => superuser, mode => 644, }
通过使用始终相同的标题,可以很容易地在配置中引用文件资源,而无需重复与操作系统相关的逻辑。
另一个示例可能是使用依赖于文件的服务。
service { 'sshd': subscribe => File[sshdconfig], }
有了这种依赖关系,sshd 服务将始终在 sshdconfig 文件发生更改后重新启动。这里要记住的一点是 File[sshdconfig] 是小写 File 的声明,但如果我们将其更改为 FILE[sshdconfig],那么它就会成为引用。
声明资源时需要牢记的一个基本点是,每个配置文件只能声明一次。多次重复声明同一资源将导致错误。通过这个基本概念,Puppet 确保配置得到良好建模。
我们甚至有能力管理资源依赖关系,这有助于管理多个关系。
service { 'sshd': require => File['sshdconfig', 'sshconfig', 'authorized_keys'] }
元参数
元参数在 Puppet 中称为全局参数。元参数的主要功能之一是,它适用于 Puppet 中的任何类型的资源。
资源默认值
当需要定义默认资源属性值时,Puppet 提供了一组语法来存档它,使用没有标题的大写资源规范。
例如,如果我们想设置所有可执行文件的默认路径,可以使用以下命令完成。
Exec { path => '/usr/bin:/bin:/usr/sbin:/sbin' } exec { 'echo Testing mataparamaters.': }
在上面的命令中,第一个语句 Exec 将设置 exec 资源的默认值。可执行资源需要完全限定路径或看起来像可执行文件的路径。通过这种方式,可以为整个配置定义单个默认路径。默认值适用于 Puppet 中的任何资源类型。
默认值不是全局值,但它们仅影响定义它们的范围或紧接着它的下一个变量。如果想要为完整配置定义 default,那么我们将在下一节中定义 default 和类。
资源集合
聚合是将事物收集在一起的方法。Puppet 支持非常强大的聚合概念。在 Puppet 中,聚合用于将资源(Puppet 的基本单元)分组在一起。 Puppet 中的聚合概念是通过使用两种强大的方法(称为类和定义)实现的。
类和定义
类负责对节点的基本方面进行建模。可以说节点是一个 Web 服务器,而这个特定节点就是其中之一。在 Puppet 中,编程类是单例的,每个节点可以评估一次。
另一方面,定义可以在单个节点上多次使用。它们的工作原理类似,因为人们使用该语言创建了自己的 Puppet 类型。它们被创建为可多次使用,每次输入不同。这意味着可以将变量值传递到定义中。
类和定义之间的区别
类和定义之间的唯一关键区别是,在定义构建结构和分配资源时,类在每个节点上只被评估一次,而另一方面,定义在同一个节点上被多次使用。
类
Puppet 中的类是使用 class 关键字引入的,并且该特定类的内容被包裹在花括号内,如下例所示。
class unix { file { '/etc/passwd': owner => 'superuser', group => 'superuser', mode => 644; '/etc/shadow': owner => 'vipin', group => 'vipin', mode => 440; } }
在下面的例子中,我们使用了一些与上面类似的简写。
class unix { file { '/etc/passwd': owner => 'superuser', group => 'superuser', mode => 644; } file {'/etc/shadow': owner => 'vipin', group => 'vipin', mode => 440; } }
Puppet 类中的继承
在 Puppet 中,默认支持 OOP 继承概念,其中类可以扩展以前的功能,而无需在新创建的类中再次复制和粘贴完整的代码位。继承允许子类覆盖父类中定义的资源设置。使用继承时要记住的一件关键事情是,一个类只能从一个父类继承功能,不能超过一个。
class superclass inherits testsubclass { File['/etc/passwd'] { group => wheel } File['/etc/shadow'] { group => wheel } }
如果需要撤消父类中指定的某些逻辑,我们可以使用undef 命令。
class superclass inherits testsubcalss { File['/etc/passwd'] { group => undef } }
使用继承的替代方法
class tomcat { service { 'tomcat': require => Package['httpd'] } } class open-ssl inherits tomcat { Service[tomcat] { require +> File['tomcat.pem'] } }
Puppet 中的嵌套类
Puppet 支持类嵌套的概念,允许使用嵌套类,即一个类位于另一个类中。这有助于实现模块化和范围界定。
class testclass { class nested { file { '/etc/passwd': owner => 'superuser', group => 'superuser', mode => 644; } } } class anotherclass { include myclass::nested }
参数化类
在 Puppet 中,类可以扩展其功能以允许将参数传递到类中。
要在类中传递参数,可以使用以下构造 −
class tomcat($version) { ... class contents ... }
在 Puppet 中要记住的一个关键点是,带有参数的类不是使用 include 函数添加的,而是可以将生成的类作为定义添加。
node webserver { class { tomcat: version => "1.2.12" } }
类中的默认值作为参数
class tomcat($version = "1.2.12",$home = "/var/www") { ... class contents ... }
运行阶段
Puppet 支持运行阶段的概念,这意味着用户可以根据需要添加多个阶段,以便管理任何特定资源或多个资源。当用户想要开发复杂的目录时,此功能非常有用。在复杂的目录中,有大量的资源需要编译,同时要记住定义的资源之间的依赖关系不应受到影响。
运行阶段在管理资源依赖关系方面非常有用。这可以通过在定义的阶段中添加类来实现,其中特定类包含资源集合。通过运行阶段,Puppet 保证每次目录运行并应用于任何 Puppet 节点时,定义的阶段都将以指定的可预测顺序运行。
为了使用此功能,需要在已经存在的阶段之外声明其他阶段,然后可以使用相同的资源关系语法在需要 "->" 和 "+>" 之前将 Puppet 配置为按指定顺序管理每个阶段。然后,该关系将保证与每个阶段相关联的类的顺序。
使用 Puppet 声明性语法声明其他阶段
stage { "first": before => Stage[main] } stage { "last": require => Stage[main] }
一旦声明了阶段,类就可以使用该阶段与主阶段以外的阶段相关联。
class { "apt-keys": stage => first; "sendmail": stage => main; "apache": stage => last; }
与 apt-key 类相关的所有资源将首先运行。Sendmail 中的所有资源将成为主类,与 Apache 相关的资源将成为最后阶段。
定义
在 Puppet 中,任何清单文件中的资源收集都是通过类或定义完成的。定义与 Puppet 中的类非常相似,但它们是用 define 关键字(而不是类) 引入的,并且它们支持参数而不是继承。它们可以使用不同的参数在同一系统上多次运行。
例如,如果想要创建一个控制源代码存储库的定义,而又想在同一系统上创建多个存储库,那么可以使用定义而不是类。
define perforce_repo($path) { exec { "/usr/bin/svnadmin create $path/$title": unless => "/bin/test -d $path", } } svn_repo { puppet_repo: path => '/var/svn_puppet' } svn_repo { other_repo: path => '/var/svn_other' }
这里要注意的关键点是如何将变量与定义一起使用。我们使用 ($) 美元符号变量。在上面,我们使用了 $title。定义可以同时具有 $title 和 $name,可以使用它们来表示名称和标题。默认情况下,$title 和 $name 设置为相同的值,但可以设置 title 属性并传递不同的名称作为参数。$title 和 $name 仅在定义中起作用,而不是在类或其他资源中。
模块
模块可以定义为所有配置的集合,Puppet 主机将使用这些配置在任何特定 Puppet 节点(代理)上应用配置更改。它们也称为不同类型配置的可移植集合,这些配置是执行特定任务所必需的。例如,模块可能包含配置 Postfix 和 Apache 所需的所有资源。
节点
节点是剩下的非常简单的步骤,即我们如何将我们定义的内容("这就是 Web 服务器的样子")与选择哪些机器来执行这些指令相匹配。
节点定义与类完全一样,包括支持继承,但它们很特殊,以至于当节点(运行 Puppet 客户端的托管计算机)连接到 Puppet 主守护进程时,其名称将在定义的节点列表中查找。将评估节点定义的信息,然后节点将发送该配置。
节点名称可以是短主机名或完全限定域名 (FQDN)。
node 'www.vipin.com' { include common include apache, squid }
上述定义创建了一个名为 www.vipin.com 的节点,并包括通用的 Apache 和 Squid 类
我们可以将相同的配置发送到不同的节点,每个节点之间用逗号分隔。
node 'www.testing.com', 'www.testing2.com', 'www3.testing.com' { include testing include tomcat, squid }
匹配节点的正则表达式
node /^www\d+$/ { include testing }
节点继承
节点支持有限的继承模型。与类一样,节点只能从一个其他节点继承。
node 'www.testing2.com' inherits 'www.testing.com' { include loadbalancer }
在上面的代码中,www.testing2.com 除了一个额外的负载均衡器类之外,还继承了 www.testing.com 的所有功能。
高级支持功能
引用 − 在大多数情况下,我们不需要在 Puppet 中引用字符串。任何以字母开头的字母数字字符串都应保留不加引号。但是,对于任何非负值,引用字符串始终是最佳做法。
带引号的变量插值
到目前为止,我们在定义方面提到了变量。如果需要将这些变量与字符串一起使用,请使用双引号,而不是单引号。单引号字符串不会进行任何变量插值,双引号字符串可以。变量可以用 { 括起来,这样更容易一起使用,也更容易理解。
$value = "${one}${two}"
作为最佳实践,应该对所有不需要字符串插值的字符串使用单引号。
大写
大写是用于引用、继承和设置特定资源的默认属性的过程。基本上有两种基本使用方法。
引用 − 这是引用已创建资源的方式。它主要用于依赖性目的,必须将资源名称大写。例如,require => file [sshdconfig]
继承 −从子类覆盖父类的设置时,请使用资源名称的大写版本。使用小写版本将导致错误。
设置默认属性值 − 使用没有标题的大写资源可以设置资源的默认值。
数组
Puppet 允许在多个区域使用数组 [一、二、三]。
主机定义中的多个类型成员(例如别名)在其值中接受数组。具有多个别名的主机资源将如下所示。
host { 'one.vipin.com': alias => [ 'satu', 'dua', 'tiga' ], ip => '192.168.100.1', ensure => present, }
以上代码将添加一个主机'one.brcletest.com'到主机列表中,该主机有三个别名'satu' 'dua' 'tiga'。如果要将多个资源添加到一个资源中,可以按照以下示例所示进行操作。
resource { 'baz': require => [ Package['rpm'], File['testfile'] ], }
变量
与大多数其他编程语言一样,Puppet 支持多个变量。Puppet 变量用 $ 表示。
$content = 'some content ' file { '/tmp/testing': content => $content }
如前所述,Puppet 是一种声明性语言,这意味着它的范围和赋值规则与命令式语言不同。主要区别在于,不能在单个范围内更改变量,因为它们依赖于文件中的顺序来确定变量的值。顺序在声明性语言中并不重要。
$user = root file { '/etc/passwd': owner => $user, } $user = bin file { '/bin': owner => $user, recurse => true, }
变量范围
变量范围定义所有定义的变量是否有效。与最新功能一样,Puppet 目前是动态范围的,在 Puppet 术语中,这意味着所有定义的变量都会根据其范围而不是定义的位置进行评估。
$test = 'top' class Testclass { exec { "/bin/echo $test": logoutput => true } } class Secondtestclass { $test = 'other' include myclass } include Secondtestclass
限定变量
Puppet 支持在类或定义中使用限定变量。当用户希望在已定义或将要定义的其他类中使用相同变量时,这非常有用。
class testclass { $test = 'content' } class secondtestclass { $other = $myclass::test }
在上面的代码中,$other 变量的值评估内容。
条件
条件是当定义的条件或必需的条件满足时,用户希望执行一组语句或代码的情况。Puppet 支持两种类型的条件。
选择器条件只能在定义的资源中使用,以选择机器的正确值。
语句条件是清单中更广泛使用的条件,有助于包含用户希望包含在同一清单文件中的其他类。在类中定义一组不同的资源,或做出其他结构决策。
选择器
当用户希望根据 Facts 或其他变量指定与默认值不同的资源属性和变量时,选择器很有用。在 Puppet 中,选择器索引的工作方式类似于多值三向运算符。选择器还能够在无值中定义自定义默认值,这些值在清单中定义并符合条件。
$owner = $Sysoperenv ? { sunos => 'adm', redhat => 'bin', default => undef, }
在 Puppet 0.25.0 的后续版本中,选择器可以用作正则表达式。
$owner = $Sysoperenv ? { /(Linux|Ubuntu)/ => 'bin', default => undef, }
在上面的例子中,选择器 $Sysoperenv 值匹配 Linux 或 Ubuntu,则 bin 将是选定的结果,否则用户将被设置为未定义。
语句条件
语句条件是 Puppet 中的另一种条件语句,与 Shell 脚本中的 switch case 条件非常相似。在此,定义了多组 case 语句,并将给定的输入值与每个条件进行匹配。
与给定输入条件匹配的 case 语句将被执行。此 case 语句条件没有任何返回值。在 Puppet 中,条件语句的一个非常常见的用例是根据底层操作系统运行一组代码位。
case $ Sysoperenv { sunos: { include solaris } redhat: { include redhat } default: { include generic} }
Case 语句还可以指定多个条件,用逗号分隔。
case $Sysoperenv { development,testing: { include development } testing,production: { include production } default: { include generic } }
If-Else 语句
Puppet 支持基于条件的运算概念。为了实现它,If/else 语句根据条件的返回值提供分支选项。如以下示例所示 −
if $Filename { file { '/some/file': ensure => present } } else { file { '/some/other/file': ensure => present } }
Puppet 的最新版本支持变量表达式,其中 if 语句也可以根据表达式的值进行分支。
if $machine == 'production' { include ssl } else { include nginx }
为了实现代码的多样性,以及执行复杂的条件操作,Puppet 支持嵌套的 if/else 语句,如下面的代码所示。
if $ machine == 'production' { include ssl } elsif $ machine == 'testing' { include nginx } else { include openssl }
虚拟资源
虚拟资源是那些除非实现否则不会发送到客户端的资源。
以下是在 Puppet 中使用虚拟资源的语法。
@user { vipin: Ensure => present }
在上面的例子中,用户 vipin 被虚拟定义以实现可以在集合中使用的定义。
User <| title == vipin |>
注释
注释用于任何代码位,以创建关于一组代码行及其功能的附加节点。在 Puppet 中,目前有两种支持的注释类型。
- Unix shell 样式注释。它们可以位于自己的行或下一行。
- 多行 c 样式注释。
以下是 shell 样式注释的示例。
# 这是一个注释
以下是多行注释的示例。
/* 这是一个注释 */
运算符优先级
Puppet 运算符优先级符合大多数系统中的标准优先级,从最高到最低。
以下是表达式列表
- ! = 不等于
- / = 乘法和除法
- - + = 减法、加法
- << >> = 左移和右移
- == != = 不等于、等于
- >= <= > < = 大于、等于、小于或等于、大于、小于
比较表达式
当用户想要在满足给定条件时执行一组语句时,使用比较表达式。比较表达式包括使用 == 表达式进行相等性测试。
if $environment == 'development' { include openssl } else { include ssl }
不等于示例
if $environment != 'development' { $otherenvironment = 'testing' } else { $otherenvironment = 'production' }
算术表达式
$one = 1 $one_thirty = 1.30 $two = 2.034e-2 $result = ((( $two + 2) / $one_thirty) + 4 * 5.45) - (6 << ($two + 4)) + (0×800 + -9)
布尔表达式
布尔表达式可以使用or、and、& not来实现。
$one = 1 $two = 2 $var = ( $one < $two ) and ( $one + 1 == $two )
正则表达式
Puppet 支持使用 =~ (匹配) 和 !~ (不匹配) 的正则表达式匹配。
if $website =~ /^www(\d+)\./ { notice('Welcome web server #$1') }
类似大小写和选择器正则表达式匹配为每个正则表达式创建有限范围的变量。
exec { "Test": command => "/bin/echo now we don’t have openssl installed on machine > /tmp/test.txt", unless => "/bin/which php" }
同样,我们可以使用unless,unless一直执行命令,除非unless下的命令成功退出。
exec { "Test": command => "/bin/echo now we don’t have openssl installed on machine > /tmp/test.txt", unless => "/bin/which php" }
使用模板
当希望拥有一个预定义的结构时,可以使用模板,该结构将在 Puppet 中的多个模块中使用,并且这些模块将分布在多台机器上。使用模板的第一步是创建一个使用模板方法呈现模板内容的模板。
file { "/etc/tomcat/sites-available/default.conf": ensure => "present", content => template("tomcat/vhost.erb") }
为了加强组织和模块化,Puppet 在处理本地文件时会做出一些假设。Puppet 在模块目录内的 apache/templates 文件夹中查找 vhost.erb 模板。
定义和触发服务
在 Puppet 中,它有一个名为 service 的资源,它能够管理在任何特定机器或环境中运行的所有服务的生命周期。服务资源用于确保服务已初始化和启用。它们还用于服务重启。
例如,在我们之前的 tomcat 模板中,我们设置了 apache 虚拟主机。如果想要确保在虚拟主机更改后重新启动 apache,我们需要使用以下命令为 apache 服务创建服务资源。
service { 'tomcat': ensure => running, enable => true }
定义资源时,我们需要包含通知选项以触发重启。
file { "/etc/tomcat/sites-available/default.conf": ensure => "present", content => template("vhost.erb"), notify => Service['tomcat'] }