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

什么是持续交付?

持续交付 (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 地址的 ACCEPT 行添加到当前计算机的 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 进行持续交付入门