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[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 资源的默认值。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] } 

声明阶段后,可以使用 stage 将类与主阶段以外的阶段关联。

class { 
   "apt-keys": stage => first; 
   "sendmail": stage => main; 
   "apache": stage => last; 
}

与类 apt-key 关联的所有资源将首先运行。Sendmail 中的所有资源将是主类,而与 Apache 关联的资源将是最后一个阶段。

定义

在 Puppet 中,任何清单文件中的资源集合是通过类或定义完成的。定义与 Puppet 中的类非常相似,但是它们是用define 关键字(而不是 class)引入的,并且它们支持参数而不是继承。它们可以在同一系统上多次运行,并使用不同的参数。

例如,如果要创建一个控制源代码存储库的定义,您正在尝试在同一系统上创建多个存储库,则可以使用定义而不是类。

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 和 $name 仅在定义中有效,在类或其他资源中无效。

模块

模块可以定义为所有配置的集合,Puppet master 将使用这些配置在任何特定 Puppet 节点(代理)上应用配置更改。它们也被称为不同类型配置的可移植集合,这些配置是执行特定任务所必需的。例如,一个模块可能包含配置 Postfix 和 Apache 所需的所有资源。

节点

节点是非常简单的剩余步骤,即我们如何将我们定义的内容(“这是 Web 服务器的样子”)与选择满足这些指令的机器相匹配。

节点定义与类非常相似,包括支持继承,但它们也有一些特殊之处:当一个节点(运行 Puppet 客户端的受管理计算机)连接到 Puppet master 守护进程时,其名称将在定义的节点列表中查找。系统会评估节点的定义信息,然后节点会发送相应的配置。

节点名称可以是简短的主机名或完全限定域名 (FQDN)。

node 'www.vipin.com' { 
   include common 
   include apache, squid 
}

上述定义创建了一个名为 www.vipin.com 的节点,并包含了 common、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 的所有功能,并额外添加了一个 loadbalancer 类。

高级支持功能

引用 - 在大多数情况下,我们不需要在 Puppet 中引用字符串。任何以字母开头的字母数字字符串都无需加引号。但是,对于任何非负值,始终最佳实践是为字符串加引号。

带引号的变量插值

到目前为止,我们已经从定义的角度提到了变量。如果需要在字符串中使用这些变量,请使用双引号,而不是单引号。单引号字符串不会进行任何变量插值,而双引号字符串会进行。变量可以用{}括起来,这使得它们更容易一起使用,也更容易理解。

$value = "${one}${two}" 

最佳实践是,对于所有不需要字符串插值的字符串,都应该使用单引号。

大写

大写是一个用于引用、继承和设置特定资源的默认属性的过程。基本上有两种使用它的基本方法。

  • 引用 - 它是引用已创建资源的方式。它主要用于依赖目的,必须将资源的名称大写。例如,require => file [sshdconfig]

  • 继承 - 从子类覆盖父类的设置时,请使用资源名称的大写版本。使用小写版本会导致错误。

  • 设置默认属性值 - 使用没有标题的大写资源可以设置资源的默认值。

数组

Puppet 允许在多个区域使用数组 [One, two, three]。

一些类型成员,例如主机定义中的别名,在其值中接受数组。具有多个别名的主机资源如下所示。

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\n' 
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 支持两种类型的条件语句。

选择器条件只能在定义的资源内使用,以选择机器的正确值。

语句条件在清单中使用更为广泛,有助于包含用户希望在同一清单文件中包含的其他类。在类中定义一组不同的资源,或做出其他结构性决策。

选择器

当用户希望根据事实或其他变量指定与默认值不同的资源属性和变量时,选择器非常有用。在 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 风格注释的示例。

# this is a comment

以下是一个多行注释的示例。

/* 
This is a comment 
*/ 

运算符优先级

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') 
}

与 case 和选择器类似,正则表达式匹配为每个正则表达式创建有限作用域的变量。

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 在 modules 目录内的 apache/templates 文件夹内查找 vhost.erb 模板。

定义和触发服务

在 Puppet 中,它有一个名为 service 的资源,该资源能够管理在任何特定机器或环境上运行的所有服务的生命周期。服务资源用于确保服务已初始化并启用。它们还用于服务重启。

例如,在我们之前设置 apache 虚拟主机的 tomcat 模板中。如果希望确保在虚拟主机更改后重新启动 apache,则需要使用以下命令为 apache 服务创建服务资源。

service { 'tomcat': 
   ensure => running, 
   enable => true 
}

在定义资源时,需要包含 notify 选项以触发重启。

file { "/etc/tomcat/sites-available/default.conf": 
   ensure => "present", 
   content => template("vhost.erb"), 
   notify => Service['tomcat']  
}
广告