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

什么是持续交付?

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

其理念是,通过更频繁地更新,您无需等待特定的时间段,并且您的组织能够更好地应对变化。

一些Ansible用户每小时甚至更频繁地向最终用户部署更新——有时每次有经过批准的代码更改时都会部署。为实现此目标,您需要能够以零停机时间快速应用这些更新的工具。

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

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

我们不会在这里介绍如何运行这些Playbook。请阅读GitHub项目中包含的README以及有关该信息的示例。相反,我们将仔细研究Playbook的每个部分并描述其功能。

站点部署

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

---
# 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

注意

如果您不熟悉Playbook和Play之类的术语,则应查看使用Playbook

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

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

可重用内容:角色

现在您应该对角色及其在Ansible中的工作方式有所了解。角色是组织内容的一种方式:任务、处理器、模板和文件,将其组织成可重用的组件。

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

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

配置:组变量

组变量是应用于服务器组的变量。它们可以在模板和Playbook中使用,以自定义行为并提供易于更改的设置和参数。它们存储在与清单位于同一位置的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组也有类似的组变量。

这些变量在许多地方使用。您可以在Playbook中使用它们,例如在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及其功能的信息here,并且您可以在使用变量部分阅读更多关于Ansible变量的信息。

滚动升级

现在您拥有一个完全部署的站点,其中包含Web服务器、负载均衡器和监控。您如何更新它?这就是Ansible的编排功能发挥作用的地方。虽然某些应用程序使用术语“编排”来表示基本的排序或命令爆破,但Ansible将编排称为“像乐团一样指挥机器”,并拥有一个相当复杂的引擎。

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

查看Playbook,您可以看到它由两个Play组成。第一个Play非常简单,如下所示:

- hosts: monitoring
  tasks: []

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

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

- hosts: webservers
  user: root
  serial: 1

这只是一个普通的剧本定义,作用于webservers组。 serial关键字告诉 Ansible 同时操作多少台服务器。如果未指定,Ansible 将并行化这些操作,直到配置文件中指定的默认“forks”限制。但是对于零停机滚动升级,您可能不希望一次操作这么多主机。如果您只有少数几台 web 服务器,则可能需要将serial设置为 1,一次一台主机。如果您有 100 台,也许您可以将serial设置为 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 弹性 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 使您可以轻松管理复杂的环境并自动化常见操作。

另见

使用Playbook

剧本入门

角色

剧本角色入门

使用变量

Ansible 变量入门

Ansible.com:持续交付

使用 Ansible 进行持续交付入门