到目前为止,我们已经看到了您如何开发和测试 Ansible 行动手册。最后一个方面是如何将行动手册投入生产。在大多数情况下,在行动手册投入生产之前,您需要处理多个环境。这类似于您的开发人员编写的软件。许多公司有多个环境,通常您的行动手册会遵循以下步骤:
- 发展环境
- 测试环境
- 暂存环境
- 生产
有些公司以不同的方式命名这些环境,有些公司有额外的环境,例如认证环境,在该环境中,所有软件在投入生产之前都必须经过认证。
当您编写行动手册和设置角色时,我们强烈建议您从一开始就牢记环境的概念。与您的软件和运营团队交谈,弄清楚您的设置需要满足多少环境可能是值得的。我们将列出一些方法,并举例说明在您的环境中可以遵循的方法。
本章将涵盖以下主题:
- 基于 Git 分支的代码
- 软件分发策略
- 部署带有修订控制系统的 web 应用
- 使用转速包部署网络应用
- 用 RPM 打包构建编译软件
为了能够遵循本章中的示例,您将需要一台能够构建 RPM 包的 UNIX 机器。我的建议是 Fedora 或 CentOS 安装(裸机或虚拟机)。
你可以在 https://github.com/PacktPublishing/Learning-Ansible-2.从这本书的 GitHub 资源库下载所有的文件第三版/树/主/章节 09 。
假设您有四个环境需要处理,如下所示:
- 发展
- 测试
- 阶段
- 生产
在基于 Git 分支的方法中,每个分支有一个环境。您将始终首先对开发进行更改,然后将这些更改提升到测试(合并或挑选,并以 Git 标记提交)、阶段和生产。在这种方法中,您将持有一个清单文件、一组可变文件,最后,每个分支机构都有一堆专用于角色和行动手册的文件夹。
在这种方法中,您将始终保持开发和掌握分支。初始代码被提交给开发分支,一旦稳定,您将把它提升到主分支。主分支中存在的相同角色和行动手册将在所有环境中运行。另一方面,每个环境都有单独的文件夹。我们来看一个例子。我们将向您展示如何为两个环境(试运行和生产)提供单独的配置和清单。您可以将其扩展到您的场景,以适应您使用的所有环境。首先,让我们看看playbooks/variables.yaml
中的行动手册,它将在这些多个环境中运行,并包含以下内容。GitHub 上提供了完整的代码:
- hosts: web
user: vagrant
tasks:
- name: Print environment name
debug:
var: env
- name: Print db server url
debug:
var: db_url
- name: Print domain url
debug:
var: domain
...
如您所见,本行动手册中有两组任务:
- 针对数据库服务器运行的任务
- 针对 web 服务器运行的任务
打印特定环境中所有服务器通用的环境名称也是一项额外的任务。我们还将有两个不同的库存文件。
第一个名为inventory/production
,内容如下:
[web]
ws01.fale.io
ws02.fale.io
[db]
db01.fale.io
[production:children]
db
web
第二个将被称为inventory/staging
,内容如下:
[web]
ws01.staging.fale.io
ws02.staging.fale.io
[db]
db01.staging.fale.io
[staging:children]
db
web
如您所见,在每个环境中,我们有两台机器用于web
部分,一台机器用于db
。此外,我们有一套不同的舞台和生产环境的机器。附加部分[ENVIRONMENT:children]
允许您创建一组组。这意味着在ENVIRONMENT
部分定义的任何变量都将适用于db
和web
组,除非它们分别在各个部分被覆盖。下一个有趣的部分是查看每个环境的变量值,并查看它们在每个环境中是如何分离的。
让我们从所有环境都相同的变量开始,位于inventory/group_vars/all
:
db_user: mysqluser
我们两个环境中唯一相同的变量是db_user
。
我们现在可以查看特定于生产的变量,位于inventory/group_vars/production
:
env: production
domain: fale.io
db_url: db.fale.io
db_pass: this_is_a_safe_password
如果我们现在查看位于inventory/group_vars/staging
中的阶段特定变量,我们会发现与生产变量相同的变量,但值不同:
env: staging
domain: staging.fale.io
db_url: db.staging.fale.io
db_pass: this_is_an_unsafe_password
我们现在可以验证我们收到了预期的结果。首先,我们将针对分段环境运行:
ansible-playbook -i inventory/staging playbooks/variables.yaml
我们应该会收到类似如下的输出。GitHub 中提供完整的代码输出:
PLAY [web] ***********************************************************
TASK [Gathering Facts] ***********************************************
ok: [ws01.staging.fale.io]
ok: [ws02.staging.fale.io]
TASK [Print environment name] ****************************************
ok: [ws01.staging.fale.io] => {
"env": "staging"
}
ok: [ws02.staging.fale.io] => {
"env": "staging"
}
TASK [Print db server url] *******************************************
ok: [ws01.staging.fale.io] => {
"db_url": "db.staging.fale.io"
}
ok: [ws02.staging.fale.io] => {
"db_url": "db.staging.fale.io"
}
...
我们现在可以在生产环境中运行:
ansible-playbook -i inventory/production playbooks/variables.yaml
我们将收到以下结果:
PLAY [web] ***********************************************************
TASK [Gathering Facts] ***********************************************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [Print environment name] ****************************************
ok: [ws01.fale.io] => {
"env": "production"
}
ok: [ws02.fale.io] => {
"env": "production"
}
TASK [Print db server url] *******************************************
ok: [ws01.fale.io] => {
"db_url": "db.fale.io"
}
ok: [ws02.fale.io] => {
"db_url": "db.fale.io"
}
...
您可以看到 Ansible 运行获得了为登台环境定义的所有相关变量。
如果您使用这种方法来为多个环境获得稳定的主分支,最好使用特定于环境的目录、group_vars
和库存组的组合来解决这种情况。
部署应用可能是信息和通信技术领域最复杂的任务之一。这主要是因为它经常需要改变大多数机器的状态,这些机器在某种程度上是应用的一部分。事实上,在部署过程中,您经常会发现自己不得不同时更改负载平衡器、分发服务器、应用服务器和数据库服务器的状态。新技术,如容器,正试图使这些操作更简单,但通常不容易或不可能将遗留应用移动到容器中。
我们现在将看看各种软件分发策略,以及 Ansible 如何帮助每一种策略。
这可能是分发软件最古老的策略。想法是将文件放在本地机器上(通常用于开发代码),一旦进行更改,文件的副本就会放在服务器上(通常通过 FTP)。这种部署代码的方式通常用于 web 开发,在 web 开发中,代码(通常是 PHP)不需要任何编译。
这种分配策略应该避免,因为它有多个问题:
- 很难回滚。
- 不可能跟踪各种部署的变化。
- 没有部署历史记录。
- 部署过程中很容易出错。
虽然这种分发策略可以很容易地通过 Ansible 实现自动化,但我强烈建议您立即转向不同的策略,让您拥有更安全的分发策略。
许多公司正在使用这种技术来分发他们的软件,主要是针对未编译的软件。这种技术背后的思想是设置您的服务器来使用您的代码存储库的本地副本。对于 SVN,这是可能的,但要正确管理并不容易,而 Git 允许简化这种技术,使其非常受欢迎。
这种技术比我们刚刚看到的技术有很大的优势;主要如下:
- 轻松回滚
- 非常容易获得变化的历史
- 非常容易的部署(主要是如果使用 Git)
另一方面,这种技术仍然有多个缺点:
- 没有部署历史记录
- 编译软件的硬盘
- 可能的安全问题
我想多讨论一下使用这种技术可能会遇到的安全问题。非常吸引人的是将您的 Git 存储库直接下载到您用来分发内容的文件夹中,因此如果它是一个 web 服务器,这将是/var/www/
文件夹。这有明显的优势,因为要部署你只需要执行git pull
。缺点是 Git 将创建/var/www/.git
文件夹,其中将包含您的整个 Git 存储库(包括历史),如果没有适当的保护,任何人都可以免费下载。
About 1% of Alexa's top 1 million websites have the Git folder publicly accessible, so be very careful if you want to use this distribution strategy.
另一种使用修订控制系统的方法是利用标记系统,这种方法稍微复杂一点,但有一些优势。此方法要求您在每次必须完成新部署时进行标记,然后检查服务器上的特定标记。
这具有前面方法的所有优点,并增加了部署历史。编译的软件问题和可能的安全问题与前面的方法相同。
部署软件的一种非常常见的方式(主要用于编译的应用,但也有利于非编译的应用)是使用某种打包系统。一些语言,比如 Java,已经包含了系统(在 Java 的例子中是 WAR),但是也有可以用于任何类型应用的打包系统,比如 RPM 包管理器 ( RPM )。RPM 最初由 Erik Troan 和 Marc Ewing 开发,并于 1997 年为红帽 Linux 发布。从那以后,它被许多 Linux 发行版采用,是 Linux 世界中分发软件的两种主要方式之一,另一种是 DEB。这些系统的缺点是,它们比以前的方法稍微复杂一点,但是这些系统可以授予更高的安全级别,以及版本控制。此外,这些系统很容易在 CI/CD 管道中注入,因此真正的复杂性比乍看起来要低得多。
要了解我们如何按照我们在软件分发策略一节中讨论的方式部署代码,我们需要一个环境,显然我们将使用 Ansible 来创建它。首先,为了确保我们的角色被正确加载,我们需要具有以下内容的ansible.cfg
文件:
[defaults]
roles_path = roles
然后,我们需要playbooks/groups/web.yaml
文件来允许我们正确引导我们的网络服务器:
- hosts: web
user: vagrant
roles:
- common
- webserver
从前面的文件内容可以想象,我们将需要创建角色common
和webserver
,这与我们在第 4 章、处理复杂部署中创建的角色非常相似。我们将从roles/common/tasks/main.yaml
文件开始,内容如下。GitHub 上提供了完整的代码:
- name: Ensure EPEL is enabled
yum:
name: epel-release
state: present
become: True
- name: Ensure libselinux-python is present
yum:
name: libselinux-python
state: present
become: True
- name: Ensure libsemanage-python is present
yum:
name: libsemanage-python
state: present
become: True
...
以下是其在roles/common/templates/motd
中的motd
模板:
This system is managed by Ansible
Any change done on this system could be overwritten by Ansible
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
Hostname: {{ inventory_hostname }}
eth0 address: {{ ansible_eth0.ipv4.address }}
All connections are monitored and recorded
Disconnect IMMEDIATELY if you are not an authorized user
我们现在可以进入webserver
角色——更具体地说,进入roles/webserver/tasks/main.yaml
文件。GitHub 中提供了完整的代码文件:
---
- name: Ensure the HTTPd package is installed
yum:
name: httpd
state: present
become: True
- name: Ensure the HTTPd service is enabled and running
service:
name: httpd
state: started
enabled: True
become: True
- name: Ensure HTTP can pass the firewall
firewalld:
service: http
state: enabled
permanent: True
immediate: True
become: True
...
我们还需要在roles/webserver/handlers/main.yaml
中用以下内容创建处理程序:
---
- name: Restart HTTPd
service:
name: httpd
state: restarted
become: True
我们将以下内容添加到roles/webserver/templates/index.html.j2
文件中:
<html>
<body>
<h1>Hello World!</h1>
<p>This page was created on {{ ansible_date_time.date }}.</p>
<p>This machine can be reached on the following IP addresses</p>
<ul>
{% for address in ansible_all_ipv4_addresses %}
<li>{{ address }}</li>
{% endfor %}
</ul>
</body>
</html>
最后,我们需要触摸roles/webserver/files/website.conf
文件,暂时保留为空,但它需要存在。
我们现在可以调配几台 CentOS 机器(我调配了ws01.fale.io
和ws02.fale.io
)并确保库存正确。我们可以通过运行他们的小组行动手册来配置这些机器:
ansible-playbook -i inventory/production playbooks/groups/web.yaml
我们将收到以下输出。GitHub 上提供完整的代码输出:
PLAY [web] ***********************************************************
TASK [Gathering Facts] ***********************************************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
TASK [common : Ensure EPEL is enabled] *******************************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [common : Ensure libselinux-python is present] ******************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [common : Ensure libsemanage-python is present] *****************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
TASK [common : Ensure we have last version of every package] *********
changed: [ws02.fale.io]
changed: [ws01.fale.io]
...
我们现在可以将浏览器指向端口80
上的节点,检查 HTTPd 页面是否如预期显示。现在我们已经启动并运行了基本的 web 服务器,我们现在可以专注于部署 web 应用了。
我们现在将使用 Ansible 从修订控制系统(Git)直接向我们的服务器执行网络应用的第一次部署。因此,我们将部署一个简单的 PHP 应用,它将只由一个 PHP 页面组成。该来源可在以下存储库中获得:https://github.com/Fale/demo-php-app。
要部署它,我们需要在playbooks/manual/rcs_deploy.yaml
中放置以下代码:
- hosts: web
user: vagrant
tasks:
- name: Ensure git is installed
yum:
name: git
state: present
become: True
- name: Install or update website
git:
repo: https://github.com/Fale/demo-php-app.git
dest: /var/www/application
become: True
我们现在可以使用以下命令运行部署器:
ansible-playbook -i inventory/production/playbooks/manual/rcs_deploy.yaml
这是预期的结果:
PLAY [web] ***********************************************************
TASK [Gathering Facts] ***********************************************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [Ensure git is installed] ***************************************
changed: [ws01.fale.io]
changed: [ws02.fale.io]
TASK [Install or update website] *************************************
changed: [ws02.fale.io]
changed: [ws01.fale.io]
PLAY RECAP ***********************************************************
ws01.fale.io : ok=3 changed=2 unreachable=0 failed=0
ws02.fale.io : ok=3 changed=2 unreachable=0 failed=0
目前,我们的应用还不可访问,因为我们没有该文件夹的 HTTPd 规则。为此,我们需要更改roles/webserver/files/website.conf
文件,内容如下:
<VirtualHost *:80>
ServerName app.fale.io
DocumentRoot /var/www/application
<Directory /var/www/application>
Options None
</Directory>
<DirectoryMatch ".git*">
Require all denied
</DirectoryMatch>
</VirtualHost>
如您所见,我们只是向通过app.fale.io
网址到达我们服务器的用户显示该应用,而不是向所有人显示。这将确保您的所有用户都有一致的体验。此外,您可以看到我们正在阻止对.git
文件夹(及其所有内容)的所有访问。出于我们在本章的“软件分发策略”一节中提到的安全原因,这是必需的。
我们现在可以重新运行 web 行动手册,以确保我们的 HTTPd 配置得到传播:
ansible-playbook -i inventory/production playbooks/groups/web.yaml
这就是我们将要收到的结果。GitHub 上提供完整的代码输出:
PLAY [web] ***********************************************************
TASK [Gathering Facts] ***********************************************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
TASK [common : Ensure EPEL is enabled] *******************************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [common : Ensure libselinux-python is present] ******************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
TASK [common : Ensure libsemanage-python is present] *****************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
...
您现在可以检查并查看一切是否正常。
我们已经看到了如何从 Git 获得一个源代码,并将其部署到一个 web 服务器上,以便用户可以立即使用它。我们现在将深入研究另一种分发策略:部署一个带有 RPM 包的网络应用。
要部署一个 RPM 包,我们首先需要创建它。为此,我们首先需要一个 SPEC 文件。
我们需要做的第一件事是创建一个 SPEC 文件,这是一个指导rpmbuild
如何实际创建 RPM 包的食谱。我们将在spec/demo-php-app.spec
中找到 SPEC 文件。以下是代码片段内容,完整代码可在 GitHub 上获得:
%define debug_package %{nil}
%global commit0 b49f595e023e07a8345f47a3ad62a6f50f03121e
%global shortcommit0 %(c=%{commit0}; echo ${c:0:7})
Name: demo-php-app
Version: 0
Release: 1%{?dist}
Summary: Demo PHP application
License: PD
URL: https://github.com/Fale/demo-php-app
Source0: %{url}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz
%description
This is a demo PHP application in RPM format
...
在继续之前,让我们看看各个部分的作用和意义:
%define debug_package %{nil}
%global commit0 b49f595e023e07a8345f47a3ad62a6f50f03121e
%global shortcommit0 %(c=%{commit0}; echo ${c:0:7})
前三行是变量声明。
第一个将禁用调试包的生成。默认情况下,rpmbuild
每次都会创建一个调试包,并包含所有的调试符号,但在这种情况下,我们没有任何调试符号,因为我们没有进行任何编译。
第二个将提交的散列放在commit0
变量中。第三个计算shortcommit0
的值,它被计算为commit0
字符串的前八个字符:
Name: demo-php-app
Version: 0
Release: 1%{?dist}
Summary: Demo PHP application
License: PD
URL: https://github.com/Fale/demo-php-app
Source0: %{url}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz
在第一行中,我们声明了名称、版本、发行号和摘要。版本和版本的区别在于版本是上游版本,而版本是该上游版本的 SPEC 版本。
许可证是源许可证,而不是 SPEC 许可证。该网址用于跟踪上游网站。rpmbuild
使用source0
字段来查找源文件是如何被调用的(如果存在多个文件,我们可以使用source1
、source2
等)。此外,如果源字段是有效的 URI,我们可以使用spectool
自动下载它们。这是封装在 RPM 软件包中的软件的description
:
%description
This is a demo PHP application in RPM format
prep
阶段是解压缩源并最终应用补丁的阶段。%autosetup
将uncompress
作为第一个来源,以及应用所有补丁。在这一部分中,您还可以执行在build
阶段之前需要执行的其他操作,并且有为build
阶段准备环境的目标:
%prep
%autosetup -n %{name}-%{commit0}
这里,我们将把build
阶段的所有动作。在我们的例子中,我们的源不需要被编译,因此它是空的:
%build
在install
阶段,我们将文件放在%{buildroot}
文件夹中,这将模拟目标文件系统:
%install
mkdir -p %{buildroot}/var/www/application
ls -alh
cp index.php %{buildroot}/var/www/application
需要files
部分来声明哪些文件要放入包中:
%files
%dir /var/www/application
/var/www/application/index.php
需要changelog
来跟踪谁在什么时候发布了一个新版本,以及哪个版本发生了变化:
%changelog
* Sun Feb 24 2019 Fabio Alessandro Locati - 0.1
- Initial packaging
现在我们有了 SPEC 文件,我们需要构建它。为此,我们可以使用生产机器,但这会增加该机器的攻击面,因此最好避开它。有多种方法来构建你的 RPM 软件。四种主要方式如下:
- 用手
- 使用 Ansible 自动执行手动操作
- Jenkins
- 清酒曲
让我们非常简要地看一下差异。
构建 RPM 包的最简单方法是手动方式。
最大的优势是你只需要几个简单的步骤就可以安装build
,正因为如此,很多用 RPM 开始的人都是从这里开始的。缺点是该过程将是手动的,因此人为错误会破坏结果。此外,手动构建很难审核,因为唯一可审核的部分是输出,而不是过程本身。
要构建 RPM 包,您将需要一个 Fedora 或一个 EL(红帽企业 Linux、CentOS、科学 Linux、Oracle 企业 Linux)系统。如果您正在使用 Fedora,则需要执行以下命令来安装所有必要的软件:
sudo dnf install -y fedora-packager
如果您正在运行一个 EL 系统,您需要执行的命令如下:
sudo yum install -y mock rpm-build spectool
无论是哪种情况,您都需要将您要使用的用户添加到mock
组,为此,您需要执行以下操作:
sudo usermod -a -G mock [yourusername]
Linux loads the users at login, so to apply a group change, you need to restart your session.
此时,我们可以将 SPEC 文件复制到文件夹中(通常,$HOME
是一个好文件),并执行以下操作:
mkdir -p ~/rpmbuild/SOURCES
这将创建过程中需要的$HOME/rpmbuild/SOURCES
文件夹。-p
选项将自动创建路径中所有缺失的文件夹。我们使用spectool
下载源文件,并将其放置在适当的目录中。spectool
会自动从 SPEC 文件中获取网址,这样我们就不用记住了:
spectool -R -g demo-php-app.spec
我们现在需要创建一个src.rpm
文件,为此,我们可以使用rpmbuild
:
rpmbuild -bs demo-php-app.spec
该命令将输出如下内容:
Wrote: /home/fale/rpmbuild/SRPMS/demo-php-app-0-1.fc28.src.rpm
名称中可能存在一些小的差异;例如,你可能会有一个不同的$HOME
文件夹,如果你使用不同于 Fedora 24 的东西来构建包,你可能会有不同于fc24
的东西。此时,我们可以用以下代码创建二进制文件:
mock -r epel-7-x86_64 /home/fale/rpmbuild/SRPMS/demo-php-app-0-1.fc28.src.rpm
Mock 允许我们在干净的环境中构建 RPM 包,并且,由于-r
选项,它允许我们为不同版本的 Fedora、EL 和 Mageia 构建。这个命令会给你一个很长的输出,我们不会在这里讨论,但是在最后几行,有有用的信息。如果一切都构建正确,这是您应该看到的最后几行:
Wrote: /builddir/build/RPMS/demo-php-app-0-1.el7.centos.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.d4vPhr
+ umask 022
+ cd /builddir/build/BUILD
+ cd demo-php-app-b49f595e023e07a8345f47a3ad62a6f50f03121e
+ /usr/bin/rm -rf /builddir/build/BUILDROOT/demo-php-app-0-1.el7.centos.x86_64
+ exit 0
Finish: rpmbuild demo-php-app-0-1.fc28.src.rpm
Finish: build phase for demo-php-app-0-1.fc28.src.rpm
INFO: Done(/home/fale/rpmbuild/SRPMS/demo-php-app-0-1.fc28.src.rpm) Config(epel-7-x86_64) 0 minutes 58 seconds
INFO: Results and/or logs in: /var/lib/mock/epel-7-x86_64/result
Finish: run
倒数第二行包含可以找到结果的路径。如果您查看该文件夹,您应该会发现以下文件:
drwxrwsr-x. 2 fale mock 4.0K Feb 24 12:26 .
drwxrwsr-x. 4 root mock 4.0K Feb 24 12:25 ..
-rw-rw-r--. 1 fale mock 4.6K Feb 24 12:26 build.log
-rw-rw-r--. 1 fale mock 3.3K Feb 24 12:26 demo-php-app-0-1.el7.centos.src.rpm
-rw-rw-r--. 1 fale mock 3.1K Feb 24 12:26 demo-php-app-0-1.el7.centos.x86_64.rpm
-rw-rw-r--. 1 fale mock 184K Feb 24 12:26 root.log
-rw-rw-r--. 1 fale mock 792 Feb 24 12:26 state.log
这三个日志文件在编译过程中出现问题时非常有用。src.rpm
文件将是我们用第一个命令创建的src.rpm
文件的副本,而x86_64.rpm
文件是我们创建的模拟文件,也是我们需要安装在机器上的文件。
因为手动完成所有这些步骤可能会很长、很无聊并且容易出错,所以我们可以使用 Ansible 自动完成它们。最终的行动手册可能不是最干净的,但能够以可重复的方式执行所有操作。
为此,我们打算从头开始造一台新机器。我把这个机器叫做builder01.fale.io
,我们也要修改inventory/production
文件来配合这个修改:
[web]
ws01.fale.io
ws02.fale.io
[db]
db01.fale.io
[builders]
builder01.fale.io
[production:children]
db
web
builders
在进入builders
角色之前,我们需要对webserver
角色进行一些更改,以启用一个新的存储库。首先是在文件的末尾添加一个位于roles/webserver/tasks/main.yaml
的任务,代码如下:
- name: Install our private repository
copy:
src: privaterepo.repo
dest: /etc/yum.repos.d/privaterepo.repo
become: True
第二个变化实际上是创建roles/webserver/files/privaterepo.repo
文件,内容如下:
[privaterepo]
name=Private repo that will keep our apps packages
baseurl=http://repo.fale.io/
skip_if_unavailable=True
gpgcheck=0
enabled=1
enabled_metadata=1
我们现在可以执行webserver
小组行动手册,通过以下命令使更改生效:
ansible-playbook -i inventory/production playbooks/groups/web.yaml
应该会出现以下输出。GitHub 上提供完整的代码输出:
PLAY [web] ***********************************************************
TASK [Gathering Facts] ***********************************************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [common : Ensure EPEL is enabled] *******************************
ok: [ws02.fale.io]
ok: [ws01.fale.io]
TASK [common : Ensure libselinux-python is present] ******************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
TASK [common : Ensure libsemanage-python is present] *****************
ok: [ws01.fale.io]
ok: [ws02.fale.io]
...
不出所料,唯一的变化是部署了我们新生成的存储库文件。
我们还需要为builders
创建一个角色,其文件位于roles/builder/tasks/main.yaml
,内容如下。GitHub 上提供了完整的代码:
- name: Ensure needed packages are present
yum:
name: '{{ item }}'
state: present
become: True
with_items:
- mock
- rpm-build
- spectool
- createrepo
- httpd
- name: Ensure the user ansible is in the mock group
user:
name: ansible
groups: mock
append: True
become: True
...
另外,作为builder
角色的一部分,我们需要包含以下内容的roles/builder/handlers/main.yaml
处理程序文件:
- name: Restart HTTPd
service:
name: httpd
state: restarted
become: True
从tasks
文件中可以猜到,我们还需要roles/builder/files/repo.conf
文件,内容如下:
<VirtualHost *:80>
ServerName repo.fale.io
DocumentRoot /var/www/repo
<Directory /var/www/repo>
Options Indexes FollowSymLinks
</Directory>
</VirtualHost>
我们还需要一份新的playbooks/groups/builders.yaml
战术手册,内容如下:
- hosts: builders
user: vagrant
roles:
- common
- builder
我们现在可以使用以下内容创建主机本身:
ansible-playbook -i inventory/production playbooks/groups/builders.yaml
我们期待类似以下的结果:
PLAY [builders] ******************************************************
TASK [Gathering Facts] ***********************************************
ok: [builder01.fale.io]
TASK [common : Ensure EPEL is enabled] *******************************
changed: [builder01.fale.io]
TASK [common : Ensure libselinux-python is present] ******************
ok: [builder01.fale.io]
TASK [common : Ensure libsemanage-python is present] *****************
changed: [builder01.fale.io]
TASK [common : Ensure we have last version of every package] *********
changed: [builder01.fale.io]
TASK [common : Ensure NTP is installed] ******************************
changed: [builder01.fale.io]
TASK [common : Ensure the timezone is set to UTC] ********************
changed: [builder01.fale.io]
...
现在我们已经准备好了基础设施的所有部分,我们可以用以下内容创建playbooks/manual/rpm_deploy.yaml
文件。GitHub 上提供了完整的代码:
- hosts: builders
user: vagrant
tasks:
- name: Copy SPEC file to user folder
copy:
src: ../../spec/demo-php-app.spec
dest: /home/vagrant
- name: Ensure rpmbuild exists
file:
name: ~/rpmbuild
state: directory
- name: Ensure rpmbuild/SOURCES exists
file:
name: ~/rpmbuild/SOURCES
state: directory
...
正如我们所讨论的,这个剧本有很多不太干净的命令和外壳。将来,可能会编写一个具有相同功能但带有模块的剧本。大多数操作都是一样的,正如我们在上一节中讨论的那样。新的行动即将结束;事实上,在这种情况下,我们将生成的 RPM 文件复制到一个特定的文件夹中,我们调用createrepo
在那个文件夹中生成一个存储库,然后我们强制所有 web 服务器将生成的包更新到最后一个版本。
To ensure the security of your application, it is important that the repository is only accessible internally and not publicly.
我们现在可以使用以下命令运行行动手册:
ansible-playbook -i inventory/production playbooks/manual/rpm_deploy.yaml
我们期待如下结果。GitHub 上提供完整的代码输出:
PLAY [builders] ******************************************************
TASK [setup] *********************************************************
ok: [builder01.fale.io]
TASK [Copy SPEC file to user folder] *********************************
changed: [builder01.fale.io]
TASK [Ensure rpmbuild exists] ****************************************
changed: [builder01.fale.io]
TASK [Ensure rpmbuild/SOURCES exists] ********************************
changed: [builder01.fale.io]
TASK [Download the sources] ******************************************
changed: [builder01.fale.io]
TASK [Ensure no SRPM files are present] ******************************
changed: [builder01.fale.io]
TASK [Build the SRPM file] *******************************************
changed: [builder01.fale.io]
...
虽然这本书没有涉及,但是在更复杂的情况下,您可能希望使用 CI/CD 管道来创建和管理 RPM 包。两条主要管道基于两种不同类型的软件:
- 清酒曲
- Jenkins
酒曲软件是由 Fedora 社区和红帽开发的。它是根据 LGPL 2.1 许可证的条款发布的。这是目前 Fedora、CentOS 以及许多其他公司和社区用来创建其所有 rpm 的管道(均用于官方测试,也称为 scratch builds )。默认情况下,Koji 不是由 commit 触发的;需要从用户手动调用**(通过网络界面或命令行界面)。Koji 将自动下载 Git 中 SPEC 文件的最后一个版本,从边缓存(这是可选的,但建议)或原始位置下载源代码,并触发模拟构建。Koji 只支持模拟构建,因为它是唯一允许一致和可重复构建的系统。根据配置,Koji 可以永久或在有限的时间内存储所有输出工件。这是为了确保高度的可审计性。**
**Jenkins 是最常用的 CI/CD 管理器之一,也可以用于 RPM 管道。最大的缺点是它需要从头开始配置,结果需要更多的时间,但这意味着它具有更大的灵活性。此外,Jenkins 的一大优势是,许多公司已经有了 Jenkins 的实例,这使得设置和维护基础架构变得更加容易,因为您可以重用已经有的安装,因此您不必管理更少的系统。
RPM 打包对于非二进制应用非常有用,接近于二进制应用的必需品。这也是真的,因为非二进制和二进制情况下的复杂度差别很小。事实上,构建和安装将以完全相同的方式工作。唯一会改变的是 SPEC 文件。
让我们看看编译和打包一个用 C 语言编写的简单Hello World!
应用所需的 SPEC 文件:
%global commit0 7c288b9d80a6ef525c0cca8a744b32e018eaa386
%global shortcommit0 %(c=%{commit0}; echo ${c:0:7})
Name: hello-world
Version: 1.0
Release: 1%{?dist}
Summary: Hello World example implemented in C
License: GPLv3+
URL: https://github.com/Fale/hello-world
Source0: %{url}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz
BuildRequires: gcc
BuildRequires: make
%description
The description for our Hello World Example implemented in C
%prep
%autosetup -n %{name}-%{commit0}
%build
make %{?_smp_mflags}
%install
%make_install
%files
%license LICENSE
%{_bindir}/hello
%changelog
* Sun Feb 24 2019 Fabio Alessandro Locati - 1.0-1
- Initial packaging
如您所见,它与我们在 PHP 演示应用中看到的非常相似。让我们看看不同之处。
让我们深入了解一下 SPEC 文件的各个部分:
%global commit0 7c288b9d80a6ef525c0cca8a744b32e018eaa386
%global shortcommit0 %(c=%{commit0}; echo ${c:0:7})
如您所见,我们没有禁用调试包的权限。每次打包编译好的应用,都要让rpm
创建调试符号包,这样在崩溃的情况下,更容易调试和理解问题。
此处显示了 SPEC 文件的以下部分:
Name: hello-world
Version: 1.0
Release: 1%{?dist}
Summary: Hello World example implemented in C
License: GPLv3+
URL: https://github.com/Fale/hello-world
Source0: %{url}/archive/%{commit0}.tar.gz#/%{name}-%{shortcommit0}.tar.gz
如您所见,本节中的更改仅是由于新包具有不同的名称和URL
这一事实,但它们并不因为这是一个可编译的应用而联系在一起:
BuildRequires: gcc
BuildRequires: make
在未编译的应用中,我们在构建时不需要任何包,而在这种情况下,我们将需要make
和gcc
(编译器)应用。不同的应用可能需要不同的工具和/或库在构建时出现在系统上:
%description
The description for our Hello World Example implemented in C
%prep
%autosetup -n %{name}-%{commit0}
%build
make %{?_smp_mflags}
description
是特定于包的,不受包编译的影响。同样地,%prep
阶段起作用。
在%build
阶段,我们现在要做%{?_smp_mflags}
。这需要告诉rpmbuild
实际运行make
来构建我们的应用。_smp_mflags
变量将包括一组优化多线程编译的参数:
%install
%make_install
在%install
阶段,我们将发出%make_install
命令。该宏将使用一组附加参数调用make install
,以确保库以及二进制文件等位于正确的文件夹中:
%files
%license LICENSE
%{_bindir}/hello
在这种情况下,我们只需要在%install
阶段将位于buildroot
右文件夹中的hello
二进制文件放入,并添加包含许可证的LICENSE
文件:
%changelog
* Sun Feb 24 2019 Fabio Alessandro Locati - 1.0-1
- Initial packaging
%changelog
与我们看到的另一个 SPEC 文件非常相似,因为它不受编译的影响。
完成此操作后,您可以将其放入spec/hello-world.spec
并通过保存到playbooks/manual/hello_deploy.yaml
中来调整playbooks/manual/rpm_deploy.yaml
,代码如下。GitHub 上提供了完整的代码:
- hosts: builders
user: vagrant
tasks:
- name: Copy SPEC file to user folder
copy:
src: ../../spec/hello-world.spec
dest: /home/ansible
- name: Ensure rpmbuild exists
file:
name: ~/rpmbuild
state: directory
- name: Ensure rpmbuild/SOURCES exists
file:
name: ~/rpmbuild/SOURCES
state: directory
...
如你所见,我们唯一改变的是所有对demo-php-app
的引用都被hello-world
取代了。使用以下命令运行它:
ansible-playbook -i inventory/production playbooks/manual/hello_deploy.yaml
我们将得到以下结果。GitHub 上提供完整的代码输出:
PLAY [builders] ******************************************************
TASK [setup] *********************************************************
ok: [builder01.fale.io]
TASK [Copy SPEC file to user folder] *********************************
changed: [builder01.fale.io]
TASK [Ensure rpmbuild exists] ****************************************
ok: [builder01.fale.io]
TASK [Ensure rpmbuild/SOURCES exists] ********************************
ok: [builder01.fale.io]
TASK [Download the sources] ******************************************
changed: [builder01.fale.io]
TASK [Ensure no SRPM files are present] ******************************
changed: [builder01.fale.io]
TASK [Build the SRPM file] *******************************************
changed: [builder01.fale.io]
TASK [Execute mock] **************************************************
changed: [builder01.fale.io]
...
You could eventually create a playbook that accepts the name of the package to build as a parameter, so that you don't need a different playbook for every package.
我们已经看到了如何在您的环境中分发软件,所以现在,我们将谈论部署策略,也就是说,如何升级您的应用而不使您的服务受到影响。
在更新过程中,您可能会遇到三个不同的问题:
- 更新部署期间的停机时间。
- 新版本有问题。
- 新版本似乎很有效,直到它失败。
第一个问题是每个系统管理员都知道的。在更新过程中,您可能会重新启动一些服务,在服务开始和结束之间的这段时间里,您的应用在该计算机上不可用。要解决这个问题,您需要多台机器,前面有一个智能负载平衡器,它将在特定节点上执行升级之前,从可用节点池中删除指定的节点,以便在节点升级后立即将其添加回来。
第二个问题可以通过多种方式预防。最干净的是在 CI/CD 管道中进行测试。事实上,用简单的测试很容易发现这些问题。这也可以通过我们即将看到的方法来预防。
第三个问题是迄今为止最复杂的。很多时候,甚至是全球性的问题,都是由这类问题产生的。通常,问题是新版本有一些性能问题或内存泄漏。由于大多数部署都是在服务器负载最小的时期完成的,一旦负载增加,性能问题或内存泄漏可能会杀死您的服务器。
To be able to use those methods in a proper way, you have to be able to ensure that your software can accept rollbacks. There are cases where this is not possible (that is, a database table gets removed in an update). We will not discuss how to avoid this, since this is part of the development strategy, and is not related to Ansible.
为了解决这些问题,使用了两种常见的部署模式:金丝雀部署和蓝/绿部署。
canary 部署是一种技术,它涉及将一小部分机器(通常为 5%)更新到新版本,并指示负载平衡器只向其发送等量的流量。这有几个优点:
- 在更新过程中,您的容量永远不会少于 95%
- 如果新版本完全失败,您将损失 5%的容量
- 由于负载平衡器在新版本和旧版本之间分配流量,如果新版本有问题,只有 5%的用户会看到问题
- 您只需要比预期负载多 5%的容量
Canary 部署能够以非常小的开销(5%)和回滚情况下的低成本(5%)防止我们提到的所有三个问题。由于这些原因,这种技术被大公司大量使用。通常,为了确保用户体验与居住在彼此附近的用户相似,地理位置用于选择用户是使用旧版本还是新版本。
当测试似乎成功时,百分比可以逐渐增加,直到达到 100%。
可以在 Ansible 中以多种方式实现加那利部署。我建议的方式是最干净的,也就是使用库存文件,这样你就有了如下的东西:
[web-main]
ws[00:94].fale.io
[web-canary]
ws[95:99].fale.io
[web:children]
web-main
web-canary
通过这种方式,您可以设置 web 组上的所有变量(无论操作系统版本是什么,或者至少应该是什么,变量都将是相同的),但是您可以轻松地针对金丝雀组、主组或同时针对这两个组运行剧本。另一种选择是创建两个不同的清单文件,一个用于 canary 组,另一个用于主组和组,具有相同的名称,以便共享变量。
蓝色/绿色部署与 canary 部署相比有很大的不同,它有一些优点也有一些缺点。主要优点如下:
- 更容易实现
- 允许更快的迭代
- 所有用户同时被移动
- 回滚不会降低性能
在这些缺点中,最主要的是你需要比你的应用需要的多一倍的机器。如果应用在云(私有、公共或混合)上运行,则可以轻松缓解这一缺点,方法是为部署扩展应用资源,然后再缩减它们。
在 Ansible 中实现蓝/绿部署非常容易。最简单的方法是创建两个不同的库存(一个用于蓝色,一个用于绿色),然后简单地管理您的基础架构,就好像它们是不同的环境,如生产、试运行、开发等。
有时,Ansible 感觉很慢,主要是如果你有一长串任务要执行和/或你有大量的机器。造成这种情况的原因有很多,避免这种情况的方法也有很多,下面我们就来看看其中的三种方法。
Ansible 默认较慢的原因之一是,对于每个模块执行和每个主机,Ansible 将执行以下操作:
- SSH 握手
- 执行任务
- 关闭 SSH 连接
如您所见,这意味着如果您有 10 个任务要在单个远程服务器上执行,Ansible 将打开(和关闭)连接 10 次。由于 SSH 协议是一个加密协议,这使得 SSH 握手成为一个更长的过程,因为两个部分每次都必须协商密码。
Ansible 允许我们通过在剧本开始时启动连接,并在整个执行过程中保持连接活跃,从而大幅减少执行时间,这样就不需要在每个任务中重新打开连接。在 Ansible 的一生中,这个功能已经多次更改名称,以及启用它的方式。从 1.5 版本开始,它被称为流水线,启用它的方法是在您的ansible.cfg
文件中添加以下行:
pipelining=True
默认情况下不启用此功能的原因是许多发行版都附带了sudo
中的requiretty
选项。Ansible 中的流水线模式和sudo
中的requiretty
选项冲突,会让你的剧本失败。
If you want to enable the pipelining mode, ensure that the sudo requiretty
mode is disabled on your target machines.
如果要多次执行类似的操作,可以用不同的参数多次重复相同的任务,或者使用with_items
选项。除了with_items
让你的代码更容易阅读和理解之外,它还可以提高你的性能。一个例子是软件包(即apt
、dnf
、yum
、package
模块)的安装,如果您使用with_items
,Ansible 将执行一个命令,而如果您不使用,每个软件包将执行一个命令。可以想象,这有助于提升您的性能。
即使您已经实现了我们刚刚谈到的加快行动手册执行速度的方法,您仍然可能会发现有些任务需要很长时间。这在一些任务中非常常见,即使在许多其他模块中也是可能的。通常会给您带来这个问题的模块如下:
- 包装管理(即
apt
、dnf
、yum
、package
) - 云机创作(即
digital_ocean
、ec2
)
这种缓慢的原因通常是不明确的。例如,如果您使用打包管理模块来更新您的机器。这需要在每台机器上下载几十或几百兆字节,并安装大量软件。加快这种操作的一种方法是在您的数据中心有一个本地存储库,并让您的所有机器都指向它,而不是您的分发存储库。这将允许您的机器以更高的速度下载,而无需使用通常受限于带宽或计量的公共连接。
It's often important to understand what the modules do in the background to optimize the playbook's execution.
在云机器创建的情况下,Ansible 只需执行对所选云提供商的 API 调用,并等待机器准备就绪。DigitalOcean 机器可能需要一分钟的时间来创建(而其他云需要更长的时间),因此 Ansible 将等待这段时间。一些模块有一个异步模式来避免这个等待期,但是你必须确保机器在使用之前已经准备好了;否则,使用创建的机器的模块将失败。
在本章中,我们已经看到了如何使用 Ansible 部署应用,以及可以使用的各种分发和部署策略。我们还看到了如何使用 Ansible 创建 RPM 包,以及如何使用不同的方法优化 Ansible 的性能。
在下一章中,我们将研究如何在 Windows 机器上使用 Ansible,在哪里可以找到其他人编写的角色以及如何使用它们,以及 Ansible 的用户界面。**