配置管理101:编写Puppet清单
###介绍
简而言之,服务器配置管理(也通常称为 IT 自动化)是一种将您的基础架构管理转变为代码库的解决方案,它在一组可版本化和易于重用的配置脚本中描述部署服务器所需的所有过程。 随着时间的推移,它可以极大地提高任何服务器基础架构的完整性。
在 previous guide 中,我们讨论了为服务器基础架构实施配置管理策略的主要好处、配置管理工具的工作原理以及这些工具通常具有的共同点。
本系列的这一部分将引导您完成使用 Puppet 自动配置服务器的过程,Puppet 是一种流行的配置管理工具,能够以透明的方式管理复杂的基础设施,使用主服务器来编排节点的配置。 我们将专注于创建简化示例所需的语言术语、语法和功能,以使用 Apache 完全自动化部署 Ubuntu 18.04 Web 服务器。
这是我们需要自动化以实现目标的步骤列表:
- 更新
apt
缓存 - 安装 Apache
- 创建自定义文档根目录
- 在自定义文档根目录下放置一个
index.html
文件 - 应用模板来设置我们的自定义虚拟主机
- 重启阿帕奇
我们将首先了解 Puppet 使用的术语,然后概述可用于编写清单的主要语言功能。 在本指南的最后,我们将分享完整的示例,以便您自己尝试。
注意: 本指南旨在向您介绍 Puppet 语言以及如何编写清单以自动化您的服务器配置。 有关 Puppet 的更多介绍性视图,包括安装和开始使用此工具所需的步骤,请参阅 Puppet 的官方文档。
##入门
在我们更深入地了解 Puppet 之前,我们必须熟悉此工具引入的重要术语和概念。
- Puppet 条款
- Puppet Master:控制节点配置的主服务器
- Puppet Agent Node:由Puppet Master控制的节点
- Manifest:包含一组要执行的指令的文件
- Resource:声明系统元素以及如何更改其状态的代码部分。 例如,要安装一个包,我们需要定义一个 package 资源并确保其状态设置为“已安装”
- Module:以预定义方式组织的清单和其他相关文件的集合,以促进共享和重用部分配置
- Class:就像使用常规编程语言一样,Puppet 中使用类来更好地组织配置并更容易重用部分代码
- Facts:包含系统信息的全局变量,如网络接口和操作系统
- Services:用于触发服务状态变化,如重启或停止服务
Puppet 配置是使用基于 Ruby 的自定义 DSL(领域特定语言)编写的。 ###Resources 使用 Puppet,任务或步骤通过声明 resources 来定义。 资源可以表示包、文件、服务、用户和命令。 它们可能有一个状态,如果声明的资源的状态与系统上的当前状态不同,它将触发系统更改。 例如,如果之前未安装软件包,清单中的 package 资源设置为 installed
将触发系统上的软件包安装。
这是 package 资源的样子:
package { 'nginx': ensure => 'installed' }
您可以通过声明 exec
资源来执行任意命令,如下所示:
exec { 'apt-get update': command => '/usr/bin/apt-get update' }
请注意,第一行的 apt-get update
部分不是实际的命令声明,而是此唯一资源的标识符。 通常我们需要从资源中引用其他资源,为此我们使用它们的标识符。 在这种情况下,标识符是 apt-get update
,但它可以是任何其他字符串。 ###Resource 依赖 在编写清单时,请务必记住,Puppet 不会按照定义的相同顺序评估资源。 对于刚开始使用 Puppet 的人来说,这是一个常见的困惑来源。 资源必须明确定义彼此之间的依赖关系,否则无法保证首先评估和执行哪个资源。
举个简单的例子,假设你想执行一个命令,但你需要确保首先安装了一个依赖项:
package { 'python-software-properties': ensure => 'installed' } exec { 'add-repository': command => '/usr/bin/add-apt-repository ppa:ondrej/php5 -y' require => Package['python-software-properties'] }
require
选项接收对另一个资源的引用作为参数。 在这种情况下,我们指的是标识为 python-software-properties
的 Package 资源。 需要注意的是,虽然我们使用 exec
、package
等来声明资源(使用小写字母),但在引用之前定义的资源时,我们使用 Exec
、[X182X ] 等(大写)。
现在假设您需要确保一个任务在 之前执行 另一个。 对于这样的情况,我们可以使用 before
选项来代替:
package { 'curl': ensure => 'installed' before => Exec['install script'] } exec { 'install script': command => '/usr/bin/curl http://example.com/some-script.sh'
- Manifest 格式清单基本上是资源声明的集合,使用扩展名
.pp
。 您可以在下面找到一个执行两个任务的简单 playbook 示例:更新apt
缓存并在之后安装vim
:
- Manifest 格式清单基本上是资源声明的集合,使用扩展名
exec { 'apt-get update': command => '/usr/bin/apt-get update' } package { 'vim': ensure => 'installed' require => Exec['apt-get update'] }
在本指南结束之前,我们将看到一个更真实的清单示例,详细解释。 下一节将概述可用于编写 Puppet 清单的最重要的元素和功能。 ##Writing Manifests ###Working with Variables 变量可以在清单中的任何位置定义。 最常见的变量类型是字符串和字符串数组,但也支持其他类型,例如布尔值和哈希值。
下面的示例定义了一个稍后在资源中使用的字符串变量:
$package = "vim" package { $package: ensure => "installed" }
- Using Loops 循环通常用于使用不同的输入值重复任务。 例如,不是为安装 10 个不同的包创建 10 个任务,您可以创建一个任务并使用循环对要安装的所有不同包重复该任务。
在 Puppet 中重复具有不同值的任务的最简单方法是使用数组,如下例所示:
$packages = ['vim', 'git', 'curl'] package { $packages: ensure => "installed" }
从版本 4 开始,Puppet 支持迭代任务的其他方式。 下面的示例与前面的示例执行相同的操作,但这次使用 each
迭代器。 此选项为您循环遍历资源定义提供了更大的灵活性:
$packages.each |String $package| { package { $package: ensure => "installed" } }
- Using Conditionals 条件可以用于动态决定是否应该执行代码块,例如,基于变量或命令的输出。
Puppet 支持使用传统编程语言可以找到的大多数条件结构,例如 if/else
和 case
语句。 此外,像 exec
这样的一些资源将支持像条件一样工作的属性,但只接受命令输出作为条件。
假设您要执行基于 fact 的命令。 在这种情况下,当您要测试变量的值时,您需要使用支持的条件结构之一,例如 if/else
:
if $osfamily != 'Debian' { warning('This manifest is not supported on this OS.') } else { notify { 'Good to go!': } }
另一种常见的情况是,当您想根据另一个命令的输出来调节命令的执行时。 对于这种情况,您可以使用 onlyif
或 unless
,如下例所示。 该命令只有在/bin/which php
输出成功时才会执行,即命令退出状态为0:
exec { "Test": command => "/bin/echo PHP is installed here > /tmp/test.txt", onlyif => "/bin/which php" }
同样,unless
会一直执行该命令,除非unless
下的命令成功退出:
exec { "Test": command => "/bin/echo PHP is NOT installed here > /tmp/test.txt", unless => "/bin/which php" }
- Working with Templates 模板通常用于设置配置文件,允许使用旨在使这些文件更加通用和可重用的变量和其他功能。 Puppet 支持两种不同的模板格式:Embedded Puppet (EPP) 和 Embedded Ruby (ERB)。 然而,EPP 格式仅适用于最新版本的 Puppet(从 4.0 版开始)。
下面是一个用于设置 Apache 虚拟主机的 ERB 模板示例,使用一个变量来设置该主机的文档根目录:
<VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot <%= @doc_root %> <Directory <%= @doc_root %>> AllowOverride All Require all granted </Directory> </VirtualHost>
为了应用模板,我们需要创建一个 file
资源,该资源使用 template
方法呈现模板内容。 这是应用此模板替换默认 Apache 虚拟主机的方式:
file { "/etc/apache2/sites-available/000-default.conf": ensure => "present", content => template("apache/vhost.erb") }
Puppet 在处理本地文件时做了一些假设,以加强组织和模块化。 在这种情况下,Puppet 将在您的模块目录中的文件夹 apache/templates
中查找 vhost.erb
模板文件。
- Defining 和触发服务 服务资源用于确保服务已初始化和启用。 它们还用于触发服务重启。
让我们考虑一下我们之前的模板使用示例,我们在其中设置了一个 Apache 虚拟主机。 如果您想确保 Apache 在虚拟主机更改后重新启动,您首先需要为 Apache 服务创建一个 service 资源。 这就是 Puppet 中定义此类资源的方式:
service { 'apache2': ensure => running, enable => true }
现在,在定义资源时,您需要包含 notify
选项以触发重新启动:
file { "/etc/apache2/sites-available/000-default.conf": ensure => "present", content => template("vhost.erb"), notify => Service['apache2'] }
- Example Manifest 现在让我们看看一个清单,它将在 Ubuntu 14.04 系统中自动安装 Apache Web 服务器,如本指南的介绍中所述。
完整的示例,包括用于设置 Apache 的模板文件和由 Web 服务器提供的 HTML 文件,可以在 Github 上找到 。 该文件夹还包含一个 Vagrantfile,可让您使用由 Vagrant 管理的虚拟机在简化的设置中测试清单。
您可以在下面找到完整的清单:
默认.pp
$doc_root = "/var/www/example" exec { 'apt-get update': command => '/usr/bin/apt-get update' } package { 'apache2': ensure => "installed", require => Exec['apt-get update'] } file { $doc_root: ensure => "directory", owner => "www-data", group => "www-data", mode => 644 } file { "$doc_root/index.html": ensure => "present", source => "puppet:///modules/main/index.html", require => File[$doc_root] } file { "/etc/apache2/sites-available/000-default.conf": ensure => "present", content => template("main/vhost.erb"), notify => Service['apache2'], require => Package['apache2'] } service { 'apache2': ensure => running, enable => true }
- 清单解释
- line 1 清单以变量定义
$doc_root
开头。 此变量稍后在资源声明中使用。
- line 1 清单以变量定义
- lines 3-5 这个 exec 资源执行
apt-get update
命令。
- lines 3-5 这个 exec 资源执行
- lines 7-10 这个package资源安装包
apache2
,定义apt-get update
资源是一个需求,也就是说只有在之后才会执行评估所需的资源。
- lines 7-10 这个package资源安装包
- lines 12-17 我们在这里使用 file 资源来创建一个新目录,它将作为我们的文档根目录。
file
资源可用于创建目录和文件,也可用于应用模板和将本地文件复制到远程服务器。 这个任务可以在配置的任何时候执行,所以我们不需要在这里设置任何require
。
- lines 12-17 我们在这里使用 file 资源来创建一个新目录,它将作为我们的文档根目录。
- lines 19-23 我们在这里使用另一个 file 资源,这次将我们本地的 index.html 文件复制到服务器内部的文档根目录。 我们使用
source
参数让 Puppet 知道在哪里可以找到原始文件。 此命名法基于 Puppet 处理本地文件的方式; 如果您查看 Github 示例存储库 ,您将看到应如何创建目录结构以让 Puppet 找到此资源。 需要在执行此资源之前创建文档根目录,这就是为什么我们包含一个require
选项来引用先前的资源。
- lines 19-23 我们在这里使用另一个 file 资源,这次将我们本地的 index.html 文件复制到服务器内部的文档根目录。 我们使用
- lines 25-30 一个新的 file 资源用于应用 Apache 模板并通知服务重新启动。 对于此示例,我们的配置被组织在一个名为 main 的模块中,这就是模板源为 main/vhost.erb 的原因。 我们使用
require
语句来确保模板资源仅在安装包apache2
后才会执行,否则 Apache 使用的目录结构可能还不存在。
- lines 25-30 一个新的 file 资源用于应用 Apache 模板并通知服务重新启动。 对于此示例,我们的配置被组织在一个名为 main 的模块中,这就是模板源为 main/vhost.erb 的原因。 我们使用
- lines 32-35 最后,service 资源声明了
apache2
服务,我们通知该服务从应用虚拟主机模板的资源重新启动。
- lines 32-35 最后,service 资源声明了
- Conclusion Puppet 是一个强大的配置管理工具,它使用富有表现力的自定义 DSL 来管理服务器资源和自动化任务。 它的语言提供了高级资源,可以为您的配置设置提供额外的灵活性; 重要的是要记住,资源的评估顺序与它们定义的顺序不同,因此在定义资源之间的依赖关系时需要小心,以便建立正确的执行链。
在本系列的 下一个指南 中,我们将了解 Chef,这是另一个强大的配置管理工具,它利用 Ruby 编程语言来自动化基础设施管理和供应。