原文:Pro linux system administration
协议:CC BY-NC-SA 4.0
十九、结构管理
现在,您已经了解了如何为一些系统构建组件,我们将向您展示如何快速地同时构建数千个组件,并为每种类型的系统提供正确的配置!在前 18 章中,我们想向您展示 Linux 系统的每个部分是如何配置的,什么开关适用于什么命令,以及每个开关的结果是什么。所有这些对于了解自动化配置和配置管理现在将为您做些什么至关重要。
在这一章中,我们将会看到配置管理的三个方面。
- 自动调配和安装新主机
- 自动管理您的配置,包括文件、用户和软件包
- 如何以代码形式测试您的配置
我们要研究的第一个过程是自动配置或安装新主机,有时称为引导。在 CentOS 世界中,自举通常被称为 Kickstart(在 Kickstart 工具用于执行它之后)。在 Ubuntu 和 Debian 上,这个过程叫做种子预置。
资源调配是自动向主机安装分发版的一种方式。当我们在第二章第一次看到安装发行版时,我们演示了如何手动安装。您插入了一张 DVD,并按照屏幕提示安装您的发行版。自动配置是一种安装发行版的方式,不会受到配置问题的提示。这使得资源调配变得快速而简单,并且它还具有确保每个构建都相同的优势。
Tip
您可以对服务器主机和桌面主机使用资源调配。这不仅是一种快速构建(或重建)服务器主机的方法,也是一种为用户自动安装桌面的快速方法。
我们要考察的第二个过程是配置管理和自动化。到目前为止,您已经看到您可以积累许多已安装的包、用户、配置文件和其他设置。如果您不采取措施来控制和自动化您的环境,它会很快变得复杂和难以管理。配置管理允许您集中配置、记录并自动化配置。这使您能够管理和控制对环境的更改,并防止意外或恶意的配置更改。
第三是测试您的基础设施,就像测试您的应用程序代码一样。因为配置管理只是代码,这使得测试您的配置更容易,并有助于确保更少的错误进入您的生产服务器。这可以减少管理上的错误,因为在投入生产之前,可以对事物进行测试和审查。这些都可以连接到您的常规 Jenkins 或其他持续集成/持续部署(CI/CD)基础架构中,以实现无缝操作。
供应、配置管理和测试应该在工作流中使用。在您的系统配置过程中,您可以使用 Cobbler 或不同的配置系统来安装操作系统,然后使用 Ansible 或 Puppet 来应用经过测试的配置,作为该过程的一部分。这意味着当您第一次访问控制台时,它已经具有正确的磁盘布局、正确的操作系统、正确的网络配置、正确的用户和软件包、正确的服务配置,并且这些服务已经启动。不仅如此,在那之后你建立的每一个系统也是恰到好处的,即使它由成千上万个这样的系统组成。在您完成 CI 构建后,它会自动为您完成!
准备金提取
我们已经谈了一点什么是供应,但是在不同的发行版之间,如何进行供应会有所不同。我们将解释如何自动配置 CentOS 和 Ubuntu 主机。
资源调配通常是一个两阶段的过程。
- 启动您的主机,并向其发送安装所需的文件。
- 自动化安装步骤。
该过程从主机启动开始。还记得我们在第五章中告诉你的启动顺序吗?在许多主机上,您可以配置引导序列,在其他地方寻找引导指令;例如,您可以从 DVD 或 u 盘启动。除了这些方法之外,您还可以从网络资源获取引导指令。
这个引导过程背后的技术被称为预引导执行环境(PXE)。网络引导服务器因此被称为 PXE 引导(发音为“pixie boot”)服务器。我们打算构建的主机使用网络查询来查找 PXE 启动服务器,通常是对 DHCP 服务器的网络查询,该服务器可能会提供启动所需的文件,然后使用称为小文件传输协议(TFTP)的文件传输协议将这些文件传输到主机。
Note
你可以在 http://en.wikipedia.org/wiki/Preboot_Execution_Environment
了解更多关于 PXE 的信息。
初始引导完成后,您的配置过程将继续安装预打包版本的发行版,通常会有一系列针对安装时提示您的各种配置问题的自动脚本响应。
Note
我们使用基于网络的配置来创建我们的主机,而不是任何替代品,如 CD 或 DVD。这是因为我们相信基于网络的资源调配是自动构建主机的最简单、最容易、最高效的方式。
使用 CentOS Cobbler 进行配置
CentOS 有各种各样的工具来配置主机,从最基本的自动安装的 Kickstart,到用于主机配置的全功能 GUI 管理工具,如 Cobbler ( http://cobbler.github.io/
)和 Spacewalk ( http://spacewalk.redhat.com/
)。
我们将看看三种工具的组合:
- Kickstart:基于 Red Hat 的操作系统的安装自动化工具
- Preseed:一个用于基于 Debian 的操作系统的安装自动化工具
- Cobbler:提供 PXE 引导服务器的配置服务器
我们将带您完成创建一个 Cobbler 服务器和一个要安装的构建的过程。在本章的稍后部分,我们将向您展示如何配置 Kickstart 来自动化您的配置和安装选项。
安装补鞋匠
让我们从在您的主机上安装 Cobbler 开始。要运行 Cobbler,您需要安装 EPEL 存储库。
$ sudo yum install –y epel-release
然后我们需要安装补鞋匠。
$ sudo yum install –y cobbler
这将安装一些额外的 YUM 实用程序和createrepo
包,它们有助于存储库管理。我们还安装了一些 Cobbler 使用的附加包:DHCP 守护进程、TFTP 服务器和 Apache web 服务器。您可能已经安装了这些包,在这种情况下,YUM 将跳过它们。
Note
我们在第十章中讨论 DHCP,在第十一章中讨论 Apache。
一旦所有东西都安装好了,我们需要在引导时启用cobblerd
(守护进程)并启动它。
$ sudo systemctl enable cobblerd httpd
$ sudo systemctl start cobblerd
$ sudo systemctl start httpd
Cobbler 需要访问 Apache 服务器才能启动。此外,我们需要确保cobblerd
服务可以访问httpd
服务器端口。SELinux 默认情况下会阻止这一点,因此我们需要发出以下命令:
$ sudo setsebool -P httpd_can_network_connect true
Cobbler 有一些特定的 SELinux 设置,您可以使用以下命令查看它们:
$ sudo getsebool -a|grep cobbler
cobbler_anon_write --> off
cobbler_can_network_connect --> off
cobbler_use_cifs --> off
cobbler_use_nfs --> off
httpd_can_network_connect_cobbler --> off
httpd_serve_cobbler_files --> off
我们将启用以下 SELinux 布尔值:
$ sudo setsebool -P httpd_serve_cobbler_files on
$ sudo setsebool -P httpd_can_network_connect_cobbler on
配置补鞋匠
在安装了所需的包之后,您需要配置 Cobbler。Cobbler 带有一个方便的检查功能,告诉你需要做些什么来配置它。要查看需要做什么,请运行以下命令:
The following are potential configuration items that you may want to change:
1 : The 'server' field in /etc/cobbler/settings must ... by all machines that will use it.
2 : For PXE to be functional, the 'next_server' field ... the IP of the boot server on the PXE network.
3 : SELinux is enabled. Please review the following ... https://github.com/cobbler/cobbler/wiki/Selinux
4 : change 'disable' to 'no' in /etc/xinetd.d/tftp
5 : some network boot-loaders are missing from /var/lib/cobbler/loaders, ...is the easiest way to resolve these requirements.
6 : debmirror package is not installed, it will be required to manage debian deployments and repositories
7 : ksvalidator was not found, install pykickstart
8 : The default password used by the sample templates ... 'your-password-here'" to generate new one
9 : fencing tools were not found, and are required to use ... cman or fence-agents to use them
Restart cobblerd and then run 'cobbler sync' to apply changes.
您可以看到,要让 Cobbler 运行起来,您需要做几件事情。让我们逐一解决这些问题。
首先,配置/etc/cobbler/settings
文件。您需要更新该文件中的两个字段:server
和next_server
。您需要用主机的 IP 地址替换现有的值(通常是 127.0.0.1 ),这样 PXE 启动的主机才能找到您的补丁包主机。在我们的例子中,我们指定以下内容:
server 192.168.0.1
next_server 192.168.0.1
要更新 Cobbler 的配置,您可以运行以下命令:
$ sudo cobbler sync
Note
您需要在任何时候更改/etc/cobbler/settings
文件时运行$ sudo cobbler sync
命令。常见的错误包括在settings
文件中的选项后留下尾随空格。请确保删除文件中任何多余的空格。
你还需要配置一个 DHCP 服务器(就像我们在第十章中介绍的那样)。这里您有两个选择:您可以让 Cobbler 管理您现有的 DHCP 服务器,或者您可以告诉您现有的 DHCP 服务器指向 Cobbler。
在您运行了cobbler sync
并重新运行cobbler check
之后,您会注意到需要检查的未完成事项列表已经减少了。我们现在要安装补鞋匠加载器和debmirror
二进制文件。
$ sudo cobbler get-loaders
对于debmirror
,你需要从 Debian 下载文件,解压,并复制到一个公共位置(或者,你可以使用 FPM,就像我们在第九章中展示的那样,创建一个包,以可重复的方式为你做这件事!).
我们至少需要安装这些 Perl 模块:
$ sudo yum install -y perl-LockFile-Simple perl-IO-Zlib perl-Digest-MD5 perl-Net-INET6Glue perl-LWP-Protocol-https
接下来,我们将下载并安装debmirror
包,解压缩它,并将其放在/usr/local/bin
目录中。
$ curl -s http://archive.ubuntu.com/ubuntu/pool/universe/d/debmirror/debmirror_2.25ubuntu2.tar.xz -o debmirror_2.25.tar.xz
$ tar xf debmirror_2.25.tar.xz && sudo cp debmirror-2.25ubuntu2/debmirror /usr/local/bin/
为了测试我们已经为debmirror
正确安装了所有东西,运行debmirror --help
并确保您没有得到任何 Perl 模块错误。
最后,我们将更改主机上的默认 root 密码。首先,您可以使用python3
创建一个安全的 SHA-512 密码,如下所示:
python3 -c 'import crypt; print(crypt.crypt("yourpasswordhere", crypt.mksalt(crypt.METHOD_SHA512)))'
$6$KnsQG.tEetSCSmid$HpqUNyEk1UPkt9Dc9MPcwPY...guKOGdUeNXoA7.ugUBGGaDIk8RY8FRYVOwzmsM.u01
然后需要更新/etc/cobbler/settings
文件中的default_password_
crypted
:
设置。每次换车后记得跑cobbler sync
。
Note
Python 3 默认不安装在 CentOS 上,但可以在 Ubuntu 上获得。前面生成密码的脚本可以在任何已经安装了 Python 3 的主机上运行,并且可以跨主机复制。
现在,当我们运行$ sudo cobbler check
时,列表只包含三个项目,我们不需要处理它们。
补鞋匠管理您的 DHCP
如果您想让 Cobbler 管理您的 DHCP 服务器,那么您需要在/etc/cobbler/settings
文件中启用另一个选项。
manage_dhcp: 1
您还需要更新一个模板文件,Cobbler 将使用它来配置您的 DHCP 服务器,/etc/cobbler/dhcp.template
。清单 19-1 展示了这个文件的一个例子。
# ******************************************************************
# Cobbler managed dhcpd.conf file
#
# generated from cobbler dhcp.conf template ($date)
# Do NOT make changes to /etc/dhcpd.conf. Instead, make your changes
# in /etc/cobbler/dhcp.template, as /etc/dhcpd.conf will be
# overwritten.
#
# ******************************************************************
ddns-update-style interim;
allow booting;
allow bootp;
ignore client-updates;
set vendorclass = option vendor-class-identifier;
option pxe-system-type code 93 = unsigned integer 16;
key dynamic-update-key {
algorithm hmac-sha256;
secret "RZqM/JutbhgHiBR8ICG0LDyN+9c1LpNU83ycuU9LPaY=";
}
zone 0.168.192.in-addr.arpa. {
key dynamic-update-key;
primary 192.168.0.1;
}
zone example.com. {
key dynamic-update-key;
primary 192.168.0.1;
}
subnet 192.168.0.0 netmask
255.255.255.0 {
option routers 192.168.0.254;
option domain-name "example.com";
option domain-name-servers 192.168.0.1;
option broadcast-address 192.168.0.255;
next-server $next_server;
filename "/pxelinux.0";
group "static" {
use-host-decl-names on;
host au-mel-rhel-1 {
hardware ethernet 00:16:3E:15:3C:C2;
fixed-address au-mel-rhel-1.example.com;
}
}
pool {
range 192.168.0.101 192.168.0.150;
deny unknown clients;
}
pool {
range 192.168.0.151 192.168.0.200;
allow unknown clients;
default-lease-time 7200;
max-lease-time 21600;
}
}
Listing 19-1.The /etc/cobbler/dhcp.template File
如果您有一个带有配置的现有 DHCP 服务器,您应该更新此模板以反映该配置。你可以看到我们已经调整了清单 19-1 中的模板,以反映我们在第十章中使用的 DHCP 配置。我们添加了两个设置。
allow booting;
allow bootp;
这两个选项告诉 DHCP 服务器响应来自请求网络启动的主机的查询。
清单 19-1 中需要注意的另外两个重要设置是next-server
和文件名配置选项。next-server
选项设置为$next_server
。这个值将被我们刚刚在/etc/cobbler/settings
文件的next_server
选项中配置的 IP 地址所取代。这告诉我们的 DHCP 服务器将请求网络引导的主机路由到哪里。
filename
选项被设置为/pxelinux.0
,这是 PXE 启动的主机在启动过程中应该查找的启动文件的名称。我们将很快建立这个文件。
现在,更改这些文件后,您需要运行以下命令:
$ sudo cobbler sync
Caution
如果您有一个现有的 DHCP 服务器,这个模板将通过覆盖/etc/dhcpd.conf
配置文件来覆盖它的配置。只有在您确定知道自己在做什么的情况下才这样做,并在运行命令之前复制一份现有的/etc/dhcpd.conf
文件。
补鞋匠没有管理你的 DHCP
如果你不想让 Cobbler 管理你的 DHCP,那么你只需要调整你现有的 DHCP 配置文件,/ etc/dhcpd.conf
,添加next-server
和filename
选项。让我们用这个选项更新我们在第九章中创建的配置的相关部分,如清单 19-2 所示。
allow booting;
allow bootp;
subnet 192.168.0.0 netmask 255.255.255.0 {
option routers 192.168.0.254;
option domain-name "example.com";
option domain-name-servers 192.168.0.1;
option broadcast-address 192.168.0.255;
filename "/pxelinux.0";
next-server 192.168.0.1;
group "static" {
use-host-decl-names on;
host au-mel-rhel-1 {
hardware ethernet 00:16:3E:15:3C:C2;
fixed-address au-mel-rhel-1.example.com;
}
}
pool {
range 192.168.0.101 192.168.0.150;
deny unknown clients;
}
pool {
range 192.168.0.151 192.168.0.200;
allow unknown clients;
default-lease-time 7200;
max-lease-time 21600;
}
}
Listing 19-2.Existing dhcpd.conf Configuration File
您可以看到,我们在 DHCP 部分的开头添加了两个选项。
allow booting;
allow bootp;
这两个选项告诉 DHCP 服务器响应来自启动客户端的查询。
我们还在子网定义中添加了next-server
选项。
next-server 192.168.0.1
next-server
选项告诉 DHCP 将请求 PXE 网络启动的主机发送到哪里。我们需要指定我们的服务器的 IP 地址。
最后,我们添加了设置为/pxelinux.0
的filename
选项,这是 PXE 启动的主机在启动过程中应该查找的启动文件的名称。我们将很快建立这个文件。
Tip
配置完 DHCP 服务器后,您需要重新启动 Cobbler 服务器以应用新的配置。
配置 TFTP
一旦守护程序启动,您需要启用您的 TFTP 服务器将您的引导文件发送到要安装的主机。为此,您可以编辑/etc/xinet.d/tftp
文件来启用 TFTP 服务器。在这个文件中,找到这一行:
disable = yes
把它改成这样:
disable = no
接下来,像这样启用 TFTP 服务器:
$ sudo systemctl enable tftp
$ sudo systemctl start tftp
您需要通过打开一些必需的端口,例如 69、80、25150 和 25151,来确保您的主机可以通过您的防火墙连接到 Cobbler 服务器,方法是创建如下所示的firewalld
规则:
$ sudo firewall-cmd --zone=public --add-service=tftp --permanent
$ sudo firewall-cmd --zone=public --add-service=httpd –permanent
$ sudo firewall-cmd --zone=public --add-port=25150:25151/tcp –permanent
这些规则允许 192.168.0.0/24 子网中的任何主机通过适当的端口访问引导服务器。你可以在第七章找到更多关于防火墙规则的信息。
使用鞋匠
一旦您配置了 Cobbler,您就可以开始使用它了。Cobbler 允许您指定一个您希望用来构建主机的发行版,导入该发行版的文件,然后创建一个配置文件。然后,您可以使用这个发行版和概要文件构建主机。
我们已经将我们的 ISO 文件分别挂载到了/mnt/centos
和/mnt/ubuntu
。这是这样做的:
$ sudo mount –o loop /path/to/downloaded.iso /path/to/mountpoint
让我们从使用import
命令创建第一个概要文件开始。
$ sudo cobbler import --path=/mnt/centos --name=CentOS7 --arch=x86_64
task started: 2016-12-22_055922_import
task started (id=Media import, time=Thu Dec 22 05:59:22 2016)
Found a candidate signature: breed=redhat, version=rhel6
Found a candidate signature: breed=redhat, version=rhel7
Found a matching signature: breed=redhat, version=rhel7
Adding distros from path /var/www/cobbler/ks_mirror/CentOS7-x86_64:
creating new distro: CentOS7-x86_64
trying symlink: /var/www/cobbler/ks_mirror/CentOS7-x86_64 -> /var/www/cobbler/links/CentOS7-x86_64
creating new profile: CentOS7-x86_64
associating repos
checking for rsync repo(s)
checking for rhn repo(s)
checking for yum repo(s)
starting descent into /var/www/cobbler/ks_mirror/CentOS7-x86_64 for CentOS7-x86_64
processing repo at : /var/www/cobbler/ks_mirror/CentOS7-x86_64
need to process repo/comps: /var/www/cobbler/ks_mirror/CentOS7-x86_64
looking for /var/www/cobbler/ks_mirror/CentOS7-x86_64/repodata/*comps*.xml
Keeping repodata as-is :/var/www/cobbler/ks_mirror/CentOS7-x86_64/repodata
*** TASK COMPLETE ***
我们也将导入我们的 Ubuntu ISO。
$ sudo cobbler import --path=/mnt/ubuntu --name Ubuntu-16.04 --breed=ubuntu --os-version=xenial
您发出带有import
选项的cobbler
命令。--path
选项指定了您想要导入的发行版的来源——在我们的例子中是/mnt/ubuntu
和/mnt/centos
。--name
是您希望为发行版指定的任何名称,您可以添加--breed
和--os-version
来帮助 import 命令找到与您的发行版匹配的正确签名。
Note
如果在导入时出现错误,请确保运行$ sudo cobbler signature update
并重试。在这里了解更多签名: http://cobbler.github.io/manuals/2.8.0/3/2/3_-_Distro_Signatures.html
。
您还可以与在线存储库同步。这里有一个例子:
$ sudo cobbler reposync
task started: 2016-12-22_063019_reposync
task started (id=Reposync, time=Thu Dec 22 06:30:19 2016)
hello, reposync
run, reposync, run!
running: /usr/bin/debmirror --nocleanup --verbose --ignore-release-gpg --method=http --host=archive.ubuntu.com --root=/ubuntu --dist=xenial,xenial-updates,xenial-security --section=main,universe /var/www/cobbler/repo_mirror/Ubuntu-16.04-x86_64 --nosource -a amd64
这将与在线存储库同步,在这种情况下,使用我们之前安装的debmirror
二进制文件来同步我们的 Ubuntu Xenial 版本。
Tip
您的主机上需要足够的磁盘空间来复制您想要保留的任何发行版。托管您自己的存储库同步将大大加快您的部署,并减少在线网络下载。
Cobbler 将运行导入过程,然后返回到提示符处。根据主机的性能(如果通过网络导入,还取决于连接速度),这可能需要一些时间。
您可以通过以下命令查看发行版安装:
$ sudo cobbler distro list
CentOS7-x86_64
Ubuntu-16.04-x86_64
导入也将创建两个概要文件;我们可以使用以下命令来查看它们:
$ sudo cobbler profile list
CentOS7-x86_64
Ubuntu-16.04-x86_64
在您创建了您的发行版和概要文件之后,您可以使用report
选项在 Cobbler 中看到完整的细节,如清单 19-3 所示。
$ sudo cobbler report
distros:
==========
Name : CentOS7-x86_64
Architecture : x86_64
TFTP Boot Files : {}
Breed : redhat
Comment :
Fetchable Files : {}
Initrd : /var/www/cobbler/ks_mirror/CentOS7-x86_img/initrd.img
Kernel : /var/www/cobbler/ks_mirror/CentOS7-x86_img/vmlinuz
Kernel Options : {}
Kernel Options (Post Install) : {}
Kickstart Metadata : {'tree': 'http://@@http_server@@/cblr/links/CentOS7-x86_64'}
Management Classes : []
OS Version : rhel7
Owners : ['admin']
Red Hat Management Key : <<inherit>>
Red Hat Management Server : <<inherit>>
Template Files : {}
Name : Ubuntu-16.04-x86_64
Architecture : x86_64
TFTP Boot Files : {}
Breed : ubuntu
Comment :
Fetchable Files : {}
Initrd : /var/www/cobbler/ks_mirror/Ubuntu-16.04/install/netboot/ubuntu-installer/amd64/initrd.gz
Kernel : /var/www/cobbler/ks_mirror/Ubuntu-16.04/install/netboot/ubuntu-installer/amd64/linux
Kernel Options : {}
Kernel Options (Post Install) : {}
Kickstart Metadata : {'tree': 'http://@@http_server@@/cblr/links/Ubuntu-16.04-x86_64'}
Management Classes : []
OS Version : xenial
Owners : ['admin']
Red Hat Management Key : <<inherit>>
Red Hat Management Server : <<inherit>>
Template Files : {}
Listing 19-3.A Cobbler Report
该选项显示所有的发行版,它们的概要文件当前被导入到 Cobbler 中。
Note
您可能会看到通过导入一个发行版而创建的多个发行版和配置文件。
清单 19-3 显示了我们的普通CentOS7-x86_64
发行版和我们创建的概要文件CentOS7-x86_64
。清单 19-3 中的大部分信息对我们来说并不重要;我们将很快使用这些配置文件创建一个新系统。
Note
通过查看cobbler
命令的man
页面或访问 http://cobbler.github.io/manuals/2.8.0/
的文档,您可以在您的个人资料中看到其他可以编辑的选项。
用鞋匠建造一个主机
现在您已经添加了一个概要文件和一个发行版,您可以引导一个主机并安装您的发行版。选择要构建的主机(或虚拟机)并重新启动它。您的主机可能会自动搜索网络上的引导设备,但您更有可能需要调整其 BIOS 设置来调整引导顺序。要从 Cobbler 引导,您需要指定您的主机首先从网络引导。
我们在与 Cobbler 服务器相同的主机专用适配器接口上创建了一个 VirtualBox 主机。我们在第三章中创建了 VirtualBox 主机,并创建了一个 8Gb 硬盘的基本主机。在系统设置中,我们将为引导设备选择网络。
在图 19-1 中,我们正在选择网络引导选项。当我们完成时,我们将回到这里并把它设置到硬盘上。我们现在像平常一样启动主机。
图 19-1。
Setting Network Boot
当您的主机启动时,它将从网络请求一个 IP 地址,并从您的 DHCP 服务器获得一个答案,如图 19-2 所示。
图 19-2。
Requesting DHCP address
您的主机将引导至引导菜单。你可以在图 19-3 中看到这个菜单的例子。
图 19-3。
The Cobbler menu
从该菜单中,您可以选择您想要安装的配置文件(如CentOS7-x86_64
)。如果您没有选择要安装的配置文件,Cobbler 将自动启动菜单上的第一项(local
),这将继续本地主机上的引导过程。
Note
如果这台主机上没有安装操作系统,local
引导过程显然会失败。
我们将选择CentOS7-x86_64
,这将自动在我们的主机上安装 CentOS。如果我们选择Ubuntu-16.04-x86_64
,我们会安装 Ubuntu 数字 19-4 到 19-6 讲述了这个故事。
图 19-6。
Selecting Boot from Hard Disk
图 19-5。
Installing Ubuntu
图 19-4。
Installing CentOS
请记住,当我们在 VirtualBox 上安装完主机后,我们需要打开主机电源,并需要更改引导设备,然后再次启动主机。
图 19-8。
Ubuntu installed
图 19-7。
CentOS installed
我们选择了一个配置文件,然后该配置文件使用相关 Kickstart 或预置文件中包含的说明开始安装过程。如果您正在观看安装过程,您将看到安装屏幕的进度—所有这些都不需要您输入来继续或选择选项。
使用 Cobbler,您还可以为特定主机指定配置选项。您不需要这样做,但是如果您对主机有一个特定的角色,并且想要指定一个特定的配置文件或 Kickstart 配置,这将非常有用。
为此,您可以使用system
命令将主机添加到 Cobbler 中,通过它们的 MAC 或 IP 地址来识别它们。
$ sudo cobbler system add --name=web1.example.com --mac=08:00:27:66:EF:C2
--profile=CentOS7-x86_64 --interface eth1
这里,我们添加了一个名为web1.example.com
的系统,它具有指定的 MAC 地址。
Note
您通常可以在网络引导过程中看到您的 MAC 地址,或者您经常可以在网卡的标签上找到它。或者,你也可以像在 VirtualBox 中那样看到你的虚拟界面。
新主机使用CentOS7-x86_64
配置文件。到目前为止,它与我们以前构建的主机没有什么不同。如果具有适当 MAC 地址的主机连接到我们的 Cobbler 主机,那么 Cobbler 将使用这些配置设置来配置主机。
如果您需要更改构建主机的方式,可以创建新的配置文件。您的新配置文件可以继承其他配置文件的设置,这些配置文件被视为其父配置文件。我们将创建一个名为centos-base
的概要文件,它将从父CentOS7-x86_64
继承发行版和其他设置。
$ sudo cobbler profile add --name centos-base --parent CentOS7-x86_64
这就是我们如何为不同的主机组使用不同的公共 Kickstart 或预置文件。Kickstart 和预置文件可能有不同的磁盘配置或为特定配置文件定制的包列表。要添加一个特定的 Kickstart 或 Preseed 文件,首先要复制并修改任何一个现有的文件,将其添加到/var/lib/cobbler/kickstarts
目录中。然后,您可以使用--kickstart
选项将其添加到配置文件中。
您可以使用list
和report
选项列出已配置的主机。
$ sudo cobbler system list
web1.example.com
使用report
选项可以看到gateway.example.com
系统定义的完整列表。
$ sudo cobbler system report –name=web1.example.com
我们还可以使用remove
命令删除一个系统。
$ sudo cobbler system remove --name=web1.example.com
Note
你可以在cobbler
命令的man
页面上阅读更多的补鞋匠功能。
Cobbler Web 界面
Cobbler 还有一个简单的 web 界面,您可以使用它来管理它的一些选项。在这个阶段,它非常简单,命令行界面的功能更加全面,但是如果您想要实现它,它是可用的。您可以在 http://cobbler.github.io/manuals/2.8.0/5_-_Web_Interface.html
找到说明。
故障排除补鞋匠
您可以通过监控主机上的元素(包括日志文件)和使用网络监控工具(如tcpdump
或tshark
命令)来排除网络引导过程的故障。
您可以通过查看/var/log/messages
日志文件来监控 DHCP 进程的输出。补鞋匠还记录到/var/log/cobbler/cobbler.log
文件和包含在kicklog
和syslog
目录中的文件,它们也在/var/log/cobbler
下。
您还可以监控引导主机和引导服务器之间的网络流量。为此,您可以使用各种网络监控工具。
$ sudo tcpdump port tftp
Cobbler 有一个 wiki 页面,包含位于 http://cobbler.github.io/manuals
的文档。该文档包括一些在 http://cobbler.github.io/manuals/2.8.0/7_-_Troubleshooting.html
进行故障排除的有用提示。补鞋匠社区也有邮件列表和其他支持渠道,你可以在这里看到: http://cobbler.github.io/community.html
。
安装
在 CentOS 上,用于自动安装主机的语言称为 Kickstart。在 Ubuntu 上,它被称为 Preseed。为了简单起见,因为它是一种更容易使用的语言,我们将向您展示如何使用 Kickstart 来自动安装 CentOS 和 Ubuntu。在 Ubuntu 不支持的地方,我们将向您展示如何使用 Preseed 来配置它。
Kickstart 配置文件包含自动化安装过程所需的说明。对于大多数安装选项来说,这是一个简单的脚本化过程,但可以扩展到一些复杂的配置。
您可以在 http://pykickstart.readthedocs.io/en/latest/kickstart-docs.html
找到 Kickstart 的最新详细文档。
您可以在 https://wiki.debian.org/DebianInstaller/Preseed
找到关于 Preseed 及其指令的文档。在本节的后面,我们将使用其中的一些指令。
您已经看到了如何使用 Cobbler 为您的配置环境指定 Kickstart 文件。让我们从清单 19-4 中一个简单的 Kickstart 文件的一些内容开始。
install
# System authorization information
auth --enableshadow --passalgo sha512
# System bootloader configuration
bootloader --location=mbr
# Partition clearing information
clearpart --all --initlabel
# Use text mode install
text
Listing 19-4.A Kickstart File
清单 19-4 显示了以install
选项开始的配置指令列表,它通过执行安装来规定安装过程的行为。
然后您可以看到带有选项的配置指令,例如auth --enableshadow --passalgo sha512
,它告诉 Kickstart 如何回答特定的安装问题。auth
语句在这里有值--enableshadow
和--passalgo sha512
,它们分别启用影子密码和指定密码散列必须使用 SHA512 密码算法。
接下来的选项bootloader
的值为--location=mbr
,告诉 Kickstart 将引导加载程序安装到 MBR 中。接下来是指令clearpart
,它清除主机上的所有分区,并为它们创建默认标签。最后一个选项text
,指定我们应该使用基于文本的安装,而不是 GUI。
Tip
您可以使用 Kickstart 来升级和安装主机。如果您有现有的主机,您可以从新版本的操作系统进行网络引导,并使用 Kickstart 文件来编写脚本和升级。
指令太多,无法一一讨论,因此我们在表 19-1 中向您展示了必须指定的指令以及一些您可能会发现有用的其他主要指令。
表 19-1。
Required Kickstart Directives
| 管理的 | 描述 | | --- | --- | | `auth` | 配置身份验证。 | | `bootloader` | 配置引导加载程序。 | | `keyboard` | 配置键盘类型。 | | `lang` | 配置主机上的语言。 | | `part` | 配置分区。这是安装所必需的,但升级时不需要。 | | `rootpw` | 指定 root 用户的密码。 | | `timezone` | 指定主机所在的时区。 |您还可以在 http://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#chapter-2-kickstart-commands-in-fedora
找到有用的可用指令列表和解释。
Tip
如果您在 CentOS 上,您可以在/root/anaconda-ks.cfg
文件中看到一个示例 Kickstart 文件,它是在您安装主机时创建的。这将向您展示您当前的主机是如何构建的,并且可以用作构建类似主机的示例。
安装源
您已经看到了指定安装行为的install
和upgrade
指令。您还可以指定安装文件的来源。
url --url http://192.168.0.1/centos/
对于 Cobbler,我们定义了一个变量来指定安装源的位置。
url --url=$tree
指令也可以用来指定一个 FTP 服务器。
url --url ftp://jsmith:passsword@192.168.0.1/centos
当从本地安装的 CD 或 DVD 和硬盘安装到本地分区时,我们可以指定一些替代源,包括cdrom
。
harddrive --dir=/centos --partition=/installsource
键盘、语言和时区
下一个片段我们将向您展示如何配置我们的键盘、语言和时区。
# System keyboard
keyboard us
# System language
lang en_AU
# System timezone
timezone Australia/Melbourne
在这里,我们将us
指定为keyboard
指令的值,以表示美国键盘。我们已经指定我们的语言为en_AU
(英语澳大利亚语),我们的时区为Australia/ Melbourne
。
管理用户
您还可以使用 Kickstart rootpw
指令设置root
用户的密码。
rootpw --iscrypted $6$D7CxLkSBeC9.k.k3$S8G9s3/Y5LJ4dio....S5GS78p2laxALxaJ.lCN9tzKB1zIpYz38Fs9/
rootpw
指令是所有 Kickstart 文件的必需 Kickstart 选项。当指定了--iscrtypted
选项时,root 用户的密码可以是纯文本值,也可以是加密值。您可以锁定 root 用户帐户,这样就没有人可以使用--lock
选项登录了(如果指定了--lock
,那么您也不需要密码)。
您可以使用user
指令在 Kickstart 上创建一个新用户。
user jsmith --password password
前面的代码创建了一个名为jsmith
的新用户,密码为password
。通过添加--iscrypted
选项,您可以添加一个带有加密密码的用户。我们将像使用rootpw
指令一样创建我们的加密密码。
防火墙和网络
在 CentOS 上,您可以配置主机的初始防火墙和网络配置。
# Firewall configuration
firewall --enabled --http --ssh --smtp
# SELinux configuration
selinux --enabled
这里,我们使用firewall
选项启用了防火墙,并允许通过 HTTP、SSH 和 SMTP 进行访问。(您可以使用--disabled
选项禁用防火墙。)我们还启用了 SELinux——如果您真的需要,可以使用selinux --disabled
选项禁用它。
您可以使用 Kickstart 配置网络连接,如下所示:
# Network information
network --bootproto=static --device=eth0 --gateway=192.168.0.254
--ip=192.168.0.1 --nameserver=192.168.0.1 --netmask=255.255.255.0 --onboot=on
您还可以使用network
选项为一个或多个接口指定网络配置。您可以看到我们已经设置了配置eth0
接口所需的各种选项。您也可以指定 DHCP,例如:
network --bootproto=dhcp --device=eth0 --onboot=on
在带有 Cobbler 的 CentOS 上,如果您使用特定的主机(使用cobbler system
命令创建的主机),您可以将特定的网络配置值传递给 Cobbler 系统配置。
$ sudo cobbler system edit --name=gateway.example.com --mac=00:0C:29:3B:22:46
--profile=centos-base --interface=eth0 --ip=192.168.0.1 --subnet=255.255.255.0 --
gateway=192.168.0.254 --hostname=gateway --bootproto=static
这里,我们指定了edit
命令来更改现有的 Cobbler 定义的系统,并将网络配置值传递给我们的系统。这将为接口eth0
定义一个静态网络配置。我们使用--static=1
选项指定引导协议是静态的;我们将为 DHCP 配置指定--static=0
。使用--interface=eth0
选项指定要配置的接口。
然后,在我们的 Kickstart 文件中,我们指定了 Cobbler 所谓的代码片段,而不是指定网络线路。
$SNIPPET('network_config')
当构建您的主机时,Cobbler 将您指定的网络配置传递给这个代码片段和它包含的一个模板。然后这被转换成适当的network
行,您的主机就配置好了。
Tip
这个片段是对 Cobbler 的片段系统的简单使用。您可以使用代码片段定义各种其他的动作,您可以在/var/lib/cobbler/snippets
目录中看到这些动作的选择,includ
使用我们在本节中使用的network_config
代码片段。你可以在sample.ks
文件中看到如何使用这些代码片段,也可以在 http://cobbler.github.io/manuals/2.8.0/3/5_-_Kickstart_Templating.html
和 http://cobbler.github.io/manuals/2.8.0/3/6_-_Snippets.html
找到如何使用模板和代码片段的说明。
磁盘和分区
您已经看到了 Kickstart 用来配置磁盘和分区的一个选项clearpart
,它清除主机上的分区。然后,您可以使用 part 选项在主机上配置分区,如下所示:
# Partition clearing information
clearpart --all --initlabel
part /boot --asprimary --bytes-per-inode=4096 --fstype="ext4" --size=150
part / --asprimary --bytes-per-inode=4096 --fstype="ext4" --size=4000
part swap --bytes-per-inode=4096 --fstype="swap" --size=512
Note
在 CentOS 上,您可以通过指定autopart
选项来创建类似的配置。autopart
选项自动创建三个分区。第一个分区是 1GB 或更大的根(/
)分区,第二个是交换分区,第三个是适合该架构的引导分区。一个或多个默认分区大小可以用part
指令重新定义。
您可以使用part
选项来创建特定的分区。在前面的代码中,我们首先创建了两个分区,/boot
和/
,都是 ext4。我们为/boot
分区指定了 150MB 的大小,为/
或根分区指定了 4000MB(或 4GB)的大小。我们还创建了一个大小为 512MB 的交换分区。
使用 CentOS 上的 Kickstart,我们可以创建软件 RAID 配置,例如:
part raid.01 --asprimary --bytes-per-inode=4096 --fstype="raid" --grow --ondisk=sda
--size=1
part raid.02 --asprimary --bytes-per-inode=4096 --fstype="raid" --grow --ondisk=sdb
--size=1
part raid.03 --asprimary --bytes-per-inode=4096 --fstype="raid" --grow --ondisk=sdc
--size=1
part raid.04 --asprimary --bytes-per-inode=4096 --fstype="raid" --grow --ondisk=sdd
--size=1
part raid.05 --asprimary --bytes-per-inode=4096 --fstype="raid" --grow --ondisk=sde
--size=1
raid / --bytes-per-inode=4096 --device=md0 --fstype="ext4" --level=5 raid.01 raid.02
raid.03 raid.04 raid.05
我们指定了五个 RAID 磁盘,每个磁盘使用其全部内容,如--grow
选项所示。要使用的相应磁盘由--ondisk
选项指定,这里的范围从sda
到sde
。最后,我们使用raid
选项将md0
RAID 磁盘指定为/
或根分区。
您还可以在自动安装过程中使用 LVM 创建分区。例如,在 CentOS 上,您可以像这样创建它们:
part /boot --fstype ext4 --size=150
part swap --size=1024
part pv1 --size=1 --grow
volgroup vg_root pv1
logvol / --vgname=vg_root --size=81920 --name=lv_root
在前面的示例中,我们在磁盘的剩余部分创建了一个 150MB 的引导分区、一个 1GB 的交换分区和一个名为pv1
的物理卷,使用--grow
选项来填充磁盘的剩余部分。然后,我们创建了一个名为vg_root
的 80GB LVM 逻辑卷。
包装管理
使用 Kickstart,您可以指定要安装的软件包。在 CentOS 上,您可以指定一个以%packages
开头的部分,然后是您想要安装的软件包组或软件包的列表。
%packages
@ Administration Tools
@ Server Configuration Tools
@ System Tools
@ Text-based Internet
dhcp
我们指定一个 at 符号(@
)、一个空格,然后指定我们想要安装的软件包组的名称,例如管理工具。我们也可以通过列出名字来指定单个的包,不带符号和空格,就像我们在这里对dhcp
包所做的那样。
Ubuntu 使用类似的设置。
%packages
@ kubuntu-desktop
dhcp-client
这里我们已经安装了 Kubuntu-Desktop 包组和dhcp-client
包。详见 http://pykickstart.readthedocs.io/en/latest/kickstart-docs.html#chapter-7-package-selection
。
Note
我们将在第八章中讨论包组。
安装前和安装后
您可以在 Kickstart 安装您的主机之前和之后运行脚本。预运行脚本在解析 Kickstart 配置文件之后、配置主机之前运行。任何预运行脚本都在 Kickstart 文件的末尾指定,并以行%pre
为前缀。
每个%post
和%pre
段必须有一个对应的%end
。
完成配置并安装主机后,将触发运行后脚本。它们也应该在 Kickstart 文件的末尾指定,并以一个%post
行作为前缀。这是我们的sample.ks
配置文件中的%post
部分:
%post
$SNIPPET('post_install_kernel_options')
$SNIPPET('post_install_network_config')
%end
这里我们指定了两个配置内核和网络选项的 postrun Cobbler 代码片段。
这个运行后脚本空间对于运行任何所需的安装应用程序或脚本非常有用。
压
Preseed 是 Debian 安装自动化工具。它比 Kickstart 更不透明,但它执行相同的自动安装功能。
为了提供一些上下文,您看到的每一行d-i
都是对 Debian 安装程序的调用。该文件的格式与传递给debconf-set-selection
命令的指令相同。它采取以下形式:
<owner> <question name> <question type> <value>
因此,为您的系统设置语言环境类似于下面这样:
d-i debian-installer/locale string en
主人是debian-installer
,问题是debian-installer/locale
,类型是string
,数值是en
,或者英文。
安装源
通过live-installer
问题选择 Cobbler 作为初始安装源。
d-i live-installer/net-image string http://$http_server/cobbler/links/$distro_name/install/filesystem.squashfs
在安装过程中,您可以设置您的 apt 库。这将你的 apt 源指向了 Ubuntu 镜像。
d-i mirror/country string manual
d-i mirror/http/hostname string archive.ubuntu.com
d-i mirror/http/directory string /ubuntu
d-i mirror/http/proxy string
您可以使用这些设置选择不同的 apt 池,以从 backports 等获得可用的包:
#d-i apt-setup/restricted boolean true
#d-i apt-setup/universe boolean true
#d-i apt-setup/backports boolean true
您可以取消注释您想要的池。
键盘、语言和时区
设置键盘和语言可能是一个耗时的过程,但安装程序不会。您可以预先选择以下选项:
d-i debian-installer/locale string en
d-i debian-installer/country string AU
d-i debian-installer/locale string en_AU.UTF-8
d-i debian-installer/language string en
d-i console-setup/ask_detect boolean false
d-i console-setup/layoutcode string us
d-i console-setup/variantcode string
d-i keyboard-configuration/layoutcode string us
d-i clock-setup/ntp boolean true
d-i clock-setup/ntp-server string ntp.ubuntu.com
d-i time/zone string UTC
d-i clock-setup/utc boolean true
这里我们设置了地区和国家,我们禁用了键盘提示来询问我们的选择,并回答了所有关于键盘布局的问题。然后,我们为我们的时钟启用 NTP,并将它们的时间设置为 UTC。
管理用户
有了 Cobbler 和 Preseed,我们可以启用 root 用户,这是 Ubuntu 通常做不到的。
d-i passwd/root-login boolean true
d-i passwd/root-password-crypted password $default_password_crypted
d-i passwd/make-user boolean false
因此,当您构建主机时,您需要以 root 用户身份登录。为了保持您熟悉的设置,您可以添加一个用户ubuntu
,或者在安装结束时添加一个用户SNIPPET
。
#d-i passwd/user-fullname string Ubuntu User
#d-i passwd/username string ubuntu
防火墙和网络
你可以用 Preseed 以任何适合你的方式建立网络,但是你不能做任何防火墙配置。如果需要,您还可以在 Cobbler 的安装后脚本中添加防火墙配置。
# IPv4 example
#d-i netcfg/get_ipaddress string 192.168.1.42
#d-i netcfg/get_netmask string 255.255.255.0
#d-i netcfg/get_gateway string 192.168.1.1
#d-i netcfg/get_nameservers string 192.168.1.1
#d-i netcfg/confirm_static boolean true
您可以在网络设置中设定静态 IP 或允许 DHCP。
磁盘和分区
目前,磁盘分区可能相当复杂。这里我们只是创建一个简单的 LVM 分区设置:
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-lvm/confirm boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
包装管理
使用 Preseed,您可以指定要安装的软件包。对于分组包,您可以使用tasksel
(或者任务选择所有者)来多选一个包组——就像ubuntu-desktop
一样。
tasksel tasksel/first multiselect ubuntu-desktop
对于单个包,您可以只使用以下内容:
d-i pkgsel/include string openssh-server
如果您愿意,可以选择多个包。
d-i pkgsel/include string openssh-server build-essential
马斯河
我们将向您简要介绍 MAAS 工具——或者 Ubuntu 的金属即服务。你可能还记得,当我们第一次创建我们的 Ubuntu 安装时,我们在初始闪屏中看到了“安装 MAAS 机架或区域服务器”。这是一个 Ubuntu 服务,用于管理裸机或物理计算机,就像管理虚拟计算机一样。该服务能够安装裸机 Ubuntu 和 CentOS 服务器,以及 RHEL 和 Windows。
MAAS 服务致力于在您的金属服务器上提供云功能。这个工具的技术基础并不新。它使用 PXE、小文件传输协议(TFTP)和 Debian Preseed 来构建节点。它旨在管理物理数据中心,并且具有很强的可扩展性,可以管理数千个节点。
一旦 MAAS 启动了主机,您就可以使用 Juju(一个 Ubuntu 供应框架)来供应它。这可以将软件、用户帐户和其他资源安装到您的服务器上,或者您可以使用其他配置服务,如 Puppet 或 Ansible。你可以在这里了解更多: https://maas.ubuntu.com/docs/juju-quick-start.html
。
你可以在这里看到它是如何工作的: https://maas.io/how-it-works
。这里有个快速入门教程: https://maas.io/get-started
。你甚至可以在这里试着运行一个流浪测试套件: https://github.com/battlemidget/vagrant-maas-in-a-box
。
结构管理
我们已经在本书中向您展示了配置 Linux 服务器包括相当多的任务,例如,配置主机;创建用户;以及管理应用程序、守护程序和服务。这些任务可以在一个主机的生命周期中重复多次,以便添加新的配置或补救由于错误、熵或开发而改变的配置。它们也很耗时,而且通常不能有效利用时间和精力。
对此问题通常的第一反应是尝试自动化任务,这导致了定制脚本和应用程序的开发。以这种特殊方式开发的脚本很少被发布、记录或重用,所以相同的工具被反复开发。这些脚本也往往不能很好地扩展,并且经常需要频繁维护。
配置管理工具可以高效地自动化这些任务,并为您的主机提供一致且可重复的生命周期。我们将向您展示如何使用这些工具之一 Puppet 来自动化您的配置。
介绍木偶
Puppet ( https://puppet.com/
,前身为 Puppetlabs)是一个开源的配置管理工具,在大多数安装中依赖于客户机/服务器部署模型。Puppet 可以作为开源或商业企业产品使用。企业产品结合了多个 Puppet 开源产品,并提供了一个企业仪表板来协调和配置您的资源以及商业支持协议。开源版本没有花哨的企业功能,是社区支持的,并使用 Apache 2.0 许可证进行许可。我们将向您概述 Puppet 以及如何使用它来配置您的环境和主机。
Note
在撰写本文时,Puppet world 正在从 3.x 版向 4.x 版转变。3.x 版将于 2016 年底停产,因此您应该使用 4.x 版。4 版与 3 版在本质上是不同的。撰写本文时的最新版本是 v4.8。
当使用 Puppet 时,安装并配置称为 Puppet masters 的中央服务器。然后,客户端软件安装在您要管理的目标主机(称为节点)上。当一个节点连接到傀儡主节点时,该节点的配置清单在主节点上编译,发送到该节点,然后由傀儡代理应用到该节点上。
Tip
还有另一种方法将清单应用于节点,这种方法被称为无主控 puppet,或称为puppet apply
。它不依赖于傀儡主架构和证书签名。
为了提供客户机/服务器连接,Puppet 使用在 TCP 端口 8140 上的 HTTPS 上运行的 RESTful web 服务。在 4.x 版中,Puppet 服务器是一个基于 JVM 的应用程序。为了提供安全性,使用内部生成的自签名证书对会话进行加密和验证。每个傀儡客户机生成一个自签名证书,然后在傀儡主机上进行验证和授权。
此后,每个客户端都会联系服务器,以确认其配置是最新的。默认情况下,每 30 分钟联系一次服务器,但这个时间间隔是可定制的。如果有新的配置可用,或者节点上的配置已经更改,则重新编译配置清单,然后应用到客户端。这样,如果客户机上的任何现有配置发生了变化,它将根据服务器的预期配置进行修正。任何活动的结果都会被记录下来并传送给主机。
Puppet 工作原理的核心是一种语言,它允许您清晰地表达您的配置。这被称为 Puppet 声明脚本语言(Puppet DSL)。您的配置组件被组织到称为资源的实体中,这些实体又可以组合到集合中。资源包括以下内容:
- 类型
- 标题
- 属性
清单 19-5 显示了一个简单资源的例子。
file { '/etc/passwd':
owner => 'root',
group => 'root',
mode => '0644',
}
Listing 19-5.A Puppet Resource
清单 19-5 中的资源是一个file
类型的资源。file
资源配置被管理文件的属性。在这种情况下,它配置了/etc/passwd
文件,并将其所有者和组设置为 root 用户,将其权限设置为 0644。
Tip
有一个编写木偶清单的风格指南,你应该尽早熟悉它。这里可以看到: https://docs.puppet.com/guides/style_guide.html
。
资源类型告诉 Puppet 您正在管理哪种资源——例如,user
和file
类型分别用于管理节点上的用户和文件操作。默认情况下,Puppet 附带了许多资源类型,包括管理文件、服务、包、cron
作业和存储库等类型。
Tip
在 https://docs.puppet.com/puppet/4.8/type.html
可以看到内置资源类型的完整列表。你也可以用 Ruby 编程语言开发你自己的类型。
资源的标题将它标识为 Puppet。每个标题由资源类型的名称(如file
)和资源的名称(如/etc/passwd
)组成。这两个值组合在一起构成资源的标题(例如,File['/etc/passwd']
)。
Note
在资源标题中,资源类型的名称大写(File
),资源的名称封装在块括号和单引号(['/etc/passwd']
)中。
这里的名字/etc/passwd
也告诉 Puppet 要管理的文件的路径。Puppet 管理的每个资源必须是惟一的——例如,只能有一个名为File['/etc/passwd']
的资源。
资源的属性描述被管理的配置细节,例如定义特定用户和该用户的属性(例如,用户所属的组或用户主目录的位置)。在清单 19-5 中,我们管理文件的所有者、组和模式(或权限)属性。每个属性用=>
符号与它的值分开,并用逗号结束。
Puppet 还使用了集合的概念,它允许您将许多资源组合在一起。例如,Apache 这样的应用程序由一个包、一个服务和许多配置文件组成。在 Puppet 中,这些组件中的每一个都被表示为一个资源(或多个资源),然后被收集并应用到一个节点。在本章的后面,我们将会看到其中的一些集合类型。
安装木偶
让我们从安装木偶开始。对于 Puppet,客户机和服务器的安装略有不同,我们将向您展示如何分别安装。
傀儡主人需要至少 3Gb 的内存,以便 JVM 和操作系统有足够的空间。另外,TCP 端口 8140 需要在傀儡主机上打开。
CentOS 装置
使用 Puppet 的最新包,当我们安装服务器时,默认情况下会安装所有需要的包。在 CentOS 上,在服务器和客户机上,您都需要为基于 Red Hat 的机器添加 Puppet 存储库。
$ sudo yum install -y https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm
有几个组件组成了 Puppet 生态系统,比如 Facter,这是一个用于发现节点上的系统事实的工具。系统事实包括操作系统、IP 地址和任何自定义事实。另一个是 Hiera,这是一个用于声明 Puppet 配置数据的键/值查找数据库。最后,还有 MCollective,一个用于管理 Puppet 节点的编排工具。
在主服务器上,您安装puppetserver
,这将安装facter
、hiera
、agent
以及来自傀儡库的其他所需的包。
$ sudo yum install puppetserver
在 Puppet 节点或客户机上,我们可以自行安装puppet-agent
包,它将包含或需要它运行所需的所有内容。
$ sudo yum install –y puppet-agent
和前面一样,这当然需要首先安装 YUM 存储库。
Ubuntu 安装
在 Ubuntu 上,我们再次安装 apt 库,然后在主服务器上安装puppetserver
,这将关闭所有必要的 Puppet 组件,如 Facter、Hiera 和代理。
在服务器或主服务器上,我们需要这样做:
$ wget https://apt.puppetlabs.com/puppetlabs-release-pc1-xenial.deb -O xenial.deb && sudo dpkg -i xenial.deb
$ sudo apt-get update
$ sudo apt-get install -y puppetserver
On the client, you need the following:
$ sudo apt-get install –y puppet-agent
现在,您的系统上已经安装了所有必需的组件。
配置木偶
我们将通过设置我们的傀儡师来开始配置傀儡。我们对傀儡主人的配置将位于/etc/puppetlabs
目录下。
正如我们已经说过的,Puppet 有几个组成生态系统的组件。Puppet 的主体服务器配置文件位于/etc/puppetlabs/puppetserver/conf.d/puppetserver.conf
。您很少需要编辑这个文件,但是它有各种路径设置和使用的 TLS 密码。
另一个主配置文件位于代理和主服务器上。它位于/etc/puppetlabs/puppet/puppet.conf
。您可以在[service]
部分定义全局配置设置或特定业务设置,如[main]
、[master]
、[agent]
或[user]
。
配置主服务器
通常情况下,主人的/etc/puppetlabs/puppet/puppet.conf
看起来像这样:
[main]
certname = puppetmaster.example.com
server = puppet
environment = production
runinterval = 30m
strict_variables = true
[master]
dns_alt_names = puppetmaster,puppet,puppet.example.com
[main]
部分包含主服务器和代理服务器的默认值。这里我们确定certname
,它将是我们在启动时生成的 TLS 证书中指定的通用名称。这与dns_alt_names
设置有关,它提供了代理可以用来验证傀儡主人的替代 DNS 名称。server = puppet
是这个傀儡代理将尝试连接的傀儡主人的名字。你可以看到这与dns_alt_names
中的一个相匹配。
当傀儡代理连接到傀儡主服务器时,它们可以指定应该用来收集其目录的环境。这通常用于测试您的傀儡代码的版本控制系统(VCS)分支,或者可以用于为多个组织多宿主您的傀儡主人。
不要犯这样的错误,即创建反映您的主机可能扮演的角色的环境。也就是说,不要有用于开发、UAT、试运行和生产的环境,并为这些环境分配相关的主机。将你所有的主机视为产品,并处理这些主机在傀儡清单中可能扮演的不同角色和角色,会更容易。这通常会导致您的 Puppet 代码在系统和 VCS 分支之间产生可怕的分歧。无论如何都要创建一个环境来测试您的 Puppet 代码,但是要尽快将它投入到产品分支中。使用 Hiera 和“角色和概要”模式来实现这一点。参见 https://docs.puppet.com/hiera/3.2/
和 https://docs.puppet.com/pe/2016.4/r_n_p_full_example.html
。
runinterval
是每次木偶运行之间的时间间隔,也就是说,代理将调用木偶大师的目录。strict_variables
意味着当引用未知变量时解析会产生错误。
在[master]
部分,我们定义了傀儡主服务器的设置。除了dns_alt_names
值,我们不打算在这里设置任何东西。可能属于这里的设置是codedir
,Puppet 将在这里寻找我们将要编写的 Puppet 代码或清单。然而,我们将采用默认值,这意味着我们的codedir
将是/etc/puppetlabs/code
。
在这里,您将设置使用 PuppetDB 的报告设置和配置。使用 PuppetDB 是一个很好的主意,因为它允许您从多个节点收集数据,从而进行复杂的编目,但这超出了本练习的范围。更多详情请看这里: https://docs.puppet.com/puppetdb/
。
我们建议您为您的傀儡主机(例如puppet.example.com
)创建一个 DNS CNAME
,或者将其添加到您的/etc/hosts
文件中。
# /etc/hosts
127.0.0.1 localhost
192.168.0.1 au-mel-ubuntu-1 puppet puppetmaster puppet.example.com puppetmaster.example.com
Note
我们将在第十章中讲述如何创建CNAME
s。
编写清单
我们将把我们的实际配置存储在目录/etc/puppetlabs/code/environments/production
下的目录manifests
中。在该目录中,您很可能会看到以下目录和文件:
ll /etc/puppetlabs/code/environments/production/
-rw-r--r-- 1 root root 879 Dec 5 23:53 environment.conf
drwxr-xr-x 2 root root 4096 Dec 5 23:53 hieradata/
drwxr-xr-x 2 root root 4096 Dec 5 23:53 manifests/
drwxr-xr-x 2 root root 4096 Dec 5 23:53 modules/
Puppet 服务器将读取一个environment.conf
文件,以确定这个环境需要的具体设置。hieradata
目录将包含 Hiera 数据库,用于变量查找。清单目录是木偶寻找site.pp
文件的地方。这个文件用于创建我们的配置的根目录。目录是我们安装木偶模块的地方。模块是执行一组特定任务的木偶文件的集合。我们稍后会更详细地解释它们。
manifests
目录需要包含一个名为site.pp
的文件,它是我们配置的根目录。让我们现在就创建它。
$ sudo touch /etc/puppetlabs/code/environments/production/manifests/site.pp
Note
包含配置的清单文件的后缀为.pp
。
我们还将在我们的production
目录的基础上再创建三个目录,第一个是site
,在那个目录中还有profile
和role
。
$ sudo mkdir -p /etc/puppetlabs/code/environments/production/site/{profile,role}
site
目录实际上是另一个模块,像role
一样,将用于包含这个特定环境的特定角色和配置文件信息。我们将需要编辑我们的environment.conf
文件,让木偶看到这些。我们需要在modulepath
指令中添加以下内容:
$ sudo vi /etc/puppetlabs/code/environments/production/environment.conf
modulepath = ./sites:./modules:$basemodulepath
Modules
在 Puppet 中,您可以创建包含资源、类和定义集合的*.pp
文件,但是 Puppet 有另一种更复杂的集合类型,称为模块。您可以将类、定义、模板、文件和资源的集合组合到模块中。模块是配置的可移植集合;例如,一个模块可能包含配置 Postfix 或 Apache 所需的所有资源。
您可以在此阅读如何使用模块:
另外,在 https://forge.puppet.com/
站点上有大量用户贡献的模块。其他人几乎肯定已经编写了一个模块来配置您可能需要的服务或应用程序,在许多情况下,您可以下载并重用这些模块,而不必自己编写模块。要安装这些模块,可以使用 r10k ( https://docs.puppet.com/pe/latest/r10k.html
)来帮助管理模块的安装。
您也可以使用puppet
module
命令来管理模块。puppet module
命令将为您创建骨架模块,从傀儡锻造厂搜索现有模块,安装模块,并为您管理模块的生命周期。例如,您可以像这样从 Forge 安装一个模块:
$ puppet module search apache
NAME DESCRIPTION AUTHOR KEYWORDS
puppetlabs-apache Installs, configures, ... @puppetlabs apache web
example42-apache Puppet module for apache @example42 apache
herculesteam-augeasproviders_apache Augeas-based apache ty... @herculesteam types apache
...
$ sudo /opt/puppetlabs/bin/puppet module install puppetlabs-apache
Notice: Preparing to install into /etc/puppetlabs/code/environments/production/sites ...
Notice: Created target directory /etc/puppetlabs/code/environments/production/sites
Notice: Downloading from https://forgeapi.puppet.com ...
Notice: Installing -- do not interrupt ...
/etc/puppetlabs/code/environments/production/sites
└─┬ puppetlabs-apache (v1.11.0)
├── puppetlabs-concat (v2.2.0)
└── puppetlabs-stdlib (v4.14.0)
你可以看到,我们首先搜索了一个apache
模块,找到了 900 多个具有关键字apache
的模块。然后我们安装了puppetlabs-apache
模块。然后,您可以阅读这个模块的文档并创建您的 Apache 服务( https://forge.puppet.com/puppetlabs/apache
)。
你可以在 https://docs.puppet.com/puppet/4.8/modules_fundamentals.html#writing-modules
阅读更多关于如何创建你自己的模块以及它们是如何构成的。
我们现在将创建一个节点定义,以便我们可以将每个节点与一个配置文件相匹配。配置文件可以被描述为它是哪种主机。相比之下,角色就像是主人提供的服务。比如我们可以有一个web_server
的角色。我们可以有一个 UAT 的侧面图web_server
。也就是说,它是一个 web 服务器,拥有 UAT 人需要的东西,可能使它与我们的生产 web 服务器略有不同——不同的数据库后端配置,不同的身份验证要求,等等——但本质上它仍然有一个 web 服务器的角色,可以在其上部署我们的应用程序。
这可能需要一点时间来理解,对于如何将这个结构实现到你的木偶清单中,没有完美的答案。各个公司将根据最适合其公司的实践来进行不同的实现。有关角色和档案模式的更多讨论,请参见 https://www.youtube.com/watch?v=RYMNmfM6UHw
。
我们将通过定义我们的site.pp
文件来继续我们的配置,如清单 19-6 所示。
sudo vi /etc/puppetlabs/code/environments/production/manifests/site.pp
node /^web\d+\.example\.com$/ {
include profile::web_server
}
Listing 19-6.The site.pp File
清单 19-6 中的节点声明是傀儡主人在节点“登记”时知道如何处理它们的方式这里我们使用了正则表达式,但是您也可以使用如下计划字符串:
node 'web1.example.com' { ... }
node 'web1.example.com', 'web2.example.com' { ... }
在我们的声明中,我们说任何使用 TLS 证书名称登记的节点都以web
( ^
web
)开始,后面有一个或多个数字(^web
\d+
),然后是域名(\.example\.com
),仅此而已(\.com
$
)。然后我们为这个节点提供profile::web_server
概要文件。
当没有匹配时,有一个特殊的节点声明,即default
节点定义。
node default { ... }
您可以使用这个默认的节点声明来通知人们某个节点没有定义,或者应用一组默认的安全限制。如果没有default
节点,也没有节点的匹配定义,Puppet 将无法为该节点编译清单。
Note
你可以在这里找到更多关于定义节点的信息: https://docs.puppet.com/puppet/latest/lang_node_definitions.html
。
用 RAL 启动傀儡服务器
这里有一个巧妙的技巧。您可以使用 Puppet resource 命令来启动您的 Puppet master 服务器(puppetserver
)。木偶资源命令允许你直接与木偶资源抽象层(RAL)交互。RAL 是 Puppet 交互和管理系统的方式。使用 Puppet 资源,我们将启动puppetserver
,并使它像这样在引导时启动:
sudo /opt/puppetlabs/bin/puppet resource service puppetserver ensure=running enable=true
我们还没有描述 Puppet 是如何管理资源的,您很快就会对这个命令的作用有更深的理解,但是简单地说,它的作用如下:
- 启动服务(
ensure=running
) - 进行必要的更改以在引导时启动服务(
enable=true
) - 使用任何底层系统来启动服务(
service puppetserver
)
在我们的例子中,它将使用systemctl
命令(start
和enable
)。您可以在 CentOS、Ubuntu 或任何其他支持的系统上运行这个命令,它将启动puppetserver
进程。如果你在 Mac 上启动 Apache 服务,它会使用launchctl
——它使用任何适合它运行的系统的东西。
我们可以看到它是否已经开始使用正常的systemctl
命令,我们可以在这里看到日志:
$ sudo journalctl -u puppetserver -f
-- Logs begin at Tue 2016-12-20 09:24:04 UTC. --
Dec 21 09:25:29 puppetmaster systemd[1]: Starting puppetserver Service...
Dec 21 09:25:29 puppetmaster puppetserver[4877]: OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
Dec 21 09:26:30 puppetmaster systemd[1]: Started puppetserver Service.
此外,可以在/var/log/puppetlabs
中找到正在运行的服务器日志。
我们可以用它来关注下一部分的任务。
连接我们的第一个客户
一旦配置并启动了 Puppet master,就可以配置并启动您的第一个客户机了。在客户端,正如我们前面提到的,您需要使用您的发行版的包管理系统安装puppet-agent
包。我们将在web.example.com
主机上安装一个客户端,然后连接到我们的puppet.example.com
主机。这个安装还将创建一个带有puppet.conf
配置文件的/etc/puppetlabs/puppet/
目录。
当连接我们的客户机时,我们首先希望从命令行运行 Puppet 客户机,而不是作为服务运行。这将允许我们在连接时看到发生了什么。傀儡客户端二进制文件被称为puppet agent
,您可以在清单 19-7 中看到一个到主客户端的连接。
web$ sudo /opt/puppetlabs/bin/puppet agent --server puppet.example.com --test --waitforcert 15
Info: Creating a new SSL key for web1.example.com
Info: Caching certificate for ca
Info: csr_attributes file loading from /etc/puppetlabs/puppet/csr_attributes.yaml
Info: Creating a new SSL certificate request for web1.example.com
Info: Certificate Request fingerprint (SHA256): 3E:D9:02:08:98:79:FB:8C:40:65:75:4E:15:7C:51:89:4C:14:25:90:16:2A:DB:29:D6:3C:F4:82:64:7E:C8:62
Info: Caching certificate for ca
Notice: Did not receive certificate
Listing 19-7.Puppet Client Connection to the Puppet Master
在清单 19-7 中,我们执行了带有许多选项的puppet agent
二进制文件。第一个选项,- server
,指定要连接的傀儡主机的名称或地址。我们也可以在客户端的/etc/puppetlabs/puppet/puppet.conf
配置文件的主要部分指定这一点。
[main]
server=puppet.example.com
--test
选项在前台运行 Puppet 客户机,并阻止它作为守护进程运行,这是默认行为。--test
通常是错误的,人们认为它只是“测试”木偶的运行,并没有破坏性。这个被错误命名的选项实际上是onetime
、verbose
、no-daemonize
、no-usecacheonfailure
、detailed-exitcodes
、no-splay
、show_diff
和no-use_cached_catalog
的元参数。如果你想要一个非破坏性的木偶运行,你需要指定--noop
。
Tip
--debug
选项提供对故障排除有用的进一步输出。
在清单 19-7 中,您可以看到我们的连接的输出。客户端创建了一个证书签名请求和一个私钥来保护我们的连接。Puppet 使用 TLS 证书来验证主服务器和客户端之间的连接。客户端现在正在等待主服务器签署其证书并启用连接。此时,客户端仍在运行并等待签名的证书。它将继续每 15 秒检查一次签名证书,直到它收到一个或被取消(使用 Ctrl+C 等)。
Note
您可以像我们一样使用- waitforcert
选项来更改 Puppet 客户机等待的时间。您可以指定不等待证书的时间(以秒为单位)或 0。
现在在 master 上,我们需要签署证书。我们使用puppet cert
命令来完成这项工作。
puppet$ sudo /opt/puppetlabs/puppet/bin/puppet cert list
"web1.example.com" (SHA256) 3E:D9:02:08:98:79:FB:8C:40:65:75:4E:15:7C:51:89:4C:14:25:90:16:2A:DB:29:D6:3C:F4:82:64:7E:C8:62
--list
选项显示所有等待签名的证书。然后,我们可以使用sign
选项签署我们的证书。您可以使用证书指纹来验证您签署的是正确的证书。
puppet$ sudo /opt/puppetlabs/puppet/bin/puppet cert sign web1.example.com
Signing Certificate Request for:
"web1.example.com" (SHA256) 3E:D9:02:08:98:79:FB:8C:40:65:75:4E:15:7C:51:89:4C:14:25:90:16:2A:DB:29:D6:3C:F4:82:64:7E:C8:62
Notice: Signed certificate request for web1.example.com
Notice: Removing file Puppet::SSL::CertificateRequest web1.example.com at '/etc/puppetlabs/puppet/ssl/ca/requests/web1.example.com.pem'
Note
您可以使用puppet cert sign --all
命令签署所有等待证书。
在客户端上,我们签署了证书后,应该会看到以下条目:
Notice: Did not receive certificate
Info: Caching certificate for web1.example.com
Info: Caching certificate_revocation_list for ca
Info: Using configured environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Error while evaluating a Function Call, Could not find class ::profile::web_server for web1.example.com at /etc/puppetlabs/code/environments/production/manifests/site.pp:2:3 on node web1.example.com
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run
客户端现在已经通过了主服务器的身份验证,但是我们有一个错误,没有应用任何东西。
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Error while evaluating a Function Call, Could not find class ::profile::web_server for web1.example.com at /etc/puppetlabs/code/environments/production/manifests/site.pp:2:3 on node web1.example.com
Warning: Not using cache on failed catalog
Error: Could not retrieve catalog; skipping run
错误相当详细。我们预料到了这个错误,所以让我们看看它告诉我们什么。它说在/etc/puppetlabs/code/environments/production/manifests/site.pp
的第 2 行我们找不到web1.example.com
的::profile::web_server
类。查看site.pp
文件的第 2 行,我们看到以下内容:
include profile::web_server
我们已经告诉它包含一个我们还没有创建的概要文件。我们必须创造它。让我们接下来做那件事。
Tip
在错误中,您是否注意到::profile
在::
之前?这表明错误在 Puppet 的顶层范围内。
创建我们的第一个配置
现在我们的客户端已经连接上了,我们将为它添加一些配置。在 Puppet master 上,我们需要添加我们的profile
模块,并添加一些配置以应用到我们的客户端。
模块应具有以下结构:
modulename/
|- manifests
|- init.pp
|- files
|- templates
至少,你需要manifests
目录;您可能会看到有更多目录的其他模块,比如spec
和lib
,分别用于测试和模块代码。
我们已经在/etc/puppetlabs/code/environments/production/sites
中创建了profile
模块目录。让我们在profile
目录下创建一个manifests
文件。在这个目录中,我们将创建一个名为init.pp
的文件。该文件在技术上不是必需的,并且不会保存任何配置。您可以在清单 19-8 中看到这个文件的内容。
class profile {
}
Listing 19-8.Our init.pp Configuration
它只是一个空的傀儡文件。这是木偶文件的标准格式。class
声明是一个傀儡类型。profile
是一个标题。那么该类类型期望该类中的木偶代码在花括号{...}
之间。木偶语言基础见 https://docs.puppet.com/puppet/4.8/lang_resources.html
。
现在,在配置文件目录中,我们将创建我们的web_server.pp
文件。傀儡主机自动加载器,寻找和加载傀儡文件的机制,当它看到include profile::web_server
时,将首先在它的模块路径中寻找目录概要文件,然后在其中的manifests
目录中寻找。然后,它将加载所有的*.p
p 文件,直到它找到像这里声明的类profile::webserver { ... }
指令:
$ sudo vi /etc/puppetlabs/code/environments/production/sites/profile/manifests/web_server.pp
class profile::web_server {
}
在这个文件中,在{...}
之间我们将声明一个资源。这个资源叫做notify
,一个资源声明如下:
notify { "profile::webserver – loaded": }
notify
是资源类型。"profile::webserver – loaded":
就是标题。它所做的是在 Puppet 运行的运行时日志中打印一条消息。与所有资源类型一样,您可以添加属性,notify
可以获得一个name
、message
和withpath
属性。所以,你可以这样写:
notify { "profile::webserver – loaded":
name => 'a name',
message => 'this is our message'
}
请随意尝试。你会在下面的链接中找到所有的资源及其属性: https://docs.puppet.com/puppet/latest/type.html
。保存该文件,让我们再次在web1.example.com
上运行傀儡代理。
$ sudo /opt/puppetlabs/bin/puppet agent --server puppet.example.com --test
Info: Using configured environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for web1.example.com
Info: Applying configuration version '1482320904'
Notice: profile::webserver – loaded
Notice: /Stage[main]/Profile::Web_server/Notify[profile::webserver – loaded]/message: defined 'message' as 'profile::webserver – loaded'
Notice: Applied catalog in 0.01 seconds
在这里,您可以看到我们的notify
的输出。Notify 可能是调试 Puppet 代码的一种便捷方式,因为您可以打印出变量之类的东西来查看您的代码是否如您所期望的那样工作。
我们现在让我们的profile::web_server
模块工作了。现在,在这个简介中,我们将很快加入角色apache_web
。这可能开始听起来有点像俄罗斯娃娃,但这个想法是你从服务器的类型中抽象出指定逻辑。现在,让我们继续配置我们的角色。
创建以下目录:/etc/puppetlabs/code/environments/production/sites/role/manifests
。在那里我们创建了清单 19-9 中的文件。
$ sudo vi /etc/puppetlabs/code/environments/production/sites/role/manifests/apache_web.pp
class role::apache_web (
String $vhost_name,
String $vhost_doc_root,
Numeric $vhost_port
) {
include apache
apache::vhost { $vhost_name:
port => $vhost_port,
docroot => $vhost_doc_root,
}
}
Listing 19-9.The role::apache_web Class
这个类有更多的内容。在这里我们声明了类role::apache_web
,并且我们提供了一个参数列表,我们期望这个类在被使用时被提供。Puppet 中的类参数既可以在创建类时声明,也可以在为节点编译该类时从键/值数据库(如 Hiera)中查找。它们被声明在类名之后,括号内用逗号分隔。在这里阅读更多关于职业参数: https://docs.puppet.com/puppet/4.8/lang_classes.html
。
在 Puppet 中,您可以定义传入变量的数据类型,如果它们不是正确的类型,Puppet 将会出错。我们使用了字符串和数字,但是其他的,像布尔、数组和散列在这里描述: https://docs.puppet.com/puppet/4.8/lang_data_type.html
。
在清单 9-11 中,我们已经包含了apache
模块。这是一个模块,我们现在要使用前面描述的 Puppet module 命令来安装,它将是puppetlab/apache
模块。查看文档,我们可以通过给定名称、端口和doc_root
来声明一个虚拟主机,并且我们已经使用了提供给该类的参数。
apache::vhost
就是所谓的已定义资源类型。定义的资源类型是可以多次评估的普通 Puppet 代码块。你仍然不能有多个同名的已定义资源,所以你不能有两个声明apache::vhost {'
www.example.com
': }
,但是你可以在同一个清单中声明apache::vhost {'
www.example.com
': }
和apache::vhost {'api.example.com': }
就可以了。
现在让我们在傀儡服务器上安装puppetlabs-apache
模块。
$ sudo /opt/puppetlabs/puppet/bin/puppet module install puppetlabs-apache
Notice: Preparing to install into /etc/puppetlabs/code/environments/production/sites ...
Notice: Created target directory /etc/puppetlabs/code/environments/production/sites
Notice: Downloading from https://forgeapi.puppet.com ...
Notice: Installing -- do not interrupt ...
/etc/puppetlabs/code/environments/production/sites
└─┬ puppetlabs-apache (v1.11.0)
├── puppetlabs-concat (v2.2.0)
└── puppetlabs-stdlib (v4.14.0)
这已经安装了puppetlabs-apache
模块以及所需的concat
和stdlib
模块,并将它们安装到/etc/puppetlabs/code/environments/production/sites
目录中。你可以在这里看到那个模块的文档: https://forge.puppet.com/puppetlabs/apache
。
现在让我们再次进入profile::web_server
类,添加我们想要安装的虚拟主机。
sudo vi /etc/puppetlabs/code/environments/production/sites/profile/manifests/web_server.pp
class profile::web_server {
class { role::apache_web:
vhost_name => 'www.example.com',
vhost_doc_root => '/var/www/html',
vhost_port => 80
}
}
我们现在已经调用了类role::apache_
web
,并提供了我们在role::apache_web
类中需要的vhost_
参数。在这个概要文件中,你也可以包含一些将站点部署到/var/www/html
的傀儡代码。
应用我们的第一个配置
现在让我们在web1
上运行我们的傀儡代理,看看会发生什么。
Caution
下一个动作是破坏性的,将从 Puppet 节点中清除任何现有的 Apache 配置。
$ sudo /opt/puppetlabs/bin/puppet agent --server puppet.example.com --test
Info: Using configured environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Loading facts
Info: Caching catalog for web1.example.com
Info: Applying configuration version '1482323449'
Notice: profile::webserver – loaded
Notice: /Stage[main]/Apache/Package[httpd]/ensure: created
Info: /Stage[main]/Apache/Package[httpd]: Scheduling refresh of Class[Apache::Service]
Info: Computing checksum on file /etc/httpd/conf.d/README
Info: /Stage[main]/Apache/File[/etc/httpd/conf.d/README]: Filebucketed /etc/httpd/conf.d/README to puppet with sum 20b886e8496027dcbc31ed28d404ebb1
...
Notice: /Stage[main]/Apache::Service/Service[httpd]/ensure: ensure changed 'stopped' to 'running'
Info: /Stage[main]/Apache::Service/Service[httpd]: Unscheduling refresh on Service[httpd]
Notice: Applied catalog in 20.26 seconds
这是我们木偶表演的缩短版。您可以看到 Apache 包已经安装。我们移除了README
(并将其保存到一个文件桶中;见 https://docs.puppet.com/puppet/latest/man/filebucket.html
)并启动了 Apache 服务。
我们现在可以使用以下命令测试 Apache 服务是否启动并运行:
$ curl -I http://localhost
HTTP/1.1 200 OK
Date: Wed, 21 Dec 2016 13:07:46 GMT
Server: Apache/2.4.6 (CentOS)
Connection: close
Content-Type: text/html;charset=UTF-8
Puppet 代理的每次后续运行都将确保节点保持其当前配置。如果你再做一次木偶表演,什么都不会改变。现在自己试试吧。在我们的 web 服务器节点上,我们将删除在 Puppet 运行期间创建的/etc/httpd/conf.d/25-
www.example.com.conf
文件。然后,我们将在该节点上再次运行 Puppet。
$ sudo rm –f /etc/httpd/conf.d/25-www.example.com.conf
$ sudo /opt/puppetlabs/bin/puppet agent --server puppet.example.com --test
Info: Using configured environment 'production'
...
Notice: /Stage[main]/Role::Apache_web/Apache::Vhost[www.example.com]/Concat[25-www.example.com.conf]/File[/etc/httpd/conf.d/25-www.example.com.conf]/ensure: defined content as '{md5}6bee975590cb7b26b89cfd48d8d65bdf'
Info: Concat[25-www.example.com.conf]: Scheduling refresh of Class[Apache::Service]
Info: Class[Apache::Service]: Scheduling refresh of Service[httpd]
Notice: /Stage[main]/Apache::Service/Service[httpd]: Triggered 'refresh' from 1 events
Notice: Applied catalog in 1.71 seconds
在这里,您可以看到文件被替换,Apache 服务被重新启动。
Creating a Puppet Configuration
将现有配置转换为 Puppet 的最佳方式是从小处着手。选择一个函数或应用程序,比如sudo
或 SSH 守护进程,并将其配置管理从手动转换为使用 Puppet 进行管理。当这些功能稳定后,向 Puppet 配置添加额外的组件。完成这项任务的一个好方法是根据功能对主机进行分类。例如,我们的 www.example.co
m 主机可以运行许多服务,比如 Apache 或 Postfix,因此合乎逻辑的第一步是配置这些服务,然后慢慢添加该主机上也支持的附加功能。
为多台主机指定配置
我们仅仅触及了 Puppet 配置功能的表面,所以让我们看看如何将当前的配置扩展到多个客户机或节点。我们将演示如何区分两个客户机上的配置,并对每个客户机应用稍有不同的配置。
为了实现这种区分,我们将使用 Puppet 的合作伙伴工具 Facter。Facter 是一个系统清单工具,它返回关于您的主机的事实。我们可以使用 facter 二进制文件从命令行运行 Facter,看看它对我们的web1.example.com
客户机了解多少。
web1$ sudo /opt/puppetlabs/bin/facter -p
...
facterversion => 3.5.0
filesystems => ext2,ext3,ext4
identity => {
gid => 0,
group => ‘root’,
privileged => true,
uid => 0,
user => ‘root’
}
is_virtual => true
kernel => Linux
kernelmajversion => 3.10
kernelrelease => 3.10.0-327.28.3.el7.x86_64
kernelversion => 3.10.0
load_averages => {
15m => 0.05,
1m => 0.05,
5m => 0.04
}
...
我们已经向您展示了 Facter 中可用的一小部分事实,但是您可以看到它了解我们主机的很多信息,包括它的名称、网络信息、操作系统,甚至操作系统的版本。
那么,这对木偶有什么用呢?好吧,这些事实中的每一个都可以作为变量提供给 Puppet。Puppet 在应用任何配置之前运行 Facter,收集客户机的事实,然后将它们发送给 Puppet master 用于配置客户机。例如,hostname
事实在我们的 Puppet 配置中作为变量$hostname – or as $fact['hostname']
可用。让我们看看清单 19-10 中的一个例子。
More About Facter
事实以结构化事实或数组或散列的形式提供。这意味着您可以通过使用结构化语法的$facts
变量来使用它们,换句话说,$facts['hostname']
。你可以在这里了解更多: https://docs.puppet.com/puppet/4.8/lang_facts_and_builtin_vars.html
。
某些事实被称为可信事实。这些都是节点无法自报的事实。它们源自 TLS 证书。这在处理敏感数据时提供了某种程度的保证。您可以通过$trusted['fact_name']
访问它们。你可以在这里了解更多: https://docs.puppet.com/puppet/latest/lang_facts_and_builtin_vars.html#trusted-facts
。
更多信息可以访问 https://docs.puppet.com/facter/
。
Facter 也是高度可扩展的。使用少量的 Ruby 代码,您可以添加自己的事实,例如,为您的环境定制的信息。你可以在 https://docs.puppet.com/facter/3.5/fact_overview.html
了解如何添加这些自定义事实。
class sudo {
package { sudo:
ensure => 'present',
}
file { '/etc/sudoers':
source => [
"puppet:///modules/sudo/sudo_${hostname}",
"puppet:///modules/sudo/sudo_${os['family']}",
'puppet:///modules/sudo/sudo_default'
]
owner => 'root',
group => 'root',
mode => '0440',
}
}
Listing 19-10.Using Facts
这里我们定义了一个提供sudo
的类。您可以看到,在这个sudo
类中,我们定义了一个文件资源类型,它指定了该文件和一个源文件的基本安全要求,或者是一个傀儡主机的本地文件,当代理签入时,我们会将该文件发送到节点。
当代理签到时,傀儡师会在sudo
模块中搜索modules/sudo/files
目录中一个名为sudo_web1
的文件;如果找不到,它将寻找sudo_Redhat
(对于 CentOS 主机,sudo_Debian
对于 Ubuntu),如果找不到匹配,它将提供sudo_default
文件。
根据所连接的客户端,他们将获得适合自己的文件。但这不是事实的唯一用途。我们还可以使用事实来确定如何配置特定的节点,如清单 19-11 所示。
node default {
case $facts['os']['name'] {
'CentOS', 'RedHat': { include centos } # include the centos
/^(Ubuntu|Debian)$/: { include ubuntu } # include the ubuntu class
default: { include common } # include the common class
}
}
Listing 19-11.A Fact in a case Statement
这里我们创建了默认的节点定义,这是用于所有没有明确定义节点的节点的节点配置。在这个节点定义中,我们使用了 Puppet 语言的一个特性,一个case
语句。case
语句是许多编程语言通用的概念,它根据变量的值指定结果——在本例中,我们使用了$facts['os']['name']
事实,它包含客户端上运行的操作系统的名称(例如,CentOS 或 Red Hat 或 Debian 或 Ubuntu)。
Tip
Puppet 还有另外两种条件句:选择器和if/else
子句。你可以在 https://docs.puppet.com/puppet/4.8/lang_conditional.html
了解这些。
在清单 19-11 中,如果$facts['os']['name']
的值是CentOS
,那么centos
类就包含在这个客户端上。我们可以定义不止一种情况,只要它们是由逗号分隔的字符串。如果值为Ubuntu
,则包含ubuntu
类;这里你可以看到我们可以使用一个正则表达式来匹配 Ubuntu 或 Debian。最后一个值default
,是该值与redhat
或ubuntu
都不匹配时的行为。在这种情况下,common
类被应用到客户端。
在清单 19-12 中,我们使用了另一个傀儡条件,一个选择器。
$ssh_service = $facts['os']['name'] ? {
'CentOS' => 'sshd',
'Ubuntu' => 'ssh',
default => 'ssh',
}
service { $ssh_service:
ensure => ‘running’,
}
Listing 19-12.A Selector
在清单 19-12 中,我们引入了一个新的类型service
,它管理主机上的服务。我们将我们的service
资源命名为$ ssh_service
,并在它上面定义了变量。我们使用了一个叫做选择器的傀儡语言结构,结合$fact['os']['name']
事实来指定 SSH 服务的名称。这是因为在我们指定的每个操作系统上,SSH 守护进程的名称都不同。例如,在 CentOS 上 SSH 守护进程被称为sshd
,而在 Ubuntu 上它被称为ssh
。
title
属性使用$ssh_service
的值来指定在每个发行版上将调用什么守护进程。反过来,Puppet 使用这个来决定启动或停止什么服务。当$facts['os']['name']
的值既不是CentOS
也不是Ubuntu
时,使用default
值。
最后,ensure
属性已经被设置为running
以确保服务将被启动。我们可以将ensure
属性设置为stopped
,以确保它不会启动。
Note
Puppet 语言有很多有用的特性和很多不同的方式来表达你的代码。记得在 https://docs.puppet.com/guides/style_guide.html
查阅木偶风格指南。
相关资源
Puppet 中的资源也有关系的概念。例如,service
资源可以连接到安装它的包。使用这个,我们可以在安装新版本的包时触发服务的重启。这让我们可以做一些有用的事情。考虑清单 19-13 中的简单例子。
class ssh {
service { 'sshdaemon':
name => $facts['os']['name'] ? {
'CentOS' => 'sshd',
'Ubuntu' => 'ssh',
default => 'ssh',
},
ensure => 'running',
require => File['/etc/ssh/sshd_config'],
}
file { '/etc/ssh/sshd_config':
owner => 'root',
group => 'root',
mode => '0644',
source => 'puppet://modules/ssh/sshd_config',
notify => Service['sshdaemon'],
}
}
Listing 19-13.Requiring Resources
清单 19-13 显示了一个名为ssh
的新类,它包含了我们在清单 19-12 中创建的service
资源。我们已经创建了一个file
资源来管理/etc/ssh/sshd_config
文件。我们在这里创建的ssh
服务略有不同;我们在服务类型的name
属性上做了一个选择器。它的工作方式与清单 19-12 中的完全相同。除了service
资源中的require
和file
资源中的notify
之外,您已经看到了这些资源中几乎所有的属性。然而,这些不是普通的属性,它们被称为元参数。让我们看看每个元参数,看看它做什么。
require
元参数允许您建立与一个或多个资源的关系。您在require
元参数中指定的任何资源都将在此资源之前配置;因此,Puppet 将在处理Service['sshdaemon']
资源之前处理和配置File['/etc/ssh/sshd_config']
资源。这种方法确保在启动 SSH 守护进程服务之前安装适当的配置文件。您可以对包资源做类似的事情。
class httpd {
package { 'httpd':
ensure => 'present',
}
service { 'httpd':
ensure => 'running',
enabled => true,
require => Package['httpd'],
}
}
这里,必须在启动Service['httpd']
服务之前安装包资源Package['httpd']
。
Tip
我们还向Service['http']
资源添加了enabled
属性。当设置为true
时,该属性确保我们的服务在主机启动时启动(类似于使用systemctl enable
命令)。
在清单 19-13 中,我们还指定了另一个元参数,称为notify
。这个元参数已经被添加到File['/etc/ssh/sshd_config']
资源中。notify
元参数告诉其他资源关于资源的变化和更新。在这种情况下,如果File['/etc/ssh/sshd_config']
资源被更改(例如,如果配置文件被更新),那么 Puppet 将通知Service['sshdaemon']
资源,使其运行,从而重启 SSH 守护进程服务。
Tip
您可以构建的另外两个关系是subscribe
和before
。你可以在 https://docs.puppet.com/puppet/latest/metaparameter.html
看到这两个,也可以阅读其他有用的元参数。
使用模板
除了从 Puppet 文件服务器检索文件之外,您还可以利用模板函数在这些文件中应用特定的值来配置服务或应用程序。Puppet 模板使用一种叫做 EPP 的 Ruby 模板语言(参见 https://docs.puppet.com/puppet/latest/lang_template_epp.html
)。这是一个在 Puppet master 上编译时发生的函数,使用起来很简单,如清单 19-14 所示。
file { '/etc/ssh/sshd_config':
path => '/etc/ssh/sshd_config',
owner => 'root',
group => 'root',
mode => '0644',
content => epp('ssh/sshd_config.epp', { 'root_login' => 'no' }),
notify => Service['sshdaemon'],
}
Listing 19-14.Using Templates
在清单 19-14 中,我们使用了之前创建的相同的File['/etc/ssh/sshd_config']
资源,但是我们将源属性换成了content
属性。使用content
属性,而不是从 Puppet 文件服务器中检索文件,文件的内容从该属性中填充。该文件的内容可以用字符串指定,如下所示:
content => 'this is the content of a file',
或者,如清单 19-14 所示,我们可以使用一个叫做epp
的特殊木偶函数。为了使用 template 函数,我们指定一个模板文件,Puppet 用适当的值填充模板中的任何 EPP 代码,这些值已经作为散列传递给函数。清单 19-15 显示了一个简单的模板。
Port 22
Protocol 2
ListenAddress <%= $ipaddress %>
SyslogFacility AUTHPRIV
PermitRootLogin <%= $root_login %>
PasswordAuthentication no
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
X11Forwarding yes
Banner /etc/motd
Listing 19-15.sshd_config Template
我们在清单 19-15 中只使用了一部分 EPP 来指定我们的 SSH 守护进程<%= $ipaddress %>
的ListenAddress
。<%= $value %>
语法是如何在模板中指定变量的。这里我们指定 Puppet 应该将ListenAddress
设置为$ipaddress
变量的值。这个变量又是ipaddress
事实的值,它包含了eth0
接口的 IP 地址。我们还将{ 'root_login' => 'no' }
键/值作为散列值传入。这现在作为变量<%= $root_login %>
可用。
当我们现在连接一个应用了File['/etc/ssh/sshd_config']
资源的客户端时,客户端上的ipaddress
事实的值将被添加到模板中,并且root_login
将被评估为no
,然后在客户端上的/etc/ssh/sshd_config
文件中被应用。
您可以在 EPP 模板中执行各种各样的功能——不仅仅是指定变量。你可以在 https://docs.puppet.com/puppet/latest/lang_template_epp.html
阅读更多关于如何使用模板的细节。
您还可以使用 Ruby 的 ERB 模板语言探索更古老的 Puppet 模板风格。句法上类似 EPP 你可以在这里看到它的页面: https://docs.puppet.com/puppet/latest/lang_template_erb.html
。
更多木偶
在这一章中,我们几乎没有谈到木偶——还有很多要看的。在接下来的小节中,我们将描述一些我们没有涉及到的主题,您可以进一步探索以充分利用 Puppet。
功能
Puppet 还有一个函数集合。函数是有用的命令,可以在傀儡主人上运行来执行动作。您已经看到了两个函数:template
,我们用它来创建一个模板配置文件,以及include
,我们用它来为我们的节点指定类。还有许多其他函数,包括调用外部命令并返回结果的generate
函数,以及在主服务器上记录消息并用于测试配置的notice
函数。
您可以在 https://docs.puppet.com/puppet/latest/function.html
查看完整的功能列表。
报告
Puppet 能够报告发生在您的节点或客户机上的事件。结合 PuppetDB,您可以获得关于您的系统的大量报告;如果您有企业版,您将能够在 Puppet 仪表板中看到这些。您可以在 https://docs.puppet.com/puppet/latest/reporting_about.html
了解更多报道。
外部节点
正如您可能想象的那样,当您开始拥有大量节点时,您的配置会变得非常复杂。如果在清单中定义所有节点及其配置变得很麻烦,那么可以使用一个称为外部节点的特性来更好地扩展它。外部节点允许您将节点及其配置存储在外部源中。
ENC 作为一个命令在傀儡主机上运行,并返回一个描述任何特定节点清单的 YAML 文档。它可以来自任何来源,例如数据库。
您可以在 https://docs.puppet.com/guides/external_nodes.html
了解更多关于外部节点量词的信息。
记录您的配置
许多系统管理员的一大苦恼是文档,他们既需要编写文档,又需要保持文档的更新。然而,Puppet 对于如何为您创建的任何模块编写文档并希望通过 Puppet Forge 等网站发布到更广泛的社区有一些建议。你可以在 https://docs.puppet.com/puppet/latest/modules_documentation.html
阅读清单文档。
Puppet 故障排除
Puppet 有一个大而有用的社区以及大量的文档。从这些傀儡网站开始:
推荐以下书籍:
- 由斯潘塞·克鲁姆、威廉·范·赫韦林根、本·克罗、詹姆斯·特恩布尔和杰弗里·麦丘恩制作的《木偶》
- 由 Alessandro Franceschi 和 Jaime Soriano Pastor 制作的延长木偶(Packt,2016 年)
这些还有更多可以在这里找到: https://puppet.com/resources/books
。
Ansible 简介
Ansible 的方法与 Puppet 不同。其核心是开源的 Ansible 软件,可以协调大规模车队的供应。它最初是由迈克尔·德汉设计的,他也是写《补鞋匠》的人。Ansible Inc .是为 Ansible(塔式产品)的商业支持而成立的公司,已被 Red Hat 收购,该公司继续支持开源社区并提供基于订阅的商业支持服务。
用 Python 写的,它被设计成无代理的。它使用 SSH 作为其传输机制,这意味着没有像使用 Puppet 那样的证书管理;相反,您使用现有的 SSH 密钥管理来提供安全的传输。它的工作原理是通过 SSH 向目标服务器发送一个可解析的负载(一个剧本)。有效负载是一组在目标系统上执行的 Python 脚本。
Note
在撰写本文时,Ansible 目前支持 Python 的 2.6 和 2.7 版本。从 Ansible 版本 2.2 开始,它已经初步支持 Python 版本 3。
像 Puppet 一样,Ansible 可以安装文件和管理包以及许多其他资源,包括创建云资源。它通过调用剧本来实现这一点。可行的行动手册由连续的任务组成。当您逐步执行每个任务时,您就执行了一个模块。
模块是应该在目标节点上执行的动作。有大量的核心模块。它们被调用来管理文件、包和服务;他们还可以管理云服务基础设施。Ansible 自带的核心模块有很多,这里都有记载: http://docs.ansible.com/ansible/modules_by_category.html
。
目标系统或清单是主机的集合,可以组合成组。这些可以是静态的,也可以是用助手脚本动态收集的。您可以为这些主机和组分配变量,以便在您的行动手册中使用。你可以在这里看到更多关于主机和组的信息: http://docs.ansible.com/ansible/intro_inventory.html
。
在主机或组中声明的任何变量都可以在您的行动手册中使用。Ansible 使用 Jinja2 Python 模板引擎来支持复杂的过滤和剧本编译。您也可以在命令行或行动手册本身中声明变量。
您还可以找到系统事实,比如木偶事实,它们也可以被剧本中的模板引擎使用。我们还可以执行可以从外部服务或本地文件中读取的查找。你可以在 http://docs.ansible.com/ansible/playbooks_variables.html#variables
找到更多关于变量的信息。
在本节中,我们将构建一个 web 服务器。然后,我们将使用 ServerSpec 来验证我们的配置。
可行的安装和配置
Ansible 易于安装,可以从.deb
或.rpm
包、Python Pip 安装或 tar 文件等中获得,这取决于您的系统。我们将使用 Debian 包,因为我们在 Xenial 服务器上运行它。
让我们像这样运行aptitude
命令:
$ sudo aptitude install –y ansible
也可通过yum install
获得。您也可以通过 Python Pip 安装它,这是一个 Python 包管理器。这在大多数操作系统上都是可用的。
$ sudo pip install ansible
安装完成后,您可以编辑全局配置文件ansible.cfg
,它将位于/etc/ansible
目录中。当 Ansible 启动时,它会查找配置文件,首先在环境变量ANSIBLE_CONFIG
中,然后在本地目录的ansible.cfg
中,然后在主目录的∼/.ansible.cfg
中,最后在/etc/ansible/ansible.cfg
中是系统默认值。
开始时,不需要编辑这个文件。如果你已经下载了第三方的 Ansible 模块到一个特定的位置,你可以在ansible.cfg
文件中声明这个位置。其他东西,比如 SSH 选项,也可以放在这里。
inventory = /etc/ansible/hosts
library = /usr/share/my_modules/
roles_path = /etc/ansible/roles
log_path = /var/log/ansible.log
fact_caching = memory
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ProxyCommand="ssh -W %h:%p -q jumphost"
在这里,您可以看到您可能想要更改或添加的一小部分内容。清单是 Ansible 期望看到你的主机列表或一个程序,它将动态地收集你的主机列表。该库用于您自己的或共享的模块。role_path
是您可能安装角色的地方。角色是任务、变量、模板、文件和处理程序的集合,可用于配置特定的服务,如 Nginx(参见 https://galaxy.ansible.com/
了解人们创建和共享的一大堆角色)。
fact_
caching
可以存储在本地主机的内存中,也可以存储在不同的共享服务中,比如 Redis (Redis 是一个开源的键/值存储数据库)。这有助于加速 Ansible 多个用户的事实收集。
对于ssh_args
,默认的 SSH 选项是ControlMaster=auto
和ControlPersist=60s
,它们允许在一个连接上共享多个会话(这意味着我们不需要连接目标主机、执行任务、断开连接、再次连接、执行另一个任务,等等)。我们在这里添加的选项是如何通过跳转主机运行命令,因此任何目标主机都将通过这个 SSH 代理服务器进行访问。
没有要启动的ansible
服务。但是,您可能希望自动化在您的主机上运行行动手册的过程;这就是安斯比尔塔( https://www.ansible.com/tower
)的用武之地。它是一个由 Red Hat 提供的商业自动化和作业调度工具。
当然,您也可以通过使用其他连续交付(CD)解决方案来自动化您的 Ansible 剧本,例如在 Jenkins ( https://jenkins.io/
)这样的工具中将运行作为构建步骤的一部分来执行。
使用 ansible 命令
Ansible 非常适合在多台主机上运行特别命令。任何模块(记录在 http://docs.ansible.com/ansible/modules_by_category.html
)可用于通过ansible
命令执行特别命令。如果我们把特定的任务捆绑成一个任务列表,这就叫做剧本,可以用ansible-playbook
命令调用剧本。
让我们首先看看运行一个简单任务的ansible
命令。在基础层面,Ansible 需要知道三件事。
- 如何找到目标主机
- 作为目标的主机
- 要运行的模块以及该模块的任何参数
虽然ansible
命令有更多的选项,但我们可以用它来运行我们的第一个任务。我们将使用setup
模块,该模块收集我们可以在任务或行动手册中使用的事实。
$ ansible –c local localhost –m setup
localhost | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.0.61",
"10.0.2.15"
],
...
"ansible_virtualization_type": "virtualbox",
"module_setup": true
},
"changed": false
}
作为 JSON 字符串返回了一长串可回答的事实;我们只展示了一小部分。ansible
命令使用本地连接(-c local
)并在目标主机localhost
上运行。我们在该主机上执行设置模块(-m setup
)。
现在让我们更进一步;在这个本地主机上,我们将安装 Nginx 包。为此,我们使用apt
或yum
模块,这取决于我们所针对的主机操作系统。
$ ansible -c local localhost -m apt -a 'name=nginx state=latest update_cache=yes'
localhost | FAILED! => {
"changed": false,
"cmd": "apt-get update '&&' apt-get install python-apt -y -q --force-yes",
"failed": true,
"msg": "W: chmod 0700 of directory /var/lib/apt/lists/partial failed - SetupAPTPartialDirectory (1: Operation not permitted)\nE: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)\nE: Unable to lock directory /var/lib/apt/lists/\nW: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)\nW: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)\nE: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)\nE: Unable to lock the administration directory (/var/lib/dpkg/), are you root?",
"rc": 100,
"stderr": "W: chmod 0700 of directory /var/lib/apt/lists/partial failed - SetupAPTPartialDirectory (1: Operation not permitted)\nE: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)\nE: Unable to lock directory /var/lib/apt/lists/\nW: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)\nW: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)\nE: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)\nE: Unable to lock the administration directory (/var/lib/dpkg/), are you root?\n",
"stdout": "",
"stdout_lines": []
}
我们出错了!您可以从输出中看到,我们试图执行"apt-get update '&&' apt-get install python-apt -y -q --force-yes"
命令,但是被拒绝了。这不应该是一个惊喜;我们不会让未经授权的用户在没有适当的sudo
权限的情况下安装软件包。让我们为 Ansible 提供在请求中使用sudo
的能力。
$ ansible -c local localhost --become -m apt -a 'name=nginx state=latest update_cache=yes'
localhost | SUCCESS => {
"cache_update_time": 1481951319,
"cache_updated": true,
"changed": true,
"stderr": "",
"stdout": "....”
}
现在我们已经为ansible
命令添加了--become
参数,现在它将尝试通过sudo
来执行这些命令。输出再次被缩短,但是您可以看到我们有"change": true
,这意味着任务是在系统上执行的,并且系统被改变了。
如果我们再次运行那个ansible
任务会发生什么?
$ ansible -c local localhost --become -m apt -a 'name=nginx state=latest update_cache=yes'
localhost | SUCCESS => {
"cache_update_time": 1481951681,
"cache_updated": true,
"changed": false
}
同样,我们成功了,但是这一次,因为 Nginx 已经安装,没有什么需要更改,所以"changed"
是false
。也就是说,在一台主机上安装一个东西,如何在多台主机上做到这一点?
易变库存
Ansible inventory 是一种定义我们希望对其执行命令的主机列表的方法。这可以在脚本的帮助下动态发现,或者作为一个普通的静态主机列表。我们将向您展示如何配置静态主机列表。如果你愿意,你可以在这里阅读动态主机列表: http://docs.ansible.com/ansible/intro_dynamic_inventory.html
。
清单文件可以在本地目录中,也可以在系统范围的/etc/ansible/hosts
文件中。在我们的主机清单中,我们可以定义主机和主机组。主机组在方括号中定义,其中可以有嵌套的主机组。
$ sudo vi /etc/ansible/hosts
somehost.example.com
[all_centos]
gateway.example.com
backup.example.com
[all_ubuntu]
mail.example.com
monitor.example.com
[dbs]
db.example.com
[all_servers:children]
all_centos
all_ubuntu
dbs
在我们的主机列表中,我们定义了一个主机,例如somehost.example.com
。然后,我们用[]
括号定义了三个主机组。它们包括属于特定操作系统的主机,或者全是 CentOS 或者全是 Ubuntu,但是这些组可以是对您的安装有意义的任何内容。最后,我们有一组组[all_servers:children]
主机组,它包含all_ubuntu
和all_centos
主机组以及[ dbs]
主机。
让我们看看现在如何在几台主机上执行一些东西。我们将假设运行这个命令的用户已经将他们的公共 SSH 密钥部署到所有主机上。在某些情况下,比如运行流浪主机,你会发现你使用的用户名在每台主机上都是不同的。Xenial 主机将使用ubuntu
用户,CentOS 将默认使用vagrant
用户。我们可以通过添加以下变量声明来满足宿主文件中的这些差异:
[all_ubuntu:vars]
ansible_user=ubuntu
[all_centos:vars]
ansible_user=vagrant
您会注意到,[dbs]
组中的主机没有被声明为 Ubuntu 或 CentOS,因此我们可以使用类似的声明来管理这些主机。
[dbs]
db.example.com ansible_user=vagrant
我们可能还希望通过特定的 jumphost(有时称为堡垒或代理)到达特定的主机。我们可以声明如下:
[remote:vars]
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q jumphost"'
这样,分类在[remote]
组中的所有主机都将通过主机jumphost
到达。
Tip
不确定ProxyCommand
是做什么的?查看这个页面,了解这个和其他有趣的 SSH 技巧: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts
。
现在有了主机配置,我们可以运行一个测试,看看我们可以使用下面的ansible
命令看到我们的所有主机:
$ ansible all_servers -m ping
gateway.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
...
db.example.com | SUCCESS => {
"changed": false,
"ping": "pong"
}
我们已经成功地在主机上连接、验证并执行了模块ping
。如果我们成功了,这个模块就用pong
来响应。如果我们无法获得成功的连接,我们将得到类似于以下内容的错误:
mail.example.com | UNREACHABLE! => {
"changed": false,
"msg": "ERROR! SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue",
"unreachable": true
}
确保您能够以执行ansible
的用户身份 SSH 到目标主机。您可以使用–vvv
选项来增加ansible
命令输出的详细程度,这将有助于您跟踪任何连接问题。
假设出于某种原因,我们需要在所有主机上重启sshd
。单独管理每台主机将是一项艰巨的任务。我们只有大约五个不同的主机,但是你可以有几千个。对于 Ansible,执行 1 和执行 1,000 是相同的命令。
$ ansible all_servers --become –m service –a "name=sshd state=restarted"
这里,我们已经为目标(all_servers
)提供了主机,我们将在目标主机(--become
)上使用sudo
来执行我们的命令,我们希望使用服务模块(-m service
),该模块将接受参数-a "name=sshd state=restarted"
。
每个模块都有不同的参数,您可以将它们作为键/值对(key=value
)来传递。它们都在我们之前给出的模块文档链接中明确列出。
--become
选项有更多可用的选项。默认的--become-method
是sudo
,但是根据你的系统,你可以选择su
、pbrun
、pfexec
、runas
或者doas
。如果您需要为这些选项提供验证密码,您可以使用--ask-become-pass
,它会提示您输入密码。如果不作为根用户运行操作,可以用--become-user
选项选择不同的用户。
现在,您可以在整个系统群中发出临时命令,或者只针对较小的组或单个主机。但是我们如何执行几个任务呢?这就是我们使用战术手册的地方。
翻译剧本
既然我们已经配置了 Ansible,并且可以在我们的主机上执行模块,我们将运行几个任务来启动一个特定的服务。Ansible 提供了一个名为ansible-playbook
的命令,它被设计用来连接特定的主机并运行一系列任务。行动手册非常强大,它们可以在您的主机上串行或并行运行,并且可以将任务委派给其他主机,等待这些任务完成后再继续下一个任务。通过这种方式,您可以构建复杂的部署行动手册。
剧本是 YAML 的文件。我们之前已经谈过 YAML 档案。YAML 文件是一种用于数据序列化的标记语言,它让我们描述包括列表和关联数组在内的键/值。行动手册可以描述要在一组主机上运行的任务,或者包括其他行动手册以及可指派的角色。现在就来看一个吧。
---
- hosts: ahostgroup
become: true
gather_facts: true
vars:
- akey: avalue
tasks:
- name: do something
module: module=arguments
handlers:
- name: a handler
module: module=arguments
这是一个基本的剧本布局。我们声明关键主机,并给它一个我们想要运行它的主机的值,可以是一个组或一个单独的主机。我们可以声明其他键值,比如become
和gather_facts
,它们是布尔值,可以是true
或false
。
gather_facts
选项将触发对所有目标主机的初始请求,并收集它们所有可用的事实。如果你在游戏中没有使用任何事实,你可以将它设置为false
,这样会加快你的跑步速度。如果你使用它,你就可以在你的戏剧中使用这些事实作为条件句或者作为戏剧中的价值。
我们可以在vars
键中列出我们自己的变量作为关联数组。我们的模板引擎可以使用这些键/值对。任务和处理程序类似,本质上都是任务。处理程序主要用于“处理”服务的重启。您可以从任务通知处理程序执行任务,例如重新启动服务。
让我们看看下面的剧本。在本例中,我们将创建一个行动手册来安装我们的备份软件 Bareos。在本例中,我们将在同一台主机上安装 Bareos 及其所需的 MariaDB。行动手册的第一部分是这样的:
$ vi playbooks/backup.yml
- hosts: backup.example.com
become: true
gather_facts: true
vars:
url: http://download.bareos.org/bareos/release/latest/{{ ansible_distribution }}_{{ ansible_distribution_major_version }}
bareos_db_packages: bareos-database-mysql
sql_import: '/usr/lib/bareos/scripts/ddl/creates/mysql.sql'
这里我们针对的是一个主机,backup.example.com
。我们将用sudo
执行需要升级权限的操作(so
执行本行动手册的用户必须已经在目标主机上拥有sudo
权限)。我们将收集关于东道主的事实,并在我们的剧本中使用它们。
在vars
部分,我们已经指定了我们将在行动手册中使用的一些变量。这些可以很容易地看到和编辑,使我们的行动手册的持续管理更容易。你会注意到{{ words }}
。这是我们的模板引擎语法。它告诉 Ansible,{{ }}
中的值是变量,或者是事实,或者像我们刚刚创建的那样。
回想我们用ansible
命令运行setup
模块时,那里的输出列表包含了ansible_distribution
和ansible_distribution_major_version
的键值。
"ansible_distribution": "CentOS",
"ansible_distribution_major_version": "7",
在 CentOS 系统上,当我们运行该剧时,Jinja2 模板引擎将像这样替换变量:
url: http://download.bareos.org/bareos/release/latest/CentOS/7
Variables and Conditionals
Ansible 和 Jinja2 可厉害了。有了 Jinja2,您就拥有了巨大的能力,可以使用 Python 方法处理变量来添加或修改剧本中的值。
为了说明这是如何工作的,让我们举一个例子。我们有这样一个文件路径:
/etc/bareos/bareos-dir.conf
通常,您只想看到没有路径的文件名。使用 Ansible 和 Jinja2,我们可以做到这一点:
{{ /etc/bareos/bareos-dir.conf | basename }}
我们可以使用 Ansible 来测试我们的结果。使用带有debug
模块的ansible
命令,我们可以像这样打印替换的结果:
$ ansible -c local localhost -m debug -a "msg={{ '/etc/bareos/bareos-dir.conf' |basename }}"
localhost | SUCCESS => {
"msg": "bareos-dir.conf"
}
这里我们采用了完整的路径,并使用管道(|
)进入 Jinja2 basename
过滤器,我们可以只提取文件名。使用debug
模块可以打印出替换的结果。
有关过滤器的更多信息,请参见 http://docs.ansible.com/ansible/playbooks_filters.html
。
您还可以使用主机组名和 YAML 文件向 Ansible 提供变量。您可以在您的/etc/ansible
目录或本地目录中创建一个名为group_vars
的目录。在那里,如果您创建一个与您的主机组同名的目录,那么当您执行一个 Ansible 命令时,任何*.yml
文件都将被用来查找任何变量(它们也可以被命名为*.yaml
或者是 JSON 文件,*.json
)。
更多信息请看这里: http://docs.ansible.com/ansible/intro_inventory.html#group-variables
。
Ansible 还提供了使用工具 Ansible Vault 加密敏感变量的能力。Ansible Vault 允许将秘密(如数据库密码、私有 SSH 密钥和其他敏感数据)与 Ansible 剧本一起存储。当您运行ansible
或ansible-playbook
命令时,您可以用一个可以在命令行上传递的密码来加密这些秘密。
加密文件只是普通的 YAML 文件,可以存在于你的group_vars
子目录中。我们将很快展示更多这方面的内容。
更多详情请看这里: http://docs.ansible.com/ansible/playbooks_vault.html
。
使用条件句,我们可以根据某个值来决定何时运行一个任务。使用 Jinja2,您还可以在模板引擎中执行复杂的条件,以返回想要的结果或动作。Ansible 提供了一个您会经常使用的简单的when
条件。
- name: install mariadb
apt: name=mariadb-server state=latest
when: ansible_distribution == "Ubuntu"
这里我们选择当ansible_distribution
等于Ubuntu
时运行这个任务。当发行版不是 Ubuntu 时,这个任务将被跳过。
如果我们有这样的数据结构:
our_config: {
our_url: ‘https://endpoint.example.com’
}
然后,我们可以在 Jinja2 模板文件中使用类似这样的复杂条件,以确保我们的 URL 在未声明的情况下被赋予默认值。
{% if our_config["our_url"] is not defined -%}
url: {{ our_url | default('https://www.example.com') }}
{% endif %}
这里我们说如果关联数组our_config
没有定义键our_url
,那么我们应该得到 https://www.example.com
的default
URL。在这种特殊情况下,既然定义了our_url
,那么 URL 将是 https://endpoint.example.com
。
定义行动手册任务
让我们继续定义我们的剧本任务。我们已经定义了我们的主机和变量,现在我们必须按照我们希望的顺序执行任务。一般来说,我们希望确保安装了必要的库,下载并安装了正确的包,然后在最终启动服务之前配置它们。
让我们看一下在我们的备份服务器上安装 Bareos 所需的任务。
tasks:
- name: install epel
yum: name=epel-release state=latest
在第一部分中,我们使用yum
模块首先安装epel
存储库。我们指定我们想要latest
发布。name
是可选的,但有助于讲述每一步的故事。yum
和apt
模块采用相似的参数,但是当然只能在支持任一包管理器的系统上运行。任务的格式如下:
- name: optional or description
module_name: module_arg1=value1.... module_argx=valuex
我们也可以这样安装存储库。
- name: add bareos
get_url:
url: "{{ url }}/bareos.repo"
dest: /etc/yum.repos.d/bareos.repo
mode: 0444
当然,如果你愿意,这也可以适用于其他类型的文件。这次我们使用get_url
来建立一个http://
连接,下载 URI,并将内容复制到/etc/yum.repos.d/bareos.repo
文件中。显然,内容是 Bareos 存储库,我们使用了变量部分中列出的url
变量,并将其与/bareos.repo
组合来完成 URI。我们可以使用yum_repository
使用 URI 的细节为我们创建存储库(您也可以用类似的方式添加 Apt 存储库)。
有关管理软件包和存储库的更多信息,请参见 http://docs.ansible.com/ansible/list_of_packaging_modules.html
。
- name: install pip
yum: name={{ item }} state=latest update_cache=yes
with_items:
- python-pip
- python-devel
- name: install mariadb
yum: name={{ item }} state=latest
with_items:
- mariadb-devel
- mariadb-server
notify: mariadb_restarted
接下来的任务与第一个相似,但是使用了一个循环。我们说我们想使用yum
模块来安装一些包。要安装这些包,我们可以为每个包写一个任务,说安装最新的包,并确保我们有一个最新的存储库缓存(update_cache=yes
)。但是由于这是重复的,我们将使用一个循环。
我们说,遍历with_items:
列表中列出的项目并安装它们。Ansible 将用为我们列出的那些包替换{{ item }}
。
你可以在这里阅读更多关于循环的内容: http://docs.ansible.com/ansible/playbooks_loops.html
。
您还会注意到有一个对处理程序的notify: mariadb_
restarted
调用。处理程序只是在剧本任务块的末尾运行的任务。它告诉名为mariadb_restarted
的处理程序,如果这些包发生变化,就执行与之相关的任务。但是,这并不会立即启动数据库,我们很快就会这样做。实际的mariadb_restarted
处理器将在稍后描述。
- name: install pre-reqs
pip: name=mysql state=latest
在这个任务中,我们再次安装一个名为mysql
的 Pip 包。Pip 是 Python 模块的一个包管理器,它采用与apt
和yum
模块相似的参数。接下来,我们将启动数据库。
- name: start db service
service: name=mariadb enabled=yes state=started
前面是一个使用service
模块启动(started
)数据库的例子。其他服务状态有stopped
、restarted
、reloaded
。这里的enabled
表示我们希望这个服务在启动时启动。我们要求这个步骤在create db
步骤到来之前运行。
接下来,我们将继续安装 Bareos 软件包。
- name: install bareos
yum: name={{ item }} state=installed
with_items:
- bareos-database-mysql
- bareos-client
- bareos-director
- bareos-storage
- bareos-storage-glusterfs
- bareos-bconsole
- bareos-filedaemon
这里我们正在安装 Bareos 包,并再次使用with_items
循环来避免重复。bareos-database-mysql
包将在我们的{{ sql_import }}
变量中创建文件,我们将在下一步中使用它来创建我们的数据库。
- name: create db
mysql_db: login_user=root name=bareos state=import target={{ sql_import }}
- name: create db user bareos
mysql_user: login_user=root name=bareos password={{ backup_database_password }} encrypted=yes priv=bareos.*:ALL state=present
接下来,我们将使用mysql_db
模块为 Bareos 创建数据库。我们可以导入数据库结构,这就是state=import
和target={{ sql_import }}
的目的。在这个实例中,我们使用默认的根用户访问,但是我们也可以在这个模块中使用用户/密码组合来获得对数据库的访问。sql_import
变量在我们剧本的顶部定义,是由 Bareos 安装提供的导入 SQL 脚本。
我们继续在 MariaDB 数据库上创建 Bareos 用户。我们为用户提供用户、密码、密码类型和权限。state
是present
,意思是我们要创建用户;如果我们想删除用户,我们可以选择absent
。
我们在这里使用的密码变量(password={{ backup_database_password }}
)很有趣。这是一个敏感的秘密,所以我们需要确保在任何地方都没有明文,但是 Ansible 仍然需要访问它。有了 Ansible Vault,我们可以提供这种保护。
我们已经使用密码生成器生成了一个强密码,并成功存储了该密码。然后,使用现有的 MySQL 安装,我们创建了一个mysql
密码散列。
SELECT PASSWORD(‘strongpasswordstring’);
+---------------------------------------------------------+
| password('strongpasswordstring') |
+---------------------------------------------------------+
| *35D93ADBD68F80D63FF0D744BA55CF920B2A45BD |
+---------------------------------------------------------+
然后我们创建了一个playbooks/group_vars/dbs
目录、一个vars.yml
文件和一个vault.yml
文件。在vars.yml
文件中,我们将添加以下内容:
backup_database_password: "{{ vault_backup_database_password }}"
然后在vault.yml
文件中,我们将像这样添加我们的散列 MySQL 密码:
vault_backup_database_password: '*35D93ADBD68F80D63FF0D744BA55CF920B2A45BD'
我们这样做的原因是,一旦我们加密了这个文件,我们将无法看到我们正在使用的密钥。通过设置纯文本变量(backup_database_password
)来查看加密变量(vault_backup_database_password
),我们让关注我们的人更容易知道这些变量是如何存储的。所以要明确的是,当ansible
命令查找backup_database_password
时,它将查找vault_backup_database_password
并返回该值。
我们现在将使用ansible-vault
命令加密这个文件。
$ ansible-vault encrypt playbooks/group_vars/dbs/vault.yml
我们被要求创建并输入密码,我们也会安全地存储该密码。
接下来我们有配置文件。我们创建了一个playbooks/files
目录,并在其中添加了我们的bareos-*.conf
文件。
- name: add bareos-dir.conf
copy: src=files/bareos-dir.conf dest=/etc/bareos/bareos-dir.conf owner=bareos group=bareos mode=0640
- name: add bareos-sd.conf
copy: src=files/bareos-sd.conf dest=/etc/bareos/bareos-sd.conf owner=bareos group=bareos mode=0640
- name: add bareos-fd.conf
copy: src=files/bareos-fd.conf dest=/etc/bareos/bareos-fd.conf owner=bareos group=bareos mode=0640
- name: add bconsole.conf
copy: src=files/bconsole.conf dest=/etc/bareos/bconsole.conf owner=root group=bareos mode=0640
我们使用copy
模块将本地文件复制到服务器,并将它们放在适当的目标文件中。copy
模块支持为我们创建的文件分配owner
、group
和mode
权限。src
的路径是相对于playbooks
目录的。
在这个简单的剧本中,我们没有利用 Ansible 自带的模板引擎。如果您还记得第十四章,在那里我们设置了 Bareos,我们需要将客户端和密码添加到我们的 Bareos 配置文件中。我们可以使用模板来创建这些值,并使设置这些文件的协调变得更容易。
模板就像一个文件,但是它被模板引擎解析来查找和替换变量。所以,像下面这样的值:
$ vi playbooks/files/bareos-fd.conf
Client {
Name = bareos-fd
Description = "Client resource of the Director itself."
Address = localhost
Password = "YVcb9Ck0MvIXpZkZCM8wBV1qyEi1FD6kJjHUrk+39xun" # password for FileDaemon
}
可以替换为以下内容:
$ vi playbooks/templates/backup_bareos_fd.conf.j2
Client {
Name = bareos-fd
Description = "Client resource of the Director itself."
Address = localhost
Password = "{{ bareos_fd_dir_password }}"
}
模板文件一般都有.j2
后缀来表示 Jinja2 模板引擎。我们将密码值存储在 Ansible Vault 加密文件中,就像我们存储数据库密码一样。template
模块的语法类似于copy
模块。关于模板的更多信息,请参见 http://docs.ansible.com/ansible/template_module.html
。
我们还可以通过其他方式将值添加到文件中。我们可以搜索和替换行,替换文件中标记的文本块等等。关于不同类型文件模块的更多信息可以在这里找到: http://docs.ansible.com/ansible/list_of_files_modules.html
。
- name: create backup directory
file: path=/data/backups/FileStorage state=directory owner=bareos group=bareos mode=0750
接下来,我们使用file
模块创建目录来存储我们的备份。这个目录path
在目标主机上,state
可以是absent
删除文件或目录,file to create a file
,link to create a symlink
,directory to create a directory
,touch to create an empty file
,hard
创建硬链接。
最后,我们有我们的处理程序。正如我们所说,这将在行动手册中任务块的末尾运行。
handlers:
- name: mariadb_restart
service: name=mariadb state=restarted
因此,现在如果我们运行我们的行动手册,并且我们的数据库有一个包更改,我们的数据库将在行动手册结束时自动重启。您也可以为 Bareos 组件服务添加处理程序。
Caution
对于数据库,自动升级和重新启动数据库版本可能不是明智的做法,因为包版本更新会导致数据库中不可预测的行为,这可能是灾难性的!
运行行动手册
从这一点来看,运行剧本相当容易。Ansible 使用ansible-playbook
命令来执行剧本。它有类似于ansible
命令的命令选项。现在就让我们来看看它们;见表 19-2 。
表 19-2。
ansible-playbook Options
| `-i` | 库存文件的路径。 | | `--ask-become-pass` | 提示输入远程主机密码以提升权限。 | | `--list-hosts` | 向您展示您的行动手册将针对的主机。 | | `--list-tags` | 列出您的行动手册中可用的标签。 | | `--list-tasks` | 显示将在您的行动手册中执行的任务。 | | `--module-path=` | 添加不同的模块路径。 | | `-v -vv –vvv` | 增加调试的详细程度。 | | `--syntax-check` | 验证行动手册语法是否正确。 | | `--` `user` | 远程用户登录身份。 | | `--private-key=` | 用户的私有 SSH 密钥。 | | `--connection=` | 选择连接类型(`paramiko`、`ssh`、`winrm`、`local`);默认为智能。 | | `--extra-vars=` | 您可以在运行时向剧本添加键/值对,也可以传入任何包含变量的文件(包括`ansible-vault`文件)。 | | `--start-at-task=` | 从这项任务开始。 | | `--step` | 在进行下一项任务之前,逐步完成每项任务并要求确认。 | | `--tags` | 仅运行带有这些标签的任务。 | | `--skip-tags` | 不要运行这些标记的任务。 |为了确保我们在执行本行动手册时以正确的主机为目标,我们发布以下内容:
$ sudo ansible-playbook --list-hosts -b playbooks/backup.yml
ERROR! Decryption failed
我们已经发出了--list-hosts
,但是我们有一个解密失败的消息。这是因为我们有加密的 Ansible 保险库,我们不能读取它。让我们添加添加到密码的提示。
$ sudo ansible-playbook --list-hosts --ask-vault-pass -b playbooks/backup.yml
Vault password:
playbook: playbooks/backup.yml
play #1 (backup.example.com): TAGS: []
pattern: [u'backup.example.com']
hosts (1):
backup.example.com
这并不奇怪,因为在我们的行动手册的host: section
中,我们的目标是这台主机。如果我们使用一个组或一个正则表达式来定义我们的主机,这个清单会更直接有用。
在运行我们的playbook
命令之前,我们还需要做一件事,这并不明显。我们已经在名为dbs
的组vars
目录中创建了数据库机密,这意味着backup.example.com
主机必须是该主机组的成员。如果我们不添加它,当我们运行我们的剧本时,我们会看到这样的错误。
TASK [create db user bareos] ***************************************************
fatal: [backup.example.com]: FAILED! => {"failed": true, "msg": "ERROR! 'backup_database_password' is undefined"}
因此,我们将把backup.example.com
添加到/etc/ansible/hosts
文件中的[dbs]
主机组中。
[dbs]
db.example.com ansible_user=vagrant
backup.example.com
一个主机可以是多个组的一部分,这将使 Ansible 能够看到那个主机组的vars
。现在我们已经准备好运行剧本了。
$ sudo ansible-playbook --ask-vault-pass -b playbooks/backup.yml
Vault password:
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [backup.example.com]
TASK [install epel] ************************************************************
changed: [backup.example.com]
TASK [install pip] *************************************************************
changed: [backup.example.com] => (item=[u'python-pip', u'python-devel'])
TASK [add bareos] **************************************************************
changed: [backup.example.com]
TASK [install mariadb] *********************************************************
changed: [backup.example.com] => (item=[u'mariadb-devel', u'mariadb-server'])
TASK
[install pre-reqs] ********************************************************
changed: [backup.example.com]
TASK [start db service] ********************************************************
changed: [backup.example.com]
TASK [install bareos] **********************************************************
changed: [backup.example.com] => (item=[u'bareos-client', u'bareos-director', u'bareos-storage', u'bareos-storage-glusterfs', u'bareos-bconsole', u'bareos-filedaemon'])
TASK [create db] ***************************************************************
changed: [backup.example.com]
TASK [create db user bareos] ***************************************************
changed: [backup.example.com]
TASK [install bareos-database-mysql CentOS] ************************************
changed: [backup.example.com]
TASK [add bareos-dir.conf] *****************************************************
changed: [backup.example.com]
TASK [add bareos-sd.conf] ******************************************************
changed: [backup.example.com]
TASK [add bareos-fd.conf] ******************************************************
changed: [backup.example.com]
TASK [add bconsole.conf] *******************************************************
changed: [backup.example.com]
TASK [create backup directory] *************************************************
ok: [backup.example.com]
PLAY RECAP *********************************************************************
backup.example.com : ok=2 changed=14 unreachable=0 failed=0
我们已经成功地运行了我们的剧本。当任务执行并对您的系统进行更改时,它将被更改并添加到最后的摘要行。在不需要执行任务的地方,它将被ok
并添加到摘要行。如果没有失败的任务,那么我们认为这是成功的。
Serverspec 测试
我们现在可以自动化构建我们的主机,但是我们如何知道如果我们对我们的任务之一进行更改,它不会无意中破坏一些其他重要的配置?如果我们有一些基本的遵循需求,我们如何知道我们仍然满足每个新构建的那些义务呢?嗯,就像普通的代码一样,我们可以编写测试来保证我们构建的主机满足我们测试中定义的需求。
Ansible 和 Puppet 都适合接受测试。我们将向您展示如何使用名为 ServerSpec 的工具来帮助您的测试,但是您可以使用任何脚本语言中的其他测试框架来帮助测试您的代码。事实上,您可以使用测试驱动开发(TDD)实践,首先编写定义成功和失败场景的测试,然后编写 Ansible 或 Puppet 代码来通过这些测试。
Serverspec 是用 Ruby 编写的,并使用 rspec 框架来运行测试。虽然我们不会试图深入解释 RSpec,但是您可以在线访问一些教程站点。RSpec 的主要网站在这里: http://rspec.info/
。
如果您不喜欢安装 Ruby 和 RSpec,可以使用这个替代的基于 Python 的框架,它被设计成 Serverspec 的 Python 等价物。 https://testinfra.readthedocs.io/en/latest/
见。如果您使用 Ansible,那么这可能是您更好的选择。我们将同时测试 Puppet 和 Ansible,因此我们将选择 Serverspec。
安装 Serverspec
在这个测试场景中,我们已经检查了一个特定的 Git 存储库,它将包含我们想要测试的配置管理文件。我们将使用 Vagrant 来帮助启动和测试如何将我们的配置应用到我们的主机上。我们使用 Serverspec 来启动我们的流浪主机(如果它们尚未启动),应用我们的配置指令,然后测试这些指令的结果。
Note
我们在第三章解释了 Git 和如何设置 Vagrant,所以如果你已经忘记了,现在是重温那一章的好时机。
假设我们已经创建了一个 Git 存储库,我们将克隆到我们的本地系统。
$ git clone git@some.git.hosting:/ouruser/ourrepo.git
$ cd ourrepo
在此签出中,我们已经有一个包含以下内容的 Vagrant 文件:
$ vi Vagrantfile
Vagrant.configure(2) do |config|
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
config.vm.define "ansible" do |xenial|
xenial.vm.box = "ubuntu/xenial64"
xenial.vm.hostname = "ansible"
xenial.vm.provision :shell do |shell|
shell.path = "bootscript.sh"
end
xenial.vm.provision :ansible do |ansible|
ansible.playbook = 'ansible/playbooks/ansible.yml'
end
end
config.vm.define "puppet" do |centos|
centos.vm.box = "centos/7"
centos.vm.hostname = "puppet"
centos.vm.provision :shell do |shell|
shell.path = "bootscript.sh"
end
centos.vm.provision "puppet"
end
end
这将允许我们运行 Xenial Ubuntu 主机和 CentOS 7 主机。Xenial 主机将应用 Ansible 剧本,而 CentOS 主机将运行 Puppet 应用。我们将很快创建配置文件。然后,我们将测试两台主机是否具有相同的配置,这将是一台监听端口 80 的 HTTPD 服务器。
Tip
请记住,使用 vagger 是共享您的配置环境的一个很好的方式,因为您可以共享相同的代码来构建您的主机。
现在我们来谈谈 Serverspec。Serverspec 是一个 Ruby gem。您可以使用另一个名为 Bundler ( http://bundler.io/
)的 Ruby gem 在一个名为 Gemfile 的文件中跟踪您为特定应用程序安装的 gem。我们将使用它来安装我们需要的宝石。这也有助于我们将 gems 的特定版本固定到我们的git
提交中,并且有助于我们跟踪我们正在使用的gem
发布版本的变化。
$ sudo yum install –y ruby rubygems && gem install bundler --no-ri --no-rdoc
在这里,我们使用 CentOS 主机,安装ruby
和rubygems
包,然后将 gem bundler
安装到本地帐户中(没有相关的文档和帮助,安装速度更快)。
安装 Bundler gem 后,我们现在将在我们的存储库目录中创建一个 gem 文件,如下所示:
$ vi Gemfile
source 'https://rubygems.org'
gem 'serverspec'
这里我们已经将 Serverspec gem 添加到了 gem 文件中。source
语句是我们下载宝石的地方,也就是人们发布 Ruby 宝石的公共rubygems.org
服务器。
现在我们可以使用bundle
命令在本地安装 gem。
$ bundle install --path vendor/cache
Fetching gem metadata from https://rubygems.org/.......
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Installing diff-lcs 1.2.5
Installing multi_json 1.12.1
Installing net-ssh 3.2.0
...
这将安装serverspec
宝石和任何serverspec gem
需求。我们现在已经准备好为我们当前签出的存储库初始化serverspec
,这相当于运行一个设置脚本。我们用serverspec-init
命令来做这件事。
$ serverspec-init
Select OS type:
1) UN*X
2) Windows
Select number: 1
有人问我们要测试什么类型的操作系统;这里我们选择1) UN*X
。
Select a backend type:
1) SSH
2) Exec (local)
Select number: 1
我们现在可以选择如何访问主机:要么在本地运行serverspec
命令,要么使用 SSH 登录到主机并从该主机内部运行命令。我们将使用 SSH,因此选择 1。
Vagrant instance y/n: y
Auto-configure Vagrant from Vagrantfile? y/n: y
0) ansible
1) puppet
Choose a VM from the Vagrantfile: 0
+ spec/
+ spec/ansible/
+ spec/ansible/sample_spec.rb
+ spec/spec_helper.rb
Serverspec 已经检测到了 Vagrant 文件,现在想知道我们是否要为我们自动配置这些主机中的一个。这里选择puppet
或ansible
都可以,所以我们将使用copy
命令添加另一个。
我们现在已经设置了 Serverspec。已经创建了一个spec
目录,通过spec/spec_helper.rb
文件管理 Serverspec。Serverspec 将在spec
目录中查找与在Vagrantfile
中声明的主机匹配的目录,并运行它在那些以*_spec.rb
结尾的目录中找到的任何测试。因此,我们现在可以将spec/ansible
目录复制到spec/puppet
,现在两台主机都将被测试。
$ cp –r spec/ansible spec/puppet
如果我们看一下sample_spec.rb
文件,它将显示我们的 Serverspec 测试。
require 'spec_helper'
describe package('httpd'), :if => os[:family] == 'redhat' do
it { should be_installed }
end
describe package('apache2'), :if => os[:family] == 'ubuntu' do
it { should be_installed }
end
describe service('httpd'), :if => os[:family] == 'redhat' do
it { should be_enabled }
it { should be_running }
end
describe service('apache2'), :if => os[:family] == 'ubuntu' do
it { should be_enabled }
it { should be_running }
end
describe service('org.apache.httpd'), :if => os[:family] == 'darwin' do
it { should be_enabled }
it { should be_running }
end
describe port(80) do
it { should be_listening }
end
_spec.rb
文件应该易于阅读,这是 RSpec 的设计目标之一,使测试变得清晰。第一行是 Rubyrequire
类似于 Python 导入语句,只是让spec_helper
对我们可用。
接下来的几行是这样的。我们想describe
一个叫做httpd
的包。如果我们在一个redhat
家庭主机上,那么应该安装这个包。现在你可以阅读其他的,它们是相似的,描述了当我们运行 Serverspec 时我们期望发现什么。Serverspec 将接受这些简单的描述,并处理它将如何验证我们的测试。
我们将删除描述 Darwin 系列操作系统(Mac OS)的倒数第二个测试,但是其余的测试非常适合我们的目的。
运行测试
让我们从运行测试开始;然后,我们可以看到为了通过测试我们需要做的工作。我们使用安装 Serverspec 时附带的一些工具来完成这项工作。
$ rake spec:ansible
Package "apache2"
should be installed (FAILED - 1)
Service "apache2"
should be enabled (FAILED - 2)
should be running (FAILED - 3)
Port "80"
should be listening (FAILED - 4)
为了运行我们的测试,我们将执行所谓的rake
任务。rake
是一个 Ruby 版本的make
工具;这里可以看到更多: https://github.com/ruby/rake
。我们将要运行的任务叫做spec:ansible
,它将启动 Ansible 漫游主机,然后运行 Serverspec 测试。
您可以在Failures:
部分看到测试失败的原因。
Failures:
1) Package "httpd" should be installed
On host `puppet'
Failure/Error: it { should be_installed }
expected Package "httpd" to be installed
sudo -p 'Password: ' /bin/sh -c rpm\ -q\ httpd
package httpd is not installed
# ./spec/puppet/sample_spec.rb:4:in `block (2 levels) in <top (required)>'
您可以看到 Serverspec 试图运行rpm –q httpd
命令,但是找不到安装的httpd
。这是意料之中的,因为我们还没有安装它。我们现在将编写 Ansible 代码来安装它,并在我们的 Vagrant 主机上提供它。
$ vi ansible/playbooks/ansible.yml
---
- hosts: all
gather_facts: true
become: true
tasks:
- name: install apache2
apt: name=apache2 state=latest
我们现在也将为木偶做同样的事情。
$ vi manifests/site.pp
class httpd {
package {'httpd': ensure => 'latest' }
}
include httpd
我们现在将使用vagrant provision
命令调配我们的主机。让我们再次运行 Serverspec 测试。
$ rake spec
Package "apache2"
should be installed
Service "apache2"
should be enabled
should be running
Port "80"
should be listening
Finished in 0.06987 seconds (files took 11.03 seconds to load)
4 examples, 0 failures
...
Package "httpd"
should be installed
Service "httpd"
should be enabled (FAILED - 1)
should be running (FAILED - 2)
Port "80"
should be listening (FAILED - 3)
运行rake spec
将在我们的spec/
文件夹中的任何主机上运行测试。我们对 Ansible 主机的测试已经全部通过。这是因为在 Ubuntu 上,当你安装服务包时,它们会自动启动。有了 CentOS,他们不会这样做,直到他们被告知。让我们将 CentOS 主机设置为绿色。
$ vi manifests/site.pp
class httpd {
package {'httpd': ensure => 'latest' }
service { ‘httpd’: enable => true, ensure => true }
}
include httpd
再次运行 provision 和rake spec
,我们现在可以看到我们在 Ansible 和 Puppet 中都是绿色的。
Package "apache2"
should be installed
Service "apache2"
should be enabled
should be running
Port "80"
should be listening
Finished in 0.06992 seconds (files took 8.41 seconds to load)
4 examples, 0 failures
-----
Package "httpd"
should be installed
Service "httpd"
should be enabled
should be running
Port "80"
should be listening
Finished in 0.11897 seconds (files took 7.98 seconds to load)
4 examples, 0 failures
你可以运行更多的测试,这些都是非常基本的,但是我们现在知道了当你对系统做很多改变的时候这是多么的有帮助。现在,您应该将这些测试连接到您的 Jenkins 或 CI 测试基础架构,并在提交到您的主 VCS 分支之前运行它。
这里有一个 Serverspec 的教程: http://serverspec.org/tutorial.html
。
你可以在这里看到更多要测试的资源类型: http://serverspec.org/resource_types.html
。
摘要
在本章中,我们向您介绍了一些简单的配置工具,这些工具使构建和安装主机的过程变得快速而简单。您学习了如何执行以下操作:
- 安装和配置 Cobbler
- 使用选定的操作系统自动引导主机
- 安装选定的操作系统并自动回答安装问题
我们还引入了一个配置管理工具 Puppet,它将帮助您一致而准确地管理您的环境。您学习了如何执行以下操作:
- 安装木偶
- 配置木偶
- 使用 Puppet 来管理主机的配置
- 使用 Puppet 的更高级功能
- 安装和配置 Ansible
- 运行可翻译的行动手册
- 安装并运行 Serverspec 来测试您的配置