剧本示例:持续交付和滚动升级

什么是持续交付?

持续交付 (CD) 意味着频繁地向您的软件应用程序交付更新。

这样做的目的是,通过更频繁地更新,您不必等待特定的时间段,并且您的组织在响应变更方面会变得更加熟练。

一些 Ansible 用户每小时甚至更频繁地将更新部署到最终用户 - 有时是每次有经过批准的代码变更时。要实现这一点,您需要能够以零停机时间的方式快速应用这些更新的工具。

本文档详细介绍了如何使用 Ansible 最完整的剧本示例之一作为模板来实现此目标:lamp_haproxy。此示例使用了许多 Ansible 功能:角色、模板和组变量,它还附带了一个编排剧本,可以对 Web 应用程序堆栈执行零停机时间滚动升级。

这些剧本将 Apache、PHP、MySQL、Nagios 和 HAProxy 部署到一组基于 CentOS 的服务器上。

我们不会在这里介绍如何运行这些剧本。请阅读 GitHub 项目中包含的 README 以及有关该信息的示例。相反,我们将仔细查看剧本的每个部分并描述它执行的操作。

站点部署

让我们从 site.yml 开始。这是我们的站点范围部署剧本。它可用于初始部署站点,以及将更新推送到所有服务器

---
# This playbook deploys the whole application stack in this site.

# Apply common configuration to all hosts
- hosts: all

  roles:
  - common

# Configure and deploy database servers.
- hosts: dbservers

  roles:
  - db

# Configure and deploy the web servers. Note that we include two roles
# here, the 'base-apache' role which simply sets up Apache, and 'web'
# which includes our example web application.

- hosts: webservers

  roles:
  - base-apache
  - web

# Configure and deploy the load balancer(s).
- hosts: lbservers

  roles:
  - haproxy

# Configure and deploy the Nagios monitoring node(s).
- hosts: monitoring

  roles:
  - base-apache
  - nagios

注意

如果您不熟悉剧本和任务之类的术语,您应该回顾 使用剧本

在这个剧本中,我们有 5 个任务。第一个针对 all 主机并将 common 角色应用于所有主机。这是针对站点范围的内容,例如 yum 存储库配置、防火墙配置,以及需要应用于所有服务器的任何其他内容。

接下来的四个任务针对特定主机组并将特定角色应用于这些服务器。除了用于 Nagios 监控、数据库和 Web 应用程序的角色外,我们还实现了一个 base-apache 角色,该角色安装并配置基本的 Apache 设置。这由示例 Web 应用程序和 Nagios 主机使用。

可重用内容:角色

到目前为止,您应该对角色以及它们在 Ansible 中的工作原理有所了解。角色是一种将内容(任务、处理程序、模板和文件)组织成可重用组件的方式。

此示例有六个角色:commonbase-apachedbhaproxynagiosweb。您如何组织角色取决于您和您的应用程序,但大多数站点都会有一个或多个应用于所有系统的通用角色,然后是一系列特定于应用程序的角色,这些角色安装和配置站点的特定部分。

角色可以具有变量和依赖项,您可以向角色传递参数以修改其行为。您可以在 角色 部分中了解更多有关角色的信息。

配置:组变量

组变量是应用于服务器组的变量。它们可以在模板和剧本中使用,以自定义行为并提供易于更改的设置和参数。它们存储在与清单相同位置的名为 group_vars 的目录中。以下是 lamp_haproxy 的 group_vars/all 文件。正如您所料,这些变量将应用于清单中的所有计算机

---
httpd_port: 80
ntpserver: 192.0.2.23

这是一个 YAML 文件,您可以创建列表和字典以获得更复杂的变量结构。在本例中,我们只设置了两个变量,一个用于 Web 服务器的端口,另一个用于我们的计算机应用于时间同步的 NTP 服务器。

这是另一个组变量文件。这是 group_vars/dbservers,它适用于 dbservers 组中的主机

---
mysqlservice: mysqld
mysql_port: 3306
dbuser: root
dbname: foodb
upassword: usersecret

如果您查看示例,您会发现 webservers 组和 lbservers 组也具有类似的组变量。

这些变量在各种地方使用。您可以在剧本中使用它们,例如,在 roles/db/tasks/main.yml

- name: Create Application Database
  mysql_db:
    name: "{{ dbname }}"
    state: present

- name: Create Application DB User
  mysql_user:
    name: "{{ dbuser }}"
    password: "{{ upassword }}"
    priv: "*.*:ALL"
    host: '%'
    state: present

您也可以在模板中使用这些变量,例如,在 roles/common/templates/ntp.conf.j2

driftfile /var/lib/ntp/drift

restrict 127.0.0.1
restrict -6 ::1

server {{ ntpserver }}

includefile /etc/ntp/crypto/pw

keys /etc/ntp/keys

您可以看到模板和变量的变量替换语法 {{ 和 }} 都是相同的。大括号内的语法是 Jinja2,您可以对内部数据执行各种操作并应用不同的过滤器。在模板中,您还可以使用 for 循环和 if 语句来处理更复杂的情况,例如,在 roles/common/templates/iptables.j2

{% if inventory_hostname in groups['dbservers'] %}
-A INPUT -p tcp  --dport 3306 -j  ACCEPT
{% endif %}

这正在测试我们当前操作的计算机的清单名称 (inventory_hostname) 是否存在于清单组 dbservers 中。如果是,该计算机将获得端口 3306 的 iptables ACCEPT 行。

这是来自同一模板的另一个示例

{% for host in groups['monitoring'] %}
-A INPUT -p tcp -s {{ hostvars[host].ansible_default_ipv4.address }} --dport 5666 -j ACCEPT
{% endfor %}

这循环遍历名为 monitoring 的组中的所有主机,并将每个监控主机的默认 IPv4 地址添加到当前计算机的 iptables 配置中,以便 Nagios 可以监控这些主机。

您可以了解更多关于 Jinja2 及其功能的信息 这里,您还可以阅读有关 Ansible 变量的一般信息,请查看 使用变量 部分。

滚动升级

现在您已经拥有一个完全部署的网站,包括 Web 服务器、负载均衡器和监控系统。那么如何对其进行更新呢?这就是 Ansible 的编排功能发挥作用的地方。虽然一些应用程序使用“编排”一词来表示基本的排序或命令轰炸,但 Ansible 将编排定义为“像指挥乐队一样指挥机器”,并为此提供了一个非常精密的引擎。

Ansible 能够以协调的方式对多层应用程序进行操作,从而轻松地编排我们 Web 应用程序的复杂零停机滚动升级。这在名为 rolling_update.yml 的单独剧本中实现。

查看剧本,您会发现它由两个剧本组成。第一个剧本非常简单,如下所示

- hosts: monitoring
  tasks: []

这里发生了什么,为什么没有任务?您可能知道 Ansible 会在对服务器进行操作之前从服务器收集“事实”。这些事实对各种事情很有用:网络信息、操作系统/发行版版本等等。在我们的例子中,我们需要在执行更新之前了解有关我们环境中所有监控服务器的一些信息,因此这个简单的剧本强制在我们的监控服务器上执行一个事实收集步骤。您有时会看到这种模式,这是一个值得了解的有用技巧。

下一部分是更新剧本。第一部分如下所示

- hosts: webservers
  user: root
  serial: 1

这只是一个普通的剧本定义,作用于 webservers 组。 serial 关键字告诉 Ansible 一次对多少台服务器进行操作。如果没有指定,Ansible 会将这些操作并行化,直到配置文件中指定的默认“forks”限制。但是对于零停机滚动升级,您可能不希望一次对那么多主机进行操作。如果您只有一小部分 Web 服务器,您可能希望将 serial 设置为 1,一次对一台主机进行操作。如果您有 100 台服务器,也许可以将 serial 设置为 10,一次对 10 台主机进行操作。

以下是更新剧本的下一部分

pre_tasks:
- name: disable nagios alerts for this host webserver service
  nagios:
    action: disable_alerts
    host: "{{ inventory_hostname }}"
    services: webserver
  delegate_to: "{{ item }}"
  loop: "{{ groups.monitoring }}"

- name: disable the server in haproxy
  shell: echo "disable server myapplb/{{ inventory_hostname }}" | socat stdio /var/lib/haproxy/stats
  delegate_to: "{{ item }}"
  loop: "{{ groups.lbservers }}"

注意

  • serial 关键字强制剧本以“批次”执行。每个批次都算作一个完整的剧本,其中包含对主机的子选择。这会对剧本的行为产生一些影响。例如,如果批次中的所有主机都失败,则剧本失败,进而导致整个运行失败。在与 max_fail_percentage 结合使用时,您应该考虑这一点。

pre_tasks 关键字只是允许您列出在调用角色之前要运行的任务。这在过一会儿会更清楚。如果您查看这些任务的名称,您会发现我们正在禁用 Nagios 警报,然后从 HAProxy 负载均衡池中删除我们当前正在更新的 Web 服务器。

delegate_toloop 参数一起使用,会导致 Ansible 遍历每个监控服务器和负载均衡器,并对监控服务器或负载均衡器执行该操作(委托该操作),“代表”Web 服务器。用编程术语来说,外层循环是 Web 服务器的列表,内层循环是监控服务器的列表。

请注意,HAProxy 步骤看起来有点复杂。我们在本例中使用 HAProxy,因为它可以免费使用,但是如果您在基础设施中拥有(例如)F5 或 Netscaler(或者您可能拥有 AWS Elastic IP 设置?),您可以使用 Ansible 模块来与它们通信。您可能还希望使用其他监控模块而不是 Nagios,但这只是显示了“预任务”部分的主要目标 - 将服务器从监控中移除,并将其从轮换中移除。

下一步只是将适当的角色重新应用到 Web 服务器。这将导致 webbase-apache 角色中的任何配置管理声明应用于 Web 服务器,包括 Web 应用程序代码本身的更新。我们不必这样做 - 我们也可以只更新 Web 应用程序,但这是一个很好的例子,说明如何使用角色来重用任务。

roles:
- common
- base-apache
- web

最后,在 post_tasks 部分,我们反转对 Nagios 配置的更改,并将 Web 服务器放回负载均衡池中。

post_tasks:
- name: Enable the server in haproxy
  shell: echo "enable server myapplb/{{ inventory_hostname }}" | socat stdio /var/lib/haproxy/stats
  delegate_to: "{{ item }}"
  loop: "{{ groups.lbservers }}"

- name: re-enable nagios alerts
  nagios:
    action: enable_alerts
    host: "{{ inventory_hostname }}"
    services: webserver
  delegate_to: "{{ item }}"
  loop: "{{ groups.monitoring }}"

同样,如果您使用的是 Netscaler 或 F5 或 Elastic Load Balancer,只需将相应的模块替换进去即可。

管理其他负载均衡器

在本例中,我们使用简单的 HAProxy 负载均衡器作为 Web 服务器的前端。它易于配置且易于管理。正如我们提到的,Ansible 支持各种其他负载均衡器,例如 Citrix NetScaler、F5 BigIP、Amazon Elastic Load Balancers 等等。

对于其他负载均衡器,您可能需要向它们发送 shell 命令(就像我们上面对 HAProxy 所做的那样),或者如果您的负载均衡器公开了 API,则调用 API。对于 Ansible 有模块的负载均衡器,如果您要与 API 通信,您可能希望将其作为 local_action 运行。您可以在 控制任务运行位置:委托和本地操作 部分中了解更多关于本地操作的信息。如果您为某些没有模块的硬件开发了一些有趣的东西,它可能成为一个不错的贡献!

端到端持续交付

现在您有了自动部署应用程序更新的方法,如何将所有这些步骤整合在一起呢?许多组织使用 JenkinsAtlassian Bamboo 等持续集成工具将开发、测试、发布和部署步骤整合在一起。您可能还希望使用 Gerrit 等工具为对应用程序代码本身或 Ansible 剧本(或两者)的提交添加代码审查步骤。

根据您的环境,您可能会持续部署到测试环境,对该环境运行一组集成测试,然后自动部署到生产环境。或者,您也可以保持简单,只使用滚动更新来按需部署到测试或生产环境。这完全取决于您。

为了与持续集成系统集成,您可以使用 ansible-playbook 命令行工具轻松触发剧本运行,或者,如果您使用的是 AWX,可以使用 tower-cli 命令或内置的 REST API。(tower-cli 命令“joblaunch”将通过 REST API 生成一个远程作业,并且非常棒)。

这应该让您对如何使用 Ansible 架构多层应用程序并对该应用程序进行编排操作有一个很好的了解,最终目标是持续交付给您的客户。您可以将滚动升级的想法扩展到应用程序的几个不同的部分;也许添加前端 Web 服务器以及应用程序服务器,或者用 NoSQL 数据库替换 SQL 数据库。Ansible 使您能够轻松地管理复杂的环境并自动执行常见操作。

另请参阅

使用剧本

剧本简介

角色

剧本角色简介

使用变量

Ansible 变量简介

Ansible.com:持续交付

使用 Ansible 进行持续交付简介