使用 Hiera 和 Puppet
使用 Hiera,您可以将系统的配置数据外部化,并轻松了解这些值是如何分配给服务器的。将数据与 Puppet 代码分离后,您可以加密敏感值,例如密码和密钥。
分离代码和数据可能很棘手。在配置管理的情况下,能够设计数据层级结构具有重要价值——特别是能够级联服务器分类并分配一个或多个选项的层级结构。这是 Hiera 提供的主要价值——能够将“如何配置 /etc/ntp.conf”的代码与定义“每个节点应使用哪些 ntp 服务器”的值分开。最简洁地说,Hiera 让您可以将“如何做”与“做什么”分开。
分离代码和数据的想法不仅仅是为了拥有更简洁的 Puppet 环境;它还允许工程师创建更可重用的 Puppet 模块。它还将您的变量放在一个位置,以便它们也可以重用,而无需跨模块导入清单。Hiera 的用例包括管理软件包和版本或用作节点分类器。Hiera 最引人注目的用例之一是加密凭据和其他敏感数据,我将在本文后面讨论。
Puppet 节点数据最初是通过节点继承来管理的,但现在已不再支持,随后通过使用 params.pp 模块子类进行管理。在 Hiera 之前,需要在模块本地修改 params.pp 模块类,这经常会损害模块的可重用性。params.pp 今天仍然在模块中使用,但从 Puppet 版本 3 开始,Hiera 不仅是默认设置,而且还是检查变量值的第一个位置。当变量在 Hiera 和模块中都定义时,默认情况下 Hiera 优先。正如您将看到的,将模块与 params.pp 一起使用并将部分或全部变量数据存储在 Hiera 中很容易,从而可以轻松地逐步迁移。
要开始将 Hiera 与您现有的 Puppet 3 实施一起使用,您无需进行任何重大更改或代码迁移。您只需要一个 Hiera 的层级结构文件和一个包含键/值对的 yaml 文件。这是一个 Hiera 层级结构的示例
hiera.yaml:
:backends:
- yaml
:yaml:
:datadir: /etc/puppet/hieradata
:hierarchy:
- "node/%{::fqdn}"
- "environment/%{::env}/main"
- "environment/%{::env}/%{calling_module}"
- defaults
以及一个 yaml 文件
/etc/puppet/hieradata/environment/prod/main.yaml:
---
$nginx::credentials::basic_auth: 'password'
Hiera 可以有多个后端,但现在,让我们从 yaml 开始,它是默认设置,不需要额外的软件。:datadir:
只是层级结构搜索路径应开始的位置的路径,通常是 Puppet 配置中的一个位置。:hierarchy:
部分是定义 Hiera 如何执行键/值查找的核心算法的位置。:hierarchy:
是会随着时间推移而增长和变化的东西,它可能会比这个示例复杂得多。
在 :hierarchy:
中定义的每个路径中,您可以引用任何 Puppet 变量,即使设置了 $operatingsystem
和 $ipaddress
也是如此。使用 %{variable}
语法将拉取该值。
此示例实际上是我使用并推荐的一种特殊的层级结构设计,它使用从 facter 中分配给所有节点的名为 @env
的事实。此 @env
值可以在主机上设置,可以基于 FQDN 或 EC2 或其他位置的标签,但重要的是,这是将一个大的 main.yaml 文件分离到名为 prod、dev 等的目录中,因此,也是将 Hiera 值初步分离到类别中。
此特定示例的第二个组成部分是一个名为 %{calling_module}
的特殊 Hiera 变量。此变量是唯一的,并且为 Hiera 保留,以指示要搜索的 yaml 文件名将与执行 Hiera 查找的 Puppet 模块相同。因此,当在 Puppet 中查找变量时,此层级结构的行为方式如下
$nginx::credentials::basic_auth
首先,Hiera 知道它正在 /etc/puppet/hieradata/node 中查找名为 <hostname.domain.tld>.yaml 的文件以及 nginx::credentials::basic_auth
的值。如果文件或变量不存在,则下一步是在 /etc/puppet/hieradata/environment/<prod|stage|dev>/main.yaml 中查找,这是使用一个 yaml 文件包含大部分 Hiera 值的好方法。如果您有大量 nginx 示例的值,并且想要为了可管理性而将它们分开,您可以简单地将它们移动到 /etc/puppet/hieradata/environment/<prod|stage|dev>/nginx.yaml 文件。最后,作为默认设置,Hiera 将在 hieradata 目录顶部的 defaults.yaml 中检查该值。
您的 Puppet 清单对此查找应如下所示
modules/nginx/manifests/credentials.pp
class nginx::credentials (
basic_auth = 'some_default',
){}
当包含此类时,它将从 Hiera 中拉取值,并且可以在清单中包含它的任何时候使用。此处设置的 some_default
值只是一个占位符;Hiera 将覆盖在参数化类中设置的任何内容。实际上,如果您有一个类正在考虑转换为从 Hiera 中拉取数据,只需从 {} 中的类定义中移动一个变量到 () 中的参数化部分,Puppet 将对该变量执行 Hiera 查找。您甚至可以保持现有定义不变,因为 Hiera 将覆盖它。这种 Hiera 查找称为自动参数查找,是从 Hiera 中拉取数据的几种方法之一,但它是实践中最常见的方法。您还可以使用以下方式指定 Hiera 查找
modules/nginx/manifests/credentials.pp
class nginx::credentials (
basic_auth = hiera('nginx::credentials::basic_auth'),
){}
这些都将默认为 Hiera 数据文件中的优先级查找方法。这意味着 Hiera 将返回第一个匹配的值并停止进一步查找。这通常是您唯一想要的行为,并且是一个合理的默认值。有两种值得一提的查找方法:hiera_array
和 hiera_hash
。hiera_array
将在层级结构的文件中查找所有匹配的值,并将它们组合在一个数组中。在示例层级结构中,这将使您能够查找节点和环境的单个键的所有值——例如,为一个主机的 /etc/resolv.conf 添加额外的 DNS 搜索路径。要使用 hiera_array
查找,您必须显式定义查找类型(而不是依赖自动参数查找)
modules/nginx/manifests/credentials.pp
class nginx::credentials (
basic_auth = hiera_array('nginx::credentials::basic_auth'),
){}
hiera_hash
查找的工作方式相同,只是它将所有匹配的值收集到一个哈希中并返回该哈希。这通常对于高级 create_resources
变量导入以及高级 Puppet 环境中的许多其他用途非常有用。
也许 Hiera 最强大的功能是从各种后端存储技术中拉取数据的能力。Hiera 后端太多,无法一一列举,但它们包括 JSON、Redis、MongoDB 甚至 HTTP,以创建 URL 驱动的 Puppet 值 API。让我们看看两个有用的后端:Postgres 和 hiera-eyaml。
要从 psql 后端开始,您需要在 Puppet master(或每个节点,如果您使用无 master 的 Puppet 运行和 Puppet apply)上安装 hiera-psql gem,并使用一个简单的 hiera.yaml 文件
:hierarchy:
* 'environment/%{env}'
* default
:backends:
* psql
:psql:
:connection:
:dbname: hiera
:host: localhost
:user: root
:password: password
您可以使用一个名为 hiera 的数据库和一个名为 config 的表在本地 Postgres 安装上执行查找,该表具有三列:Path、Key 和 Value。
path key value
'environment/prod' 'nginx::credentials::basic_auth' 'password'
如果您想将您的 Hiera 数据暴露给 Puppet 外部的自定义内部应用程序,或者如果您想创建 DevOps Web 控制台或报告,这将非常有用。
将凭据存储在 Puppet 模块中是一个坏主意。如果您将凭据存储在 Puppet 中,并将清单存储在外部代码存储库中,那么您不仅无法与安全访问权限较低的开发人员共享这些清单,而且显然会将重要的安全数据暴露在组织外部,并且可能违反各种类型的合规性。那么,如何在 Puppet 中加密敏感数据,同时保持清单的相关性和可共享性?答案是使用 hiera-eyaml。
Tom Poulton 创建了 hiera-eyaml,以允许工程师做到这一点:仅加密实际文件内的敏感数据字符串,而不是加密整个文件,这也可以使用 hiera-gpg 完成(一个非常有用的加密 gem,但本文未涵盖)。
要开始使用,请安装 hiera-eyaml gem,并在 Puppet master 上生成密钥对
$ eyaml createkeys
然后将密钥移动到安全位置,例如 /etc/puppet/secure/keys。您的 hiera.yaml 配置应如下所示
hiera.yaml:
---
:backends:
- eyaml
- yaml
:yaml:
:datadir: /etc/puppet/hieradata
:eyaml:
:datadir: /etc/puppet/hieradata
:extension: 'yaml' # <- so all files can be named .yaml
:pkcs7_private_key: /path/to/private_key.pkcs7.pem
:pkcs7_public_key: /path/to/public_key.pkcs7.pem
:hierarchy:
- "node/%{::fqdn}"
- "environment/%{::env}/main"
- "environment/%{::env}/%{calling_module}"
* defaults
要加密值,您只需要公钥,因此将其分发给任何需要创建加密值的人
$ eyaml encrypt -s 'password'
这将生成一个加密块,您可以将其作为任何 yaml 文件中的值添加
main.yaml:
nginx::credentials::user: slackey #cleartext example value
nginx::credentials::basic_auth : > #encrypted example value
ENC[PKCS7,Y22exl+OvjDe+drmik2XEeD3VQtl1uZJXFFF2Nn
/HjZFXwcXRtTlzewJLc+/gox2IfByQRhsI/AgogRfYQKocZg
IZGeunzwhqfmEtGiqpvJJQ5wVRdzJVpTnANBA5qxeA==]
就地编辑加密值是 hiera-eyaml 后端最酷的功能之一。eyaml edit
在您选择的编辑器中打开 eyaml 文件的副本,并自动解密文件中的所有值。在这里,您可以像修改纯文本一样修改值。当您通过保存文件退出编辑器时,它会自动加密所有修改后的值,并将新文件保存到位。您可以看到未加密的纯文本被标记,以允许 eyaml 工具识别每个加密块,以及最初使用的加密方法。这用于确保仅当纯文本值已更改并且使用原始加密机制加密时才再次加密该块。
nginx::credentials::user: user1
nginx::credentials::basic_auth : DEC(1)::PKCS7[very secret password]!
一旦您有超过一百个条目左右,加密文本的块和字符串可能会变得相当繁琐。由于这些 yaml 文件旨在由人类直接修改,因此您希望它们易于导航。根据我的经验,将加密值保存在单独的文件中(例如 secure.yaml)是有意义的,其层级结构路径为
:hierarchy:
- "node/%{::fqdn}"
- "environment/%{::env}/secure"
- "environment/%{::env}/main"
- "environment/%{::env}/%{calling_module}"
这不是必需的,因为每个值都是单独加密的,并且可以安全地分发给其他团队。但是,它可能在您的环境中运行良好,因为您可以将加密文件存储在单独的存储库中,可能在不同的 Git 存储库中。只有私钥需要在 Puppet master 上受到保护。我还建议为每个环境使用单独的密钥,因为这可以更精细地控制谁可以解密 Hiera 中的不同数据文件,以及更大的安全隔离。一种方法是使用 @env 事实的可能值命名密钥,并将其包含在层级结构的路径中。您需要使用正确的密钥加密值,并且此命名约定使您可以轻松判断哪个密钥是正确的。
:pkcs7_private_key: /path/to/private_key.pkcs7.pem-%{::env}
:pkcs7_public_key: /path/to/public_key.pkcs7.pem-%{::env}
在 Puppet 模板中使用 Hiera 值时(无论是否加密),您必须小心地将它们拉入包含模板的类中,而不是跨类从模板中调用值——例如,在名为 mymodule 的模块中的模板 mytest.erb 中
mytest.erb:
...
username: user1
passwd: <%= scope.lookupvar('nginx::credentials::basic_auth') %>
↪#don't do this
...
由于操作顺序,Puppet 可能尚未将值加载到 nginx::credentials::basic_auth
中。此外,如果您正在使用 %calling_module
Hiera 变量,则在这种情况下,调用模块将是 mymodule,而不是 nginx,因此它不会在 nginx.yaml 文件中找到该值,正如人们可能预期的那样。
为了避免这些和其他问题,最好将值导入到 mymodule 类并分配局部值
mymodule.pp:
class mymodule {
include nginx::credentials
$basic_auth = "${nginx::credentials::basic_auth}"
file { '/etc/credentials/boto_cloudwatch.cfg':
content => template ("mymodule/mytest.erb"),
}
然后从模板中引用局部值
mytest.erb:
...
username: user1
passwd: <%= @basic_auth %>
您现在可以开始逐步将加密的 Hiera 值引入到您的 Puppet 环境中。也许在您将数据与 Puppet 代码分离后,您可以将您的一些模块贡献给 PuppetForge 供其他人使用!
资源文档—Hiera 1 概述: https://docs.puppetlabs.com/hiera/1
“初探:安装和使用 Hiera”: http://puppetlabs.com/blog/first-look-installing-and-using-hiera
TomPoulton/hiera-eyaml: https://github.com/TomPoulton/hiera-eyaml
dalen/hiera-psql: https://github.com/dalen/hiera-psql
“在 Puppet 中加密敏感数据”: http://www.theguardian.com/info/developer-blog/2014/feb/14/encrypting-sensitive-data-in-puppet