现代软件开发对基础设施的管理提出了更苛刻的要求:
产品要适应瞬息万变的市场,要求基础设施有更快的响应速度。持续交付和DevOps的推行要求产品团队对部署和运维要有更高的自主性。技术的快速进步和演化,也使得基础设施的配置不得不频繁变化。在这种快速变化的过程中,要求基础设施既要灵活,也要安全、可靠。
传统的基础设施运维管理具有以下几个问题:
- 被动响应。产品团队获取服务器资源采用的是申请制,中间存在若干审批过程,以及需要等待运维团队实施,响应不及时。
- 自动化缺乏串联。虽然有一定的自动化,但不能做到无人值守,需要执行一些临时命令介入。由于环境释放和重建的成本高,因而倾向于不释放,导致资源利用率低。
- 和产品团队脱节。很难根据需求随时动态增加环境。需要额外的文档来描述环境,可能更新不及时。
产品团队是实施持续交付的过程中,必须考虑将基础设施的维护纳入进来,作为支持产品运行的一部分。以下是产品团队的持续交付流水线全景图。
从上图可以看出,产品团队除了管理项目本身代码外,还要管理环境定义脚本。环境定义脚本可以由基础设施自动化工具执行,动态创建、销毁和更新产品运行所需的环境(包括服务器、负载均衡器、防火墙配置、第三方依赖等)。
如果实现了这一点,就实现了基础设施即代码的雏形。Kief在《Infarftruce As Code》一书中对基础设施即代码定义如下:
“基础设施即代码是一种使用新的技术来构建和管理动态基础设施的方式。它把基础设施、工具和服务以及对基础设施的管理本身作为一个软件系统,采纳软件工程实践以结构化的安全的方式来管理对系统的变更。”
基础设施即代码有四项关键原则:
- 再生性。环境中的任何元素可以轻松复制。
- 一致性。无论何时,创建的环境各个元素的配置是完全相同的。
- 快速反馈。能够频繁、容易地进行变更,并快速知道变更是否正确。
- 可见性。所有对环境的变更应该容易理解、可审计、受版本控制。
基础设施即代码的目标是:
- 标准化。以代码来定义环境,实现开发环境、测试环境、生产环境的标准化。
- 自动化。以自动化工具来驱动代码准备环境。包括创建环境、更新环境以及销毁环境。
- 可视化。以监控来可视化环境信息。环境当前状态可视、环境变更历史可视、可追溯。
基础设施即代码实践会产生高成熟度的持续交付和DevOps。
在实施基础设施即代码时,要遵守以下实践:
1. 使用DSL描述环境
Ansible、Chef、SaltStack、Terraform等基础设施自动化工具都有各自的描述性语言来实现对基础设施的定义。使用DSL更容易通过描述性的语言定义基础设施,也有助于代码重用。团队成员能建立起共同理解,从而维护脚本。
以下是Ansible的一个playbook示例。
- ---
- - hosts: local
- tasks:
- - name: Install Nginx
- apt: pkg=nginx state=installed update_cache=true
- notify:
- - Start Nginx
- handlers:
- - name: Start Nginx
- service: name=nginx state=started
2. 自测试系统
在编写环境代码的配置时,也要编写对环境的测试。确保所有服务器都进行了正确的配置,遵守了所有的安全规则,也对网络连通性等进行了验证。我们一般提倡将测试代码和配置代码放在一起维护。这样配置代码更新时,能保证测试代码也被及时更新。
一些典型的基础设施自动化测试工具有ServerSpec、Testinfra等。以下是一个ServerSpec的示例。
- require 'spec_helper'
- describe package('httpd'), :if => os[:family] == 'redhat' do
- it { should be_installed }
- end
- describe package('apache2'), :if => os[:family] == 'ubuntu' do
- it { should be_installed }
- end
- describe service('httpd'), :if => os[:family] == 'redhat' do
- it { should be_enabled }
- it { should be_running }
- end
- describe service('apache2'), :if => os[:family] == 'ubuntu' do
- it { should be_enabled }
- it { should be_running }
- end
- describe service('org.apache.httpd'), :if => os[:family] == 'darwin' do
- it { should be_enabled }
- it { should be_running }
- end
- describe port(80) do
- it { should be_listening }
- end
3. 一切进行版本化
一旦采用了环境定义脚本实现对环境的控制后,需要将环境定义脚本纳入到版本管理中。并且之后所有的环境变更都应该先修改环境定义脚本,由环境定义脚本触发对环境的变更。登录到服务器执行一些临时性命令是被坚决禁止的。因为这极有可能会破坏环境的一致性。重建服务器时,也不能保证能应用所有需要的变更。
下图是基础设施即代码的一个典型使用场景。
除此之外,如果想要在生产环境中创建可伸缩性的服务,也需要借助“基础设施即代码”这一实践。在高峰时期,系统可以根据定义的环境自动创建并加入新的节点来实现动态扩容,并在低峰时将其销毁。当监控发现某节点失败,系统可以根据定义的环境自动创建新的节点来替换失败节点,实现自动灾难恢复。
***是我们在某团队实施基础设施即代码的案例解析。这张图是某团队的基础设施架构图。
该团队使用AWS作为基础设施平台。我们选用ansible作为基础设施自动化工具,并结合AWS提供的cloudformation服务实现快速创建和销毁资源。所有网元都有清晰的角色划分,配套对应的配置脚本。从网络配置到网元配置以及应用配置都实现了全自动化。所有的配置脚本都和源代码一起托管在GitHub。团队所有成员都可以查看并修改。
【本文是51CTO专栏作者“ThoughtWorks”的原创稿件,微信公众号:思特沃克,转载请联系原作者】