木偶 - 快速指南


木偶 - 概述

Puppet 是 Puppet Labs 开发的配置管理工具,用于自动化基础设施管理和配置。Puppet 是一个非常强大的工具,有助于实现基础设施即代码的概念。该工具是用 Ruby DSL 语言编写的,有助于将完整的基础设施转换为代码格式,可以轻松管理和配置。

Puppet 遵循客户端-服务器模型,其中任何集群中的一台机器充当服务器,称为 puppet master,另一台充当客户端,称为节点上的从机。Puppet 能够从头开始管理任何系统,从初始配置开始直到任何特定机器的使用寿命结束。

傀儡系统的特点

以下是 Puppet 最重要的功能。

幂等性

Puppet 支持幂等性,这使其独一无二。与 Chef 类似,在 Puppet 中,人们可以在同一台机器上安全地多次运行同一组配置。在此流程中,Puppet 会检查目标计算机的当前状态,并且仅在配置发生任何特定更改时才会进行更改。

幂等性有助于管理任何特定机器的整个生命周期,从机器的创建、机器的配置更改,直到生命周期结束。Puppet 幂等性功能对于保持机器多年更新非常有帮助,而不是在发生任何配置更改时多次重建同一台机器。

跨平台

在Puppet中,借助使用Puppet资源的资源抽象层(RAL),我们可以针对系统的指定配置,而无需担心底层配置中定义的实现细节以及配置命令如何在系统内部工作文件。

木偶 - 工作流程

Puppet 使用以下工作流程在系统上应用配置。

工作流程
  • 在 Puppet 中,Puppet master 所做的第一件事就是收集目标机器的详细信息。使用所有 Puppet 节点上存在的因素(类似于 Chef 中的 Ohai),它可以获取所有机器级别的配置详细信息。这些详细信息被收集并发回给傀儡师。

  • 然后,Puppet Master 将检索到的配置与定义的配置详细信息进行比较,并根据定义的配置创建目录并将其发送到目标 Puppet 代理。

  • 然后,Puppet 代理应用这些配置以使系统进入所需状态。

  • 最后,一旦目标节点处于所需状态,它就会向 Puppet Master 发回一份报告,这有助于 Puppet Master 了解系统当前的状态(如目录中所定义)。

Puppet - 关键组件

以下是 Puppet 的关键组件。

关键部件

傀儡资源

Puppet 资源是对任何特定机器进行建模的关键组件。这些资源都有自己的实现模型。Puppet 使用相同的模型来获取处于所需状态的任何特定资源。

供应商

提供者基本上是 Puppet 中使用的任何特定资源的实现者。例如,包类型“apt-get”和“yum”都对包管理有效。有时,特定平台上会提供多个提供商。尽管每个平台总是有一个默认的提供商。

显现

清单是资源的集合,它们耦合在函数或类内部以配置任何目标系统。它们包含一组用于配置系统的 Ruby 代码。

模块

模块是Puppet的关键构建块,它可以被定义为资源、文件、模板等的集合。它们可以很容易地分布在所定义的不同类型的操作系统中,因为它们具有相同的风格。由于它们可以轻松分发,一个模块可以在相同的配置下多次使用。

模板

模板使用 Ruby 表达式来定义自定义内容和变量输入。它们用于开发自定义内容。模板在清单中定义并复制到系统上的某个位置。例如,如果想使用可定制的端口定义 httpd,则可以使用以下表达式来完成。

Listen <% = @httpd_port %>

本例中的 httpd_port 变量是在引用此模板的清单中定义的。

静态文件

静态文件可以定义为有时需要执行特定任务的通用文件。使用 Puppet 可以简单地将它们从一个位置复制到另一个位置。所有静态文件都位于任何模块的文件目录内。对清单中文件的任何操作都是使用文件资源完成的。

木偶 - 建筑

以下是 Puppet 架构的图示。

建筑学

木偶大师

Puppet Master 是处理所有配置相关内容的关键机制。它使用 Puppet 代理将配置应用于节点。

傀儡特工

Puppet Agent 是由 Puppet master 管理的实际工作机器。它们内部运行着 Puppet 代理守护进程服务。

配置存储库

这是在需要时保存和拉取所有节点和服务器相关配置的存储库。

事实

事实是与节点或主机相关的详细信息,基本上用于分析任何节点的当前状态。根据事实,在任何目标机器上进行更改。Puppet 中有预定义的和自定义的事实。

目录

所有用 Puppet 编写的清单文件或配置首先会转换为称为目录的编译格式,然后将这些目录应用到目标计算机上。

傀儡 - 安装

Puppet 工作在客户端服务器架构上,其中我们将服务器称为 Puppet Master,将客户端称为 Puppet 节点。此设置是通过在客户端和所有服务器计算机上安装 Puppet 来实现的。

对于大多数平台,Puppet 可以通过选择的包管理器安装。但是,对于少数平台,可以通过安装tarballRubyGems来完成。

先决条件

Factor 是Chef 中不包含Ohai 的唯一先决条件。

标准操作系统库

我们需要有任何底层操作系统的标准库集。其余所有系统都附带 Ruby 1.8.2 + 版本。以下是操作系统应包含的库项目列表。

  • 64位基数
  • 计算机图形学
  • 摘要/md5
  • ETC
  • 文件工具
  • ip地址
  • 开放式SSL
  • 字符串扫描
  • 系统日志
  • 乌里
  • 韦伯里克
  • webrick/https
  • xmlrpc

因素安装

正如所讨论的,这个事实并没有随 Ruby 标准版本一起出现。因此,为了在目标系统中获取facter,需要从源手动安装它,因为facter库是Puppet的先决条件。

该软件包可用于多个平台,但为了安全起见,可以使用tarball安装它,这有助于获取最新版本。

首先,使用wget实用程序从 Puppet 的官方网站下载tarball

$ wget http://puppetlabs.com/downloads/facter/facter-latest.tgz  ------: 1 

接下来,解压缩 tar 文件。使用 CD 命令进入解压目录。最后,使用facter目录中的install.rb文件安装facter 。

$ gzip -d -c facter-latest.tgz | tar xf - -----: 2 
$ cd facter-* ------: 3 
$ sudo ruby install.rb # or become root and run install.rb -----:4 

从源安装 Puppet

首先,使用wget从 Puppet 站点安装 Puppet tarball 。然后,将 tarball 解压到目标位置。使用CD命令移动到创建的目录中。使用install.rb文件,在底层服务器上安装 Puppet。

# get the latest tarball 
$ wget http://puppetlabs.com/downloads/puppet/puppet-latest.tgz -----: 1

# untar and install it 
$ gzip -d -c puppet-latest.tgz | tar xf - ----: 2 
$ cd puppet-* ------: 3 
$ sudo ruby install.rb # or become root and run install.rb -------: 4 

使用 Ruby Gem 安装 Puppet 和 Facter

# Installing Facter 
$ wget http://puppetlabs.com/downloads/gems/facter-1.5.7.gem 
$ sudo gem install facter-1.5.7.gem

# Installing Puppet 
$ wget http://puppetlabs.com/downloads/gems/puppet-0.25.1.gem 
$ sudo gem install puppet-0.25.1.gem 

木偶 - 配置

一旦我们在系统上安装了 Puppet,下一步就是将其配置为执行某些初始操作。

打开计算机上的防火墙端口

为了让Puppet服务器集中管理客户端的服务器,需要在所有机器上打开一个指定的端口,即如果我们尝试配置的任何机器都没有使用8140端口,则可以使用8140。我们需要在所有机器上启用 TCP 和 UDP 通信。

配置文件

Puppet 的主要配置文件是etc/puppet/puppet.conf。所有配置文件都是在 Puppet 基于包的配置中创建的。配置 Puppet 所需的大部分配置都保存在这些文件中,一旦 Puppet 运行,它就会自动获取这些配置。但是,对于某些特定任务(例如配置 Web 服务器或外部证书颁发机构 (CA)),Puppet 具有单独的文件和设置配置。

服务器配置文件位于conf.d目录中,也称为Puppet master。这些文件默认位于/etc/puppetlabs/puppetserver/conf.d路径下。这些配置文件采用 HOCON 格式,保留了 JSON 的基本结构,但更具可读性。当 Puppet 启动时,它会从 conf.d 目录中获取所有 .cong 文件,并使用它们进行任何配置更改。这些文件中的任何更改仅在服务器重新启动时才会发生。

列表文件和设置文件

  • 全局配置文件
  • 网络服务器配置文件
  • 网络路由.conf
  • puppetserver.conf
  • 认证配置文件
  • master.conf(已弃用)
  • ca.conf(已弃用)

Puppet 中有不同的配置文件,它们特定于 Puppet 中的每个组件。

Puppet.conf

Puppet.conf 文件是 Puppet 的主要配置文件。Puppet 使用相同的配置文件来配置所有必需的 Puppet 命令和服务。所有与 Puppet 相关的设置,例如 Puppet master、Puppet agent、Puppet apply 和证书的定义都在此文件中定义。Puppet 可以根据需要引用它们。

配置文件类似于标准 ini 文件,其中设置可以进入主要部分的特定应用程序部分。

主要配置部分

[main] 
certname = Test1.vipin.com 
server = TestingSrv 
environment = production 
runinterval = 1h 

傀儡大师配置文件

[main] 
certname = puppetmaster.vipin.com 
server = MasterSrv 
environment = production 
runinterval = 1h 
strict_variables = true  
[master] 

dns_alt_names = MasterSrv,brcleprod01.vipin.com,puppet,puppet.test.com 
reports = puppetdb 
storeconfigs_backend = puppetdb 
storeconfigs = true 
environment_timeout = unlimited 

详细概览

在 Puppet 配置中,要使用的文件有多个配置部分,其中每个部分都有不同种类的多个设置。

配置部分

Puppet配置文件主要由以下配置部分组成。

  • Main - 这被称为全局部分,由 Puppet 中的所有命令和服务使用。其中一个定义了主部分中的默认值,该值可以被 puppet.conf 文件中存在的任何部分覆盖。

  • Master - 此部分由 Puppet master 服务和 Puppet cert 命令引用。

  • Agent - 此部分由 Puppet 代理服务引用。

  • 用户- 它主要由 Puppet apply 命令以及许多不太常见的命令使用。

[main] 
certname = PuppetTestmaster1.example.com 

配置文件的关键组成部分

以下是配置文件的关键组成部分。

评论行

在 Puppet 中,任何注释行都以 ( # ) 符号开头。这可能涉及任意数量的空间。我们也可以在同一行中进行部分评论。

# This is a comment. 
Testing = true #this is also a comment in same line 

设置行

设置行必须包含 -

  • 任意数量的前导空格(可选)
  • 设置名称
  • 等于 = 符号,可以被任意数量的空格包围
  • 设置值

设置变量

在大多数情况下,设置的值将是单个单词,但在某些特殊情况下,很少有特殊值。

路径

在配置文件设置中,获取目录列表。在定义这些目录时,应记住它们应由系统路径分隔符分隔,在 *nix 平台上为 (:),在 Windows 上为分号 (;)。

# *nix version: 
environmentpath = $codedir/special_environments:$codedir/environments 
# Windows version: 
environmentpath = $codedir/environments;C:\ProgramData\PuppetLabs\code\environment 

在定义中,首先扫描列出的文件目录,如果没有找到,则随后移动到列表中的其他目录。

文件和目录

所有采用单个文件或目录的设置都可以接受可选的权限哈希。当服务器启动时,Puppet 将强制执行列表中的那些文件或目录。

ssldir = $vardir/ssl {owner = service, mode = 0771} 

在上面的代码中,允许的哈希值是所有者、组和模式。所有者密钥和组密钥只有两个有效值。

Puppet - 环境会议

在 Puppet 中,所有环境都有environment.conf文件。每当主服务器为任何节点或分配给该特定环境的所有节点提供服务时,此文件都可以覆盖多个默认设置。

地点

在 Puppet 中,对于定义的所有环境,environment.conf 文件位于其主环境的顶层,紧邻清单和模块目录。考虑一个示例,如果您的环境位于默认目录(Vipin/testing/environment)中,则测试环境的配置文件位于Vipin/testing/environments/test/environment.conf

例子

# /etc/testingdir/code/environments/test/environment.conf  
# Puppet Enterprise 需要 $basemodulepath;请参阅下面的模块路径下的注释”。
模块路径 = 站点:距离:模块:$basemodulepath  
# 使用我们的自定义脚本获取代码当前状态的 git 提交:
config_version = get_environment_commit.sh

格式

Puppet 中的所有配置文件都以相同的方式使用相同的 INI 格式。environment.conf文件遵循与其他文件(如 puppet.conf 文件)相同的类似 INI 的格式。environment.conf 和puppet.conf之间的唯一区别是environment.conf 文件不能包含 [main] 部分。environment.conf 文件中的所有设置必须位于任何配置部分之外。

值中的相对路径

大多数允许的设置接受文件路径或路径列表作为值。如果任何路径是相关路径,则它们以没有前导斜杠或驱动器号的方式开始 - 它们将主要相对于该环境的主目录进行解析。

值插值

Environment.conf 设置文件能够使用其他设置的值作为变量。有多个有用的变量可以插入到environment.conf 文件中。这是一些重要变量的列表 -

  • $basemodulepath - 用于在模块路径设置中包含目录。Puppet 企业用户通常应包含modulepath的此值,因为 Puppet 引擎在basemodulepath中使用 module 。

  • $environment - 作为 config_version 脚本的命令行参数很有用。您只能在 config_version 设置中插入此变量。

  • $codedir - 用于定位文件。

允许的设置

默认情况下,Puppetenvironment.conf 文件仅允许覆盖所列配置中的四个设置。

  • 模块路径
  • 显现
  • 配置版本
  • 环境超时

模块路径

这是environment.conf 文件中的关键设置之一。modulepath 中定义的所有控制器默认由 Puppet 加载。这是 Puppet 加载其模块的路径位置。需要明确地设置这一点。如果未设置上述设置,Puppet 中任何环境的默认模块路径将为 -

<环境中的模块目录>:$basemodulepath 

显现

这用于定义主清单文件,Puppet master 在启动和编译定义清单中的目录时将使用该文件,该清单将用于配置环境。在此,我们可以定义单个文件、文件列表,甚至是由多个清单文件组成的目录,这些清单文件需要按定义的字母顺序进行评估和编译。

需要在environment.conf 文件中显式定义此设置。如果没有,Puppet 将使用环境默认清单目录作为其主清单。

配置版本

Config_version 可以定义为用于标识目录和事件的明确版本。当 Puppet 默认编译任何清单文件时,它会将配置版本添加到生成的目录以及 Puppet Master 在 Puppet 节点上应用任何定义的目录时生成的报告中。Puppet 运行一个脚本来执行上述所有步骤,并使用所有生成的输出作为 Config_version。

环境超时

它用于获取有关 Puppet 为给定环境加载数据应使用的时间量的详细信息。如果该值在 puppet.conf 文件中定义,则这些值将覆盖默认超时值。

示例environment.conf 文件

[掌握]
   清单 = $confdir/environments/$environment/manifests/site.pp
   模块路径 = $confdir/environments/$environment/modules

上面代码中$confdir是环境配置文件所在目录的路径。$environment是正在完成配置的环境的名称。

生产就绪环境配置文件

# 环境配置文件  
# Puppet 开始评估代码的主清单目录或文件  
# 这是默认值。仅适用于 site.pp 文件或任何其他文件  
清单=清单/  
# 添加到模块路径的目录,按第一个匹配的第一个使用的顺序查找:  
#modules - 外部模块的目录,由基于 Puppetfile 的 r10k 填充  
# $basemodulepath - 来自:puppet config print basemodulepath  
模块路径 = 站点:模块:$basemodulepath  
# 设置该环境的缓存超时。  
# 这会覆盖整个 Puppet 服务器的 puppet.conf 中直接设置的内容  
# 环境超时=无限制  
# 使用缓存,每当部署新的 Puppet 代码时,您都需要刷新缓存  
# 这也可以手动运行:bin/puppet_flush_environment_cache.sh  
# 禁用目录缓存:  
环境超时= 0  
# 这里我们将 Puppet 环境(和 git 分支)传递给控制仓库中的一个  
# 获取最后一次 git 提交的标题和基本信息
config_version = 'bin/config_script.sh $环境'

木偶大师

在 Puppet 中,Puppet master 的客户端服务器架构被视为整个设置的控制权限。Puppet Master 在设置中充当服务器并控制所有节点上的所有活动。

对于任何需要充当 Puppet master 的服务器,它都应该运行 Puppet 服务器软件。该服务器软件是控制节点上所有活动的关键组件。在此设置中,要记住的一个关键点是让超级用户访问将在设置中使用的所有计算机。以下是设置 Puppet Master 的步骤。

先决条件

专用网络 DNS - 应配置前向和后向,其中每个服务器应具有唯一的主机名。如果没有配置 DNS,则可以使用专用网络与基础设施进行通信。

防火墙开放端口- Puppet master 应在特定端口上打开,以便它可以侦听特定端口上的传入请求。我们可以使用防火墙上打开的任何端口。

创建 Puppet Master 服务器

我们正在创建的 Puppet Master 将位于 CentOS 7 × 64 机器上,使用 Puppet 作为主机名。创建 Puppet Master 的最低系统配置是两个 CPU 核心和 1GB 内存。配置也可能具有更大的大小,具体取决于我们要使用此主节点管理的节点数量。在基础设施中,比使用 2 GB RAM 配置的要大。

主机名 角色 私人 FQDN
Brcleprod001 木偶大师 bnrcleprod001.brcl.com

接下来,需要生成Puppet master SSL证书,并将master机器的名称复制到所有节点的配置文件中。

安装NTP

由于 Puppet Master 是任何给定设置中代理节点的中央权威,因此 Puppet Master 的主要职责之一是维护准确的系统时间,以避免潜在的配置问题(在向节点颁发代理证书时可能会出现这种问题)。

如果出现时间冲突问题,并且主节点和节点之间存在时间差异,则证书可能会显示已过期。网络时间协议是避免此类问题的关键机制之一。

列出可用时区

$ timedatectl list-timezones

上述命令将提供可用时区的完整列表。它将为各地区提供时区可用性。

以下命令可用于设置机器上所需的时区。

$ sudo timedatectl set-timezone India/Delhi 

使用 CentOS 计算机的 yum 实用程序在 Puppet 服务器计算机上安装 NTP。

$ sudo yum -y install ntp 

将 NTP 与我们在上述命令中设置的系统时间同步。

$ sudo ntpdate pool.ntp.org 

在通常的实践中,我们将更新 NTP 配置以使用距离计算机数据中心更近的可用公共池。为此,我们需要编辑/etc下的 ntp.conf 文件。

$ sudo vi /etc/ntp.conf 

从可用的 NTP 池时区添加时间服务器。以下是 ntp.conf 文件的样子。

brcleprod001.brcl.pool.ntp.org 
brcleprod002.brcl.pool.ntp.org 
brcleprod003.brcl.pool.ntp.org
brcleprod004.brcl.pool.ntp.org 

保存配置。启动服务器并启用守护进程。

$ sudo systemctl restart ntpd 
$ sudo systemctl enable ntpd 

设置 Puppet 服务器软件

Puppet服务器软件是运行在Puppet主机上的软件。它是将配置推送到运行 Puppet 代理软件的其他机器的机器。

使用以下命令启用官方 Puppet 实验室收集存储库。

$ sudo rpm -ivh https://yum.puppetlabs.com/puppetlabs-release-pc1-el7.noarch.rpm

安装 puppetserver 包。

$ sudo yum -y install puppetserver 

在 Puppet 服务器上配置内存分配

正如我们所讨论的,默认情况下,Puppet 服务器配置在 2GB RAM 机器上。人们可以根据计算机上的可用内存以及服务器将管理的节点数量来自定义设置。

在 vi 模式下编辑 puppet 服务器配置

$ sudo vi /etc/sysconfig/puppetserver  
Find the JAVA_ARGS and use the –Xms and –Xms options to set the memory allocation. 
We will allocate 3GB of space  
JAVA_ARGS="-Xms3g -Xmx3g" 

完成后,保存并退出编辑模式。

完成上述所有设置后,我们准备使用以下命令在主机上启动 Puppet 服务器。

$ sudo systemctl start puppetserver 

接下来,我们将进行设置,以便 puppet 服务器在主服务器启动时启动。

$ sudo systemctl enable puppetserver 

Puppet.conf 主控部分

[master] 
autosign = $confdir/autosign.conf { mode = 664 } 
reports = foreman 
external_nodes = /etc/puppet/node.rb 
node_terminus = exec 
ca = true 
ssldir = /var/lib/puppet/ssl 
certname = sat6.example.com 
strict_variables = false 
manifest = 
/etc/puppet/environments/$environment/manifests/site.pp 
modulepath = /etc/puppet/environments/$environment/modules 
config_version = 

Puppet - 代理设置

Puppet代理是由Puppet实验室提供的软件应用程序,它运行在Puppet集群中的任何节点上。如果想要使用 Puppet Master 管理任何服务器,则需要在该特定服务器上安装 Puppet 代理软件。一般来说,Puppet 代理将安装在任何给定基础设施上除 Puppet master 机器之外的所有机器上。Puppet 代理软件能够在大多数 Linux、UNIX 和 Windows 计算机上运行。在下面的例子中,我们使用CentOS机器在其上安装Puppet代理软件。

步骤 1 - 使用以下命令启用官方 Puppet 实验室收集存储库。

$ sudo rpm -ivh https://yum.puppetlabs.com/puppetlabs-release-pc1-el7.noarch.rpm

步骤 2 - 安装 Puppet 代理包。

$ sudo yum -y install puppet-agent

步骤 3 - 安装 Puppet 代理后,使用以下命令启用它。

$ sudo /opt/puppetlabs/bin/puppet resource service puppet ensure=running enable = true

Puppet 代理的一个关键功能是,当 Puppet 代理第一次开始运行时,它会生成 SSL 证书并将其发送到 Puppet master,后者将对其进行管理以进行签名和批准。一旦 Puppet Master 批准了 Agent 的证书签名请求,它将能够与 Agent 节点进行通信和管理。

注意- 需要在需要配置和管理任何给定 Puppet Master 的所有节点上重复上述步骤。

Puppet - SSL 签名证书设置

当 Puppet 代理软件首次在任何 Puppet 节点上运行时,它会生成一个证书并将证书签名请求发送到 Puppet Master。在 Puppet 服务器能够通信和控制代理节点之前,它必须签署该特定代理节点的证书。在以下部分中,我们将描述如何对签名请求进行签名和检查。

列出当前证书请求

在 Puppet Master 上,运行以下命令以查看所有未签名的证书请求。

$ sudo /opt/puppetlabs/bin/puppet cert list

由于我们刚刚设置了一个新的代理节点,因此我们将看到一个批准请求。以下将是输出

"Brcleprod004.brcl.com" (SHA259) 
15:90:C2:FB:ED:69:A4:F7:B1:87:0B:BF:F7:ll:
B5:1C:33:F7:76:67:F3:F6:45:AE:07:4B:F 6:E3:ss:04:11:8d 

它的开头不包含任何+(符号),这表明证书尚未签名。

签署请求

为了对新节点上 Puppet 代理运行时生成的新证书请求进行签名,将使用 Puppet cert Sign 命令,并使用证书的主机名,该证书是由需要的新配置节点生成的待签署。由于我们有 Brcleprod004.brcl.com 的证书,因此我们将使用以下命令。

$ sudo /opt/puppetlabs/bin/puppet cert sign Brcleprod004.brcl.com 

以下将是输出

Notice: Signed certificate request for Brcle004.brcl.com 
Notice: Removing file Puppet::SSL::CertificateRequest Brcle004.brcl.com at 
'/etc/puppetlabs/puppet/ssl/ca/requests/Brcle004.brcl.com.pem' 

Puppet 服务器现在可以与签名证书所属的节点进行通信。

$ sudo /opt/puppetlabs/bin/puppet cert sign --all 

从 Puppet 设置中撤销主机

当需要从设置中删除主机并再次添加时,内核重建的配置存在一些条件。这些是偶人本身无法应对的情况。可以使用以下命令来完成。

$ sudo /opt/puppetlabs/bin/puppet cert clean hostname 

查看所有签名的请求

以下命令将生成带有 +(符号)的签名证书列表,这表明请求已获得批准。

$ sudo /opt/puppetlabs/bin/puppet cert list --all

以下是其输出

+ "puppet" (SHA256) 5A:71:E6:06:D8:0F:44:4D:70:F0:
BE:51:72:15:97:68:D9:67:16:41:B0:38:9A:F2:B2:6C:B 
B:33:7E:0F:D4:53 (alt names: "DNS:puppet", "DNS:Brcle004.nyc3.example.com")  

+ "Brcle004.brcl.com" (SHA259) F5:DC:68:24:63:E6:F1:9E:C5:FE:F5:
1A:90:93:DF:19:F2:28:8B:D7:BD:D2:6A:83:07:BA:F E:24:11:24:54:6A 

+ " Brcle004.brcl.com" (SHA259) CB:CB:CA:48:E0:DF:06:6A:7D:75:E6:CB:22:BE:35:5A:9A:B3 

完成上述操作后,我们就准备好了基础设施,其中 Puppet Master 现在能够管理新添加的节点。

Puppet - 安装和配置 r10K

在 Puppet 中,我们有一个名为 r10k 的代码管理工具,它有助于管理与我们可以在 Puppet 中配置的不同类型环境相关的环境配置,例如开发、测试和生产。这有助于在源代码存储库中存储与环境相关的配置。使用源代码控制存储库分支,r10k 在 Puppet master 机器上创建环境,并使用存储库中存在的模块安装和更新环境。

Gem 文件可用于在任何机器上安装 r10k,但为了模块化并且为了获得最新版本,我们将使用 rpm 和 rpm 包管理器。以下是相同的示例。

$ urlgrabber -o /etc/yum.repos.d/timhughes-r10k-epel-6.repo
https://copr.fedoraproject.org/coprs/timhughes/yum -y install rubygem-r10k 

在 /etc/puppet/puppet.conf 中配置环境

[main] 
environmentpath = $confdir/environments 

为 r10k 配置创建配置文件

cat <<EOF >/etc/r10k.yaml 
# The location to use for storing cached Git repos 
:cachedir: '/var/cache/r10k' 
# A list of git repositories to create 
:sources: 
# This will clone the git repository and instantiate an environment per 
# branch in /etc/puppet/environments 
:opstree: 
#remote: 'https://github.com/fullstack-puppet/fullstackpuppet-environment.git' 
remote: '/var/lib/git/fullstackpuppet-environment.git' 
basedir: '/etc/puppet/environments' 
EOF

安装 Puppet 清单和模块

r10k deploy environment -pv 

由于我们需要每 15 分钟持续更新一次环境,因此我们将为此创建一个 cron 作业。

cat << EOF > /etc/cron.d/r10k.conf 
SHELL = /bin/bash 
PATH = /sbin:/bin:/usr/sbin:/usr/bin 
H/15 * * * * root r10k deploy environment -p 
EOF

测试安装

为了测试一切是否按预期工作,需要编译 Puppet 模块的 Puppet 清单。运行以下命令并获取 YAML 输出作为结果。

curl --cert /etc/puppet/ssl/certs/puppet.corp.guest.pem \ 
--key /etc/puppet/ssl/private_keys/puppet.corp.guest.pem \ 
--cacert /etc/puppet/ssl/ca/ca_crt.pem \ 
-H 'Accept: yaml' \ 
https://puppet.corp.guest:8140/production/catalog/puppet.corp.guest 

Puppet - 验证 Puppet 设置

在 Puppet 中,可以在本地测试设置。因此,一旦我们设置了 Puppet master 和节点,就可以在本地验证设置了。我们需要在本地安装 Vagrant 和 Vagrant box,这有助于在本地测试设置。

设置虚拟机

由于我们在本地测试设置,因此实际上不需要正在运行的 Puppet master。这意味着无需在服务器上实际运行 Puppet master,我们就可以简单地使用 Puppet 来应用命令来进行 Puppet 设置验证。Puppet apply 命令将根据配置文件中虚拟机的主机名应用local/etc/puppet中的更改。

为了测试设置,我们需要执行的第一步是构建以下Vagrantfile并启动机器并将/etc/puppet文件夹安装到位。所有需要的文件都将放置在具有以下结构的版本控制系统中。

目录结构

- manifests 
   \- site.pp 
- modules 
   \- your modules  
- test 
   \- update-puppet.sh 
   \- Vagrantfile 
- puppet.conf 

流浪文件

# -*- mode: ruby -*- 
# vi: set ft = ruby : 
Vagrant.configure("2") do |config| 
   config.vm.box = "precise32" 
   config.vm.box_url = "http://files.vagrantup.com/precise64.box" 
   config.vm.provider :virtualbox do |vb| 
      vb.customize ["modifyvm", :id, "--memory", 1028, "--cpus", 2] 
   end 
  
   # Mount our repo onto /etc/puppet 
   config.vm.synced_folder "../", "/etc/puppet"  
   
   # Run our Puppet shell script   
   config.vm.provision "shell" do |s| 
      s.path = "update-puppet.sh" 
   end  
 
   config.vm.hostname = "localdev.example.com" 
end 

在上面的代码中,我们使用了 Shell 配置程序,在其中尝试运行名为update-puppet.sh的 Shell 脚本。该脚本位于 Vagrant 文件所在的同一目录中,下面列出了脚本的内容。

!/bin/bash 
echo "Puppet version is $(puppet --version)" 
if [ $( puppet --version) != "3.4.1" ]; then  
   echo "Updating puppet" 
   apt-get install --yes lsb-release 
   DISTRIB_CODENAME = $(lsb_release --codename --short) 
   DEB = "puppetlabs-release-${DISTRIB_CODENAME}.deb" 
   DEB_PROVIDES="/etc/apt/sources.list.d/puppetlabs.list"  
   
   if [ ! -e $DEB_PROVIDES ] 
   then 
      wget -q http://apt.puppetlabs.com/$DEB 
      sudo dpkg -i $DEB 
   fi  
sudo apt-get update 
   sudo apt-get install -o Dpkg::Options:: = "--force-confold" 
   --force-yes -y puppet 
else 
   echo "Puppet is up to date!" 
fi 

进一步处理,用户需要在 Manifests 目录中创建一个名为site.pp 的清单文件,该文件将在虚拟机上安装一些软件。

node 'brclelocal03.brcl.com' { 
   package { ['vim','git'] : 
      ensure => latest 
   } 
} 
echo "Running puppet" 
sudo puppet apply /etc/puppet/manifests/site.pp 

一旦用户准备好上述脚本以及所需的 Vagrant 文件配置,用户就可以 cd 到测试目录并运行vagrant up 命令。这将启动一个新的虚拟机,稍后安装 Puppet,然后使用 Shell 脚本运行它。

以下将是输出。

Notice: Compiled catalog for localdev.example.com in environment production in 0.09 seconds 
Notice: /Stage[main]/Main/Node[brclelocal03.brcl.com]/Package[git]/ensure: created 
Notice: /Stage[main]/Main/Node[brcllocal03.brcl.com]/Package[vim]/ensure: ensure changed 'purged' to 'latest'

验证多机配置

如果我们需要在本地测试多台机器的配置,只需更改 Vagrant 配置文件即可完成。

新配置的 Vagrant 文件

config.vm.define "brclelocal003" do |brclelocal003| 
   brclelocal03.vm.hostname = "brclelocal003.brcl.com" 
end  

config.vm.define "production" do |production| 
   production.vm.hostname = "brcleprod004.brcl.com" 
end

假设我们有一个新的生产服务器,它需要安装 SSL 实用程序。我们只需使用以下配置扩展旧清单即可。

node 'brcleprod004.brcl.com' inherits 'brcleloacl003.brcl.com' { 
   package { ['SSL'] : 
      ensure => latest 
   } 
} 

在清单文件中进行配置更改后,我们只需移至测试目录并运行基本的 vagrant up 命令,该命令将启动brclelocal003.brcl.combrcleprod004.brcl.com机器。在我们的例子中,我们正在尝试启动生产机器,这可以通过运行vagrant up production 命令来完成。将创建一台名为 Production 的新机器,如 Vagrant 文件中定义的那样,并将在其中安装 SSL 软件包。

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], 
} 

有了这种依赖性,一旦sshdconfig文件发生更改, sshd服务将始终重新启动。这里要记住的一点是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 资源设置默认值。Exec 资源需要完全限定的路径或看起来像可执行文件的路径。这样,我们就可以为整个配置定义一个默认路径。默认值适用于 Puppet 中的任何资源类型。

默认值不是全局值,但是,它们仅影响定义它们的范围或其下一个变量。如果想要定义完整配置的默认值,那么我们将在下一节中定义默认值和类。

资源收藏

聚合是将事物收集在一起的方法。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 支持运行阶段的概念,这意味着用户可以根据需要添加多个阶段,以管理任何特定资源或多个资源。当用户想要开发复杂的目录时,此功能非常有用。在复杂的目录中,需要编译大量资源,同时记住定义的资源之间的依赖关系不应受到影响。

Run Stage 对于管理资源依赖关系非常有帮助。这可以通过在定义的阶段添加类来完成,其中特定的类包含资源集合。通过运行阶段,Puppet 可以保证每次目录运行并在任何 Puppet 节点上应用时,定义的阶段都将以指定的可预测顺序运行。

为了使用它,需要在已经存在的阶段之外声明额外的阶段,然后可以将 Puppet 配置为在 require “->” 和 “+>” 之前使用相同的资源关系语法以指定的顺序管理每个阶段。然后,该关系将保证与每个阶段关联的类的顺序。

使用 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 master 将使用这些配置在任何特定的 Puppet 节点(代理)上应用配置更改。它们也被称为执行特定任务所需的不同类型配置的便携式集合。例如,一个模块可能包含配置 Postfix 和 Apache 所需的所有资源。

节点

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

节点定义与类完全相同,包括支持继承,但它们很特殊,当节点(运行 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 支持有限的继承模型。与类一样,节点只能从另一个节点继承。

node 'www.testing2.com' inherits 'www.testing.com' { 
   include loadbalancer 
}

在上面的代码中,除了额外的负载均衡器类之外,www.testing2.com 还继承了 www.testing.com 的所有功能。

高级支持功能

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

带引号的变量插值

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

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

作为一种最佳实践,应该对所有不需要字符串插值的字符串使用单引号。

大写

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

  • 引用- 这是引用已创建资源的方式。它主要用于依赖目的,必须将资源名称大写。例如,require => 文件 [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 支持多个变量。傀儡变量用$表示。

$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 运算符优先级符合大多数系统中的标准优先级(从最高到最低)。

以下是表达式列表

  • != 不
  • / = 乘除
  • - + = 减号、加号
  • << >> = 左移和右移
  • == != = 不等于、等于
  • >= <= > < = 大于等于、小于等于、大于、小于

比较表达式

当用户想要在满足给定条件时执行一组语句时,使用比较表达式。比较表达式包括相等性测试