剧本示例:持续交付和滚动升级
什么是持续交付?
持续交付 (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中的工作方式有所了解。角色是组织内容的一种方式:任务、处理器、模板和文件,将其组织成可重用的组件。
此示例包含六个角色:common
、base-apache
、db
、haproxy
、nagios
和web
。您如何组织角色取决于您和您的应用程序,但大多数站点将拥有一个或多个应用于所有系统的通用角色,然后是一系列特定于应用程序的角色,用于安装和配置站点的特定部分。
角色可以具有变量和依赖项,您可以向角色传递参数以修改其行为。您可以在角色部分阅读更多关于角色的信息。
配置:组变量
组变量是应用于服务器组的变量。它们可以在模板和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_to
和loop
参数一起使用,会导致 Ansible 遍历每个监控服务器和负载均衡器,并在监控或负载均衡服务器上执行该操作(委派该操作),“代表”web 服务器。用编程术语来说,外循环是 web 服务器列表,内循环是监控服务器列表。
请注意,HAProxy 步骤看起来有点复杂。在本例中,我们使用 HAProxy,因为它可以免费使用,但是如果您在基础架构中拥有(例如)F5 或 Netscaler(或者您可能设置了 AWS 弹性 IP?),则可以使用 Ansible 模块与它们进行通信。您可能也希望使用其他监控模块而不是 nagios,但这只是显示了“预任务”部分的主要目标——将服务器从监控中移除,并将其从轮换中移除。
下一步只是将正确的角色重新应用于 web 服务器。这将导致web
和base-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
运行。您可以在控制任务运行位置:委托和本地操作部分阅读有关本地操作的更多信息。如果您为某些没有模块的硬件开发了一些有趣的东西,它可能会成为一个很好的贡献!
端到端持续交付
现在您已经有了一种自动将更新部署到应用程序的方法,您如何将所有这些结合在一起?许多组织使用持续集成工具,例如Jenkins或Atlassian 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 进行持续交付入门