第六章: 最小化滚动部署的宕机
最小化滚动部署的宕机
应用部署和升级可以用各种不同的策略来实现。最好的方法取决于应用程序本身、应用程序运行的基础设施的能力、以及用户应用程序的承诺的所有服务等级协议。不管你使用什么策略,ansible都非常适合部署。本章中, 我们将介绍一些常用的部署策略以及ansible特性在这些策略中展露头脚的案例。我们还讨论两种其他部署,考虑两种部署策略之间的共性,也就是:
- 就地升级(in-place upgrades)
- 扩展和收缩(expanding and contracting)
- 快速失败(failing fast)
- 减少破坏性行为(minimizing disruptive actions)
就地升级
我们讲的第一种部署类型就是就地升级。这种类型的部署就是在现有的基础设施上操作来更新原有应用程序。这种模型可以看作是传统的模型,创建新的基础设施的时候存在时间和金钱上的高额开销。
在这种类型的升级过程中,要减少宕机时间,一般的设计模式是通过在负载均衡后面的多个主机上部署应用。负载均衡扮演了一种在应用用户和应用程序服务器之间的网关。对应用的请求会抵达负载均衡,依赖于具体配置,负载均衡会决定具体哪个后端服务器来服务请求。
要对这种模式部署的应用执行就地升级滚动,每个服务器(或服务器的一部分)会在负载均衡后面先禁用,然后升级、然后重新启用来接受新请求。对池子中的剩余机器重复做这样的操作,直到所有的服务器都升级了。正因为只有部分可用应用服务器下线升级,应用整体上还是保持对请求可用。当然,这里需要有个假设前提,就是同时存在不同版本的应用的情况下,应用执行OK。
让我们构建一个剧本来升级一个虚构的应用。我们虚构的应用程序会运行在foo-app01到foo-app08这8台服务器上,它们位于分组foo-app。这些服务器上面有一个简单通过nginx服务的网站,应用内容来自git仓库foo-app,通过变量foo-app.repo来定义。还有一个负载均衡服务器foo-lb,运行haproxy软件,前置在这些应用服务器之前。
为了操作foo-app服务器的子集,我们需要利用serial模式。 这个模式会改变ansible如何执行剧情。默认情况下,ansible会按顺序遍历任务中列举的主机列表来执行剧本任务。剧本的每个任务都要跨越所有主机执行完才会执行下一个任务。如果我们使用默认的模式,我们第一个任务是移除负载均衡后面的每一个应用服务,这样就会导致我们应用完全的断供(outage of our application)。而serial模式,可以让我们对主机子集进行操作,这样应用整体上来说还是可用的,即便部分成员下线了。在我们的例子中,我们使用serial数量2,为了保持应用成员的大部分都是在线状态:
--- - name: Upgrade foo-app in place hosts: foo-app serial: 2
接下来我们可以创建我们的任务了。第一个任务就是禁用负载均衡后面的主机。负载均衡运行在foo-lb主机上;然而,我们是在foo-app主机上操作。因此,我们需要使用delegate_to任务操作符代理任务。这个操作符指示ansible将连接到哪里来执行任务,但是保持所有的原始主机上下文变量。我们使用haproxy模块来禁用来自foo-app后端池的当前主机。
tasks: - name: disable member in balancer haproxy: backend: foo-app host: "{{ inventory_hostname }}" state: disabled delegate_to: foo-lb
主机禁用后,我们就可以升级foo-app内容了。我们使用git模块使用在foo-version变量中定义的期望版本号来更新内容路径。我们还会给这个任务添加一个notify处理器,如果内容更新结果改变的时候,来重启nginx服务器。这个可以每次都执行,这里我们使用这个作为notify的例子:
- name: pull stable foo-app git: repo: "{{ foo-app.repo }}" dest: /srv/foo-app/ version: "{{ foo-version }}" notify: - reload nginx
接下来,我们需要在负载均衡里边重新启用这个主机;然而,如果我们接下来做这个任务的话,我们会将旧版本就地退回,因为通知处理器还没有运行呢。因此,我们需要提前触发我们的处理器,可以通过meta: flush_handlers调用来实现,这点我们前面已经了解过。
- meta: flush_handlers
接下来,我们可以重启启用负载均衡后面的这个主机了。我们可以直接启用它,并依赖负载均衡,在发送请求过去之前,一直等待直到机器健康。 然而,因为我们运行的主机数量较少了,所以我们需要确保剩下的主机都是健康的。我们可以利用wait_for任务等待,直到nginx服务再次服务连接。wait_for模块会等待一个基于端口或文件路径的条件。在我们这个例子中,我们等待端口80,以及那个端口应该在里边。如果它启动了(默认情况), 这就意味着它可以接受请求了。
- name: ensure healthy service wait_for: port: 80
最后,我们可以在haproxy中重新启用这个成员。再一次,我们委托任务给foo-lb:
- name: enable member in balancer haproxy: backend: foo-app host: "{{ inventory_hostname }}" state: enabled delegate_to: foo-lb
当然,我们还需要定义我们的reload nginx处理器:
handlers: - name: reload nginx service: name: nginx state: restarted
这个剧本运行的时候,就会对我们的应用执行一个就地升级滚动。
扩容和缩容
就地升级策略的一个代替方案是扩容和缩容策略。这个策略已经变得后期流行了,得益于按需设施的自助特性,例如云计算或虚拟化池子。具有从大的可用池中按需创建新的服务器的能力,意味着每一个应用的部署都可以在全新的系统上发生。这种策略就避免了一个主机的问题。这些包含在长期运行系统上构建讨厌的东西,例如:
- 不再受ansible管理的配置文件还保留着。
- 后台消耗资源的run-away进程。
- 人们使用shell访问服务器进行的手工修改的东西。
每次重新全新的也可以消除初始化部署和升级中的差异。可以使用同样的代码路径,降低升级应用时的意外风险。这种类型的安装也可以非常方便回滚,如果新版本执行没达到预期的话。此外,随着新系统取代旧系统,应用在升级过程中不需要进入降级状态。
接下来我们再用扩容和缩容策略来实现我们之前的升级剧本。我们的模式是创建新的服务器、部署我们的应用、验证应用、将新服务器添加到负载均衡后面、然后从负载均衡里边移除旧服务器。 对于这个例子来说,我们使用OpenStack Compute Cloud来启动新实例:
--- - name: Create new foo servers hosts: localhost tasks: - name: launch instances os_server: name: foo-appv{{ version }}-{{ item }} image: foo-appv{{ version }} flavor: 4 key_name: ansible-prod security_groups: foo-app auto_floating_ip: false state: present auth: auth_url: https://me.openstack.blueboxgrid.com:5001/v2.0 username: jlk password: FAKEPASSW0RD project_name: mastery register: launch with_sequence: count=8
在这个任务中,我们使用with_sequence在数量8个上循环。每个循环上item会替换为对应数字。这样允许我们可以基于版本和数字来为我们应用创建8个全新实例。我们也假设可以有一个提前构建好的映像文件可以使用,这样我们就无需做任何更多的实例配置了。为了在将来的剧情中使用这些服务器,我们需要将它们的细节添加到inventory中。 要实现这点,我们注册运行结果到启动变量中,我们将使用它们来创建运行时inventory实体:
- name: add hosts add_host: name: "{{ item.openstack.name }}" ansible_ssh_host: "{{ item.openstack.private_v4 }}" groups: new-foo-app with_items: launch.results
快速失败
any_errors_fatal选项
max_fail_percentage选项
强制处理器
减少破坏性
延迟破坏
只运行破坏一次
总结
Deployment and upgrade strategies are a matter of taste. Each come with distinct advantages and disadvantages. Ansible does not possess an opinion on which is better, and therefore is well suited to perform deployments and upgrades regardless of the strategy. Ansible provides features and design patterns that facilitate a variety of styles with ease. Understanding the nature of each strategy and how Ansible can be tuned for that strategy will empower you to decide and design deployments for each of your applications.
In the next chapter, we'll cover topics that will help when things don't quite go as expected when executing Ansible playbooks.
目录
- 第五章: 用角色合成可重用Ansible内容
- 目录: 掌握ansible(Mastering ansible)
- 第七章: ansible故障排查