Ansible2-DevOps-实现指南-全-
Ansible2 DevOps 实现指南(全)
原文:
annas-archive.org/md5/88832cd7c65485299e65fb968ec686dd译者:飞龙
前言
Ansible 已经在 DevOps 领域掀起了风暴。其高度可扩展的架构和模块化实现使其成为在大大小小的组织中实施 DevOps 解决方案的理想工具。《使用 Ansible 2 实现 DevOps》将提供详细的教程和信息,教你如何在组织中实现 DevOps 解决方案。《使用 Ansible 2 实现 DevOps》旨在通过教育你 DevOps 实施的实际技术面以及文化和协作实施,来促进组织内良好的软件开发和交付实践。在本书的过程中,我们将逐步展示常见 DevOps 实施缺陷和自动化需求的解决方案。本书通过易读、易跟随的示例,带领你逐步实现解决方案,简单易懂。作为《使用 Ansible 2 实现 DevOps》的作者,我真诚地希望这本书能帮助你在成为 DevOps 和 Ansible 专家的道路上取得进展。因此,我创建了一个网站,帮助读者进一步学习,并可就任何问题与我联系。网址是 www.ansibledevops.com。
本书涵盖的内容
第一章,DevOps 基础,向你介绍 DevOps(一个将开发与运维相结合的词汇),并教你 DevOps 运动以及近年来 DevOps 如何彻底改变全球各地组织的软件开发和交付。
第二章,配置管理基础,将教你像 Ansible 这样的工具如何通过消除环境漂移、自动化基础设施的配置以及提供一种简单一致的方式来构建 Bug 重现环境,从而使开发人员、测试人员和运维工作更加轻松。
第三章,安装、配置和运行 Ansible,将教你在哪里获取 Ansible,如何安装和设置控制服务器,如何使用库存项授权控制服务器,如何使用 Ansible 命令行运行 playbook 或查询基础设施组。最后,本章将介绍核心模块集以及如何提供与其他技术(如 Git、JIRA、Jenkins 等)的交互式接口。
第四章,Playbook 和库存文件,将进一步扩展你对 playbook、playbook 树、角色、任务和库存文件的理解。在这一章中,读者将看到一些简单 playbook 的基本示例,并学习如何以不同的方式运行它们。
第五章,Playbooks: 超越基础,将帮助你学习 YAML 标记语言的语法要求。此外,本章还将教你 Ansible 中与角色、包含、变量、循环、块、条件、注册和事实相关的特定语法。
第六章,Ansible 中的 Jinja,将深入讲解 Jinja2 模板以及如何在 Ansible 中使用它。
第七章,Ansible Vault,将概述 Ansible 管理和部署敏感信息的方式,并讲解如何最好地利用 Ansible Vault 工具确保敏感数据保持机密。你将通过示例学习如何控制和管理高度安全的信息,并了解 Ansible 如何确保信息安全的原理。
第八章,Ansible 模块与库,将重点介绍 Ansible 解决方案提供的广泛模块。Ansible 中的模块使得 playbook 能够连接并控制第三方技术(包括一些开源和封闭源技术)。在本章中,我们将讨论最受欢迎的模块,并深入探讨如何创建 playbook 任务来帮助管理开发人员、测试人员和运维人员可以使用的一整套工具和服务。
第九章,将 Ansible 与 CI 和 CD 解决方案集成,我们将教你如何利用其他与 DevOps 相关的工具来控制 Ansible。具体的主题包括 Jenkins 和 TeamCity。读者将学习如何将 Ansible 用作构建后操作,Ansible 如何适应持续集成(CI)和持续交付(CD)流水线,并提供每种工具的示例。
第十章,Ansible 与 Docker,我们将向你介绍如何使用 Python 扩展 Ansible,并创建与特定技术栈集成的自定义模块。这将通过一系列教程来完成,教读者编写和发布自定义的 Ansible 模块。本章将教你如何读取输入、管理事实、执行自动化任务、与 REST API 交互以及生成文档。
第十一章,扩展 Ansible,将帮助你学习 Galaxy 和 Tower 的流行功能,以及如何使用和配置这两者。完成本章后,你将拥有成为你所在组织中这两种独特技术的权威所需的知识。
第十二章,Ansible Galaxy,将讲解如何使用 Ansible 配置 Docker 容器,如何将 Ansible 与 Docker 服务集成,如何管理 Docker 镜像事实,以及如何完全控制 Docker 镜像。
你需要的本书资源
本书是以 Ubuntu 作为控制服务器并通过 SSH 访问目标服务器编写的。因此,您需要一台 Ubuntu 机器或虚拟机来使用本书中的教程。在模块和插件创建章节中,您需要在 Ubuntu 系统上安装 Python。
本书适合谁阅读
本书适合任何对 DevOps 实现以及如何利用 Ansible 创建自动化解决方案感兴趣的人。
约定
在本书中,您会看到许多文本样式,用于区分不同类型的信息。以下是这些样式的一些示例及其含义。文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名会如下所示:“如果 SSH 密钥共享不可用,Ansible 还提供了通过--ask-become-pass命令行参数请求密码的选项。”
一段代码的设置方式如下:
# File name: hellodevopsworld.yml
---
- hosts: all
 tasks:
 - shell: echo "hello DevOps world"
当我们希望引起您注意某个代码块的特定部分时,相关的行或项会以粗体显示:
[databaseservers]
mydbserver105.example.org
mydbserver205.example.org
[webservers]
mywbserver105.example.org
mywbserver205.example.org
任何命令行输入或输出均如下所示:
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible
新术语和重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,都会以这种方式出现在文本中:“要安装 Ansible 插件,只需在插件管理器(作为 Jenkins 管理员)中导航并从可用插件选项卡中选择 Ansible 插件,然后安装该插件。”
警告或重要提示会以如下框体显示。
提示和技巧会以如下形式显示。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对本书的看法——您喜欢或不喜欢的部分。读者的反馈对我们非常重要,它帮助我们开发您真正能从中获益的书籍。要向我们提供一般反馈,只需通过电子邮件发送至feedback@packtpub.com,并在邮件主题中提及书名。如果您在某个领域具有专业知识,并且有兴趣编写或参与编写一本书,请参阅我们的作者指南:www.packtpub.com/authors。
客户支持
现在,您已经成为一本 Packt 图书的骄傲拥有者,我们为您提供了一些帮助,以便您最大限度地从购买中获益。
下载示例代码
您可以从www.packtpub.com账户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packtpub.com/support并注册,将文件直接通过电子邮件发送给您。您可以通过以下步骤下载代码文件:
- 
使用您的电子邮件地址和密码登录或注册我们的网站。 
- 
将鼠标指针悬停在顶部的 SUPPORT 标签上。 
- 
点击“代码下载与勘误”。 
- 
在搜索框中输入书名。 
- 
选择您要下载代码文件的图书。 
- 
从下拉菜单中选择您购买此书的地方。 
- 
点击“代码下载”。 
下载文件后,请确保使用以下最新版本解压或提取文件夹:
- 
Windows 下的 WinRAR / 7-Zip 
- 
Mac 下的 Zipeg / iZip / UnRarX 
- 
Linux 下的 7-Zip / PeaZip 
本书的代码包也托管在 GitHub 上,地址是 github.com/PacktPublishing/Implementing-DevOps-with-Ansible-2。我们还在 github.com/PacktPublishing/ 提供了其他代码包,来自我们丰富的图书和视频目录。快来查看吧!
下载本书的彩色图片
我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。彩色图片将帮助你更好地理解输出中的变化。你可以从 www.packtpub.com/sites/default/files/downloads/ImplementingDevOpswithAnsible2_ColorImages.pdf 下载此文件。
勘误
尽管我们已经尽力确保内容的准确性,但错误难免发生。如果你在我们的书中发现错误——可能是文本或代码上的错误——我们将非常感激你向我们报告。通过这样做,你不仅能避免其他读者的困惑,还能帮助我们改进后续版本的书籍。如果你发现任何勘误,请访问 www.packtpub.com/submit-errata 提交勘误,选择你的书籍,点击勘误提交表单链接,并输入勘误详情。勘误一经验证,将被接受并上传到我们的网站,或者添加到该书勘误列表中。要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support 并在搜索框中输入书名。所需信息将在勘误部分显示。
盗版
网络上的版权材料盗版问题在所有媒体中都持续存在。在 Packt,我们非常重视版权和许可的保护。如果你在互联网上发现任何我们作品的非法复制品,请立即提供网址或网站名称,以便我们采取措施。请通过 copyright@packtpub.com 联系我们,并附上涉嫌盗版材料的链接。感谢你帮助我们保护作者和我们为您提供有价值内容的能力。
问题
如果你对本书的任何方面有问题,可以通过 questions@packtpub.com 联系我们,我们将尽力解决问题。
第一章:DevOps 基础
DevOps 运动、敏捷开发、持续集成(CI)和持续交付(CD)在重塑全球软件工程格局方面发挥了重要作用。手动环境配置、发布工程师的“神职”和深夜冷披萨发布派对的时代已一去不复返。尽管披萨可能是亮点,但凌晨四点的部署噩梦根本不值得怀念。这些过时的做法已被高效的交付流水线、可扩展的微服务架构和 IaC 自动化配置管理技术所取代。由于这些创新,出现了对自动化工程师、配置管理人员和 DevOps 导向工程师的新需求。这种新需求要求一种既能推动高效开发实践、自动化配置管理,又能实现可扩展软件交付的工程资源,彻底改变了现代软件组织的面貌。
在软件工程中,DevOps 这一术语既广泛又多样。通过简单的 Google 搜索DevOps这一术语,约会出现 1800 万个独特的页面结果(这可不少!)。在 Indeed.com 上搜索 DevOps 这一术语,则提供了各种各样的行业应用。像大多数以文化为导向的术语一样,DevOps 也有流行的时髦定义和更深层次的技术含义。对于外行人来说,DevOps 可能显得有些模糊。因此,它常常被企业误解为是一个能编程的运维人员,或者是一个作为运维资源的开发人员。这种被称为 DevOps 工程师的误称,已导致了许多困惑。上述两种定义都不是 100% 准确的。
在本书中,我们将明确围绕 DevOps 实施的相关实践,并为你提供成为组织中成功的 DevOps 和 Ansible 专家的所需知识。本书将探讨 Ansible 的实施,并学习它如何与 DevOps 解决方案和流程紧密结合。我们将共同走进 Ansible 和 DevOps 的世界,看看如何利用它们实现可扩展的部署、配置管理和自动化。我们将一起踏上这段旅程,探索 DevOps 在 Ansible 2 中的精彩世界。让我们开始吧!
在本章中,我们将深入探讨 DevOps 及其方法论结构,涵盖以下主题:
- 
DevOps 101 
- 
DevOps 的历史 
- 
现代软件组织中的 DevOps 
- 
DevOps 装配线 
- 
DevOps 架构和模式 
DevOps 101
在 2009 年 DevOpsDays 大会巡回之前的几年里,"DevOps"这个词在工程和技术领域相对陌生。DevOps 导向文化的诞生是在 2008 年一次敏捷基础设施会议上由 Patrick Debois 提出的。在这次会议上,Patrick 谈到了他在一家大型企业任职期间与一个高度协作的开发团队合作的经验。在这段经历中,最为协作的时刻是在出现网站故障或紧急情况时。在这些事件中,开发人员和运维人员似乎高度专注,并且配合得异常默契。这段经历让 Patrick 渴望在非紧急活动中也能促进这种合作行为。
在敏捷基础设施大会上,Patrick Debois 还与 Andrew Shafer(当时在 Puppet Labs 工作)建立了联系。两人很快发现,他们有许多相同的目标和理念。从某种意义上说,这次偶遇激励 Patrick 继续推动 DevOps 这一新兴概念的发展。在随后的大会上,Patrick 在敏捷基础设施会议中竭力但未能成功地鼓励采用一种更为协作的软件开发和交付方法。尽管这一理念很新颖,但这一理念的实际实施似乎始终未能在 Patrick 所提供的场合中获得广泛支持。
2009 年,Patrick Debois 参加了 O'Reilly 的Velocity大会,在会上他听到 John Allspaw 讲述 Ops 和 Dev 如何协作。正是这次演讲在他脑海中播下了 DevOps 的种子。Patrick 决定开始举办一系列小型 DevOpsDays 大会,这最终将 DevOps 的概念推向主流工程文化。
虽然目前还没有一个简明的、能概括 DevOps 所有内容的一句话,但在关于定义 DevOps 的总体概念和实践上,已达成一个普遍接受的共识:文化、自动化、度量和共享,简称CAMS。CAMS 方法由 Damon Edwards 和 John Willis 在 2010 年 DevOpsDays 大会上定义。接下来将进一步详细描述。
文化
从 DevOps 运动中诞生的一个普遍接受的概念是文化概念。由于传统的 IT 组织与开发部门之间的隔阂,全球各地的组织中普遍存在信息孤岛现象。为了为快速开发和交付铺平道路,必须发生组织文化的根本变化。这一变化旨在促进组织内的协作、共享以及协同感。这种文化变革无疑是组织中 DevOps 采纳过程中最为困难的方面。
自动化
自动化曾经是手动操作的过程,对于成功的 DevOps 转型至关重要。自动化消除了构建、测试和交付软件中的猜测和魔法,并强化了软件过程的编码化。自动化也是 DevOps 中最为显著的方面之一,并提供了最高的投资回报率(ROIs)。
测量
衡量成功与失败提供了关键的业务数据,并通过有效的变革为更高效率铺平道路。这强调了通过数据和度量而非直觉反应来做出业务决策的重要性。为了确保 DevOps 转型的成功,衡量诸如吞吐量、停机时间、回滚频率、延迟和其他相关操作统计数据,可以帮助将组织引导向更高效率和自动化的目标。
分享
与先前接受的软件开发范式形成鲜明对比,分享对于成功的 DevOps 转型至关重要。这意味着应该鼓励团队分享代码、概念、实践、过程和资源。成功的 DevOps 导向组织甚至可能会让运营人员或 QA 资源嵌入到开发团队中,以促进自主性和协作团队的建设。一些组织还可能有共享或重叠的角色。这可以通过一些现代开发技术(如 TDD、BDD 等)来实现。
在撰写本文时,已有数百种,甚至成千上万种专为 DevOps 设计的工具。这些工具旨在使工程组织的工作变得更好或更高效。虽然 DevOps 的工具方面很重要,但也需要注意,不能让某个工具决定你所在组织的 DevOps 过程。再次强调,在没有先应用 CAMS 模型的情况下,无法实现这一实施。整本书中,我们会参考并讲解各种不同的工具和技术。对你来说,特别重要的是要为合适的任务选择并使用正确的工具。
DevOps 的历史
在 DevOps 广泛采用之前,组织往往承诺在指定的时间框架内开发和交付一个软件系统,但更多时候却错过了发布的截止日期。未能按时完成所需的截止日期会给组织带来额外的财务压力,并且通常意味着业务会消耗大量的资金。软件组织错过发布截止日期的原因有很多,以下是一些最常见的原因:
- 
完成纯开发工作的所需时间 
- 
将不同组件集成到一个工作软件中的所需努力量 
- 
测试团队发现的质量问题数量 
- 
软件部署失败或未能成功安装到客户的机器上 
完成一个软件项目所需的额外努力(和资金),有时甚至会消耗公司的资金,直到公司陷入破产。像 Epic MegaGames 和 Apogee 这样的公司曾一度处于行业顶端,但由于错过发布日期和未能竞争,它们很快就衰退,并最终成为失败企业和死亡软件项目的背景。
这个时代的主要风险不在于工程团队通常需要多长时间来开发一个软件项目,而在于初步开发完成后,集成、测试和发布一个软件项目需要多长时间。一旦软件项目的初步开发完成,通常会有很长的集成周期和复杂的质量保证措施。由于发现质量问题,在软件项目足够无缺陷并可以发布之前,必须进行重大重做。最终,这些版本会被复制到磁盘或 CD 中,发货给客户。
这种范式的一些副作用是,在开发、集成、质量保证或预发布期间,软件组织无法利用已开发的软件,企业通常也无法得知进展情况。这本质上带来了相当大的风险,可能导致企业破产。随着软件工程的风险达到历史最高点,而企业对类似拉斯维加斯式的赌博感到排斥,必须采取措施。
为了让企业规范开发、集成、测试和发布步骤,公司进行了战略规划并创建了软件开发生命周期(SDLC)。SDLC 提供了一个基本的流程框架,工程团队按照这个框架来了解正在开发的软件项目的当前状态。这些过程步骤包括以下内容:
- 
需求收集 
- 
设计 
- 
开发 
- 
测试 
- 
部署 
- 
运营 
SDLC(软件开发生命周期)中的过程步骤被发现是循环性的,这意味着一旦某个软件版本发布,下一次迭代(包括修复 bug、打补丁等)就会被规划,SDLC 将重新启动。在 90 年代,这意味着版本号的修订、功能的大规模重做、修复 bug、添加增强功能、新的集成周期、质量保证周期,最终是 CD 或磁盘的再版。从这个过程中,现代的 SDLC 诞生了。
下面提供了 SDLC 的示意图:

通过软件开发生命周期(SDLC)的创建和规范化,企业现在有了一种有效的方式来管理软件的创建和发布过程。尽管这个过程正确地识别了可重复的软件过程,但并未减少集成风险。集成阶段的主要问题在于合并的风险。在 DevOps、CI、CD 和敏捷方法出现之前,软件的开发任务通常会在团队之间分配,个别开发人员会回到自己的工作站进行编码。他们会在相对孤立的情况下进行开发,直到每个人完成后,才进行后续的集成开发阶段。
在集成阶段,单独的工作副本被拼凑在一起,最终形成一个统一的、可操作的软件版本。当时,集成阶段对企业来说是最大的风险,因为这个阶段可能需要的时间和创建软件版本本身一样长(或更长)。在此期间,工程资源昂贵且失败的风险最高;因此,迫切需要一种更好的解决方案。
集成阶段对企业的风险往往非常高,最终由几位软件专家提出了一种独特的方法,这最终为未来的发展铺平了道路。持续集成(Continuous Integration, CI)是一种开发实践,开发人员可以将本地工作站的开发更改逐步(且非常频繁地)合并到共享的源代码控制主干中。在 CI 环境中,通常会创建基本的自动化流程来验证每一个增量更改,并确保没有无意中破坏或失效。如果不幸发生了某些故障,开发人员可以轻松修复或回滚更改。持续合并贡献的理念意味着,组织不再需要一个集成阶段,QA(质量保证)可以在软件开发过程中开始进行。
持续集成最终通过成功的主流软件工程实施以及肯特·贝克(Kent Beck)和马丁·福勒(Martin Fowler)的不懈努力而得到了推广。这两位行业专家在 90 年代中期的克莱斯勒公司成功地将基本的持续集成技术进行了扩展。通过他们的新 CI 解决方案的成功试金石,他们注意到企业在集成阶段的风险被消除了。因此,他们热切地宣扬这种新方法论是未来的方向。CI 开始获得关注后,其他软件公司也开始注意到这一方法并成功应用了核心技术。
朝向未来的进步
到 90 年代末和 2000 年代初,持续集成(CI)已经全面开展。软件工程团队纷纷要求更频繁地集成,快速验证更改,并且他们孜孜不倦地工作以逐步开发可发布的软件。在很多方面,这被认为是工程的黄金时代。正是在持续集成革命的巅峰时期,(在 2001 年)12 位软件工程专家在犹他州雪鸟山度假村会聚,讨论一种新的软件开发方法。此次思想碰撞的结果,现如今被称为敏捷开发,被分为四个核心支柱:
- 
个人和互动胜过过程和工具 
- 
工作软件胜过全面文档 
- 
客户协作胜过合同谈判 
- 
响应变化胜过遵循计划 
也就是说,虽然右边的内容有其价值,但我们更重视左边的内容。
这套简单的原则与敏捷开发的 12 个核心理念结合,后来被称为敏捷宣言。完整的敏捷宣言可以在agilemanifesto.org/找到。
2001 年,敏捷宣言正式发布,组织很快开始将工作拆分成更小的块,并站立着开会而不是坐着开会。功能被优先考虑,工作项被分配给团队成员完成。这意味着团队现在有了严格的时间表和 2 到 4 周的交付期限。
虽然这朝着正确的方向迈出了步伐,但它仅限于开发团队的范围。一旦软件系统从开发交给质量保证,开发团队通常会保持“放手”状态,直到软件最终发布。那个时代最显著的问题是,许多复杂的部署是在几乎不了解软件工作原理的人员手中,将软件部署到物理基础设施中的问题。
随着软件组织的演变,其他部门也发生了变化。例如,质量保证(QA)实践变得更加现代化和自动化。程序员开始编写自动化测试套件,并努力以自动化方式验证软件更改。随着质量保证的变革,现代实践如测试驱动开发(TDD)、行为驱动开发(BDD)和A/B 测试应运而生。
敏捷运动诞生于 2001 年,当时敏捷宣言被签署并发布。敏捷宣言中确定的原则在许多方面标志着 DevOps 运动所采用和扩展的核心概念。敏捷宣言的发布代表了开发模式的根本变化。它提倡更短的迭代开发周期、快速反馈和更高水平的协作。听起来很熟悉吧?
大约在这个时期,持续集成(Continuous Integration)开始在软件组织中扎根,工程师们开始关注构建失败、单元测试失败和发布工程等问题。
现代软件组织中的 DevOps
解决组织中孤岛问题的方案似乎是改变文化,通过更频繁地交付软件变更来简化和自动化这一过程,改变软件解决方案的架构(从单体架构转变),并通过协同、敏捷性和速度为组织铺平道路,从而超越竞争对手。这个理念是,如果一个企业能够比竞争对手更快速地交付客户所需的功能,它就能够战胜对手。
正因为这些原因,现代的 DevOps 方法应运而生。这种方法还允许在组织内逐步采用 DevOps。
DevOps 流水线
在计算机科学的初期,程序员被视为巫师,他们的代码是一种黑暗艺术,组织为开发和发布软件支付了巨额费用。许多时候,软件项目会陷入困境,企业在尝试发布软件产品时会因失败而破产。那时的计算机科学充满了风险,开发周期长,集成期痛苦,且经常出现发布失败的情况。
2000 年代中期,云计算席卷全球。计算资源的弹性实现理念,使得那些快速扩张的组织能够轻松扩展,推动了未来创新的浪潮。到 2012 年,云计算已经成为一大趋势,成百上千的公司争先恐后地进入云端。
随着软件工程在 2000 年代初期的成熟和计算机广泛使用的增长,一种新的软件范式应运而生;它被称为软件即服务(SaaS)。过去,软件是通过 CD、软盘或直接现场安装的方式交付给客户的。这种被广泛接受的定价模型通常是一次性购买。这种新平台提供了基于订阅的收入模型,并以弹性和高度可扩展的基础设施为卖点,承诺为企业带来持续的收入。它被称为云。
随着云计算的兴起和软件使用模式的剧变,之前被接受的大爆炸式 5 次发布策略开始变得过时。由于软件发布思维的转变,组织无法再等待超过一年的集成周期才能开始执行质量保证测试计划,也不能再等待两年才能让工程和 QA 签署一个发布版本。为了解决这一问题,持续集成应运而生,软件开发的流水线系统的雏形开始形成。DevOps 的核心不仅仅是团队内部的协作优势。其前提实际上是一种商业策略,通过 DevOps 文化的实施,更高效地将功能交付到客户手中。
DevOps 流水线与制造业的相关性
在工业革命之前,商品主要是手工制作的,并且产量较小。这种方式限制了工匠能够生产的数量,以及他们能向哪些客户销售商品。手工制作商品的过程被证明是昂贵、耗时且浪费的。当亨利·福特开始开发汽车时,他着眼于寻找一种更高效的制造方法。他的探索结果是实施标准化方法论,并采用渐进式流水线方式来开发汽车。
在 1980 年代和 1990 年代,软件工程工作常常消耗公司的大量财务资源。这是因为过程中的低效、沟通不畅、缺乏协调的开发努力以及不完善的发布过程所导致的。诸如集成阶段、手工质量保证、验证发布计划和执行等低效环节,常常大大增加了整个开发和发布策略的时间。为了开始缓解这些风险,新的实践和流程逐渐形成。
由于这些趋势,软件组织开始将制造技术应用于软件工程。其中一个较为普遍的制造概念是制造装配线(也称为渐进式装配)。在世界各地的工厂中,装配线帮助组织产品制造流程,并确保在发货和交付之前,制造的商品经过精心组装和验证。装配线方法为大规模生产的产品提供了一定程度的可重复性和可量化的验证。工厂采用渐进式装配方法,以最小化浪费、最大化效率,并提供更高质量的产品。近年来,软件工程组织也开始倾向于这种渐进式装配线的做法,以帮助减少浪费、提高产出并发布更高质量的产品。由此,整体的 DevOps 概念应运而生。
DevOps 架构和实践
从 DevOps 运动中,已经出现了一些越来越流行的软件架构模式和实践。这些架构模式和实践的主要逻辑来源于对可扩展性、无停机部署以及最小化客户对升级和发布的负面反应的需求。其中一些你可能听说过(如微服务),而另一些则可能有些模糊(如蓝绿部署)。
在本节中,我们将概述一些从 DevOps 运动中演化而来的更流行的架构和实践,并学习它们如何在全球范围内的组织中被利用,以提供灵活性和速度。
封装软件开发
在软件开发中,封装通常对不同的人意味着不同的东西。在 DevOps 架构的背景下,它简单地意味着模块化。这是 DevOps 组织的一个重要实现要求,因为它提供了一种单独更新和替换组件的方法。与单体软件相比,模块化软件更容易开发、维护和升级。这适用于从宏观的架构方法到面向对象编程中的对象级别。如果你曾在一个拥有单体遗留代码库的软件组织工作过,你可能非常熟悉意大利面条代码或单体分形洋葱软件方法。下面是单体软件架构与封装架构方法的对比图:

正如我们从上面的图中看到的,模块化组织的软件解决方案显著比单体方案更容易理解,也可能更易于管理。
微服务
微服务架构与容器化和便携虚拟化几乎是同时兴起的。微服务架构的基本概念是将软件系统架构设计成一种方式,使得大型开发团队能够通过可重复的部署简便地更新软件,并且只升级已更改的部分。从某种意义上说,微服务提供了一个基本的约束和解决方案来防止开发蔓延,确保软件组件不至于变得过于庞大。只升级已更改部分的常规做法可以比作换车轮而不是每次轮胎磨损时就更换整辆车。
微服务开发范式要求开发人员具备纪律性,以确保微服务的结构和内容不会超出最初定义的范围。因此,微服务的基本组件在此列出:
- 
每个微服务应该具有一个 API 或外部通信方式。 
- 
每个微服务,在适用的情况下,都应该拥有一个独立的数据库组件。 
- 
每个微服务只能通过其 API 或外部通信方式进行访问。 
所以,基于我们所学的内容,微服务与单体架构的对比可以用以下基本图示总结:

持续集成与持续交付
持续集成与持续交付,或称 CI->CD,在软件行业中更加广为人知,已成为 DevOps 运动的一个基础组件。这些实践的实施在许多组织中有所不同。由于 CI/CD 的成熟度和发展阶段存在显著差异,实施方式也因此有所不同。
持续集成(Continuous Integration)代表了一个完全自动化的构建和部署解决方案的基础,通常是 CI/CD 追求中的起点。持续集成代表了一套特定的开发实践,旨在通过自动化验证每个对源代码控制的软件系统的更改。在许多方面,持续集成的具体实践也代表了主干软件开发,并结合了一套基本的验证系统,以确保提交没有引起任何代码编译问题,并且不包含已知的潜在问题。
这里提供了持续集成的一般实践:
- 
开发人员将代码更改提交到源代码控制系统的主干(按照马丁·福勒(Martin Fowler)提出的持续集成概念),并且至少每天执行一次。这是为了确保即使代码不完整,团队成员也能进行协作。 
- 
一个自动化系统会检测到提交并验证代码是否能够编译(语法检查)。 
- 
相同的自动化系统会对新更新的代码库执行一系列单元测试。 
- 
如果提交存在任何可识别的缺陷,系统会通知提交者。 
如果在给定提交的 CI 周期结束时存在任何可识别的缺陷,提交者有两个潜在选项:
- 
快速修复问题。 
- 
从源代码管理中恢复更改(以确保系统处于已知的工作状态)。 
尽管 CI 的实践看起来相当简单,但在许多方面,开发组织实施起来却相当困难。这通常与团队和组织的文化氛围有关。
值得注意的是,这里提到的 CI 来源于 Martin Fowler 和 James Shore。这些软件先驱在创建和倡导 CI 实施以及扎实的开发实践方面发挥了重要作用。这也是持续交付所需的基础平台,持续交付是由 Jez Humble 于 2012 年创建的。
持续交付是 CI 的延续,并要求 CI 作为基础起点。持续交付的目标是通过前面描述的基本 CI 过程验证每次提交的软件变更。持续交付提供的主要附加功能是,一旦代码变更验证完成,CD 系统将把软件部署(安装)到一个模拟环境,并进行额外的测试。
持续交付实践旨在为开发人员提供关于他们提交的质量和代码库潜在可靠性的即时反馈。最终目标是始终保持软件处于可发布状态。当正确实施时,CI/CD 能够为组织提供显著的商业价值,并有助于减少调试复杂合并和提交时的开发周期浪费,这些合并和提交往往无法正常工作或没有提供商业价值。
根据我们之前描述的内容,持续交付有以下基本操作流程:
- 
用户将代码提交到源代码控制主干 
- 
自动化 CI 过程会检测到变更 
- 
自动化 CI 过程会构建/语法检查代码库中的编译问题 
- 
自动化 CI 过程会创建一个独特版本的可部署包 
- 
自动化 CI 过程会将包推送到工件库 
- 
自动化 CD 过程会将包拉取到指定环境 
- 
自动化 CD 过程会将包部署/安装到环境中 
- 
自动化 CD 过程会对环境执行一系列自动化测试 
- 
自动化 CD 过程会报告任何失败 
- 
自动化 CD 过程会将包部署到其他环境 
- 
自动化 CD 过程允许额外的手动测试和验证 
在持续交付实施中,并不是每个变更都会自动进入生产环境,而是持续交付的原则提供了一种随时可发布的软件产品。其理念是,软件可以随时推送到生产环境,但并不一定总是这么做。
一般来说,CI/CD 的流程大致如下:
- 持续集成:

- 持续交付流程:

- 持续交付的组件:

模块化
微服务和模块化本质上相似,但并不完全相同。模块化的基本概念是避免创建单体的软件系统实现。单体软件系统通常在开发过程中不自觉地将组件紧密耦合,使得各个组件之间有着较强的相互依赖,以至于更新一个组件时,需要更新许多其他组件,才能改善功能或解决缺陷。
单体软件开发实现通常出现在设计不良或开发阶段仓促完成的遗留代码库中。这种方式常常导致软件功能脆弱,并迫使企业不断花费大量时间来更新和维护代码库。
另一方面,模块化的软件系统具有整齐封装的一组模块,由于缺乏紧密耦合的组件,可以轻松更新和维护。模块化软件系统中的每个组件提供一般自给自足的功能,可以更高效地被替换掉。
水平可扩展性
水平扩展是一种软件交付方法,允许较大的云计算组织在给定环境中启动特定服务的额外实例。然后,流入该服务的流量将被负载均衡到各个实例,以为最终用户提供一致的性能。水平扩展应用程序必须在软件开发生命周期(SDLC)的设计和开发阶段进行,并且需要开发人员具备一定的纪律性。
蓝绿部署
蓝绿部署是一个开发和部署的概念,需要两份产品副本,一份称为蓝色,另一份称为绿色,其中一份是当前发布的产品版本,另一份则在积极开发中,一旦准备好就可以作为下一个发布版本使用。使用这种开发/部署模型的另一个好处是,如果需要的话,可以回滚到之前的版本。蓝绿部署对于持续集成(CI)的概念至关重要,因为如果未来的版本没有与当前版本一起开发,热修复和故障应急管理就会成为常态,创新和整体专注度也会因此受到影响。
蓝绿部署特别允许零停机部署,并使回滚过程无缝进行(因为先前的实例从未被销毁)。一些非常著名的组织已经成功实现了蓝绿部署。这些公司包括:
- 
Netflix 
- 
Etsy 
- 
Facebook 
- 
Twitter 
- 
Amazon 
由于蓝绿部署,DevOps 世界中已经取得了一些显著成功,最小化了部署风险并提高了软件系统的稳定性。
工件管理与版本控制
工件管理在 DevOps 环境中起着至关重要的作用。工件管理解决方案为所有可部署的内容提供单一的事实来源。除此之外,它还为自动化系统提供了一种方法,可以对构建或潜在发布候选版本进行封装,确保其在初始构建之后不被篡改。在许多方面,工件管理系统对于二进制文件的作用就像源代码管理对源代码的作用一样。
在软件行业中,有许多工件管理的选择。其中一些是免费的,其他则需要购买特定工具。以下是一些比较流行的选择:
- 
Artifactory ( www.jfrog.com)
- 
Nexus ( www.sonatype.com/)
- 
Apache Archiva ( archiva.apache.org/index.cgi)
- 
NuGet ( www.nuget.org/)
现在我们对工件管理有了基本了解,接下来我们来看看工件仓库如何融入以 DevOps 为导向的环境中的一般工作流程。接下来将提供一张图,展示该解决方案在 DevOps 导向环境中的位置:

对称环境
在快速部署环境(变化通过交付流水线迅速推进)中,任何生产前和生产环境必须保持一定程度的对称性。这就是说,部署程序和软件系统的最终安装必须在各个环境中尽可能相同。例如,一个组织可能拥有以下环境:
- 
开发环境:在这里,开发人员可以测试他们的更改和集成策略。该环境作为所有开发相关事务的游乐场,为开发人员提供了一个区域来验证他们的代码更改并测试结果影响。 
- 
质量保证环境:该环境位于开发环境之后,为 QA 人员提供测试和验证代码及安装结果的地点。该环境通常作为先行环境发布,并且在签署给定构建版本进行发布之前,该环境需要通过更严格的质量标准。 
- 
阶段环境:该环境代表在生产之前的最终位置,在这里所有自动化部署技术都经过验证和测试。 
- 
生产环境:该环境代表用户/客户实际使用在线安装工作的地点。 
总结
在本章中,我们讨论了 DevOps 及其运动的起源;我们了解了 DevOps 的各种组件(CAMS);我们探讨了敏捷、持续集成、持续交付和微服务在 DevOps 中的作用;同时,我们讨论了 DevOps 所需的一些其他架构技术。
在下一章中,我们将深入探讨配置管理,它在 DevOps 中也扮演着关键角色。通过了解配置管理的技巧,我们将开始理解基础设施即代码的概念(这是 Ansible 非常擅长的)。我们将深入探讨版本控制配置状态的意义,如何开发维护基础设施状态的代码,以及创建成功的配置管理(CM)解决方案的方方面面。
第二章:配置管理基础知识
如果你正在阅读本书,你很可能在寻找一种一致的方式来配置基础设施、部署代码以及维持环境的操作一致性。这些内容都属于配置管理,简称 CM。配置管理代表了 DevOps 的基石,也是 Ansible 擅长管理的内容。配置管理代表了一种开发方式,其中可以使用自动化来配置并强制执行一组系统的状态、一致性和准确性。在一个 DevOps 导向的环境中,配置管理将变得越来越关键,用于维护开发环境、QA 系统、生产环境等。这是因为,在发布软件或更新时,维护这些环境并确保它们之间的一致性对于部署和操作成功至关重要。
在本章中,我们将全面学习配置管理。该章节将涵盖并回顾像 Ansible 这样的工具如何通过消除环境漂移、自动化基础设施配置、提供轻松一致的方式来快速创建与生产环境匹配的环境,从而使开发人员、测试人员和运维人员的工作变得更加轻松。本章的目标是学习和理解自动化配置管理技术,从本质上讲,这些配置管理技术可以应用于为小型或大型组织提供价值。在本章中,我们将特别讨论以下主题:
- 
理解配置管理 
- 
理解配置管理的起源 
- 
配置管理的目标 
- 
配置管理的基本原则 
- 
配置管理最佳实践 
- 
Ansible 如何简化 DevOps 实施 
- 
二进制工件管理和 Ansible 
配置管理是一个非常重要的话题(尤其是在像 Ansible 这样的解决方案中)。我们开始吧?
理解配置管理
除非你一直生活在泡沫中,否则你可能已经看到配置管理自动化在 IT 领域掀起了风暴。也就是说,现在可以通过代码(安装软件包、创建用户等)自动配置和准备主机,使其处于特定状态。基础设施可以被定义和编码的这个想法不仅是新颖的,而且现在已经成为常态。配置管理在许多方面代表了基础设施在 IT 中的地位,就像软件开发代表了面向终端用户软件产品的地位。如果此时你仍不清楚 CM 实际上是什么,我们来快速定义一下:
配置管理是详细记录和更新描述企业硬件和软件的信息。这些信息通常包括已应用于已安装软件包的版本和更新,以及硬件设备的位置和网络地址。
现在我们已经对 CM 的教科书定义有了基本了解,接下来让我们看看这个定义如何适应现代软件组织。
软件中的快速交付系统增加了维护生产中干净和有状态系统的巨大压力。这意味着过去的开发和发布计划如今已经成为历史:在产品新版本发布后,公司及其产品开发团队再也无法满足于传统的骄傲自满了。随着宽带、移动设备、SaaS 和互联网接入的快速普及,消费者现在比以往任何时候都更加渴望更频繁、更快速的更新。在这种强烈需求下,并且没有缓解的迹象,自动化已成为任何希望管理分发渠道并在当今快节奏数字世界中保持竞争力的公司的必备工具。
配置管理在许多方面改变了现代开发模式的格局。它鼓励团队更加协作,向软件系统或 IT 基础设施项推送增量更改,管理变更控制流程,并跟踪变化在组织中的逻辑组之间的迁移。尽管配置管理技术在不断发展(几乎与技术堆栈的变化速度一样快),但 CM 的核心概念依然稳定。这些核心特征在这个图表中有所体现:

从这个图表中,我们可以看到软件配置管理的几个核心原则。这些原则定义如下:
- 
发布管理:发布管理是创建自动化和流程协同工作的实践,旨在可靠地发布软件供目标终端用户使用。 
- 
CM 环境:CM 环境代表生产发布环境的物理或虚拟副本集。作为最佳实践,它们应通过基础设施即代码(IaC)解决方案(如 Ansible)自动配置,且基础设施应能通过自动化轻松复制。 
- 
持续集成与部署:持续集成和持续交付在 DevOps 世界中掀起了风暴。它们实际上是一套重要的实践,鼓励协作并覆盖整个交付流水线。这些实践将在后续页面中详细说明。 
- 
版本控制:版本控制是所有源代码和开发工作应存储的地方。这包括任何自动化的配置管理脚本、Ansible 剧本、开发代码和自动化的质量保证测试。 
- 
工作项跟踪:工作项跟踪就是将一个项目的大型实施工作分解、跟踪、开发、测试和部署成更小、更合理的部分,以便分别进行处理。使用像 JIRA 这样的工作项跟踪解决方案可以帮助小型或大型团队更高效地开发和发布解决方案。 
- 
可追溯性:随着软件系统、基础设施开发工作或质量保证自动化变得越来越复杂,尽可能将每项工作拆分成更小、更容易完成的任务变得非常重要。因此,在将工作项目拆分成更小、更容易完成的任务后,确保每个更改能够在交付管道中保持可追溯性就显得尤为重要。 
- 
基线:为基础设施解决方案设定基线非常重要,因为它为所有未来的基础设施实施提供了一个坚实的起点。这意味着,在任何给定的时刻,你都可以使用已知的良好基线重新映像一个系统,并在此基础上进行构建。在很多方面,这也提供了一定程度的一致性。 
配置管理的起源
配置管理可以追溯到 1950 年代初期,那是打孔卡和大型主机计算设备的时代。那时,打孔卡经常需要进行组织和传送到主机,因此大型组织需要管理这些打孔卡的配置,以确保它们的顺序正确。在打孔卡的黄金时代之后,随着维护给定软件系统或 IT 设备状态的需求逐渐显现,出现了额外的管理需求。当时能够管理此类过程的实体仅限于政府的CDC 更新和 IBM 的IBM IEBUPDATE。
直到 1990 年代初期到中期,软件配置管理(简称 CM)才开始引起中到大型组织的关注。像 IBM 和国防部这样的公司和组织是配置管理技术的早期采用者之一。此时,配置管理的范围仅限于识别加入到给定系统(或一组系统)中的配置实现和变更。这是为了追踪在系统发生故障或出现问题时,重新构建系统或部署所需的步骤,从而消除了之前的人工工作。
配置管理的目标
配置管理的目标是通过为开发人员、质量保证人员、管理人员和运维人员提供必要的工具和自动化,来简化他们的工作,以便追踪和实施目标系统的额外更改和配置。配置管理的一般逻辑流程大致如下:
如果我花四个小时为开发人员配置一个开发系统,然后开发人员第二天辞职,接着新开发人员入职,我就浪费了八个小时。相反,如果我花八个小时编写一套自动化脚本来自动配置开发系统,而自动化从开始到完成仅需 20 分钟,那么我就可以轻松且快速地重建开发人员的系统。
从最基本的层面来看,这就是配置管理的核心:节省时间、节省金钱、节省资源,并且最小化浪费。是的,前期可能需要一些工作,但这种解决方案的结果是,机器可以商品化,配置和部署系统时不再需要黑魔法,人们也不必为了实现机器已经能够完成的目标而加倍努力。除了这些价值点外,它还将团队的知识代码化,举例来说,可能有一个人知道如何设置 apache(例如),如果他离开了,解决方案的管理仍然能够继续。因此,配置管理的目标可以定义如下:
- 
跟踪对给定系统或系统集所做的更改。 
- 
提供缺陷的可追溯性和审计性,这些缺陷可能是由对系统进行的一系列更改引起的。 
- 
为了帮助减少开发人员、QA 和运维人员在维护一套自动化解决方案时的人工工作量,这些解决方案能够协助系统的配置和提供。 
- 
通过明确定义(以自动化形式)构建系统所需的步骤,为组织提供重复性。 
在前面的简单示例中,我们看到配置管理如何节省时间和金钱。但现在我们已经了解了配置管理的基本好处及其应用,让我们看看更多配置管理的例子,以及它如何为组织带来潜在的好处。
场景 1
Bob 在一家大型 IT 软件公司担任开发人员。Bob 正在处理一个需要帮助的 bug。借助正确的配置管理策略,Bob 可以轻松自动化地重建他的 bug 环境(用于本地测试和 bug 重现的虚拟机),这样他和同事就能同时在同一个 bug 上工作,并且可以最小化环境重建的麻烦。
在这个案例中,Bob 成功地使用配置管理技巧,不仅为他的同事节省了大量重现 bug 的时间,还消除了潜在的人为错误。
场景 2
Taylor 是一名质量保证工程师,他被 Acme 公司联系并成功在本地机器上重现了当前困扰 Acme 公司的缺陷。如果没有合适的配置管理解决方案,Taylor 将只能手动将重现步骤输入到 JIRA 工单中,并希望开发人员能快速修复该缺陷。
场景 3
开发团队一直在努力完成本月的版本发布。该版本包括多个基础设施变更和环境启动要求。得益于完善的配置管理计划和成功的自动化,生产版本的实施和发布只需几分钟,而不是上个月的几个小时。
场景 4
Randy 正在努力帮助公司的文档工作,他通过记录公司云计算基础设施的配置文档,确保新员工拥有设置本地计算基础设施副本所需的准确信息。得益于完善的配置管理计划和策略,Randy 能够通过简单阅读已经创建并存储在源代码控制中的配置管理自动化,节省了大量文档工作时间。
如你所见,正确使用配置管理策略能够帮助组织节省大量时间,因此也能减少浪费的基础设施成本,从而节省资金。
配置管理的基本原则
配置管理旨在帮助工程组织管理基础设施,创建可重用的自动化,并提供一个变更管理的策略。因此,配置管理提出了以下基本原则:
- 
在可能的情况下进行自动化 
- 
提供企业内部的可追溯性 
- 
为开发人员、QA、运营和管理人员提供可复现的基础设施,并通过软件开发最佳实践进行管理 
- 
制定新硬件如何进行自动化配置和提供的策略 
- 
有效且有策略地管理硬件配置 
- 
开发提供基础设施变更自服务模型的机制 
- 
教育组织关于配置管理的实践 
现在我们已经对配置管理的基本原则有了初步了解,让我们快速深入探讨每个原则,并对其做更详细的定义:
- 
尽可能实现自动化:这一配置管理的基本原则为我们提供了一个指导方针:如果计算机能够自动完成原本由人工手动完成的任务,那么就应该让计算机去做。虽然实施这种自动化的过程需要时间,并且显然会消耗人力资源,但通过自动化重复任务节省的时间是显著的,而前期的投资是值得的。 
- 
提供企业级可追溯性:大多数经验丰富的开发人员都曾花时间尝试追踪系统中的“幽灵”,无论是在本地环境还是生产环境中。花费数小时试图确定具体发生了什么变化同样令人沮丧。这个概念中的可追溯性非常重要,因为它为那些负责确定故障的人提供了部署中哪些代码行发生了变化的列表。有了良好的配置管理计划和自动化流程,所需的可追溯性可以轻松实现。 
- 
可复现的基础设施:如果你曾在工作中花时间尝试重新创建一个特定软件系统的构建环境或重现部署环境,你就会知道这一点有多么重要。如果你以前没有做过这件事,你真是一个幸运的人。通常,最耗时的工作之一就是试图重新创建开发或交付环境,并准确配置所有必要的库、包或模块,以确保系统按预期工作。有了合适的配置管理解决方案(如 Ansible),这完全是可能的。 
- 
制定新硬件和基础设施如何自动配置的策略:这可能是作为详细且全面的配置管理实施计划的一部分,最初需要完成的任务之一。有了一个良好的计划,其它步骤就会顺利进行。 
- 
开发自服务执行配置管理解决方案的机制:从长远来看,你不想成为配置管理解决方案的守门人。你会希望规划并创建一种自服务的配置管理解决方案,让你的客户(开发人员、QA、运维和管理人员)通过点击按钮使用你的解决方案。一些组织可能会使用 Jenkins 或 Circle CI 来完成这样的任务。最终,使用或创建什么样的解决方案由你来决定。 
配置管理最佳实践
现在我们已经对配置管理的总体目标有了基本了解,并知道它如何在企业中发挥作用,让我们来看看一些配置管理中的最佳实践。现代软件企业中的配置管理有很多形式。一些较为流行的配置管理工具如下所示:
- 
Ansible 
- 
Chef 
- 
Puppet 
- 
CFEngine 
这样的解决方案通常是开源选项,提供了将基础设施保持和维护为代码形式,或称为 IaC(基础设施即代码)的方法。对于不熟悉 IaC 的人来说,这里有来自维基百科的一般定义:
基础设施即代码(IaC)是通过机器可处理的定义文件来管理和提供计算基础设施(如进程、裸机服务器、虚拟服务器等)及其配置的过程,而不是通过物理硬件配置或使用交互式配置工具。
从这个定义中,我们可以开始看到基础设施即代码(IaC)在配置管理中扮演着重要角色。这是 Ansible 的一个关键亮点,开始展示它在组织中的定位。因此,将基础设施作为代码进行维护是一种配置管理的最佳实践。
另一个有效配置管理策略的关键最佳实践是变更控制。这个概念在 1990 年代初期至中期变得非常流行,为开发资源提供了跟踪与产品开发相关的源代码更改的能力。随着源代码管理变得越来越流行,人们开始为这个解决方案找到新的用途。最终,基础设施即代码(IaC)被存储在源代码管理中,这变得更加关键,以有效管理配置管理资产。
由于 IaC 的出现,一些组织开始将源代码管理(SCM)解决方案直接与他们的部署结合起来。不幸的是,这违反了 CI/CD 的最佳实践,CI/CD 要求工件(包括自动化)必须适当地版本化,并且能够轻松冻结,以便于实现轻松回滚和向前滚动。
源代码管理系统自从最早进入软件开发行业以来,已经发展了很长一段时间。现代实现如 Git 和 Mercurial 提供了新的创造性方法来分支、存储源代码并提供离线访问源代码资源。作为配置管理专家,我们的职责是鼓励源代码中的良好实践,并帮助确保我们的基础设施和自动化保持高完整性。
Ansible 如何简化 DevOps 实现
Ansible 是软件配置管理领域中相对较新的参与者。它最初作为一款开源软件,由 Ansible 团队于 2012 年管理并推出。Ansible 这个名字来源于 1980 年代的小说《安德的游戏》,由 Ansible 的创始人 Michael DeHaan 选择。这个解决方案的主要目标是提供一个易于使用且人类可读的配置管理实现。
Ansible 的初始实现是在 2012 年推出的,旨在遵循以下原则:
- 
最小开发要求:配置管理系统应当是轻量且简洁的。 
- 
执行一致性:Ansible 旨在提供一套一致的规则和预期,在其核心实现中。 
- 
安全性:Ansible 平台使用 SSH 作为推荐协议开发,提供了一种安全的传输方式,用于远程执行配置管理代码。 
- 
可扩展性:无论是针对一台服务器还是 1,000 台服务器,系统都需要能够有效扩展。Ansible 在设计时就考虑到了这一点。 
- 
高度可靠:提供一致的执行模型是 Ansible 的目标——一个非常可靠的执行平台,具有极少的错误。 
- 
易于学习:摒弃了 CFEngine 等工具的复杂性,Ansible 力求成为行业中最容易学习的配置管理工具。 
现在我们已经了解了 Ansible 的设计原则,接下来让我们看看它如何具体增强 DevOps 的实施。
如前所述,Ansible 被设计为一致、易于管理、可扩展、安全和简约。秉持这些原则,我们快速看一下一个简单的 DevOps 环境图,看看 Ansible 如何融入其中:

在图示中,我们可以看到四个独立的环境(开发、测试、阶段和生产)。这些环境在 DevOps 实施和云计算软件组织中非常常见。某些组织可能有更多,某些则更少。在 DevOps 环境中,可能有一个或多个服务器或设备需要持续地进行配置和部署。这些环境之间的一致性对于最终将软件系统发布到生产环境至关重要。
Ansible 在许多方面有助于维持环境之间的一致性,因为它提供了一个易于使用的自动化解决方案,可以按以下方式执行:
- 
同时跨环境执行 
- 
跨环境的一致性 
- 
以幂等的方式提供跨环境的一致性 
从图示中,我们可以看到,每个环境都将对称地使用配置管理工具,如 Ansible。该工具将位于一个中心位置,能够访问前述的每个环境。因此,它在许多方面都可以被视为系统的管理者。接下来的图示展示了 Ansible 提供的推送模型架构的基本结构:

从这张图中,我们可以看到一些符合我们早前提到的配置管理最佳实践的内容:
- 
配置管理资源存储在 VCS(版本控制系统)中。 
- 
Ansible 配置机可以访问 VCS 和部署服务器。在某些情况下,这是好的,但在某些情况下,将 Ansible 资源与工件打包在一起可能会更有用(我们稍后会详细讨论这个问题)。 
- 
Ansible 能够针对特定的服务器类型进行配置,并以独特的方式对每台服务器进行部署。无论是公司的 Web 服务器、应用服务器还是主数据库服务器,Ansible 都能自动化本来繁琐的配置过程,让我们能够集中精力处理更有价值的任务。这个过程通过开发剧本(即预写的配置设置)来实现,这不仅节省了时间,还节省了成本,正如稍后将讨论的那样。 
二进制工件管理与 Ansible
在任何规模的开发项目中,都会使用、生成和归档成千上万的工件。大多数这些数字碎片是内部资源,但大多数组织也会利用从外部公司获得许可的库和其他资源,这些资源通常是针对特定用途并且通常具有合同规定的时间期限。手动管理和遵守这些要求虽然可能,但将耗费大量时间,而绝大多数组织都不愿意投入如此多的时间。因此,应该存在一个工件管理系统来管理这些限制,并确保每个开发人员都能访问任何给定资源的相同版本,无论他们身处何地。正因为如此,工件库的概念应运而生。所有在开发过程中使用、创建和管理的资源都存储在一个中央位置,直到在任何给定的软件项目中需要时才会调用,无论项目的大小如何。
管理依赖关系是资源管理中另一个重要的方面,这一点不可小觑,因为几乎没有软件开发项目是没有任何依赖关系的。如果没有一个系统来管理版本和依赖关系,我们的开发人员 Bob 在第一个配置管理目标场景中可能无法有效解决票务中出现的 bug。这是因为他缺乏正确版本的一个或多个资源的知识,而这些资源在组合时导致了软件功能上的问题。这类兼容性冲突在没有维护版本化依赖库的团队中是常见的。
最后,治理作为一个重要因素,在需要一个有能力的二进制资产管理解决方案中发挥着重要作用。由于开发人员在构建软件时使用的资源不仅受限于许可协议,而且所使用的资源版本可能对于每个产品的不同小版本/大版本都是不同的,因此治理至关重要。治理要求确保每个存储在我们工件库中的资源都已正确分类、经过质量审核,并且已经注释。它确保所有相关的许可和使用要求已得到满足,并确保整个开发流程尽可能顺利进行。此外,治理为公司提供了一定的保障,避免在生产后出现许可问题。
以下是一个示意图,展示了工作站如何从构件仓库服务器/机器中拉取资源,并在软件开发中使用这些资源。一旦准备好推送到生产环境,完成的工作就会被送到配置机器,配对上适当的剧本。与选择的剧本配对后,变更会被发送到适当的位置进行实施:

Ansible,作为一个非常强大的配置部署工具,在其内置的构件管理能力方面存在一定的局限性。尽管许多人希望 Ansible 能够成为配置管理和部署的“一站式”解决方案,但它只是任何 DevOps 团队工具箱中的众多工具之一,旨在确保产品发布的及时部署和整体任务的成功。
幸运的是,并非所有的希望都已破灭,因为今天的开发环境中有几种广泛使用的流行 BRM 工具,它们能够与 Ansible 进行接口、增强并补充 Ansible 的能力,同时接手 Ansible 未能完成的部分。以下是一些表现出色的流行工具,它们在各自的功能上表现良好,并且能够与 Ansible 良好对接。虽然这些工具很受欢迎,但这个列表并不全面,也不是完整的,因为解决此功能的市场一直在变化,并且不断有新选项被添加。
- 
JFrog 的 Artifactory ( www.jfrog.com/artifactory/)
- 
SonaType 的 Nexus ( www.sonatype.com/nexus-repository-sonatype)
- 
Apache 的 Archiva ( archiva.apache.org/)
- 
Inedo 的 ProGet ( inedo.com/proget)
通过诸如 Archiva 和其他基于 Maven 的构件仓库管理工具,Ansible 已经内置了或能够轻松获取的一些包,例如maven_artifact,它们尝试从基于 Maven 的 BRM 中抓取所需的依赖项/构件。对于其他工具,如 Nexus,存在第三方模块,使得从这些系统中抓取构件变得同样简单而无痛。
概要
到目前为止,我们简要地了解了 DevOps 的世界,它的起源、使其具有革命性的核心原则,以及在当今快速发展的世界中重新思考规划、开发、测试和发布稳定软件的过程。我们还简要讨论了 Ansible 在这一大局中的作用,以及是什么使它成为现代开发者工具箱中一项重要(如果不是至关重要的话)工具。
接下来,我们将进入本书真正的核心内容——软件本身。我们将学习从在你选择的平台上安装软件的初步步骤,到确保 Ansible 配置正确,使其能够与安装环境的各个部分进行通信并成功执行任务,使用 Ansible 的命令行来导航整体设置;甚至探讨默认安装的核心模块,它们的功能以及在需要时如何找到更多模块。
第三章:安装、配置和运行 Ansible
Ansible 是 DevOps 和配置管理生态系统中一个相对较新的成员。它的极简主义、结构化的自动化格式和简单易用的开发范式吸引了众多企业和初创公司的关注。像 Twitter 这样的组织已经成功地利用 Ansible 进行大规模部署和配置管理的实施,并将其扩展到同时管理和部署到数千台服务器。Twitter 并不是唯一一个在 Ansible 领域成功大规模应用的公司;其他一些成功利用 Ansible 的知名组织还包括 Logitech、NASA、NEC、Microsoft 等数百家公司。
如今,Ansible 已被全球一些最大、最知名的科技公司和组织使用,负责管理数千个部署并为无数组织维护配置管理解决方案。学习 Ansible 解决方案的基础知识将为我们提供适当安装、配置和运行 Ansible 所需的工具,无论是小规模还是大规模应用。
在本章中,我们将正式介绍 Ansible。我们将一起学习如何安装它,深入了解其运行时架构,并学习如何配置它以运行简单的自动化序列。除了这些主题,我们还将涵盖在 Ubuntu Linux 下的 Ansible 控制服务器基础知识,并学习如何利用 Ansible 进行本地执行以及远程管理。最后,在本章中,我们将介绍 playbook 的基本概念,并探索使 Ansible 如此强大的底层构造。本章的目标是掌握以下内容:
- 
安装 Ansible 
- 
Ansible 架构 
- 
Ansible 命令行界面 
- 
配置 Ansible 
- 
Ansible 清单 
现在,我们已经完成了介绍,让我们开始吧!
安装 Ansible
Ansible 本身是跨平台的。Ansible 系统的基本安装其实相当简单。在我们安装它之前,我们需要获取 Ansible 运行时包。Ansible 可以通过以下在线解决方案进行获取:
- 
Yum(基于 Red Hat Linux 的发行版) 
- 
Apt(Debian) 
- 
Apt(Ubuntu) 
- 
Portage(Gentoo) 
- 
Pkg(FreeBSD) 
- 
macOS(dmg) 
- 
OpenCSW(Solaris) 
- 
Pacman(Arch Linux) 
- 
Pip(Python) 
- 
Tarball(源码) 
- 
源码(Source) 
以下是一组示例和命令行语法,适用于上述列出的每个选项。这些命令将帮助你快速开始使用 Ansible(它们来自 Ansible 官方网站)。
通过配置管理安装 Red Hat Enterprise Linux
在最流行的 Red Hat Enterprise Linux 发行版上安装 Ansible 非常简单。可以按照以下步骤完成:
# NOTE: Before installing Ansible you may need to install the epel-release repo
# for RHEL or 
# Scientific Linux. Additional details on how to install EPEL can be found at
# http://fedoraproject.org/wiki/EPEL
$ sudo yum install ansible
Apt for Debian/Apt for Ubuntu
在 Debian 或 Ubuntu 系列 Linux 发行版上安装 Ansible 非常简单。可以使用 Apt 包管理解决方案,按照以下命令操作:
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible
将 Ansible 移植到 Gentoo
对于 Gentoo Linux 用户,安装 Ansible 可以相当容易地完成。可以使用以下命令行语法来完成安装:
# The first command is optional, you may need to unmask the Ansible package prior to running emerge:
$ echo 'app-admin/ansible' >> /etc/portage/package.accept_keywords
$ emerge -av app-admin/ansible
FreeBSD 的 PKG
FreeBSD 特定用户可以使用 pkg install 来安装 Ansible 控制服务器解决方案,并快速启动 Ansible:
$ sudo pkg install ansible
macOS 上的 Pip
安装 Ansible 在 macOS 上的首选方法是使用 Python 的 pip 安装方案。以下将提供一个示例,展示如何操作。
如有需要,你可以通过以下命令安装 pip:
$ sudo easy_install pip
一旦安装了pip,你可以使用以下命令安装 Ansible:
$ sudo pip install ansible
Solaris/SunOS 的 OpenCSW
Solaris 用户可以使用 OpenCSW 包管理解决方案安装和配置 Ansible。可以按以下方式操作:
# pkgadd -d http://get.opencsw.org/now
# /opt/csw/bin/pkgutil -i ansiblePacman for Arch Linux
在 Arch Linux 上安装 Ansible 非常简单。以下命令应该能帮助完成这一任务:
# Note: If you have Python3 selected you must set 
# ansible_python_interpreter = /usr/bin/python2 # in your inventory variables
$> pacman -S ansible
通过 Python pip
在使用 pip 安装 Ansible 之前,我们可能需要先安装 pip。可以使用以下命令行来完成此操作:
$ sudo easy_install pip
然后使用以下命令安装 Ansible:
$ sudo pip install ansible
一旦 Ansible 安装完成
一旦 Ansible 安装在目标 Linux 机器上,我们需要验证它是否已正确安装并正常运行。为此,请在命令行输入以下命令:
# Display Ansible command line options available
$ ansible --help
# Show the Ansible version number
$ ansible --version
执行这些命令成功后,Ansible 应该分别输出版本信息和相关帮助信息。
设置控制服务器与主机之间的认证
在连接 Ansible 与远程主机时,Ansible 的最佳实践建议使用 SSH 密钥共享。SSH 密钥允许一台 Linux 主机与另一台主机通信,而无需特定的密码。在这一部分,我们将简要查看如何在控制服务器和 n 个目标机器上设置 SSH 密钥共享。
如果 SSH 密钥共享不可用,Ansible 还提供了通过 --ask-become-pass 命令行参数请求密码的选项。
首先,创建一个 /etc/ansible/hosts(如果不存在的话),并将一个或多个远程系统添加到其内容中。你的特定公钥 SSH 密钥应位于这些目标系统的 authorized_keys 文件中。
在本简短教程中,我们假设使用 SSH 密钥认证方案。它帮助我们避免输入或存储原始密码:
$ ssh-agent bash
$ ssh-add ~/.ssh/id_rsa
现在 ping 所有节点(假设你已经创建了清单文件):
$ ansible all -m ping
关于在 Ubuntu 上设置 SSH 密钥共享的完整文档集,请访问 linuxproblem.org/art_9.html。
Ansible 架构
Ansible 创建时便具备了一个非常灵活且可扩展的自动化引擎。它允许用户以多种不同的方式进行使用,并且可以根据用户的特定需求进行调整。由于 Ansible 没有代理(这意味着在它管理或执行的系统上没有长期运行的守护进程),它可以本地使用来控制单个系统(无需网络连接),或者通过控制服务器来协调和执行多个系统的自动化。
除了上述架构外,Ansible 还可以通过 Vagrant 或 Docker 来自动化配置基础设施。这种解决方案基本上允许 Ansible 用户通过运行一个或多个 Ansible 剧本来启动他们的硬件或基础设施配置。
如果你恰好是 Vagrant 用户,HashiCorp 的 Ansible 配置说明可以在以下网址找到:www.vagrantup.com/docs/provisioning/ansible.html。
正如我们简要提到的,Ansible 是开源的、基于模块的、可插拔的并且没有代理。与其他配置管理解决方案的这些关键差异使得 Ansible 具有显著的优势。让我们详细看看这些差异,并了解它们对 Ansible 开发者和用户意味着什么。
开源
成功的开源解决方案通常具有丰富的功能,这已经不是什么秘密。这是因为,除了简单的八人(甚至 100 人的)工程团队外,可能还有成千上万的开发者参与其中。每个开发和增强功能的设计都旨在满足特定需求。因此,最终交付的产品为 Ansible 的用户提供了一个非常全面的解决方案,可以以多种方式进行适应或利用。
基于模块
Ansible 已经开发与众多其他开源和闭源软件解决方案的集成。这个理念意味着 Ansible 目前兼容多种 Linux 版本、Windows 操作系统以及云服务提供商。除了操作系统层面的支持外,Ansible 目前还与数百种其他软件解决方案集成:EC2、JIRA、Jenkins、Bamboo、Microsoft Azure、DigitalOcean、Docker、Google 等。
若要查看完整的 Ansible 模块列表,请参考以下官方 Ansible 模块支持列表:docs.ansible.com/ansible/modules_by_category.html。
无代理
Ansible 相较于竞争对手的一大优势是它完全没有代理。这意味着不需要在远程机器上安装守护进程,不需要打开防火墙端口(除了传统的 SSH),不需要在远程机器上进行监控,也不需要对基础设施进行管理。实际上,这使得 Ansible 非常自给自足。
由于 Ansible 可以通过几种不同的方式实现,本节的目标是突出这些选项,并帮助我们熟悉 Ansible 支持的架构类型。一般来说,Ansible 的架构可以分为三种不同的架构类型。接下来将描述这三种类型。
可插拔
尽管 Ansible 自带广泛的软件集成支持,但它通常需要与公司内部的软件解决方案或尚未集成到 Ansible 强大 playbook 套件中的软件解决方案进行集成。解决这种需求的办法是为 Ansible 创建基于插件的解决方案,从而提供必要的定制功能。
使用 Ansible 执行本地自动化
利用 Ansible 的最简单方式是指示它管理本地系统。这意味着无需进行 SSH 连接、端口打开或共享 SSH 密钥。这种实现只需要一个用户、一组 playbook(或一个)和一个本地系统。地方自动化执行是指通过 Ansible 执行一个 playbook(即一系列自动化任务)以针对本地机器执行任务。这种特定的架构类型意味着 Ansible 在执行工作时不需要可用的网络连接或互联网连接。
这种架构类型的示意图如下:

从图中我们可以看出,Ansible 可以用于本地配置。这种架构可能看起来有些不可扩展,但通过一些创意,这种特定架构背后隐藏着强大的能力。我们来看看这种架构可以应用的几种方式:
- 
本地配置开发环境并将其配置为一键设置:理想情况下,采用这种方法时,Ansible 的 playbook 将编写并存储在本地开发源代码控制系统中,然后由新开发人员利用这些 playbook 来设置和配置他们的开发环境。这将大大节省入职培训的时间,并帮助员工快速启动。 
- 
强制执行本地基础设施配置规则并恢复系统中因外部操作而做出的更改:这种解决方案非常适合用于防止基础设施被篡改或意外更改的情况。 
- 
执行一组定时自动化任务,可用于执行自动化例程。 
从架构中我们可以看出,Ansible 的本地执行让我们能够在没有任何麻烦或复杂性的情况下,针对本地化的系统执行 playbook。让我们快速了解如何使用命令行在本地系统上运行 Ansible playbook。但首先,我们来学习如何在本地系统上运行一个临时命令。以下是示例:
Example: Ad hoc Linux echo command against a local system
#> ansible all -i "localhost," -c local -m shell -a 'echo hello DevOps World'
这个命令只是告诉 Ansible 以目标是临时清单中所有系统(在我们的简单用例中,只有 localhost)为准,然后对该系统执行命令 echo "hello DevOps world"。简单吧?现在,让我们看一下如果它以 Ansible playbook 形式呈现时会是什么样子。以下是这个 playbook 形式的示例:
# File name: hellodevopsworld.yml
---
- hosts: all
 tasks:
 - shell: echo "hello DevOps world"
这个示例代表了一个非常简单的 Ansible playbook。Ansible playbook 是用 Yet Another Markup Language(YAML)编写的。它们旨在易读、易写、结构化高且没有复杂性。在 Ansible 的世界中,playbook 的概念来源于人们在观看百老汇演出时可能收到的节目单。Playbook 简要描述了即将上演的场景和演员。因此,Ansible playbook 也包含了即将发生的事件列表(定义为任务及其详细信息)。在我们这个简单的示例中,我们指示 Ansible 向 Linux shell(在目标系统上)显示一个简单的介绍信息:hello DevOps world。
此时,您可能会想,怎么运行这样的 playbook 呢? 很高兴您问了。可以通过命令行指定 playbook 名称来运行 playbook。以下是一个示例:
# Running a Playbook from the command line:
#> ansible-playbook -i 'localhost,' -c local hellodevopsworld.yml
接下来,让我们看一下远程自动化执行。这种方法与本地执行有显著不同,因为它支持更大的扩展性。
使用 Ansible 进行远程自动化执行
Ansible 最常见的使用方式是通过远程执行。这种架构需要网络连接和 SSH 或 Windows 远程连接。通过使用远程执行,我们实际上是在指示 Ansible 通过 SSH 连接到一个或多个机器,使用之前共享的 SSH 或 RM 密钥进行身份验证,建立到一个或多个远程机器的临时 TCP 连接,并针对它们执行一组基于 playbook 的自动化任务。如果这听起来有些混乱,接下来我们将深入了解这种架构和自动化解决方案是如何工作的。
首先,作为本地管理员,我们需要先对目标机器授权给首选的 Ansible 用户。通过这个预授权,实际上我们(通常通过 SSH 密钥共享)正在为至少两台面向 Linux 的机器(控制机和目标机)配置所需的权限,使得控制服务器能够与目标机器进行通信并控制它,而无需进一步授权。本章稍后会详细介绍如何执行基本的 SSH 密钥共享实现(或可以参考 docs.ansible.com/ansible/intro_getting_started.html)。
一旦密钥共享已实现并验证有效,Ansible 就能有效地控制远程系统并对其执行自动化操作。这个过程是通过 Ansible 提供的巧妙自安装方案实现的。实际上,Ansible 自动化系统(执行时)会将自身复制到远程系统,安装到远程系统上,在目标系统上运行指定的 playbook,验证执行结果,并从目标系统中删除自己(以及 playbook)。这样,系统就处于所需状态,而无需在目标系统上维持一个常驻守护进程。
远程自动化执行架构可以通过以下插图看到:

正如我们从前面的图示中看到的,运行 Ansible 的 Linux 控制服务器使用一组安全协议(SSH 和 Windows RM)来控制、自动化和配置远程系统。这些自动化执行是并行进行的,意味着 Ansible 能够同时控制从 1 台到 10,000 台机器,而性能几乎没有显著下降。
现在我们对远程执行架构有了一个较好的了解,接下来我们来看看几个例子。Ansible(正如我们在前一节中发现的)通过 playbook 来管理人类可读的自动化。在这个第一个例子中,我们需要两台机器,都是基于 Linux 的,控制服务器上安装了 Ansible(参见前面的图示,了解哪台机器是控制服务器),并且目标服务器共享 SSH 密钥,这样控制服务器才能正确控制或管理目标服务器。
现在要创建一个 playbook,只需编辑一个名为 hellodevopsworld.yml 的文件,并将以下代码粘贴进去:
# File name: HelloDevOpsWorld.yml
---
- hosts: all
  tasks:
   - shell: echo "hello DevOps world"
将文件保存到磁盘,选择一个你喜欢的位置(我们选择了 /opt/hellodevopsworld.yml)。接下来,我们需要确定目标机器的 IP 地址或 HOSTNAME/FQDN,然后运行 playbook。对于本例,目标主机的地址是 192.168.10.10。现在,让我们从控制服务器运行 playbook 以操作目标机器:
# Example command: Execute the playbook hellodevopsworld.yml against
# 192.168.10.10
#> ansible-playbook -i "192.168.10.10," -c local hellodevopsworld.yml
从这个命令中,我们应该看到 Ansible 执行 hellowdevopsworld playbook,针对目标机器(192.168.10.10)进行操作,执行结果应该输出 hellodevopsworld 到命令行界面。以下是我们应该从 Ansible 执行过程中看到的输出:
PLAY [all] *********
GATHERING FACTS *********
ok: [192.168.10.10]
TASK: [shell echo "hello world"] *********
changed: [192.168.10.10]
PLAY RECAP *********
192.168.10.10 : ok=2 changed=1 unreachable=0 failed=0 
默认情况下,Ansible 配置为并行执行 所有 自动化任务。通过在命令行上指定 --limit 选项,可以修改这一行为,允许管理员限制并行执行的数量。这在你需要调试 playbook 或限制同时执行的数量时非常有用。
面向容器的自动化
面向容器的自动化类似于本地自动化执行,尽管它利用了 Ansible 在容器中的配置管理和自动化执行能力。与其他架构略有不同的是,它并不依赖于在执行 playbook 之前在指定主机或容器上安装 Ansible,而是通过即时安装 Ansible(在容器的配置阶段)并在安装后执行 playbook。因此,自动化流程的最佳表示方式是通过流程图来展示:

面向容器的自动化特别适用于环境的配置(如开发、测试、生产等),同时也能帮助开发人员快速启动。让我们来看一下一个使用 Ansible 作为虚拟机内引导解决方案的快速 Vagrant 设置。
首先,让我们以 root 用户身份登录到本地机器,并创建一个目录来存放我们的本地基础设施。在这个示例中,我们使用的是安装了 VirtualBox 和 Vagrant 的 macOS 机器(www.vagrantup.com/about.html),虽然这些步骤是针对 macOS 的,但同样可以很容易地应用到 Windows 机器上:
- 创建一个目录,用于存放我们的项目,并进入该目录:
#> mkdir -p ~/Desktop/helloVagrantAnsible
#> cd ~/Desktop/helloVagrantAnsible
- 使用vagrant init命令初始化 Vagrant:
#> vagrant init ubuntu/trusty64
- 编辑新创建的Vagrantfile,它应该位于~/Desktop/helloVagrantAnsible/Vagrantfile,并根据此处展示的内容进行修改(内容取自 Ansible 网站):
# This guide is optimized for Vagrant 1.7 and above.
# Although versions 1.6.x should behave very similarly, it is recommended
# to upgrade instead of disabling the requirement below.
Vagrant.require_version ">= 1.7.0"
Vagrant.configure(2) do |config|
 config.vm.box = "ubuntu/trusty64"
 # Disable the new default behavior introduced in Vagrant 1.7, to
 # ensure that all Vagrant machines will use the same SSH key pair.
 # See https://github.com/mitchellh/vagrant/issues/5005
 config.ssh.insert_key = false
 config.vm.provision "ansible" do |ansible|
 ansible.verbose = "v"
 ansible.playbook = "playbook.yml"
 end
end
从 Vagrantfile 中我们可以看到,实际上我们是在配置 Vagrant 以便在初次启动时执行 playbook.yml。一旦理解了这种实现,它非常强大,因为它为我们提供了一种使用 Ansible 作为配置工具来引导基础设施解决方案的方式。这种实现非常适合开发人员或质量保证工程师,因为它为他们提供了将基础设施作为代码存储的方法。
现在我们有了Vagrantfile,接下来让我们在与Vagrantfile相同的目录中创建playbook.yml。其内容应如下所示:
# playbook.yml
---
- hosts: all
 tasks:
 - name: "Install Apache"
 apt: name={{ item }} state=present
 with_items:
 - apache2
 - name: "Turn on Apache and set it to run on boot"
 service: name={{ item }} state=started enabled=yes
 with_items:
 - apache2
这个 playbook(如你所猜测的)仅仅是在我们在vagrant init命令中定义的 Ubuntu 系统上安装 Apache2,并告诉 Ansible 在启动时将 Apache 作为系统服务进行配置。
本教程的最后一步就是通过在命令行窗口中输入以下命令来运行我们的 Vagrant 设置:
# Start Vagrant and auto provision the system with Ubuntu,
# Ansible and Apache
$> vagrant up
如果一切顺利,你应该能看到本地的 Apache 实例在通过 Vagrant 配置并运行的 VirtualBox 中运行。
Ansible 命令行界面
Ansible 提供了一个强大的命令行界面,允许用户运行 Ansible playbook、模拟 Ansible playbook 的执行、运行临时命令等。在 Ansible galaxy(稍后会对这个特定的双关语进行解释)中,有两种可以执行的 Ansible 命令类型。ansible 命令允许用户运行临时命令,而 ansible-playbook 命令则允许用户针对目标基础设施执行一组 Ansible playbook 指令。
文档中对此表述模糊,Ansible 和 Ansible-playbook 似乎是符号链接,但有一些报告显示两者的功能不同。为了确保所报告信息的准确性,仍然需要更多的研究。在最终草拟前,Ansible 和 Ansible-playbook 将各自有一节(稍后部分提供),但这些内容可能会有所更改。
Ansible 命令行界面是进入 Ansible 核心的门户,提供了多种选项和可配置的开关,帮助用户充分利用 Ansible。让我们快速浏览一下 Ansible 命令行界面,看看有哪些可用的开关和按钮。
用法:ansible <主机模式> [选项]
在这一节中,我们将看看 Ansible 命令行模式,并了解它们的工作原理。Ansible 的命令行解决方案实现实际上很容易理解。让我们来看几个简单的例子。
--help 选项会显示可用命令行选项的完整列表。这可能是 Ansible CLI 中最重要的命令行选项之一:
Help: [#> ansible --help] 
现在我们已经知道如何使用 help 命令,让我们来看一下 version 命令。这个命令行参数为我们提供了正在使用的 Ansible 安装的版本信息:
Version: [#> ansible --version]
ansible --version 命令会输出安装的 Ansible 副本的版本信息。例如,你可能会看到类似如下内容:
#> ansible --version
ansible 2.0.0.2
config file = /etc/ansible/ansble.cfg
configured module search path = Default w/o overrides
现在我们了解了 version 命令,让我们看看如何检查我们的 playbook 语法并执行 playbook 的测试运行。这是通过 --check 选项完成的:
Check: [#> ansible foo.yml --check]
这个命令行选项允许你查看 Ansible 在执行 playbook 时可能做出的更改。这是模拟 playbook 执行的好方法,是在实际运行之前的明智第一步:
Inventory: [#> ansible -i 192.x.x.x, x.x.x.x]
在命令行中指定清单是非常重要的。它是通过 -inventory-file 参数完成的。这个命令行参数允许我们指定一个逗号分隔的主机名列表,执行 playbook 时将会针对这些主机进行操作。这个命令行允许你指定一个 Ansible 清单文件的路径(YAML 格式)。在其中,你可以指定主机名和组成你基础设施清单的主机组(Ansible 会在执行过程中针对这些主机进行操作):
InventoryFile: [#> ansible -inventory-file 192.x.x.x, x.x.x.x]
这个命令行选项指示 Ansible 显示此次执行的目标主机列表。如果你有大量主机,并将它们分组管理,而你只想针对其中一部分主机进行操作,这个选项会非常有用:
ListHosts: [#> ansible -list-hosts]
这个命令行选项指示 Ansible 将所有日志输出压缩成一行。这包括事实收集过程、任务执行输出以及所有 STDOUT 输出:
OneLine: [#> ansible –-one-line]
这个命令行选项指示 Ansible 将所有输出重定向到一个特定的文件,而不是传统的控制台位置。指定文件中的输出将会被追加到该文件中:
Output: [#> ansible –-output <filename>]
这个命令行选项指示 Ansible 对指定的 playbook 进行语法检查,而不是执行它。对于开发者来说,在执行之前确保 playbook 是有效的 YAML 格式非常有用:
SyntaxCheck: [#> ansible –-syntax-check]
这个命令行选项指示 Ansible 将默认超时时间(秒)从 10 秒更改为其他数值:
TimeOut: [#> ansible –timeout=X]
这个命令行选项指示 Ansible 在连接远程主机时使用远程用户登录,而不是默认的 SSH 共享密钥方式。如果某些主机不允许通过 SSH 连接,这个选项会很有用:
User: [#> ansible -u | --user =USERNAME]
这个命令行选项指示 Ansible 在执行输出中添加详细日志。在调试特定主机问题或异常行为时,这非常有用,尤其是在传统执行方式下无法预见到这些问题:
Verbose: [#> ansible –verbose]
有关更多命令行选项,请参考 Ansible CLI 在线帮助或文档。更多在线帮助可以在 www.ansible.com 查找。
Ansible 命令行示例
现在我们已经对 Ansible 命令行提供的各种参数有了相当清晰的理解,接下来让我们看一些示例,展示如何利用这些选项来增强 playbook 的执行。以下示例展示了 Ansible 执行的一些基本方式:
Example: Run an ad hoc command against an Ansible inventory group and limit the execution of a playbook to a max of 5 simultaneous servers:
$ ansible europe -a "whoami" -f 5 
Example: Execute a playbook against the Europe group as a privileged account (different from the SSH account
$ ansible europe -a "/usr/bin/foo" -u username --become [--ask-become-pass]
Example: Transfer a local (on the ansible control server) file to a set of remote hosts simultaneously 
$ ansible europe -m copy -a "src=/opt/myfile dest=/opt/myfile"
Example: Running an ansible playbook against a single hostname or IP address
#> ansible-playbook -i "192.168.10.10," -c local hellodevopsworld.yml
Example: Running an ansible playbook against an inventory group
#> ansible-playbook myplaybook.yml
配置 Ansible
Ansible 维护一个中央配置文件,用来指示 Ansible 在运行时如何行为。Ansible 的主要配置文件应该位于(对于大多数 Linux 发行版)以下位置:
 /etc/ansible/ansible.cfg 
这个配置文件指示 Ansible 在运行时如何行为。在 Ansible 执行的预启动序列中,配置文件会被加载到内存中,并设置一系列环境标志。这些标志和配置选项可以帮助你定制 Ansible 的运行时行为。以下配置是 ansible.cfg 文件的快照。
几乎所有 Ansible 配置选项都可以通过在 playbook 中进行修改来覆盖。对该配置文件的更改将使你能够设置基础功能/配置。
Ansible 配置文件包含与每个可配置选项相关的详细文档项目。因此,在本节中提供完整的配置文件讲解会显得多余。相反,我们来看看一些常见的可调整配置选项。
常见的基础配置项
最常见的基础配置项已在 Ansible 中预定义;然而,这些值可以通过ansible.cfg文件覆盖并自定义以满足您的特定需求。要覆盖其中任何一项,只需取消注释该行(即删除行前的#符号)。这些配置项如下所示:
# some basic default values...
#inventory = /etc/ansible/hosts
#library = /usr/share/my_modules/
#module_utils = /usr/share/my_module_utils/
#remote_tmp = ~/.ansible/tmp
#local_tmp = ~/.ansible/tmp
#forks = 5
#poll_interval = 15
#sudo_user = root
#ask_sudo_pass = True
#ask_pass = True
#transport = smart
#remote_port = 22
#module_lang = C
#module_set_locale = False
除了基础配置覆盖,Ansible 还提供了一些其他有趣的配置调整,可以对基础安装进行修改,从而提供一定程度的灵活性,以支持其操作功能。
通过使用以下内容禁用主机密钥检查(对于自动配置新系统很有用):
#host_key_checking = False
默认情况下,系统配置为以root身份运行 Ansible playbook;然而,这并不总是理想的。要更改此设置,只需取消注释以下行并指定您希望使用的用户:
# default user to use for playbooks if user is not specified
# (/usr/bin/ansible will use current user as default)
#remote_user = root
有时禁用日志记录是一个好主意(对于那些空间有限的用户)。要禁用日志记录,请更改以下配置项:
# prevents logging of task data, off by default
#no_log = False
Ansible 库存
Ansible 维护一个中央配置文件,用于标识和维护基础设施标识。这个库存文件使 Ansible 管理员/playbook 能够在执行过程中轻松列出、分组并定位基础设施项。默认的 Ansible 库存文件(在安装 Ansible 时创建)是/etc/ansible/hosts。其中包含一些基础的库存分组结构示例和基础设施的组织类别。
通常,Ansible 库存文件可以通过几种特定方式来组织主机:作为一组定义的组,或作为一组定义的基础设施项(松散定义且不在特定组中)。库存文件可以利用前面描述的任何方法,或两者的组合。让我们更详细地了解这两种库存组织系统如何运作。
已定义的库存组
Ansible 提供了一种强大且功能丰富的机制,用于管理与其控制的基础设施相关的信息。具体来说,可以创建库存文件来定义将由 Ansible 管理的基础设施。如您所料,Ansible 的库存文件也是 YAML 格式的。库存项(如服务器或设备)通常在库存文件中定义,并且通常会组织成逻辑上相关的组。以下代码块中显示了这些类型的组(其中定义了库存项)示例:
[databaseservers]
mydbserver105.example.org
mydbserver205.example.org
[webservers]
mywbserver105.example.org
mywbserver205.example.org
根据这些信息,我们可以看到有两个组:一个是 Web 服务器组,另一个是数据库服务器组。每个组都有两个唯一的服务器分配给它们,因此我们可以通过组名来定位任一组或两组基础设施。
Ansible 的主机清单条目不需要专门属于一个组。Ansible 支持主机名或 IP 可以属于一个或多个组。例如,一个主机名可能同时存在于 Web 服务器组和数据库组中。这为 Ansible playbook 执行系统提供了巨大的灵活性。
Ansible 本身允许对这些分组进行异步目标定位。这意味着自动化任务可以在两个组之间并行执行,以便针对主机清单文件中指定的特定组。
松散的主机清单条目/主机/设备
Ansible 提供了不对任何特定主机进行分组的能力。或者将分组主机与“松散主机”混合使用。这一功能允许 Ansible 开发者或用户仅将原始主机名添加到主机清单文件中,而不将其附加到特定的组中。在这种特定实现中,主机文件的内容可能类似于以下示例:
loosehost.example.com
[webservers]
foo.mycorp.com
bar.mycorp.com
[dbservers]
apple.mycorp.com
pear.mycorp.com
peaches.mycorp.com 
如截图所示,未分组的原始条目只是按 IP 地址或主机名在主机清单文件的顶部进行组织。这使得你可以默认自动地定位这些主机,而无需在命令行中指定目标组名。
执行 playbook 并定位特定的主机清单文件和组
现在我们已经定义了一组主机清单分组或原始主机清单条目,下一步是查看如何调用 Ansible 并定位特定的组集或甚至清单文件(如果不同于默认的 /etc/Ansible/hosts 文件)。这一功能在我们使用 Ansible 时非常重要。让我们首先看看如何定位特定的组。以下示例提供了一个简单的 Ansible 命令行示例,用于在分组的主机清单上执行 playbook:
#> ansible playbookfoo.yml -l 'groupname'
如我们从示例中所看到的,我们可以针对特定的基础设施组执行 Ansible playbook。执行结果是对该组基础设施执行一系列自动化任务。
总结
在本章中,我们讨论了 Ansible 提供的各种架构类型,讲解了主机清单文件的工作原理,以及如何使用 -l 命令定位特定组和服务器。我们还讲解了如何安装、设置和配置 Ansible 以适应大多数常见条件。在我们的示例中,我们主要关注 Ubuntu 系统的实现,但转换到其他操作系统应该是容易的。
在下一章中,我们将学习有关 playbook 文件和主机清单文件扩展性的信息,以及如何创建额外的主机清单文件,以帮助更有效地管理基础设施。Playbook 是 Ansible 平台的核心,它指示 Ansible 如何在特定的服务器或基础设施上运行。
到我们完成下一章时,你应该已经掌握编写操作手册的技能,并能够在几台机器中执行操作。此外,你应该能够使用 Ansible 执行大部分基本的配置管理和部署任务。完成本章后,你还应该对 Ansible 的清单系统有一个扎实的理解,并能够有效地创建分组和管理大规模基础设施。
第四章:Playbook 和清单文件
正如我们在前几章中发现的,Ansible 提供了独特、易于理解的自动化创建实现,实施配置管理解决方案,维护基础设施以及扩展自动化。自动化和配置管理实现是通过 Ansible playbook 开发和维护的(如我们在前一章中讨论的),而基础设施清单则通过一个或多个 Ansible 清单主机文件进行管理。Ansible 中的 playbook 实际上很容易理解;它只是一组按结构化格式排列的任务。這两个非常容易理解的概念帮助铺平了道路,使 Ansible 如此流行和强大。
Ansible playbook 的概念是一个容易理解和掌握的概念。同样,Ansible 清单文件的实现也可以这样说。Ansible 中的 playbook 和角色构成了我们在本书中开发的自动化的主体部分,并且是 Ansible 实现的核心构件。然而,本章将特别关注 Playbook 和清单文件。清单文件用于帮助我们维护通过 playbook 进行目标定位的基础设施,并允许我们在针对远程主机时对相似的设备和基础设施进行分组。这两个构件结合提供了一个高度可扩展的自动化解决方案,可以用来维护一台机器或 10,000 台机器。
在本章中,我们将讨论并学习有关 Ansible playbook 和清单解决方案的所有内容,并学习如何在组织中开发、维护和扩展 Ansible 足迹。我们将要覆盖的学习领域包括以下主题:
- 
Ansible Playbook 构件 
- 
Ansible Play 和 Task 
- 
变量和变量文件 
- 
主机和清单 
- 
目标基础设施 
- 
Ansible 模块 
在本章的学习过程中,我们将努力清楚地理解如何创建 Ansible playbooks,并且如何创建容错性强、健壮且易于维护的自动化。
Ansible Playbook 构件
Ansible playbook 是 Ansible 配置管理和自动化系统的核心。每个 playbook 由一个或多个 play 组成。playbook 的概念来源于体育术语,其中教练会在场外创建一组战术,并在比赛中执行它们。Ansible 的创作者借用了这一概念,创造并成功交付了一个易于使用的自动化和配置管理解决方案。Playbook 是使用 YAML 开发的(更多内容将在下一节讨论),并可以选择使用 Jinja2 进行更全面的自动化实现。
Ansible playbook 包含几个特定的结构和格式元素,具体如下:
- 
YAML 语法(Ansible 用于自动化的语言) 
- 
Jinja2(可选) 
- 
主机部分(定义在执行期间要定位的主机组) 
- 
一个或多个配置覆盖(此部分允许 playbook 开发人员覆盖配置选项或设置特定的 playbook 标志) 
- 
vars 部分(可选) 
- 
包含任务的剧本 
除了这些元素,YAML(以及本质上 Ansible)还支持在 YAML 文件中添加注释。注释仅仅是没有程序意义的标注文档,但对于开发人员和 playbook 作者来说,它有助于直接在 playbook 中留下笔记。YAML 中的注释以 # 操作符开始,# 后面的一切内容都会被 YAML 和 Ansible 忽略。
让我们花几分钟来看看 Ansible playbook 中的 hosts、vars 和 play 部分,并了解如何利用它们创建有效且易于维护的自动化脚本。
组成 playbook 的编程语言
在 Ansible 的世界里,playbook 是通过 YAML 和 Jinja 开发的。YAML 代表 YAML Ain't Another Markup Language,而 Jinja2 是其独立的名称。在这两者中,YAML 是主要语言,而 Jinja 是补充语言。
YAML
如前所述,YAML™ 是创建 playbook 时使用的主要语言。那么,YAML 究竟是什么呢?以下是 yaml.org/ 对此的解释:
“YAML(与‘camel’押韵)是一种人类友好的、跨语言的、基于 Unicode 的数据序列化语言,旨在围绕敏捷编程语言的常见本地数据类型设计。它广泛应用于从配置文件到互联网消息传递,从对象持久化到数据审计的各种编程需求。与 Unicode 字符集标准(unicode.org/)一起,这一规范提供了理解 YAML 版本 1.2 所需的所有信息,并能创建处理 YAML 信息的程序。”
正如摘录中所描述的,YAML 被设计为人类友好的。这意味着虽然它可以包含数据和基本逻辑(通常由 Jinja2 提供),但它更强调可读性和约定而非复杂性和功能。有关 YAML 的更多信息,请访问 www.yaml.org。
YAML 使用制表符缩进格式和键/值对字典来表示 YAML 文件中的数据。这使得它不仅能够被 Ansible 解析,而且同时易于阅读。YAML 独特而简洁的结构为 Ansible playbook 开发人员提供了一种无需猜测的方式来开发 playbook,并确保它们可以通过 Ansible 执行。虽然 YAML 的结构易于阅读,但有时其制表符缩进可能会让一些用户困惑。让我们来看几个简单的 YAML 文件示例,看看基本的数据结构是什么样的:
# Simple YAML Data Structure
---
planets:
  - earth: 'welcome to earth'
    species: humans
  - mars: 'we come from mars'
    species: martians
从前面的示例中,我们可以轻松看到,内容仅仅是一个行星、物种和问候语的列表。我们是否在说 YAML 其实只是一个易于阅读的管理列表的方式?从某种程度上来说,是的。让我们进一步探讨这个想法。
YAML 中的基本数据结构是由键/值对组成的;信息是通过缩进进行组织的。对于程序员来说,这并不令人惊讶,但对于初学者来说,可能会有些困惑。基本的键/值对数据结构类似于一个项目列表。在键/值数据结构中,有两个本质上相关联的数据项。键本质上是一个指针,用来引用值。值可以是原始数据(例如简单的文本),甚至可以是另一个键/值对集合。这使得管理键/值对列表变得非常简单。YAML 语法中的缩进使得组织它所代表的数据变得易于阅读。它确保没有信息混乱或格式错误,信息也易于识别和阅读。
从前面的 YAML 示例中我们可以看到,YAML 是一种格式良好的(以 Tab 键缩进的)语言。YAML 中的 Tab 键其实是个误称,因为你不能真正使用 Tab 键。应该使用空格代替。YAML 中禁止使用 Tab 键,因为不同的文本编辑器对 Tab 的实现方式不同,而且实现并不一致。
Jinja2 – 简要介绍
另一方面,Jinja 比 YAML 功能更强大。它已经集成到 Ansible 的运行时引擎中,并提供了一些类似脚本语言的功能,这些是开发者常用的。它的语法很好地融入了 YAML(如前所述),并允许开发者使用条件语句、循环、变量替换和环境变量获取等功能。关于 jinja2 及其语法的更多信息可以在jinja.pocoo.org/docs/2.9/找到。让我们快速浏览一下基本的 Jinja 语法:
# Simple Jinja syntax
{{my_var}}
这段代码乍一看似乎没有什么用,但当我们将其放入上下文中时,它变得更有价值。让我们来看看 Jinja 如何与 YAML 配合使用:
# Example Jinja Playbook
- hosts: all
  vars:
    foo: "{{ lookup('env', 'FOO' }}"
  tasks:
    - name: Copy file
      copy: src="img/{{ foo }}" dest=/some/path mode=0775 owner=user group=user
这个简单的 Ansible playbook 示例结合了 YAML 和 Jinja,提供了一种在 playbook 中使用系统环境变量内容的方式。我们将在本书后面更详细地讨论这种实现方式,但现在,至少可以看到一个结构化的示例,展示了如何在 YAML 文件中使用 Jinja。
Jinja 提供了更多的解决方案和 Ansible playbook 操作,我们将在第六章中讨论,Ansible 中的 Jinja。这段介绍仅仅是为了帮助你理解 YAML 和 Jinja 如何共存。
构建 Ansible playbook
YAML 独特且格式良好的语法提供了一种高度结构化但易于人类阅读的格式,用于表达数据。更具体地说,YAML 的数据结构通过列表来表示,每个列表项包含键/值对(或字典)。在每个 YAML 文件的开头,YAML 可选地支持一个 --- 起始符(用于标记 YAML 文件的开始),而在每个文件的结尾,YAML 支持一个 ... 终止符,这(正如你可能猜到的)表示 YAML 文件的结束。我们来看看一个非常简单的 playbook 示例:
---
- hosts: all
  vars :
    http_port : 80
    tasks:
    - name: Install nginx web server
      apt: pkg=nginx state=installed update_cache=true
      notify:
        - start nginx
现在我们已经看到一个 Ansible playbook 的示例,接下来让我们看看 playbook 中可能存在的部分,并了解每个部分的作用。
主机
主机部分是我们可以针对一组库存目标的地方。Ansible 中的库存代表一个或多个设备,Ansible 可以连接到这些设备并执行自动化任务。
变量(vars/vars_files)
Ansible playbook 中的 vars 和 vars_file 部分包含一组变量数据,可以在后续的 playbook 中使用。这些信息被称为 facts。Ansible 中的变量概念与计算机编程中的变量相似,但根据定义的位置具有不同的作用域。
Tasks/plays
Ansible plays 的目标是将一个或多个主机组与一组角色连接起来,角色由 Ansible 称为任务的事物表示。从最基本的定义来看,Ansible task 只是调用 Ansible 模块的一种方式。
很好;我们现在对构成 playbook 的各个部分有了基本的理解,但我们还不清楚如何有效地使用它们来创建 playbook,也不清楚如何针对特定的服务器或基础设施进行操作。
Ansible 的 Plays 和 Tasks
正如我们已经发现的,Ansible 配置和自动化系统的核心是 playbook。playbook 中最重要的元素是 plays 和 tasks 的概念。Plays 代表对一组 Ansible 任务的分类,而 tasks 是构成 play 的单个自动化步骤。
可以将 plays 看作是一个总的分组,而 tasks 是位于某个 play 内的具体项。例如,你可能有一个数据库 play,一个 Web 服务器 play,甚至还有一个负载均衡器 play。在本节中,我们将探索 Ansible 中 plays 和 tasks 是如何工作的。
Ansible plays
Ansible plays 以体育比赛中的 plays 命名。在 YAML 中,plays 通过 playbook 中的一个或多个 tasks 部分表示。Ansible playbook 中的 plays 部分(或多个部分)代表了 Ansible 自动化引擎的核心。在 Ansible 中,每个 task 都有一个名称(即用户友好的描述,说明要执行的动作)和一组参数,这些参数定义了系统如何执行、报告或处理执行后的结果。让我们来看看如何在给定的 playbook 中实现 Ansible plays 的几种方式。
接下来的示例为我们展示了一个包含单个 play 的 Ansible playbook:
tasks:
    - name: Can we use Ansible to Install Apache2 web server
      apt: pkg=apache2 state=installed update_cache=true
看起来很基础,对吧?Play 可以包含一个或多个任务。这些任务非常容易阅读,也很容易编写。然而,playbook 不必被孤立为一个单独的 play。例如,我们也可以像下面这样做:
tasks:
    - name: Use Ansible to Install nginx web server
      apt: pkg=nginx state=installed update_cache=true
tasks:
    - name: Use Ansible to Install MySQL web server
      apt: pkg=mysql-server state=installed update_cache=true
第二个示例也是可读的,但其中定义了两个特定的任务。第一个任务处理 nginx 的安装,而第二个任务处理 MySQL 服务器的安装。通过在一个 playbook 中使用多个任务,我们可以将自动化任务集中在一个 playbook 中,同时又能将实际的任务区分开来。很整洁,对吧?现在我们已经对 Ansible 的 play 有了充分的理解,接下来我们来看看 Ansible 任务。
Ansible 任务
在前一部分,我们主要讨论了 Ansible 的 plays。在给定的 play 下,是一组 Ansible 任务。Ansible 任务可以对本地或目标系统执行许多操作。这包括安装软件包(如 apt、yum 和 opencsw)、复制文件、启动服务等。Ansible 任务构成了将自动化和人类可读操作联系起来的“胶水”。让我们看看 Ansible 任务部分的元素,并学习如何编写它们:
tasks:
  - name: <some description>     
   <API>: PARAM1=foo PARAM2=foo PARAM3=foo
这个代码片段相当简洁;它有一个单一的任务(由 tasks: 注明)和一个任务。任务本身的 name 参数用简单的英语描述了当这个任务执行时我们试图完成的目标。在 name 定义下方的 <API> 标签将会是我们调用的 Ansible 模块。在模块 name 之后,还有一组参数会被传递给该模块,这些参数更细致地指定了我们正在调用的模块的细节。所以,为了有一个更贴近实际的示例,让我们看看以下的代码片段:
tasks:
  - name: Can we use Ansible to Install nginx web server
    apt: pkg=nginx state=installed update_cache=true
上述的 play 只是告诉 Ansible 在基于 Ubuntu 的系统上安装 nginx web 服务器。我们知道这一点是因为任务调用的模块是 apt,而参数指示 Ansible 确保软件包(pkg)nginx 已经安装。此外,在安装软件包之前,我们还指示 apt 模块更新其本地缓存。简单吧?
Ansible 的一个优点是它能够跳过那些不会对系统产生变化的任务。例如,如果 nginx 软件包已经安装,Ansible 会完全跳过该步骤,因为它足够智能,知道系统上已经存在 nginx 软件包。
现在我们已经了解了 Ansible playbook 的基本结构,我们需要了解如何扩展我们的 playbook,以处理任务中的更复杂参数。学习过程中的下一步是理解多行参数。让我们继续。
多行任务参数
一些 Ansible 任务可能会有大量参数(以至于任务的简洁性和可读性变得模糊)。YAML 实现也已成熟,支持跨多行的参数,以提高可读性。这特别是 标量折叠方法,是 YAML 语言中直接支持的。让我们看看 YAML 创建者提供的一个示例,了解 YAML 中行折叠的工作原理:
# Multiple-line strings can be written either as a 'literal block' (using |),
# or a 'folded block' (using '>').
literal_block: |
    This entire block of text will be the value of the 'literal_block' key,
    with line breaks being preserved.
    The literal continues until de-dented, and the leading indentation is
    stripped.
        Any lines that are 'more-indented' keep the rest of their indentation -
        these lines will be indented by 4 spaces.
folded_style: >
    This entire block of text will be the value of 'folded_style', but this
    time, all newlines will be replaced with a single space.
    Blank lines, like above, are converted to a newline character.
        'More-indented' lines keep their newlines, too -
        this text will appear over two lines.
因此,在 Ansible 剧本的上下文中,我们可以使用标量折叠方法来执行多行剧本任务。因此,我们可以将任务重新格式化如下:
# Initial task definition 
tasks:
    - name: Can we use Ansible to Install nginx web server
      apt: pkg=nginx state=installed update_cache=true
# Same task using the scalar folded approach to task definitions
tasks:     
    - name: Can we use Ansible to Install nginx web server       
      apt: >
        pkg=nginx 
        state=installed 
        update_cache=true
Ansible 任务的多行实现基于个人偏好和格式化。因此,在某些情况下,您可能会有一个非常长的剧本任务,这时候您可能会考虑使用多行任务。反之,较短的剧本任务可能就不需要这样实现了。同样,这完全是为了可读性和个人偏好。
现在我们已经对如何更好地组织我们的剧本数据结构有了一个清晰的认识,接下来让我们看看变量以及它们如何融入我们的剧本创建过程中。
变量和变量文件
变量是任何脚本或开发语言中的关键组成部分,Ansible 也不例外。变量充当数据元素、重要信息、数值等的命名占位符。Ansible 提供了 vars 部分和 vars_files 部分,它们是可选的,可以包含在剧本中。在这里定义的变量是以剧本为中心的,可以在剧本内使用。剧本的这些部分允许我们以两种独特的方式来定义变量。让我们通过一个示例更好地理解变量是如何定义的:
---
- hosts: all
  vars:
    myvar: helloworld
  vars_files:
    - /vars/my_vars.yml
正如我们从示例中看到的,我们在定义 Ansible 可以在剧本中使用的变量时,可以相当富有创意。变量可以通过 vars 部分、vars_files 部分,甚至通过命令行中的 ExtraVars 参数来指定。让我们来看一下变量的键/值实现,以及 vars_file 实现,探索如何利用这些方法为我们的 Ansible 剧本提供可重用的数据。
基本的变量语法
管理 Ansible 中的变量最直接的解决方案是利用 Ansible 剧本开始部分的键/值 vars 部分,这样我们可以定义一组简单的键/值数据集,并使其对剧本中的其他任务可用。
Ansible 剧本中的vars部分为我们提供了一个简单的位置,在这里我们可以创建一个全局可用的 Ansible 变量列表。在剧本初始化期间(具体来说是在事实收集阶段),Ansible 会读取 vars 部分及其相关的键/值变量。在这一阶段收集的每一项数据被称为事实。然后,这些变量可以在剧本的其他地方使用。
让我们看几个 Ansible 中键/值变量集的示例,并了解如何在剧本中使用它们:
Example: Simple Key/Value Variables in Ansible. 
---
- hosts: all
  vars:
    # Single Variable(s) Example
    myvar: helloworld
  tasks:
    - name: $myvar
      ping:
这个示例展示了如何创建和使用一个简单的$myvar变量,并将其用于剧本的任务中。接下来,让我们看看一个稍微不同的 Ansible 变量实现(在任务级别)。考虑以下剧本片段:
# Task specific variables
tasks:
  - name: copy files
    copy: src={{ item }} dest=/opt/{{ item }}
    with_items:
      - foo
      - bar
在这个示例中,我们展示了一个简单的迭代循环,用于将foo和bar文件复制到指定的目标位置。现在我们已经对基本的 Ansible 变量有了大致了解,让我们看看一些更具可扩展性的方式来管理 Ansible 变量数据。
变量文件
正如我们刚才讨论的,Ansible 在剧本中有vars部分。对于有限的数据点来说,这完全足够。然而,如果信息量预计会增加或涉及不同的环境,那么如果不仔细管理,vars部分可能会变得非常难以处理。我们可以用vars文件(或多个vars文件)代替vars部分来管理数据。vars文件是将一组数据点封装到 Ansible 控制服务器磁盘上的外部文件中的一种方式。然后,我们在 Ansible 剧本中指定vars文件,Ansible 将在适当的时候加载它。
使用vars文件时,需要注意我们可以在剧本中的几个位置包含vars文件:一个vars_file部分,或者在 Ansible 任务中(任务特定范围)。接下来的示例将有助于更好地说明这一点。
让我们看一个vars文件实际应用的示例。以下示例展示了一个简单vars文件的内容:
east_coast_host_local: virginia
west_coast_host_local: oregon
definitions:
- servers: web
   instance: apache
- servers: db
   instance: cassandra
ping_server: 192.168.10.10
正如我们从vars文件的内容中看到的,它不过是一个 YAML 文件。谁能想到呢?这里真正有趣的并不是它的内容,而是它的构建方式。但在我们继续深入研究一些有趣的内容之前,让我们看看如何通过剧本引用文件和其中的数据。与之前的vars文件对应的剧本如下:
# Example: Simple Variables File in Ansible. 
---
- hosts: all
  vars_files:
    - my_vars_file.yml
  tasks:
    - name: ping target server
      ping: $ping_server
从这个剧本的内容来看,我们可以看到它仅仅添加了一个vars_file部分,该部分在启动时加载。
这两个文件示例本质上非常简单。让我们看看如何根据前面提到的示例的上下文加载变量文件的另一种方式。这里是一个替代的剧本:
# Example: Simple Variables File in Ansible. 
---
- hosts: all
  tasks:
    - name: include default step variables
      include_vars: my_vars_file.yml
      ping: $ping_server
所以,我们可以从提供的代码中看到,我们也可以将vars文件限定于特定的任务。这在根据特定条件(每个环境或每个主机)修改任务中的某些数据点时非常有用。
现在我们已经对vars文件的工作原理有了一个大致了解,让我们再看一个如何利用vars文件的示例:
# Example: Simple Variables File in Ansible. 
---
- hosts: all
  vars_files:
    - my_vars_file.yml
    - "/opt/varsfiles/{{ env_vars }}.yml"
  tasks:
    - name: ping target server
      ping: $ping_server
你能猜到这会做什么吗?让我们看看如何从命令行执行这个剧本来获得线索:
$> ansible-playbook site.yml -e "env_vars=dev" -c local
好的,我们来理解一下这个是如何工作的。基本上,这个命令指定了一个变量env_vars=dev,这会让 playbook 加载一个它认为应该位于/opt/varsfiles/dev.yml的vars文件。因此,我们可以为不同的环境(如开发、QA 等)设置特定的变量数据,并重用我们的 playbook。很棒,对吧?
主机和库存
Ansible 提供了一个库存系统,帮助管理员管理他们通过 Ansible playbook 执行或临时命令执行所针对的设备。这个库存系统允许管理员识别库存项(设备),并根据需要对其进行分组。这些库存项通过 Ansible 库存文件进行维护,然后可以通过命令行直接指定。
默认情况下,Ansible 提供了一个默认的库存文件,通常位于/etc/ansible/hosts文件位置。如果一个库存文件不足以有效管理你的库存,可以创建额外的库存文件,并将其存储在相同的位置或你选择的位置。调用ansible-playbook命令来触发 Ansible 并执行临时命令或触发playbook run时,Ansible 提供了-i选项,可以让你直接在命令行中指定备用的库存文件。以下是一些示例,展示如何通过命令行指定库存文件:
# This example uses the default hosts file provided by Ansible
# and executes playbook.yml
$> ansible-playbook -i hosts playbook.yml
# This example specifies an alternative hosts file
# and executes playbook.yml
$> ansible-playbook -i /opt/mynewinventoryfile playbook.yml
# This example specifies a set of hosts directly on the
# command line and executes playbook.yml
$> ansible-playbook -i fqdn.example.com,playbook.yml
现在我们已经大致了解了如何通过命令行指定备用的库存文件,接下来我们来看一下如何利用 Ansible 库存文件的一些方法。
定向到基础设施
在创建旨在针对一个或多个设备的自动化时,我们需要一种方法来指示 Ansible 应该针对哪些主机以及哪些 playbook 应该针对哪些主机。为了让 Ansible 维护主机名、IP 地址和域名的有序集合,Ansible 的创建者提供了 Ansible 库存主机文件,并通过 Ansible playbook 提供分组和定向主机组的功能。Ansible 主机通常在 Ansible 库存hosts文件中定义,该文件传统上位于 Ansible 控制服务器的以下文件位置:
/etc/ansible/hosts
如我们在上一章中提到的,Ansible 的hosts文件允许 Ansible 开发者维护一个设备的列表或一组设备组,Ansible 可以通过 playbooks 来访问这些设备。我们指示 Ansible 针对特定主机组的方式是通过 Ansible playbook 中的hosts行条目。让我们考虑以下主机组和 Ansible playbook 示例:
Ansible hosts 文件示例(/etc/ansible/hosts):
# Example Ansible hosts file with two defined groups
[WEB]
192.168.10.10
[DATABASE]
192.168.10.11
通过 Ansible playbook(playbook.yml)定向到WEB主机组:
# Example Ansible Playbook, which targets the 'WEB' group
---
- hosts: WEB
  vars :
    http_port : 80
  tasks:
    - name: Install nginx web server
      apt: pkg=nginx state=installed update_cache=true
      notify:
        - start nginx
要执行前面的示例,只需切换到包含playbook.yml文件的目录并执行以下命令:
# Run Ansible and instruct it to execute the contents of
# playbook.yml against the inventory file of hosts
# (the default Ansible inventory)
$> ansible-playbook playbook.yml -i hosts
了解 Ansible 不一定需要通过剧本中的 hosts 行条目来定位一个库存组这一点非常重要。它还可以定位多个组、单个主机、通配符等等。让我们看一些其他方式的例子,了解我们如何在hosts部分输入数据:
# Example hosts line values:
 hosts: all -- Applies the current playbook to all hosts in the specified inventory file
  hosts: hostname -- Applies the playbook ONLY to the specified host 'hostname'
  hosts: groupname -- Applies the playbook to all hosts in specified groupname
  hosts: groupA,groupB -- Applies the playbook to hosts in groupB and groupB
  hosts: group1,host1 -- A combination of single hosts and groups
  hosts: *.google.com -- Applies the playbook to wildcard matches
除了这些松散的主机行值示例外,hosts部分还可以包含像这样的分组:
[WEB]
192.168.10.10
[PRODUCTION]
192.168.10.11
除了这些示例外,hosts部分还可以利用 Ansible 变量来定位特定主机。下文提供了这种类型实现的示例:
hosts: $myhosts -- apply the playbook to all hosts specified in the variable $myhosts
一旦剧本反映了$myhosts变量,我们可以使用类似下面提供的示例来设置该变量:
$> ansible-playbook playbook.yml --extra-vars="groups=PRODUCTION"
根据这个命令,我们可以看到我们能够定位到生产组(在之前的hosts文件中定义的组)。
Ansible 模块
Ansible 提供了一套非常强大的工具,可以极大地帮助操作实施。常见的操作实施包括管理给定系统的配置(确保软件包已安装、文件存在、目录结构存在等),为给定系统提供满足一组前提条件的资源等。如我们之前所发现,剧本及其任务通过对给定系统执行一系列自动化操作,帮助我们实现这些目标。
虽然我们获得的知识可以为我们提供实施简单自动化所需的基础,但我们实际上只是略微触及了 Ansible 工作的表面。Ansible 与数百个系统级任务以及成千上万的外部第三方解决方案集成,可以以我们尚未完全理解的方式加以利用。让我们揭开这些层次,看看如何利用 Ansible 进行基本的配置管理。
Ansible 通过 Ansible 模块提供了大部分任务功能。Ansible 模块本质上是独立的接口,将 Ansible 与操作系统或其他技术集成。例如,Ansible 有一个模块,可以将 Ansible 剧本自动化与 JIRA 集成。因此,JIRA 模块提供了 JIRA API 可用功能与构成 Ansible 剧本任务的自动化格式之间的直接链接。
Ansible 的实现有三种不同的模块类型。这些模块类型包括核心模块、策划模块、社区模块和自定义模块。每种模块都有其特定的功能和角色,在 Ansible 解决方案中扮演着不同的角色。让我们花点时间来看一下 Ansible 文档对这些不同模块类型的说明:
- 
核心模块:这些是由 Ansible 核心团队维护的模块,始终与 Ansible 一起发布。它们还会对所有请求赋予略高的优先级。非核心模块仍然是完全可用的。 
- 
精心策划:一些精心策划的模块示例由其他公司提交或由社区维护。这些类型模块的维护者必须关注任何报告的问题或针对该模块提出的拉取请求。 
核心提交者将审查所有被精心策划的模块。核心提交者将在模块的社区维护者批准更改后,审查对现有精心策划模块的修改。核心提交者还将确保解决由于 Ansible 引擎更改而产生的任何问题。此外,强烈建议(但目前不是必需的)这些类型的模块具有单元测试。
这些模块目前与 Ansible 一起提供,但未来可能会单独发布。
- 社区:这些模块不由核心提交者或与模块相关的公司/合作伙伴提供支持。它们由社区维护。
它们仍然完全可用,但对问题的响应速度完全取决于社区。将提供最佳努力支持,但不包括在任何支持合同下。
这些模块目前与 Ansible 一起提供,但未来很可能会单独发布。
在本节中,我们将了解核心模块解决方案,并尝试理解它的功能及其提供的能力。
在撰写本文时,Ansible 提供了 1,021 个独特的模块,并由 Ansible 的开箱即用解决方案提供。这意味着 Ansible 有潜力与任何数量的操作系统、工具和开源软件进行紧密集成。为了更好地说明这一点,让我们快速查看一下官方文档中提供的 Ansible 模块类别(docs.ansible.com/ansible/modules_by_category.html):
- 
云模块 
- 
集群模块 
- 
命令模块 
- 
加密模块 
- 
数据库模块 
- 
文件模块 
- 
身份模块 
- 
清单模块 
- 
消息模块 
- 
监控模块 
- 
网络模块 
- 
通知模块 
- 
包管理模块 
- 
远程管理模块 
- 
源控制模块 
- 
存储模块 
- 
系统模块 
- 
工具模块 
- 
网络基础设施模块 
- 
Windows 模块 
从列表中可以看出,Ansible 的集成非常强大。让我们探索一些常见的模块,看看如何在我们的 playbook 任务中使用它们。
在 Ansible 中管理软件包
Ansible 与多个 Linux 发行版的集成非常紧密。这种集成使得 Ansible playbook 可以以简洁和结构化的方式在目标系统上维护软件包。从 yum 到 apt 再到 opencws,Ansible 开发者提供的软件包管理解决方案是强大且功能丰富的。在本节中,我们将通过 Ansible playbook 来理解软件包管理的基础。
Yum
Ansible 提供了一个完整的 yum 模块,能够有效地将其与常见的 RHEL yum 仓库系统集成。通过 Ansible 的 yum 接口,几乎可以执行所有与 yum 相关的操作。以下是一些 Ansible 在 yum 方面的能力示例:
- 
安装软件包 
- 
移除软件包 
- 
添加仓库 
- 
管理 GPG 检查 
- 
列出软件包 
现在我们已经对 Ansible 的 yum 操作有了较好的了解,接下来让我们看看如何使用 yum 执行一些基本操作的示例:
- name: install the latest version of Apache
  yum:
    name: httpd
    state: latest
- name: remove the Apache package
  yum:
    name: httpd
    state: absent
- name: install the latest version of Apache from the testing repo
  yum:
    name: httpd
    enablerepo: testing
    state: present
- name: install one specific version of Apache
  yum:
    name: httpd-2.2.29-1.4.amzn1
    state: present
- name: upgrade all packages
  yum:
    name: '*'
    state: latest
- name: install the nginx rpm from a remote repo
  yum:
    name: http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
    state: present
- name: install nginx rpm from a local file
  yum:
    name: /usr/local/src/nginx-release-centos-6-0.el6.ngx.noarch.rpm
    state: present
- name: install the 'Development tools' package group
  yum:
    name: "@Development tools"
    state: present- name: install the 'Gnome desktop' environment group
  yum:
    name: "@^gnome-desktop-environment"
    state: present
- name: List Ansible packages and register result to print with debug later.
  yum:
    list: ansible
  register: result
apt-get 和 dpkg
Ansible 与apt-get的集成与 Ansible 与 yum 的集成同样紧密。对于不熟悉的用户,apt-get是一个由基于 Debian 的操作系统使用的软件包管理解决方案。apt-get的实现实际上是基于另一个名为dpkg的解决方案,而 Ansible 提供了支持这两者的模块。在本节中,我们将专门讨论 apt-get。为了更好地理解我们所提到的架构,接下来提供了一个示意图:

现在我们已经很好地理解了apt-get的工作原理,接下来我们来看看一些 Ansible 如何与这个特定的软件包管理器集成的示例:
- name: Update repositories cache and install "foo" package
  apt:
    name: foo
    update_cache: yes
- name: Remove "foo" package
  apt:
    name: foo
    state: absent
- name: Install the package "foo"
  apt:
    name: foo
    state: present
- name: Install the version '1.00' of package "foo"
  apt:
    name: foo=1.00
    state: present
- name: Update the repository cache and update package "nginx" to latest version using default release squeeze-backport
  apt:
    name: nginx
    state: latest
    default_release: squeeze-backports
    update_cache: yes
- name: Install latest version of "openjdk-6-jdk" ignoring "install-recommends"
  apt:
    name: openjdk-6-jdk
    state: latest
    install_recommends: no
- name: Update all packages to the latest version
  apt:
    upgrade: dist
- name: Run the equivalent of "apt-get update" as a separate step
  apt:
    update_cache: yes
- name: Only run "update_cache=yes" if the last one is more than 3600 seconds ago
  apt:
    update_cache: yes
    cache_valid_time: 3600
- name: Pass options to dpkg on run
  apt:
    upgrade: dist
    update_cache: yes
    dpkg_options: 'force-confold,force-confdef'
- name: Install a .deb package
  apt:
    deb: /tmp/mypackage.deb
- name: Install the build dependencies for package "foo"
  apt:
    pkg: foo
    state: build-dep
- name: Install a .deb package from the internet.
  apt:
    deb: https://example.com/python-ppq_0.1-1_all.deb
除了与yum和apt的集成外,Ansible 还与许多其他 Linux 发行版的软件包管理解决方案集成得非常好。这些(如前两个)都通过 Ansible 模块得到支持。这些其他 Linux 版本的模块被设计得尽可能提供强大的集成支持。要查看完整的软件包管理解决方案模块列表,以下链接提供了全面的指南:
docs.ansible.com/ansible/list_of_packaging_modules.html。
在 Ansible 中管理用户
在 Ansible 剧本中管理用户不必是一个令人生畏的任务。Ansible 的user模块集与 Ansible 核心和系统级用户解决方案紧密集成。Ansible 用户模块使我们能够通过 YAML 管理用户及其属性。因此,像添加、删除和更新等操作通常都很容易实现。让我们来看看如何使用 Ansible 配合用户模块执行一些基本的用户操作:
# Create a User 'dortiz' ---
- hosts: all
  tasks:
    - name: Add David Ortiz User to the System
      user: 
        name: dortiz
        comment: "David Ortiz has entered the building"
# Create a User 'jdaemon' and add to group baseballplayers ---
- hosts: all
  tasks:  
    - name: Add Johnny Daemon User to the System
      user: 
        name: jdaemon
        comment: "Johnny Daemon has entered the building"
        groups: baseballplayers
要查看可以通过 Ansible 用户模块传递的所有参数,请参考官方文档中关于用户模块的部分:docs.ansible.com/ansible/user_module.html。
在 Ansible 中进行文件和目录管理
Ansible 的文件模块提供了 Ansible playbook 与文件系统之间的集成。这使得我们能够通过 Ansible playbook 任务执行目录操作和基本的文件操作。除了基本的 创建、删除、更新 和 删除(CRUD)操作外,我们还可以设置权限、更改所有者、设置组所有者、对递归文件夹树进行操作等。
让我们通过 file 模块来看一些基本的文件和目录管理操作示例:
# Create a directory using an Ansible Task
- name: Creates a directory
  file: path=/opt/helloWorld state=directory
# Create a directory using an Ansible Task,
# which is owned by the baseballplayersgroup
- name: Creates a directory
  file: path=/opt/helloWorld state=directory
# Creates a directory owned by the baseballplayers group
# with CHMO 0775 permissions
- name: Creates directory
  file: path=/opt/helloWorld state=directory owner=baseballplayers group=baseballplayers mode=0775
# Changes the ownership of myconfiguration.conf to 
# bob and changes permissions to 0644
- name:
    file:
      path: /opt/myconfiguration.conf
      owner: bob
      group: admin
      mode: 0644
这些示例仅提供了文件管理和 Ansible 的文件模块的一个初步了解。有关可用选项的完整列表,请参考 Ansible 文档,网址为 docs.ansible.com/ansible/file_module.html。
在 Ansible 中管理服务
使用 Ansible 管理服务非常简单。服务管理可能是一个复杂的操作,通常高度依赖于操作系统和系统类型。然而,通过 Ansible 的服务模块,我们可以轻松地停止、启动和重启服务。这种集成提供了很高的可靠性,并且抽象了必须执行的基础操作系统级别操作。让我们来看看 Ansible 的 service 模块及其功能:
# Example action to start service httpd, if not running
- service:
    name: httpd
    state: started
# Example action to stop service httpd, if running
- service:
    name: httpd
    state: stopped
# Example action to restart service httpd, in all cases
- service:
    name: httpd
    state: restarted
# Example action to reload service httpd, in all cases
- service:
    name: httpd
    state: reloaded
# Example action to enable service httpd, and not touch the running state
- service:
    name: httpd
    enabled: yes
# Example action to start service foo, based on running process /usr/bin/foo
- service:
    name: foo
    pattern: /usr/bin/foo
    state: started
# Example action to restart network service for interface eth0
- service:
    name: network
    state: restarted
    args: eth0
这些示例为我们提供了 Ansible 服务控制解决方案的一些见解,以及如何使用 Ansible playbooks 管理服务。现在我们已经掌握了这一点,让我们来看看如何使用 Ansible 传输文件。
在 Ansible 中传输文件
将文件从本地 Ansible 控制服务器传输到一组目标机器,对于软件部署实现至关重要。Ansible 提供了一个非常方便的 copy 模块,帮助我们完成这一操作。copy 模块提供了许多实用的属性设置,可以通过允许我们设置文件权限、更改所有权、解密文件、创建备份等,进一步实现我们的目标。让我们来看看如何使用 copy 模块将文件从本地 Ansible 控制服务器传输到目标机器:
# Example from Ansible Playbooks
- copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: foo
    group: foo
    mode: 0644
# The same example as above, but using a symbolic mode
# equivalent to 0644
- copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: foo
    group: foo
    mode: "u=rw,g=r,o=r"
# Another symbolic mode example, adding some permissions
# and removing others
- copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf
    owner: foo
    group: foo
    mode: "u+rw,g-wx,o-rwx"
# Copy a new "ntp.conf file into place, backing up the
# original if it differs from the copied version
- copy:
    src: /mine/ntp.conf
    dest: /etc/ntp.conf
    owner: root
    group: root
    mode: 0644
    backup: yes
# Copy a new "sudoers" file into place, after passing
# validation with visudo
- copy:
    src: /mine/sudoers
    dest: /etc/sudoers
    validate: 'visudo -cf %s'
现在我们对 Ansible 模块的工作原理和一些实现细节有了很好的理解,接下来让我们看看如何扩展我们的知识和能力,开发出健壮且易于维护的 playbooks。
总结
这一章内容丰富,我们的书籍之旅也接近一半了。在本章中,我们了解了 Ansible playbook 文件的基本构造,YAML 是什么,以及 Jinja 是如何融入其中的。除了了解基本构造外,我们还学习了如何创建 Ansible playbook YAML 文件,如何创建和管理库存文件,以及更多内容。这一章真是精彩:获得知识并付诸实践。
在下一章,我们将介绍 playbook 的语法。这包括角色、包含项、playbook 目录结构以及循环和块。可以将下一章视为本章的进阶内容。那么,我们开始吧,怎么样?
第五章:剧本——超越基础
在上一章中,我们概述并讨论了如何构建 Ansible 剧本和清单主机文件。这些知识将帮助我们快速上手 Ansible,并为进一步构建打下良好的基础。我们研究了如何利用这些实现来定位基础设施组,并开始看到 Ansible 所提供的一些强大功能。虽然 Ansible 设计时的核心理念是简单性,但这并不意味着它不具备高度的可扩展性和灵活性。在许多方面,Ansible 的真正力量和可扩展性来自于其模块化设计和简化的实现标准,再加上高效的可定制剧本和角色设计模式。
尽管我们迄今为止学到的实现方法各有其作用和目的(作为基本构建块和基础元素),你能想象创建和管理一个 10,000 行的单文件 Ansible 剧本有多困难吗?或者如果这个剧本的一半只是用来设置 web 服务器呢?有没有更好的方法?当然有!实现和管理剧本的更好方法是使用 Ansible 角色和包含。这些场景是超越简单单文件剧本,成为现实的地方。
在本章中,我们将扩展我们的 Ansible 知识,学习如何利用 Ansible 角色和包含。我们将学习如何从单一剧本文件扩展到剧本层次结构,其中多个文件可以组合和重用,以提供面向对象编程(OOP)的剧本实现。除了学习角色和包含,我们还将学习如何使用注册器以及其他更高级的剧本结构。此章的具体学习目标如下:
- 
Ansible 剧本和条件逻辑 
- 
Ansible 循环和迭代器 
- 
Ansible 包含 
- 
Ansible 角色 
- 
Ansible 注册器 
- 
错误捕获 
- 
Ansible 处理器 
剧本和条件逻辑
Ansible 提供了一种很好的集成方式来执行条件操作。也就是说,当满足特定条件时,可以执行任务。这类要求的一些示例可能是,仅在目标系统是 Ubuntu 时执行任务,或仅在目标系统具有特定处理器架构时执行任务。
Ansible 通过实现 when 操作符来支持条件语句。在本节中,我们将探讨 Ansible 如何管理条件语句,并通过一个条件管理任务的示例进行演示。我们从这段代码开始:
# Reboot Debian Flavored Linux Systems using the WHEN operator tasks:
  - name: "Reboot all Debian flavored Linux systems"
    command: /sbin/reboot -t now
    when: Ansible_os_family == "Debian"
在这个例子中,我们有条件地指定Debian家族作为任务运行的要求。简单吧?除了使用Ansible_os_family实现的示例外,我们还可以指定变量条件。变量条件使我们能够指定一个变量已设置或存在作为执行 Ansible 任务的要求。看一下另一个代码片段的示例:
# Display Hello World to DevOps readers
vars:
  is_enabled: true
tasks:
    - name: "Tell only DevOps People Hello"
      shell: echo “Hello DevOps Readers“
      when: is_enabled
在这个示例中,我们可以看到,如果 when 操作符设置为 true,我们就会向用户说 hello。这个逻辑很简单,对吧?除了这种实现方式,我们还可以使用这种逻辑的反向操作,也就是说,当操作符没有设置时执行任务。让我们来看一个反向操作的示例:
tasks:
 - shell: echo "The operator has not been set"
 when: myvar is undefined
虽然可以使用这些解决方案实现简单的逻辑,但很多时候我们需要一些更全面的实现。除了 Ansible 提供的简单逻辑条件,我们还可以执行更复杂的实现。其中一个实现就是使用迭代器和条件语句。让我们来看一个如何实现这一点的示例:
# Iterator with conditional logic to stop the iteration at a specified number
tasks:
  - command: echo {{ item }}
    with_items: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
    when: item > 7
在这个示例中,我们有一个简单的 echo 命令,它接收一个迭代器数组(with_items),如果项目数量超过 7,我们就终止循环。这个示例的输出应该类似于以下屏幕截图:

太棒了!现在我们知道如何做了,让我们看看如何基于之前命令的输出使用条件语句。这里是一个示例:
 when: "'divide by zero' in output"
在 Ansible 2.0 实现之后,Ansible 的开发者们为我们提供了一种方便的方式,可以向 Ansible 角色中添加条件语句。因此,我们现在可以直接在角色声明中使用条件逻辑。这是通过以下语法来实现的:
# Conditional Logic directly in the Ansible Roles 
- hosts: all
  roles:
     - { role: centos_config, when: Ansible_os_family == 'CentOS' }
我们已经了解了条件语句在 Ansible playbook 中的实用性。现在让我们从条件语句转到迭代器和循环。迭代器和循环为我们提供了一种非常方便的方式,可以减少编写的代码量,并让我们轻松地执行重复操作。
迭代器和循环
在 Ansible(以及 YAML 中),通常有不止一种方式来完成任何给定的自动化任务。自动化操作可以用简单的 YAML 格式实现,也可以通过使用 with_items 迭代器将任务组合在一起。在本节中,我们将了解迭代器,并学习如何利用它们减少需要编写的 YAML 代码量,从而更有效地组织我们的 playbook 任务。
如果你熟悉基本的编程概念,迭代器的概念并不新颖或独特。事实上,Ansible 支持多种类型的迭代器:从传统的循环到 Do...Until、数值迭代器等等。在 Ansible playbook 中使用的迭代器与传统编程中的迭代器几乎是相同的,只是有一些特定的语法差异。
在本节中,我们将研究 Ansible 支持的多种循环方式。我们将从标准的基本循环开始,随着我们逐步深入,本节内容会逐渐转向更复杂的实现。让我们开始吧!
使用 with_items 的基本循环
Ansible 的 YAML 集成支持基本的循环语法,以减少代码中的重复。这在安装软件包、复制文件或管理一组项目时尤其方便。Ansible 实现这一功能是通过 with_items 迭代器来管理的。Ansible 的 with_items 迭代器允许我们指定执行的任务一次,并指定一个项目列表来重复执行相同的任务。让我们来看一下使用 with_items 的 Ansible 任务与没有使用此功能的相同任务集的对比:
# playbook.yml without list based iterators
---
- hosts: all
  tasks:
    - name: Install Apache2
      apt: name=apache2 state=installed
    - name: Install VIM
      apt: name=vim state=installed
    - name: Install TMUX
      apt: name=tmux state=installed
    - name: Install MOSH
      apt: name=mosh state=installed
# playbook.yml using an Iterator to install packages
---
- hosts: all
  tasks:
    - name: Install list of packages
      apt: name={{item}} state=installed
        with_items:
          - apache2
          - vim
          - tmux
          - mosh
根据这个例子,我们可以看到,通过使用 with_items 迭代器,我们可以减轻编写剧本时的一些繁琐工作。此迭代器在此案例中接受一个项目列表,并在每次迭代时替换不同的项目,重复执行任务。
除了直接在任务中内联指定 with_items 外,我们还可以利用在 YAML 变量文件或变量部分中定义的列表。这可以像这样完成:
with_items: "{{ myvarlist }}"
使用 with_nested 的嵌套循环
除了我们之前描述的简单循环外,Ansible 的语法还支持嵌套循环的概念。在许多方面,嵌套循环类似于一组数组,可以使用 with_nested 操作符对其进行迭代。嵌套循环为我们提供了一种简洁的方法,可以在单个任务中迭代多个列表。这在需要多个数据项的情况下非常有用(例如使用不同的名称和详细信息创建用户账户,或者可能是为 MySQL 数据库进行数据填充)。让我们来看一个例子:
# Demo of Nested Loops Using Ansible. To execute use the following command:
# > Ansible-playbook -i 'localhost,' -c local nested_loops.yml
---
- name: Demo of nested loops using with_nested
  hosts: all
  remote_user: root
  vars:
    listA: [1, 2]
    listB: [a, b]
  tasks:
    - name: Say Hello using Nested Loops
      debug: msg=“The values in the array are {{item[0]}} and {{item[1]}}"
      with_nested:
        - listA
        - listB
这是我们在命令行运行此剧本时控制台的输出:

从这个例子中我们可以看到,在我们的剧本中,我们可以使用数组并通过简单地使用 with_items 子句来迭代它们。是不是很方便?
使用 with_dict 迭代哈希
对于熟悉编程语言的朋友们来说,哈希的概念并不陌生。对于不熟悉的人来说,哈希仅仅是一组通过键标识的数据点。在一个哈希中可以有多个键,每个键都有一个关联的值。
让我们来看一个哈希的基本示例,以便更好地理解这种独特但流行的数据结构是如何工作的:

从这张表中我们可以看到,键仅仅是一个标识符,而该键所代表的值可以是与该特定键关联的值表中存储的任何字符串或数据项。那么这如何应用到 Ansible 呢?Ansible 提供了一个 with_dict 操作符,我们可以利用它来迭代键/值对。让我们来看一个例子:
# Example of iterating over a YAML dictionary (iterator_keyvalue.yml)
# To execute save this as a YML file and run the following command 
# > Ansible-playbook -i 'localhost,' -c local iterator_keyvalue.yml
---
- name: Say Hello to our Favorite Looney Tune Characters
  hosts: all
  vars:
    looney_tunes_characters:
      bugs:
        full_name: Bugs A Bunny
      daffy:
        full_name: Daffy E Duck
      wiley:
        full_name: Wiley E Coyote
  tasks:
    - name: Show Our Favorite Looney Tunes
      debug:
      msg: "Hello there: {{ item.key }} your real name is {{ item.value.full_name }}"
      with_dict: "{{ looney_tunes_charachters }}"
这个示例展示了一种在 Ansible playbook 中存储哈希数据并遍历结果的方式。在这个具体的例子中,我们遍历的是键,即我们“乐一通”角色的简短名字,以及相应的值,即角色的全名。当我们运行这个 playbook 时,看到的输出应该类似于以下内容:

所以从这个截图中我们可以看到,Ansible 会整齐地遍历我们请求的数据集,并向我们最喜欢的《乐一通》角色说你好。
使用 with_file 遍历文件
Ansible 的 with_file 操作符为我们提供了一种便捷的方式来遍历文件内容。这个特定的迭代操作为我们提供了按顺序遍历单个文件或多个文件的方式。为了说明它是如何工作的,我们来看一个例子:
- hello.txt:
Hello There:
- favorite_toons.txt:
Bugs Bunny
Daffy Duck
Mickey Mouse
Donald Duck
Wiley E. Coyote
- iterator_file_contents.yml:
# Example Playbook which Iterates Over the Contents of Two Files (iterator_file_contents.yml)
---
- name: Say hello to our favorite Looney Toons
  hosts: all
  tasks:
    - name: Say Hello to Our Favorite Looney Toons
      debug:
        msg: "{{ item }}"
        with_file:
          - hello.txt
          - favorite_toons.txt
从这个例子中,我们应该能够基本理解它试图实现的目标。它首先会显示 hello.txt 的内容,然后依次显示 favorite_toons.txt 的内容。因此,输出应该类似于以下截图:

从这个截图中,我们可以看到,使用嵌套的 playbook 输出了我们最喜欢的《乐一通》角色的名字,并以问候语作为前缀。
遍历顺序数字
通过顺序数字集计数是一个基础的编程概念。它本质上涉及创建一个计数器,按给定的步长向前或向后顺序计数。也就是说,我们可以使用 Ansible 的顺序数字迭代器从一个给定的数字开始向上或向下计数。然后,我们可以将这些数字数据从 Ansible 管道传输到,例如,shell 调用或调试消息。让我们快速看一个简短的例子:
# Ansible Example provided by Ansible.com
# create some test users
 - user:
     name: "{{ item }}"
     state: present
     groups: "evens"
   with_sequence: start=0 end=32 format=testuser%02x
do until 迭代器
Do...Until 迭代器在许多编程语言中已经存在了很长时间。它可能是最广泛实现的迭代器之一。这个特定的迭代解决方案为开发者提供了在满足特定条件或标志之前持续循环代码序列的能力。让我们来看一个传统编程中的 Do...Until 循环与 Ansible 实现的相同操作符的例子:
- VB.NET 示例:
  ' This example is a VB.NET example of a Do Loop
  Do
            Debug.Write(“Counter: " & index.ToString)
            index += 1
  Loop Until index > 5
- Ansible Do...Until示例:
- action:
    /usr/bin/tail -n 1 /var/log/auth.log 
  register: result
  until: result.stdout.find("Cannot create session") != -1
  retries: 100
  delay: 1
这个 Ansible Do 示例展示了如何尾随一个日志并等待特定文本的出现。当等待一个系统启动或执行某些操作并将信息记录到日志文件中时,这个功能会非常有用。
使用 play_hosts 遍历清单主机
清单主机也是数据项!每个在清单文件中定义的主机名都可以进行迭代。Ansible 开发者实现的这一功能对于执行大量配置操作、安装项等非常方便。在这一部分中,我们将学习如何有效地迭代清单文件并对定义的主机执行操作。迭代清单的最简单方法是将 play_hosts 变量与 with_items 一起使用。虽然这可能是最简单的方法,但它并不是实现这种迭代的唯一方式。让我们从一个 play_hosts 变量的示例开始:
- hosts.yml:
[webserver]
192.168.10.10
192.168.10.11
192.168.10.12
[dbserver]
192.168.30.1
192.168.30.2
192.168.30.3
- iterating_inventory_ex1.yml:
# Example of a playbook, which iterates over the inventory list.
# Specifically this will display 
# all hosts in the webserver group. This example uses the play_hosts 
# variable in conjunction 
# with with_items to provide an elegant mechanism for iterating.
--- 
- 
  hosts: webserver
  name: "Iteration Example using With_Items and Play_Hosts"
  tasks: 
    - 
      debug: ~
      msg: "Host Identified: {{ item }}"
      with_items: 
        - "{{ play_hosts }}"
这个示例展示了一个当前被执行目标指向的所有主机的列表,在本例中是 webserver 群组中的项。
除了之前展示的使用 play_hosts 变量的示例外,我们还可以使用 Ansible 群组标识符实现类似的解决方案。群组标识符是一种很好的方式来访问 Ansible 在当前运行中使用的数据(在我们的案例中是主机)。这种实现从某种程度上来说更加强大。这种解决方案的强大之处在于我们可以在群组变量中指定的键值对定义。让我们来看一个使用群组标识符来实现与前一个示例相同输出的例子:
# Display inventory using groups['webserver']
- debug:
    msg: "{{ item }}"
  with_items:
    - "{{ groups['webserver'] }}"
正如我们从这个例子中看到的那样,我们不仅可以针对当前被 play 目标指向的主机,还可以针对清单中定义的任何特定群组。例如,我们可以通过简单地指定 {{ groups['all'] }} 来获取 所有 主机的列表。或者,如果我们只想针对 dbserver 群组,我们可以做类似这样的操作:
with_items:
  - "{{ groups['dbserver'] }}"
Includes
创建一个作为单一小文件或大文件的 playbook 完全是可行的。许多新的 Ansible 开发者实际上都是以这种方式开始开发 playbook 的,通常,这种实现方法是学习 Ansible playbook 创建的正常途径。不过,迟早你会发现,学习如何重用 playbook 的部分内容将变得非常有用,并有助于更好地组织 playbook 开发工作。
从最基本的角度来看,使用 Ansible 的 include 语句可以让我们在一个或多个位置重用自动化部分。可以将其视为编程中的方法或函数,我们可以反复执行,基本上让我们可以编写一次自动化,然后多次重用。
这是一种更有效的重用自动化的方法,因为它消除了重复创建自动化或配置管理解决方案各个部分的需求。因此,我们可以开始从 第一步、第二步 类型的自动化思维模式中跳脱出来,而是开始考虑 将其配置为 Web 服务器,或者配置为 Web 和数据库服务器。
这是一个非常关键的概念。我们可能有一个顶级的 playbook,它仅仅是一个易于理解和重用的包含系列。例如:
- 
- include: add_users.yml
- 
- include: install_httpd.yml
- 
- include: configure_apache.yml
- 
- include: setup_firewall.yml
这就是 Ansible 的真正强大之处,因为那里的第一个和最后一个步骤将会在每台机器上执行。
Play 级别的包含
模块化和组织 Ansible playbook 的最有效方法之一是使用 Ansible 的include。Ansible 的 play 包含提供了一种简单的方法,将其他 YAML 文件中的play嵌入到当前 playbook 中。这种实现使我们能够有效地模块化我们的 playbook 自动化。让我们来看看如何在 Ansible playbook 中利用play包含:
# This is an example of a 'play' include, which includes the contents of playlevelplays.yml
- include: playlevelplays.yml
- name: some play
  hosts: all
  tasks:
    - debug: msg=hello
# This is an example of the contents of playlevelplays.yml
- name: some additional play
  hosts: all
  tasks:
    - debug: msg=hello I am an included file
在这个示例中,我们可以看到,要实现一个play包含,我们只需在我们的 playbook 中简单地添加- include: <filename>指令,这将把外部 Ansible playbook(其 plays)的内容嵌入到当前的 playbook 中,并在适当的步骤执行这些内容。很整洁吧?所以总结一下,我们可以简洁地定义 Ansible 的play包含:play 级别的包含允许我们通过-include指令将外部文件中的额外 Ansible plays 嵌入到我们的 playbook 中。
除了我们刚才查看的 Ansible 包含的原始实现外,Ansible 的include指令还提供了在执行时向我们包含的文件传递参数的功能。这对于将变量传递给我们包含的其他 YAML 文件非常有用。接下来我们来看一下如何使用include指令传递参数。下面给出了一个示例:
tasks:
  - include: myincludedplaybook.yml user=dbuser
根据前面的示例,我们可以在目标包含的文件中使用以下语法{{user}}来调用变量。因此,一个更完整的示例可能如下所示:
tasks:
 - include: myincludedplaybook.yml user=dbuser
 - debug:
   msg: "System {{ user }} user is AWESOME!"
现在我们已经掌握了 play 级别的包含,让我们来看一下任务包含。
任务级别的包含
除了 play 包含,Ansible 还支持include指令的另一种实现。第二种实现被称为任务包含。任务包含不同于play包含,因为包含文件的内容仅仅是一个 YAML 文件,其中包含了一个静态的任务列表。要实现任务包含,我们必须在任务级别指定include指令。接下来我们来看一下任务包含的示例解决方案:
# This is an example of a 'play' include
- include: myplaybook.yml
- name: some play
  hosts: all
  tasks:
    - debug: msg=hello
    # An Example of a task level include
    - include: additionaltasks.yml
从这个示例中,我们可以看到提供的include语句将包括文件additionaltasks.yml的内容。这里需要理解的重要概念是作用域。Play 级别的包含需要包含一个完整的 play 或一组 plays,而task包含应该只有一个 YAML 格式的任务列表。为了更清楚地理解,来看一下每种情况的示例。请参考以下两个文件,它们被恰当地命名为additionaltasks.yml。
additionaltasks.yml的内容在这里说明:
---
# additional_tasks.yml example
- name: some task
  command: /bin/ls
- name: some other tasks
  command: /bin/ps -ef
所以现在,我们知道 Ansible 支持 include 指令的两种范围:第一种导入一组 plays,第二种导入一组 tasks。理解这两个区分非常重要,因为它们是一个强大的功能,可以用来模块化自动化和配置管理的实现。通过有效使用包含,我们可以创建功能丰富的自动化和配置管理解决方案,避免代码冗余。
从 Ansible 2 开始,你可以开发无限层级的包含。这意味着一个文件可以包含另一个文件,而第二个文件中又可以包含更多文件。支持的包含数量没有限制。
动态包含
配合我们之前提到的两种基本的包含类型,Ansible 2.0 支持动态任务级别的包含。动态包含就是在 include 中支持变量转换。由于这种实现,我们需要注意的是,包含直到执行时才会被 Ansible 评估。这使得我们可以在包含中添加变量,而在 Ansible 2.0 之前是无法实现的。这个实现更具体地支持在 include 语句中使用循环和变量。这个附加功能为我们的 playbook 提供了相当大的灵活性。让我们来看看一些 动态包含 的例子:
# A basic dynamic include using a variable
- include: "{{dbserver}}.yml"
这个例子向我们展示了我们可以在 include 语句中使用变量名。这对于动态指定要包含的文件,或者在运行时指定 include 文件非常有用。除了这种实现,我们还可以使用动态包含将变量列表从主 playbook 传递到子 playbook。以下是一个例子:
# Dynamic include with parameters as a loop
- include: myplaybook.yml param={{item}}
  with_items:
  - apples
  - oranges
  - {{favorite_fruit}}
从这个例子中,我们可以看到我们通过 with_items 语法将 apples、oranges 和变量 {{favorite_fruit}} 传递给我们包含的 playbook(稍后会详细介绍)。这应该能帮助你更好地理解如何将信息从一个 playbook 传递到包含文件中。
现在,我们已经对 Ansible 如何变得更加动态有了很好的理解,接下来让我们看看 Ansible 角色,并了解它们如何融入到我们的实现和开发工作中。
Ansible 角色
Ansible 非常适合支持单文件 10,000 行的 playbook(不过请不要真的这么做)。然而,当 playbook 变得难以控制时,Ansible 提供了一种非常好的方式将自动化拆分成多个文件(如前所述,通过使用包含)。然而,当我们需要包含的文件数量越来越多时,管理和维护可能会变得非常复杂。那么,Ansible 开发人员该怎么办呢?角色来拯救!Ansible 角色为我们提供了一种独特的方式,将自动化任务分解为独立定义的职责。
除了提供配置管理的模块化外,Ansible 角色还为我们提供了一种最佳实践方法,用于在 playbook 中组织自动化并开发可重用的解决方案。Ansible 的 roles 实现简单地表示了一个自动化、结构良好的 Ansible 包含解决方案(我们在上一节中讨论过)。这意味着只要遵循预定义的目录结构,包含指令就已经被定义和实现。
总结一下,让我们考虑以下 Ansible 角色的定义:
角色是按共同目的或责任分组的一组 Ansible 任务或配置管理自动化。
为了从根本上理解 Ansible 角色的工作原理,最好从一个简单的扁平化 Ansible playbook 开始,该 playbook 安装和配置 LAMP 服务器(Linux、Apache、MySQL 和 PHP),然后使用 Ansible 角色实现相同的解决方案,并查看实现上的差异。这将为我们提供一个很好的苹果对苹果的比较,帮助我们了解角色实现与标准 playbook 的区别,以及如何组织职责。对于本教程,我们将使用基于 Ubuntu 的 Ansible 控制服务器。让我们开始吧:
AnsibleLAMPwithoutRoles.yml 的内容如图所示:
# playbook.yml
---
- hosts: all
  tasks:
    - name: Install Apache
      apt: name=apache2 state=present
    - name: Install PHP module for Apache
      apt: name=libapache2-mod-php5 state=present
    - name: Install PHP
      apt: name=libapache2-mod-php5 state=present
    - name: Install MySQL
      apt: name=libapache2-mod-php5 state=present
    - name: 3\. start Apache
      service: name=apache2 state=running enabled=yes
    - name: 4\. install Hello World PHP script
      copy: src=index.php dest=/var/www/index.php
基于这个 playbook,我们可以看到我们正在指示 Ansible 通过单个 playbook 安装和配置一个基本的 LAMP 解决方案。这包括安装 Apache2、PHP、MySQL 等等。通过 Ansible 角色,我们可以用更优雅和模块化的方式完成相同的任务。
如前所述,Ansible 角色基本上是包含语句,这些语句已经预先嵌入到 Ansible 实现中,并基于一组预定义的目录结构。让我们来看一下这个基本的配置管理实现,以及组成 Ansible 角色的目录结构如何应用。在你的本地系统上,复制以下目录和文件结构(暂时保持文件内容为空):

一旦创建了目录和文件结构,接下来我们需要填写的是顶层的 playbook.yml 文件,我们将使用这个文件来指定我们希望执行的角色并执行它们。以下是要添加到 playbook.yml 的内容:
# playbook.yml
---
- hosts: all
  roles:
    - webserver
    - dbserver
这个文件的目的是简单地充当指向我们希望在 Ansible 执行过程中执行的角色的指针。在这个例子中,定义的 roles 包括 webserver 角色和 dbserver 角色。每个角色将通过名称和文件夹约定来定义。接下来让我们来看一下这些角色本身。在我们的示例中,我们有两个任务文件需要创建/修改(webserver 任务文件和 dbserver 任务文件)。它们分别命名为 main.yml,并位于任务文件夹中。接下来我们来填写每个文件。每个文件的内容如下所示:
这里展示了 webserver/tasks/main.yml 和 dbserver/tasks/main.yml 的内容:
# roles/dbserver/tasks/main.yml
- name: Install Mysql Server Packages
  yum: name={{ item }} state=installed
  with_items:
   - mysql-server
   - MySQL-python
- name: Start Mysql Service
  service: name=mysqld state=started enabled=yes
- name: Create Example Database
  mysql_db: name=foo state=present
- name: Create Example DB User
  mysql_user: name=foo password=bar priv=*.*:ALL host='%' state=present
# roles/webserver/tasks/main.yml
---
- name: Install Apache HTTPD Server Packages
  yum: name={{ item }} state=installed
  with_items:
   - httpd
- name: Start Http Service
  service: name=http state=started enabled=yes
当我们运行这个 playbook 并定义了角色时,我们可以看到 Ansible 理解如何遍历主 playbook,并执行所需的角色,以确保 Apache 和 MySQL 正常安装并运行。
Ansible 注册变量
Ansible 注册器为我们提供了一种便捷的方式来捕获给定任务的结果,并根据捕获的结果执行一系列额外的自动化操作。在许多方面,这与变量声明类似,尽管注册器的作用更为全局。Ansible 注册器为我们提供了一种存储这些捕获数据以供后用的方法,然后可以根据之前任务的结果有条件地执行未来的任务。
简单的 Ansible 注册器
最基本的 Ansible 注册器实现要求我们仅仅对给定操作的输出进行 register。接下来将提供一个如何定义简单注册器的示例:
---
- name: A Simple Ansible Register Example
  hosts: all
  tasks:
      - shell: tail -n 100 /etc/motd
        register: motd_contents
在这个示例中,我们使用注册操作符捕获系统 MOTD 文件的最后 100 行,并将其存储在全局 register 变量 motd_contents 中。Ansible 注册器本质上在运行时创建了一个新的 Ansible fact,之后可以作为条件的一部分在 play 中再次使用。
但是,我们究竟如何在后续利用存储的注册器呢?好问题!让我们来探讨一下。
访问注册器
在同一个 play 中稍后访问 Ansible 注册器,如同它们被创建时一样,可以相当容易地实现:我们所需要做的就是使用 when 条件。我们在本章之前学习过如何利用 when 条件的基础知识。但在这种情况下,它使我们能够访问注册器。让我们来看一个如何使用 when 条件来访问我们的 register 的示例:
---
- name: A Simple Ansible Register Example
  hosts: all
  tasks:
      - shell: tail -n 100 /etc/motd
        register: motd_contents
     - shell: echo "Our MOTD file contains the word Bugs Bunny"
        when: motd_contents.stdout.find('bugs bunny') != -1
这个 playbook 中重要的行是 when 行(显然)。其中有趣的部分是 .stdout.find 这个概念,它被附加在我们注册变量的末尾。从许多方面看,这个扩展类似于一组面向对象编程(OOP)方法。将这些特定调用识别为 OOP 方法是准确的。
在我们的示例中,我们告诉 Ansible 查看 STDOUT(标准命令行输出)的内容,并找到特定的文本。如果 Ansible 能够正确找到该文本,那么任务才会被执行。很巧妙,对吧?
使用注册器的附加条件逻辑
Ansible 注册器不仅仅局限于 STDOUT 的查找方法。除了基本的搜索条件,我们还可以应用许多其他比较。在这一节中,我们将识别可以附加到 register 变量上的更常见方法,并了解我们可以执行哪些其他比较。
空值或空字符串比较
空值或空字符串的比较在大多数编程语言中都是常见的,Ansible 也不例外。对 Ansible 注册器进行空值或空字符串检查可以通过以下方法实现:
 when: <registername>.stdout == ""
除了这个特定实现之外,我们还可以将其他变量应用到带有注册表的条件语句中。让我们看看怎么做。
Vars 和 Ansible 注册表
Ansible 注册表也支持在 Ansible 注册 when 子句中使用常规预定义变量比较。这个实现让我们可以这样做:
"when varfoo is in register, execute the task" 
这个简单的英文比较可以通过以下形式在 Ansible YAML 中表示:
with_items: varfoo.stdout.find("{{bar}}") > 0
这个条件语句简单地指定,如果 varfoo 的内容存在于注册表 varfoo 的 stdout 中,则执行任务。
遍历注册表内容
最后,注册表的内容可以被迭代用来创建新事物和调整现有系统解决方案。这种实现方式可能是创建一个目录列表、触摸一组文件,或创建一个用户列表。基本上,这意味着我们可以将注册表的内容当作一个列表来使用。让我们看看 www.Ansible.com/ 提供的一个快速示例:
--
- name: registered variable usage as a with_items list
  hosts: all
  tasks:
      - name: retrieve the list of home directories
        command: ls /home
        register: home_dirs
      - name: add home dirs to the backup spooler
        file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
        with_items: "{{ home_dirs.stdout_lines }}"
        # same as with_items: "{{ home_dirs.stdout.split() }}"
根据这个例子,我们可以看到我们的 with_items 子句现在用于创建一组文件和文件夹。然而,home_dirs 变量是通过注册表设置的,而不是标准的 Ansible 变量。
Ansible Handlers
默认情况下,Ansible handlers 在 playbook 实际执行完毕时运行。它们与注册表不同,因为它们为我们提供了一种方法,在 playbook 结束时基于执行过程中提供的一组条件,只执行一次(且仅执行一次)自动化任务。从逻辑上讲,这看起来可能是这样的:
- 
运行角色 foo
- 
运行角色 bar:- 如果角色 bar的服务启动失败,则触发一个标志
 
- 如果角色 
- 
执行 handlers: - 如果触发器被标记,做某事
 
尽管这个例子在某些方面看起来与条件语句相似,但在很多方面它实际上是非常不同的。也就是说,handler 只会在一次触发时执行,而不管触发标志的次数。此外,另一个不同之处在于,handler 的作用更具全局性。也就是说,不管哪个角色触发了 handler 的标志,它都会执行,从而使得这个解决方案不具模块化。
迷惑吗?让我们看看一个 Ansible handler 的例子:
--
- name: Example Handler
  hosts: all
  tasks:
    - command: service httpd restart
      notify: restart service
    - command: service mysqld restart
      notify: restart service
    - command: service cron.d restart
      notify: restart service
    - command: service iptables restart
      notify: restart service
  handlers:
    - name: restart service
      include: tasks/restart_verify.yml
从这个例子中,我们可以看到我们有两个新的概念:notify 操作符和 handlers 操作符。Notify 在某种程度上代表了一个全局事件系统,当触发时会抛出一个事件。而 handlers 则代表监听这些事件的组件。
所以,本质上,我们可以使用 notify 解决方案,在主 playbook 执行完毕后触发一组下游任务。不错吧?
总结
在这一章中,我们发现了许多开发和管理 playbook 的新技术。我们学习了如何利用 includes 来模块化我们的 playbook 结构,并为我们的实现提供一定程度的可重用性。我们学习了如何在 playbook 中实现条件逻辑。我们了解了如何处理迭代器和循环。我们发现了如何在 Ansible 中实现角色,并了解了如何利用这种结构来组织和更好地管理复杂的配置管理和自动化任务。我们学习了如何将我们的 playbook 组织得更具可重用性。我们了解了处理程序(handlers)和注册变量(registers)的工作原理,并发现了如何使我们的自动化解决方案更加容错,同时讨论了如何有效地执行基本的配置管理实现。
这些信息代表了你成为一名成功的 Ansible 开发人员所需要的基本概况信息,并为配置管理的成功奠定基础。为了帮助你实现这一目标,www.Ansible.com/ 的文档将是一个非常有用的资源。因此,这个资源应当成为你处理与 Ansible 相关的所有事宜的首选指南。
在下一章中,我们将探索 Jinja2。Jinja 为 Ansible 提供了极大的灵活性,因为它支持条件语句、循环、变量等更多功能。当我们完成下一章时,我们应该能够较好地理解如何开发 playbook 并利用 Jinja 来支持我们的实现。
第六章:Jinja 在 Ansible 中的应用。
JINJA 由 Armin Ronacher 于 2008 年创建,旨在用更现代、结构化的解决方案替代类似的模板引擎。Jinja 是为 Python(Ansible 所用的编程语言)设计的,并且与 Ansible 结合,提供了一个能够与 YAML 无缝集成的框架。Ansible 的 YAML 实现和程序化的 playbook 解决方案整合了这个强大的 Jinja 模板引擎。像许多其他模板解决方案(Django、Smarty 等)一样,Jinja 旨在为可重用的文本提供结构支持,并根据具体环境(如环境、主机等)进行特定的修改,以及为开发人员提供他们所依赖的全面功能。
对于有 Smarty 或 Django 风格模板经验的人来说,Jinja 将非常熟悉。Jinja 模板解决方案的设计旨在为设计师、开发人员和运维人员提供支持,能够添加条件逻辑、迭代行为和面向逻辑的解决方案,同时遵循 Python 的最佳实践。这个解决方案对 playbook 开发人员尤其有用,因为它提供了高度适应性的编程灵活性,能够在环境相似但有细微差别的组织中发挥作用。可以说,Jinja 的学习曲线较低,而标记和逻辑的实用性却很高。
在这一章中,我们将学习 Ansible 如何与 Jinja 集成,以及如何利用 Jinja 在 Ansible 的 playbook 中提供高级功能。我们将了解 Jinja 的起源,学习如何利用它创建全面的 playbook 实现,了解它的语法如何与 Ansible 的 YAML playbook 语法共同存在,并看到 Jinja 如何补充 Ansible 的 playbook 实现。到本章结束时,我们将涵盖以下内容:
- 
Jinja 简介。 
- 
Jinja2 是如何被 Ansible 使用的? 
- 
Jinja 编程结构。 
- 
将 Jinja 应用于 Ansible 的 playbooks。 
- 
如何在 Jinja2 中创建循环和迭代器。 
- 
如何创建和使用 Jinja2 模板文件。 
- 
如何在 Jinja2 中使用数据结构。 
让我们开始吧,好吗?
介绍 Jinja。
Jinja 是一个模板引擎,由 Armin Ronacher 于 2008 年开发,旨在为 Python 开发人员提供一个框架,该框架可以在字符串和数据文档中使用具有全面 Python 风格的语法。这个解决方案是考虑到类似的解决方案(如 Smarty 和 Django)设计的。Jinja 在沙盒中执行模板转换(与程序的其余部分隔离),以防止模板引擎干扰 Python 程序的正常操作。
Jinja2 代表 Jinja Python 库的第二个主要版本。Jinja 被用来基于一个或多个预定义模板(也由字符串组成)生成基于字符串的文档。截至本书编写时,Jinja 被许多活跃开发的 Python 开源解决方案使用。一些使用 Jinja 的著名解决方案包括 Salt、Flask 和 Ansible。
当与 Ansible 和 YAML 配合使用时,Jinja 为 Ansible playbook 架构增添了强大的功能。在这个背景下,Jinja 为 Ansible playbook 开发者提供了将编程构造(包括变量、条件逻辑、循环等)添加到 playbook 中的能力,并将其结构化,使其能够作为一个完整的自动化编程解决方案。在我们深入探讨 Jinja 能做的所有有趣功能之前,先让我们了解一下它是如何工作的。
Jinja 是一种软件解决方案,旨在将模板与数据模型结合以生成结果文档。模板包含特殊的标记标签和逻辑,这些标签在模板解析过程中被解析并在执行时进行逻辑组合。
为了更好地解释模板引擎的概念,以下 Python 代码展示了一个使用 Jinja 的字符串处理基本示例:
基本的 Jinja Python 实现:
# A Simple Jinja Python Template example from jinja2 import Template
exampleJinjaTemplate = Template('Hello {{ place }}!')
exampleJinjaTemplate.render(place=‘World')
>>>Output: 'Hello World!'
从我们刚才看到的例子中,我们可以观察到这个简单的 Python 脚本完成了以下任务:
- 
导入 Jinja 模板引擎库。 
- 
定义一个简单的字符串模板, ['Hello {{ place }] ']。
- 
渲染模板,并将 {{ place }}标签替换为单词World。
如果你打算在 Python 中直接实现 Jinja,首先必须在 Python 中安装 Jinja 模块。这可以相对容易地完成,关于如何操作的说明可以在以下网址找到:
jinja.pocoo.org/docs/2.9/intro/#installation
上一个例子的输出是 Hello World。从这个例子中,我们可以观察到 Jinja 模板标签 {{ ... }} 在渲染过程中被翻译,而其余的基于字符串的文档保持不变。
现在我们已经对 Jinja 的基本概念有了相当清晰的了解,接下来我们将通过查看一个使用 Jinja 的简单 Ansible playbook 来了解 Jinja 在实际中的应用。
playbook.yml 的内容如下所示:
# Example Ansible Jinja2 Template
- hosts:  all
  vars:
    my_var: ‘Hello'
    my_var2: ‘World'
  tasks:
    - name: Simple Ansible MOTD Template Example
      template:
        src: motdexample.j2
        dest: /etc/motd
        mode: 0777
motdexample.j2:这是一个由 Jinja2 生成的 motd 文件,也叫做 今日消息。以下内容标签将被我们在 playbook 中定义的变量数据替换(分别为 my_var 和 my_var2):
Welcome to your System:
{{ my_var }}
{{ my_var2 }}
你能猜到这个例子在使用 Ansible 和 Jinja2 模板引擎执行时会做什么吗?Ansible 与 Jinja 配合将把 motd 文件复制到目标主机,并同时替换 {{}} 样式的变量,替换成 my_var 和 my_var2 的内容。让我们来看看目标主机上 motd 文件的输出:
/etc/motd 内容:这是一个由 Jinja2 生成的 motd 文件。以下内容标签将被我们在 playbook 中定义的变量数据替换(分别为 my_var 和 my_var2):
Welcome to your System:
Hello
World
在 Ansible playbook 的上下文中,Jinja 允许我们在 playbook 中(或像我们刚才看到的模板文件中)添加 {...} 样式的标签,Ansible 会指示 Jinja 库在执行前翻译这些标签并生成一个新文档。这意味着我们可以将常见的编程构造直接添加到 playbook 或模板文件中,使我们的自动化变得更加智能。虽然这个例子看起来可能不是很有用,但总体来说,Jinja 的实现与 Ansible 和 YAML 配合使用时提供了显著的增强。
考虑在需要生成 Apache 配置文件或 MySQL 查询时的实现。我们当然可以费力地手动生成每个配置,或者我们可以将这些内容模板化,并让 Ansible 进行迭代。在接下来的章节中,我们将深入探讨 Jinja 编程构造,并学习如何有效地利用 Jinja。
Jinja2 编程构造
Jinja2 在 Ansible 架构中被引入是在 2012 年,当时 Ansible 0.5 发布。那时 Ansible 的实现已经包含了 Jinja2 过滤器的概念,并支持基本的 Jinja2 语法。随着 Ansible 的发展,它对 Jinja 的开发支持也在不断增强。通过结合 YAML 和 Jinja2,Ansible 很快能够为 playbook 开发者提供一个全面的脚本解决方案。
到 Ansible 1.0 发布时,Ansible playbook 的概念(包括 Jinja 和 YAML)已经发展到足以支持各种语法实现。由于集成了 YAML,Jinja 和 Ansible 的流行度急剧上升。Ansible 1.0 发布后,playbook 可以编写以包含以下语法:
Jinja 标签语法:
{{ .. }} for expressions (including variables)
{% ... %} for control structures
{# ... #}} Comments
这些标签在 Jinja 世界中各自承担着独特的角色,理解每个标签的作用非常重要(以免混淆)。在接下来的章节中,我们将学习这些特殊标签,并了解如何利用它们增强我们的 playbook 逻辑。到我们完成时,应该能够深入理解如何使用 Jinja 表达式、控制语句和注释的实现。
表达式、过滤器和变量
Jinja 过滤器(也称为变量表达式)在性质上非常类似于编程中的变量,尽管有一个关键的不同点。过滤器表达式表示一个数据值项或与其他数据源结合的计算数据点的值。表达式在运行时被评估,并能为剧本和模板开发人员提供灵活性。Jinja2 表达式格式的语法如下所示:
# Basic Syntax of a Jinja Filter or Variable
{{ var | operation expression }}
var 部分的数据源可以来自多个不同的地方。在 Ansible 剧本中,目前有四种独特的方法可以在 Jinja 的上下文中获取变量值和内容:
- 
使用 --var-file选项传递一个 YAML 或 JSON 文件。
- 
创建环境变量。 
- 
向剧本添加一个顶级默认值部分。 
- 
捕获 stdout中的值。
如我们刚才看到的示例语法所示,Jinja 表达式为 Ansible 剧本开发人员提供了强大的功能。让我们看看使用 Ansible 和 Jinja2 表达式的更全面的剧本示例。
playbook.yml:
# Example playbook using simple JINJA2 variable substitution
--
- hosts: 127.0.0.1
  user: root
  vars:
    motd: ‘Welcome to your Linux Box'
  tasks:
    - name: Update the /etc/motd
      copy: content=‘{{motd}}' dest='/etc/motd' force='yes'
当我们运行剧本时,Ansible 的输出如下所示:

输出(/etc/motd 的内容):
Welcome to your Linux Box
在这个示例中,我们仅使用一个基本变量设置 MOTD 文件的内容。很简单,对吧?但 Jinja 表达式的真正强大之处在于,我们可以从多个来源获取变量数据,并在运行时对变量进行操作。
Jinja 字符串连接与操作
Jinja 提供了一个优秀的解决方案,用于操作字符串和连接文本(将其与其他文本连接)。例如,我们可能想通过基于主机名等信息,将一些附加信息添加到 MOTD 内容中,从而创建一个独特的 MOTD 文件。通过以下 Ansible 剧本和 Jinja 语法可以轻松实现这一目标。
playbook.yml:
# Example playbook using simple JINJA2 variable substitution
- hosts: 127.0.0.1
  user: root
  vars:
    motd: "Welcome to your Linux Box! You are connected to {{ inventory_hostname_short }}"
  tasks:
    - name: Update the /etc/motd
      copy: content=‘{{motd}}' dest='/etc/motd' force='yes'
如我们所见,示例展示了如何轻松地使用 Jinja 连接字符串变量。虽然我们刚刚看到的示例很有用,但 Jinja 的表达式实现并不限于简单的字符串连接。我们还可以使用 Jinja 操作字符串。例如,我们可能会使用以下任何一种解决方案:
在 Jinja 变量中使用正则表达式:
# convert "ansible" to "able"
{{ 'ansible' | regex_replace('^a.*i(.*)$', 'a\\1') }}
# convert "foobar" to "bar"
{{ 'foobar' | regex_replace('^f.*o(.*)$', '\\1') }}
# convert "localhost:80" to "localhost, 80" using named groups
{{ 'localhost:80' | regex_replace('^(?P<host>.+):(?P<port>\\d+)$', '\\g<host>, \\g<port>') }}
# convert '^f.*o(.*)$' to '\^f\.\*o\(\.\*\)\$'
{{ '^f.*o(.*)$' | regex_escape() }}
要将 Jinja 变量转换为大写和小写:
Uppercase a Variable:
{{ var|upper }}
Lowecase a Variable:
{{ var|lower }}
除了字符串处理,Jinja 的实现更为强大。我们还可以使用 Jinja 执行数学运算。让我们来看看如何操作。
Jinja 中的基本算术操作
除了基本的字符串连接,我们还可以在 Jinja 中执行数学和计算操作。例如,我们可以使用以下语法将数值相加:
var: 1
incremented_var: "{{ var + 1 }}"
计算结果将会是1+1 = 2,因此incremented_var变量的值将是2。除了基本的加法,我们还可以进行乘法、减法、除法等运算。接下来将提供一个可用的基础数学运算的完整列表:
# Computational operations using Jinja2
Addition Operator: +
Example: {{ var + 1 }}
Subtraction Operator: -
Example: {{ var - 1 }}
Multiplication Operator: *
Example: {{ var * 2 }}
Division Operator: /
Example {{ var / 2 }}
从我们刚才看到的内容,容易理解 Jinja 是如何执行基本数学运算的。但它不仅限于简单的数学运算。
Jinja 中的复合数学方程式和运算顺序
基本的运算如加法、减法、乘法和除法也可以组合在一起,创建更强大的计算结果。这是通过()运算顺序解决方案实现的,这在许多编程语言中都很常见。它基本上说明了括号内的数学运算会首先执行,并且优先级为乘法、除法、加法和减法。完成一对括号中的操作后,逻辑会向外展开。这里提供了一个示例:
Example Math Equation:
2 * (1 + 1)
Order of Operations:
1 + 1 = 2
2 * 2 = 4
Example Math Equation #2:
(( 1 + 2 ) * 3 ) / 2
Order of Operations:
1 + 2 = 3
3 * 3 = 9
9 / 2 = 4.5
在 Jinja 表达式的上下文中,这个数学运算看起来大概像这样:
my_var = "{{ (( 1 + 2 ) * 3) /2 }}"
可用的过滤器
Jinja2 不仅仅局限于计算操作。它可以用于执行各种操作任务。Ansible 还提供了许多方便的过滤器,可以应用于我们的变量实现。
以下是一个表格,列出了使用 Jinja 过滤表达式时可能需要执行的更常见的操作任务的示例:
| 表达式名称 | 示例 | 表达式描述 | 
|---|---|---|
| to_json | 
{{ some_variable | to_json }}
| 将变量数据转换为 JSON 格式并渲染。 | 
|---|
| to_yaml | 
{{ some_variable | to_yaml }}
| 将变量数据转换为 YAML 格式并渲染。 | 
|---|
| mandatory | 
{{ variable | mandatory }}
| 使变量声明和设置成为 playbook 正确执行的必要条件。 | 
|---|
| default value | 
{{ some_variable | default(5) }}
| 如果变量未定义,则为该变量设置默认值。 | 
|---|
| min|max | 
{{ [3, 4, 2] | max }}
{{ [3, 4, 2] | min }}
| 从数组中提取最小值或最大值。在这个例子中,值将分别为 2或4。 | 
|---|
| random | 
"{{ ['a','b','c']|random }}"
"{{ 59 |random}} * * * * root /script/from/cron"
"{{ 59 |random(seed=inventory_hostname) }} * * * * root /script/from/cron"
| 从列表中获取随机项、随机数或种子值,分别为此操作。 | 
|---|
| 打乱 | 
{{ ['a','b','c']|shuffle }}
# => ['c','a','b']
{{ ['a','b','c']|shuffle }}
# => ['b','c','a']
| 从现有的随机列表生成一个新的随机列表。 | 
|---|
| 数学对数 | 
{{ myvar | log }}
{{ myvar | log(10) }}
| 日志算法数学函数和日志数值算法,分别为此操作。 | 
|---|
| 平方根 | 
{{ myvar | root }}
{{ myvar | root(5) }}
| 平方根数学运算。 | 
|---|
| IPV 过滤器 | 
{{ myvar | ipv4 }}
{{ myvar | ipv6 }}
| 测试字符串是否为特定的 IPV 版本。 | 
|---|
| 引号过滤器 | 
- shell: echo {{ string_value | quote }}
| 将计算出的表达式用引号括起来。 | 
|---|
| 合并列表 | 
{{ list | join(" ") }}
| 将列表项连接成一个统一的字符串。 | 
|---|
| 基名 | 
{{ path | basename }}
| Linux 路径基名。例如,从 /etc/bar/foo.txt中提取foo.txt。 | 
|---|
| WIN 基名 | 
{{ path | win_basename }}
| 与 basename 相同,但适用于 MS Windows。 | 
|---|
现在我们对 Ansible 和 Jinja 如何利用过滤器有了很好的理解,接下来我们来探讨控制结构。
有关可用过滤器和表达式的完整列表,请查阅官方的 Ansible 2.0 文档,网址为 docs.ansible.com/ansible/playbooks_filters.html#filters-for-formatting-data。
条件逻辑(if-then-else)
任何编程语言中的控制结构提供了一种定义程序根据条件执行路径的方式。除了条件逻辑外,控制结构还为我们提供了一种在不重复代码的情况下重复相似任务的方法。这通常被称为条件逻辑和循环。Jinja 为我们提供了一组运算符,允许我们循环或条件性地执行代码。在本节中,我们将专门讨论条件逻辑,并学习如何在 Jinja 的上下文中利用它。
条件语句为开发者提供了一种根据表达式的评估来有条件地执行一系列事件的方法。在大多数语言中,这是通过if...then式的解决方案实现的。举个例子,以下是展示条件逻辑基本原理的流程图:

代码中的条件逻辑可能看起来像下面这样(Python 代码):
# Python Conditional Logic Example
x = 0
if x == 0:
    print “Hello X is 0”
else:
    print “Hello X is NOT 0”
这段 Python 代码展示了一个简单的条件逻辑示例。它简单地表示,如果x等于0,则执行print语句告诉用户这一点。在 Jinja 中,我们可以实现一个非常相似的逻辑运算符。唯一的实际区别是,在 Jinja 中,所有控制结构和条件语句都被包裹在{% %}标签内。让我们来看一下使用 Jinja 的相同实现:
{% if condition %}
    execute_this
{% elif condition2 %}
    execute_this
{% else %}
    execute_this
{% endif %} 
正如我们所看到的,Jinja 的实现也为我们提供了可选的else-if语句。这为我们在实现条件逻辑时提供了额外的功能。
在 Ansible 中,Jinja 的条件逻辑可以以多种不同的方式使用。它可以用来用条件语句包装整个任务(仅在设置了变量时才执行这些任务)。这为剧本开发者提供了极大的灵活性和强大功能。让我们看一个在 Ansible 剧本中实现条件逻辑的实际示例:
# Conditional Logic Playbook Example
---
- name: Hello World Conditional Logic
  user: root
  hosts: all
  vars:
    hello: true
    say_something: "{% if hello == true %} Hello Jinja {% else %} Goodbye Ansible {% endif %}
  tasks:
    - debug:
        msg: "{{ say_something }}"
当我们运行这个剧本时,我们会得到以下输出:

基于我们在剧本中定义的条件语句:
 hello: true
 say_something: "{% if hello == true %} Hello Jinja {% else %} Goodbye Ansible {% endif %}
我们可以将hello变量设为 false,然后得到以下输出:

需要注意的是,在 Ansible 剧本中使用的 Jinja 语法需要放在引号内,并继续遵守 YAML 字典格式标准。而在剧本之外(例如 Jinja 模板文件内)的 Jinja 条件语法,则不需要遵守 YAML 标准。
条件逻辑可以复合使用,以在 Ansible playbook 中提供更大的灵活性和功能。在我们看到的示例中,{% ... %} 标签被放置在 vars 部分,但它们不一定非得在那里。
除了直接在 Ansible playbook 中放置条件语句外,我们还可以在 Jinja 模板文件中利用 Jinja 条件逻辑。这是 Jinja 真正强大之处的体现。让我们看一个实现了条件逻辑的 Jinja 模板文件的示例:
- Ansible playbook:
# Example Ansible playbook & Jinja Template
---
- name: Hello World Conditional Logic within a Jinja template
  user: root
  hosts: all
  vars:
    vhost:
       servername: my.server
       documentroot: /var/www
       serveradmin: bob
  tasks:
    # Jinja template file example
    - template:
        src: /jinjatemplates/httpdconf.j2
        dest: /etc/httpd/httpd.conf
        owner: root
        group: wheel
        mode: "u=rw,g=r,o=r"
- httpdconf.j2:
NameVirtualHost *:80
<VirtualHost *:80>
  ServerName {{ vhost.servername }}
  DocumentRoot {{ vhost.documentroot }}
{% if vhost.serveradmin is defined %}
  ServerAdmin {{ vhost.serveradmin }}
{% endif %}
  <Directory "{{ vhost.documentroot }}">
    AllowOverride All
    Options -Indexes FollowSymLinks
    Order allow,deny
    Allow from all
  </Directory>
</VirtualHost>
运行此示例时,Ansible 将翻译 Jinja 模板,将其传送到远程主机,并放置在 /etc/httpd/httpd.conf 目录中。
循环和迭代器
没有编程语言是完整的,如果没有通过迭代重复任务来减少代码量的能力,而 Jinja 也不例外。Jinja 在其语法工具库中提供了多种循环类型。现代传统编程语言中的循环形式多种多样。例如,大多数现代编程语言都支持以下大部分循环类型:
- 
For循环
- 
Foreach循环
- 
While循环
- 
Do...Until循环
除了这些循环选项外,一些编程语言还支持其他面向对象编程(OOP)类型的循环。在 Ansible 实现的 Jinja 中,目前只支持 for 循环。虽然循环类型的实现有限,但仍然可以执行基于计数器的迭代、列表迭代和复合循环条件。在这一部分中,我们将讨论 Jinja 中的循环,并学习如何在我们的 playbook 和 Jinja 模板中实现它们。
简单计数器
计数器循环涉及对一个变量的重复递增或递减,直到满足某个条件。计数器循环对于仅需要在迭代循环序列中进行小幅整数变化的代码非常有用。为了更好地理解计数器循环的概念,我们来看一个常见编程循环的流程图示例,该循环递增一个计数器:

在这个示例中,我们基本上重复执行增量任务,直到变量 X 等于数字 10。一旦 X 的值等于 10,迭代器停止。我们示例中的循环可以通过以下基本 Python 语法表示:
# Simple Python Counter Loop 
x = 0
while x < 10:
    x+=1
print "The value of X is now" + str(x)
循环不一定总是执行数学运算。它们还可以执行自动化操作、迭代列表,或者完成几乎任何你想象得到的任务。这个循环示例仅仅是递增一个计数器。
现在我们已经理解了循环的基本概念,让我们看看如何在 Jinja 和 Ansible 的上下文中实现同类型的循环:
# Example Ansible playbook using an iterating loop
---
- name: Hello World Iterator within Ansible
  user: root
  hosts: all
  tasks:
    - debug:
        msg: "{% for x in range (0,10) %} {{x}} {% endfor %}"
实现起来很简单,对吧?接下来的问题是,我们如何在 Jinja 模板文件中实现相同类型的逻辑?很高兴你问了!让我们看看:
{% for id in range(201,221) %}  
192.168.0.{{ id }} client{{ "%02d"|format(id-200) }}.vpn  
{% endfor %}
也挺简单的,对吧?除了在循环中计数值,我们还可以遍历其他数据项。
列表迭代器
在编程解决方案中,列表迭代器提供了对一组项目执行一系列操作的能力。例如,我们可能有一个水果列表 [apples, oranges, grapes, pears],并希望遍历这个列表。在传统的编程语言中,我们可以使用 foreach 循环。foreach 循环的基本流程可能如下所示:

这个示例简单地遍历列表中的每个项目,并根据呈现的新列表项执行代码块。这种类型的循环实现方式在大多数编程语言中都很基础,Jinja 也不例外。根据这个流程,我们可以查看一个 Python 示例,并将其映射到以下代码片段:
# Simple example of a Python iterative loop
fruits = ['apples', 'oranges', 'pears', 'apricots']
# Iterate through a list of fruits
for fruit in fruits:
    print "A fruit of type: %s" % fruit
在 Jinja 中,列表迭代器的实现具有以下语法:
{# Example Jinja For iterator loop #}
{% for <item_name> in <list_name> %}
  code block
{% endfor %}
最后,让我们来看一下将这个循环序列转换为 Jinja 的实现:
- 主机文件:
[webservers]
webhost1.local
webhost2.local
[databaseservers]
dbserver1.local
dbserver2.local
- Playbook:
# Example of loops using Jinja
--
- name: Simple Ansible Playbook that loops over hosts within Jinja
  vars:
    servers_to_configure: "{{ groups['databaseservers'] }}"
  tasks:
    - template:
      src: configfile.j2
      dest: configfile.conf
- Jinja 模板:
# Simple Configuration file based on Jinja templating
all_hosts += [
{% for host in servers_to_configure %}
  "{{ hostvars[host].ansible_default_ipv6.address }}
{% endfor %}
]
这个示例将自动填充一个名为 configfile.conf 的文件,并将其上传到目标主机,内容使用 Jinja 的 for 迭代器进行转换。
使用 Jinja 的复杂迭代器
我们在本章早些时候讨论的迭代器非常简单。Jinja 还提供了一种更复杂的循环实现方式。复杂(或复合)迭代器是编程中的常见实践,也是现代算法得以实现的原因。复杂的迭代器可能有多种形式,但通常包括在循环序列中添加额外的复合条件或嵌套循环。让我们看一个说明复杂迭代器(嵌套循环)的流程图:

这个流程图看起来很复杂吗?其实不需要。让我们来看一下用 Python 表示的相同算法:
# Nested For Loop with Python
for iterating_var in sequence:
   for iterating_var in sequence:
      statements(s)
   statements(s)
在 Jinja 中,实现嵌套的 for 循环与此类似。让我们看一个示例:
# Example YAML variable file
var_name:
- group:
    - variable1
    - variable2
- group:
    - variable1
    - variable2
Jinja 模板将如下所示:
{% for var in var_name %}
<group>
{% for host in var.group %}
    <variable><host>{{ host }}</host></variable>
{% endfor %}
</group>
{% endfor %}
如我们所见,这个 Jinja 循环的实现也相当简单。接下来让我们看看如何构建一个稍微复杂一点的循环,使用迭代器和条件语句。以下是 vars 文件中变量在循环中的代码片段:
# Save this as vars.yml
---
 places:
 - name: USA
 capital: Blue
 - name: Great Britan
 capital: Yellow
 - name: Itally
 capital: Blue
 - name: Mexico
 capital: Yellow
 - name: Ireland
 capital: Yellow
 - name: Amsterdam
 capital: Orange
# Save this as playbook.yml
---
 - name: How to use variables in Jinja2 Loops
   hosts: localhost
   connection: local
   vars_files:
     - vars.yml
   gather_facts: no
   tasks:
     - name: This task uses a j2 template which has a series of loops
       template: src=./ourloop.j2 dest=./output.txt
# Save this as ourloop.j2
{% for country in places %}
 Currently people are visiting {{ country }}.
{% endfor %}
现在运行它并检查输出。不错吧?
在 Ansible Playbook 中应用 Jinja
Jinja 可以通过几种特定的方式应用于 Playbook。Jinja 最常见的实现方式是在 Playbook 的 YAML 文件中使用过滤器和变量。此信息必须放置在 YAML 键/值结构的引号上下文中。YAML 的键/值结构确实通常支持非引号值,但在 Jinja 的上下文中,我们必须将其放在引号内。例如,我们可以考虑以下内容:
---
- name: Simple Ansible Playbook that loops over hosts within Jinja
  vars:
    say_hello
    say_something: "{{ say_hello }}"
  tasks:
    - debug:
        msg: "{{ say_something }}"
从这个 playbook 中我们可以看到,Jinja 的实现方式是在引号内直接使用 {{...}} 标签。唯一支持未加引号的 Jinja 标签实现的地方是在 Jinja 模板中。Jinja 模板的解析方式不同于 YAML,因此支持宽松的 Jinja 标签实现。我们来考虑以下内容:
# Simple Configuration file based on Jinja templating
{% for host in servers_to_configure %}
  {{ host }}
{% endfor %}
总结
在这一章中,我们讲解了 Jinja 如何特别且独特地融入 Ansible 世界。我们学习了 Jinja 过滤器的工作原理,并发现如何在 playbook 中利用这些过滤器,为我们提供巧妙的 playbook 实现。除了 Jinja 过滤器外,我们还花时间学习了如何在 Jinja 过滤器上下文中对变量执行数学运算。
除了 Jinja 过滤器的概念,我们还学习了循环和迭代器。这些迭代器为我们提供了对遍历结构化数据的深刻理解,包括正向和反向计数。我们还学习到,迭代器可以用于遍历列表(例如我们在示例中遍历的水果列表)。最后,我们学习了如何在我们的 playbook 中应用 Jinja,以及利用 Jinja 时的具体要求。
在下一章,我们将介绍 Ansible Vault。Ansible Vault 提供了一种独特且安全的解决方案,用于加密、存储和解密数据。这个解决方案非常有用,因为它可以用来存储密码数据、用户名、安全的数据库查询等等。在一个组织中,常常需要敏感用户数据来配置和部署软件。这些敏感数据通常是用户名和密码。Ansible 提供了一种现成的解决方案,帮助加密和隐藏敏感信息。下一章将讲解 Ansible Vault。本书的下一章将概述 Ansible 管理、存储和部署敏感信息的方式。我们将介绍如何最好地利用 Ansible Vault 工具,确保敏感数据保持安全和保密。我们将通过示例学习如何最佳地控制和管理高度安全的信息,并了解 Ansible 如何保障你的信息安全。
让我们继续学习关于 Ansible Vault 的内容。
第七章:Ansible Vault
现代加密解决方案自计算机科学早期加密概念的发明和实现以来已经取得了长足的进展。加密和安全性是现代主流新闻媒体中的热点话题,因为著名的安全漏洞引发了对安全错误的高度关注,同时对敏感数据的保护也有所增加。随着应用程序和客户的敏感数据逐步迁移到云端,对控制和安全性的需求比以往任何时候都更加迫切。
现代 基础设施即代码(IaC)解决方案为将配置管理解决方案存储在现代源代码控制解决方案中铺平了道路。通过源代码控制解决方案管理和跟踪基础设施变更具有很高的价值,因为它为团队提供了确保 IaC 解决方案通过版本控制进行跟踪的能力,且修订版本被注释和备份。除了这些价值点,存储 Ansible playbook 和相关自动化的版本控制还确保团队能够协作创建有用的自动化、配置管理解决方案和部署任务。
创建 playbook 并将其存储在现代源代码控制解决方案中是非常有意义的,也是最佳实践。与此相反,将敏感数据存储在源代码控制中就显得不太合适了,因为这允许任何有权限访问源代码控制的人员查看可能包含机密信息的数据。于是,Ansible vault 应运而生。它承诺能够隐藏这些敏感数据,对需要加密的数据进行加密,并继续允许 playbook 开发人员将其 playbook 存储在源代码控制中。是不是很棒?
在这一章节中,我们将探索 Ansible vault。它为我们提供了一个安全、易于使用的解决方案,用于加密并存储我们 playbook 中的敏感数据或存储在变量 vault 文件中的数据。具体来说,在这一章节中,我们将涵盖以下主题:
- 
Ansible vault 架构 
- 
基本的 vault 使用方法 
- 
如何使用 Ansible vault 加密数据 
- 
如何创建、编辑和加密变量文件 
- 
如何以安全的方式解密文件 
- 
如何将加密数据嵌入 YAML playbook 中 
- 
运行 playbook 并即时解密数据 
- 
使用 Ansible vault 的技巧与窍门 
让我们开始吧!
Ansible Vault 架构
Ansible vault 旨在为 playbook 开发人员、系统管理员和相关人员提供将敏感数据存储在 playbook、变量文件或目录结构中的解决方案。Ansible vault 使用的加密系统基于 对称密钥高级加密系统 或 AES 对称密钥 解决方案。AES 对称密钥加密为我们提供了一种易于使用的方式,使用相同的密钥对数据进行加密和解密。下图展示了 AES 对称密钥加密:

Ansible Vault 解决方案的设计旨在为任何 Ansible 支持的结构化数据文件提供加密服务。这意味着我们可以加密 group_vars/ 和 host_vars/.. 库存变量目录。它还意味着我们可以加密在 include_vars/vars_files 中加载的变量文件。从前述内容可以看出,Ansible 的 Vault 解决方案支持的范围非常广泛。基本上,这意味着我们可以加密任何和所有我们希望加密的数据,唯一的例外是 playbook 本身。
Ansible 版本 2.3 引入了一项功能,支持在 Ansible YAML 文件中加密单个变量值。这是通过使用 !vault 标签实现的。此特殊标签的结果允许我们在处理文件的事实时,通知 Ansible 解密该值。
除了能够加密变量和变量文件之外,还可以加密整个 playbook。更重要的是,Ansible 还支持加密二进制文件、数据文件以及更多其他文件。这些文件可以通过 copy_file 命令即时解密。除了复制文件选项,Ansible Vault 还支持许多其他选项。在接下来的章节中,我们将查看一些如何即时加密、解密和重新设置 Vault 文件密钥的示例,并展示我们如何在本章后续内容中利用 Ansible 的 Vault 功能。在深入了解这些之前,让我们先看看如何使用 Ansible Vault 的基本实现以及如何加密、解密和重新设置 Vault 文件的密钥。
Vault 基本用法
Ansible Vault 最基本的实现是一个简单的 AES 对称密钥加密解决方案(如前所述)。这个实现通过命令行界面进行管理,具体是通过 ansible-vault 命令。使用此命令,我们可以加密、解密、重新设置密钥并编辑 Vault 特定的文件。接下来将提供这些命令的语法、描述和示例。
加密 Ansible Vault YAML 文件
这个命令语法允许我们加密 YAML 文件的内容。执行时,它会提示用户输入希望使用的密钥来进行加密。
my_vault.yml 文件的内容如下所示:
integer: 25
string: "Hello Ansible Users"
float: 25.0
boolean: Yes
然后,为了加密文件,执行以下命令:
#> ansible-vault encrypt my_vault.yml
命令执行的输出如下所示:
New Vault password: 
Confirm New Vault password:
#>
一旦文件被加密,我们可以通过 cat 命令查看加密结果,如下所示:

本示例展示了一个使用 Ansible Vault 加密和解密数据的简单方法。这个策略在命令行和手动输入方面非常有用,但它为我们的自动化执行增加了一个人为的元素,这可能并不是我们总是想要的。文件加密也可以通过单个命令行输入完成,如下所示:
$> ansible-vault encrypt my_vault.yml --vault-password-file vault_pass.txt
Encryption successful
在这个例子中,我们可以看到 Ansible vault 有接受密码文件的选项。vault_pass.txt 只是一个包含 Ansible vault 密码的纯文本文件。此命令行指示 Ansible 使用文本文件中的密码,而不是提示输入密码。这个选项使得自动化管理 vault 更加容易,因为不再需要人工干预。
解密
Ansible vault 的 decrypt 选项可以解密先前加密的 YAML vault 文件。在执行时,Ansible vault 会提示用户输入 vault 密码来解密文件。一旦输入密码,Ansible 会使用 AES 对称密钥加密算法解密文件(如果输入了正确的密钥)。让我们看一个例子。
首先,让我们从我们在前一部分中创建的加密文件开始。以下是加密的 vault 文件的截图:

然后,使用以下命令解密文件:
#> ansible-vault decrypt my_vault.yml
执行此命令的输出将如下所示:
#> Vault password:
Decryption successful
一旦文件被解密,我们可以看到解密后的文件,如下所示:

类似于我们之前讨论的加密机制,我们也可以在命令行中使用密钥文件形式指定 vault 密钥。这将帮助我们更好地自动化流程,并避免手动输入密码。下文是一个示例:
$> ansible-vault decrypt my_vault.yml --vault-password-file vault_pass.txt
Encryption successful
为 Ansible vault 文件重新设置密钥
更改 Ansible vault 用于加密/解密 vault 文件的密钥是一个相当简单的任务。只需在 Ansible vault 命令行上下文中使用 rekey 操作符即可。以下示例展示了 rekey 命令的语法:
#> ansible-vault rekey <file.yml>
运行前面的命令后,我们将被提示输入现有密钥和新密钥。输出(如果 rekey 成功)应该类似于以下内容:

就地编辑
Ansible vault 解决方案提供了一种便捷的方式,使用默认系统编辑器实时编辑加密的 vault 信息。在 Linux 操作系统中,可以通过以下命令语法在命令行设置默认编辑器:
export EDITOR='vi'
在前面的代码中,我们将编辑器设置为 vi;然而,你可以将其设置为你喜欢的任何编辑器(nano、pico、vi、vim 等)。
在 Linux 系统上,默认编辑器通常设置为 vi。
现在我们已经设置好默认编辑器,可以通过在 ansible-vault 命令中使用 edit 命令选项来编辑我们的 Ansible vault 数据。让我们看一个例子:
#> ansible-vault edit my_vault.yml
在运行 playbook 时解密 vault
手动加密、解密和重新设置 Ansible Vault 数据是一回事;在 playbook 执行过程中动态使用这些信息才是我们真正想要实现的目标。在本节中,我们将介绍如何在 playbook 执行过程中解密 Ansible Vault 加密数据。
有两种方式可以自动解密嵌入在 playbook 或变量文件中的 Vault 数据。第一种是将 Vault 密钥存储在纯文本文件中,然后将该密钥文件传递给 ansible-playbook 命令。第二种是使用 --ask-vault-pass 命令行选项提示用户输入 Vault 密码。我们来看看这两种方式是如何工作的。
使用密码文件自动解密
解密 Ansible Vault 数据的最理想方式是无需用户干预。这个选项为更灵活的自动化方法打开了大门(可以通过 Jenkins、CircleCI、Bamboo 等发起)。为了实现这一解决方案,诀窍是将 Ansible Vault 密码存储在密码文件中。例如,如果我们有一个 vars 文件,我们将使用 encrypt 选项加密它,然后将用来加密它的密钥存储在纯文本文件中。然后,在运行 ansible-playbook 命令时,我们可以直接传递 vault-password-file。其语法如下所示:
$> ansible-playbook -i inventory/qa.hosts playbooks/example.yml --vault-password-file ~/.vault_pass.txt
密码应存储为文件中的单行字符串。
手动解密 Vault 数据
解密 Ansible Vault 数据的另一种方法是让 Ansible 在执行 playbook 时提示用户输入密码。这也可以通过简单的方式来实现。以下命令语法演示了如何指示 Ansible 在执行 playbook 前提示用户输入 Vault 密码:
$> ansible-playbook -i inventory/qa.hosts playbooks/example.yml --ask-vault-pass
真实世界中的 Ansible Vault 工作流
Ansible Vault 的实现是一个非常强大的解决方案,旨在为敏感信息提供安全保护。正如你已经学习过的,它允许我们轻松地加密、解密、重新设置密钥和编辑私密数据。虽然 Vault 的使用非常简单,但要找到一种可维护的方式来使用 Ansible Vault 并不总是显而易见的。因此,在本节中,我们将讨论一些小技巧,帮助你让 Ansible Vault 的使用体验更加愉快。
带有角色的 Ansible Vault
Ansible Vault 的实现最好与角色一起使用。角色(正如我们之前讨论的)允许我们将 playbook 模块化,并在其中重用功能。我们将要关注的角色实现特定领域是 vars 文件夹。vars 文件夹是我们定义变量和数据点的地方,这些变量和数据点随后被任务和 play 使用。
为了开始本教程,我们先创建一个 Ansible playbook,并采用以下文件夹和文件结构(文件内容可以暂时为空,我们将在稍后填写详细信息):

一旦创建了这些文件,有几点应该立即显现出来。首先,我们正在创建的 playbook 是一个简单的 Vault 测试,包含一个角色和一个sensitive_data变量的实现。此外,正如你可能已经猜到的,我们将使用sensitive_data.yml文件来存储我们超级机密的信息。该文件的内容应该如下所示:
---
secret_text: |
  The contents of this message are secret. This tape will explode in 5 seconds.
从提供的文件内容中,我们可以看到一个简单的 vars 文件,其中定义了一个名为secret_text的变量。
YAML 语法支持多行变量实现。这是通过在行尾添加|符号来完成的。
现在,敏感数据已经创建,我们来使用 Ansible Vault 加密命令加密我们的 vars 文件。这可以通过以下命令行输入完成:
#> ansible-vauult encrypt sensitive_data.yml
现在文件已经加密,我们可以创建我们的角色文件,命名为main.yml,并填写我们的角色信息。main.yml的内容应如下所示:
---
- include_vars: sensitive_data.yml
- name: Copy sensitive data file from Ansible control server to target hosts
  copy:
    content="{{secret_text}}"
    dest=/tmp/secret_text.txt
最后,我们来创建我们的playbook.yml文件。这些文件将非常简单,只会指向一个单一的角色(vaulttest)。让我们来看看这些文件的内容:
---
# File: playbook.yml
- hosts: all
roles:
 - { role: vaulttest }
现在我们已经创建了所有的文件,接下来让我们将代码commit到版本控制系统(如果适用)并进行测试。运行解决方案的命令如下:
#> ansible-playbook -i 'localhost,' -c local playbook.yml --ask-vault-pass
运行时,你应该看到以下输出:

摘要
在本章中,你了解了 Ansible Vault。你还学会了如何加密文件、解密文件、重新密钥加密文件、动态编辑文件,并在 playbook 中使用这些数据。Ansible Vault 为我们提供了一种非常简单的方式来加密和存储加密数据。正如我们通过示例看到的,Ansible Vault 架构中的文件加密和解密并不需要复杂或繁琐。我们在本章讨论的技巧在 IT 运维或 DevOps 导向的组织中有着广泛的应用。
在下一章中,我们将讨论 Ansible 的广泛模块和库。本章将帮助我们识别一些 Ansible 提供的流行模块和库,以便将其与其他工具集成。让我们继续前进。
第八章:Ansible 模块和库
Ansible 提供了与数百种开源和闭源软件解决方案的集成与兼容性。这种集成使 Ansible 能够在程序化 API 层面与多种构建、测试、项目管理、云计算和交付软件解决方案进行通信。因此,模块的实现为 Ansible 提供了巨大的竞争优势,超过了市场上其他自动化和配置管理解决方案。
到本书的这一部分,我们应该已经对一些常见的剧本实现和结构化方法有了比较全面的了解。为了进一步扩展我们的知识,本章将重点介绍 Ansible 核心实现提供的各种模块。Ansible 中的模块为剧本提供了与第三方技术(部分为开源,部分为闭源)连接和控制的能力。在本章中,我们将讨论最流行的模块,并深入研究如何创建剧本任务,以帮助管理开发人员、测试人员和运维人员可用的一系列工具和服务。
为了进一步学习,本章我们将探讨 Ansible 与其他软件解决方案的集成。通过本章的学习,我们将了解一些在 Ansible 范畴内更为流行的集成点。我们将发现 Ansible 的模块化架构如何工作,并了解它如何提供与其他技术集成的钩子。
本章我们将具体讨论以下主题:
- 
介绍 Ansible 模块 
- 
将 Ansible 与其他技术集成 
- 
理解 Ansible 文档中为各种技术提供的集成选项 
- 
将 Ansible 与其他技术集成的逐步示例 
让我们开始吧!
介绍 Ansible 模块
Ansible 模块代表了将 Ansible 与其他软件解决方案连接并自动化的首选方式。在撰写本书时,Ansible 已具备与大量外部软件和硬件解决方案集成的能力。我们已经发现的一些较为明显的集成包括以下内容:
- 
Linux(操作系统和包) 
- 
文件系统管理 
- 
包管理 
到目前为止,我们讨论的这些基本模块显而易见,适用于入门级剧本的创建和管理,但你知道 Ansible 与 JIRA、Slack、Git、Artifactory、Jenkins 等工具集成吗?Ansible 模块的广泛集成已在 Ansible 文档中按以下类别进行划分:
- 
云模块 
- 
集群模块 
- 
命令模块 
- 
加密模块 
- 
数据库模块 
- 
文件模块 
- 
身份模块 
- 
库存模块 
- 
消息模块 
- 
监控模块 
- 
网络工具模块 
- 
网络模块 
- 
通知模块 
- 
包管理模块 
- 
远程管理模块 
- 
源代码管理模块 
- 
存储模块 
- 
系统模块 
- 
实用工具模块 
- 
Web 基础设施模块 
- 
Windows 模块 
如前所述,Ansible 提供了与其他技术的广泛集成。每个与其他技术的集成点都通过 Ansible playbook 任务进行利用。每个集成模块的完整文档可以在 docs.ansible.com/ansible/modules_by_category.html 找到。
Ansible 将模块实现分为几类:Core(核心)、Community(社区)、Curated(精心策划)模块集和 Custom(自定义)模块及插件。每一类的实现方式非常相似,但 Ansible 会根据不同类别做不同的组织安排。下面提供的图示展示了 Ansible 模块实现如何融入到 Ansible 架构中:

在本书的前几章中,我们一直在使用 Ansible 模块进行基本的系统级配置、包管理、文件操作等。这些实现教会了我们如何使用核心的 Ansible 模块集。
Ansible 实现有三种不同的模块类型。这些模块类型分别是 Core(核心)、Curated(精心策划)、Community(社区)和 Custom(自定义)。每种模块都有其在 Ansible 解决方案中的特定功能和作用。让我们花点时间来看一下 Ansible 文档中对这些不同模块类型的描述:
- 
Core:这些模块由 Ansible 核心团队维护,并且将始终与 Ansible 一起发布。它们还将在所有请求中获得稍高的优先级。非核心模块依然可以完全使用。 
- 
Curated:一些 Curated 模块的示例由其他公司提交或由社区维护。这些模块的维护者必须关注任何报告的问题或针对模块提出的拉取请求。 
Core Committers(核心提交者)将审查所有即将成为 Curated(精心策划)模块的内容。核心提交者将审查在模块的社区维护者批准变更后,针对现有 Curated 模块提出的更改。核心提交者还将确保由于 Ansible 引擎的变化而产生的任何问题能够得到修复。此外,强烈建议(但目前不要求)这些类型的模块拥有单元测试。
这些模块目前与 Ansible 一起发布,但未来可能会单独发布。
- Community:这些模块不由 Core Committers(核心提交者)或与模块相关的公司/合作伙伴支持。它们由社区维护。
它们仍然可以完全使用,但对问题的响应速度完全取决于社区。将提供尽最大努力支持,但不在任何支持合同范围内。
这些模块目前与 Ansible 一起发布,但未来很可能会单独发布。
在 Ansible 的世界里,Ansible 模块也被称为 任务插件 或 库插件,这描述了 Ansible 如何处理模块实现的方式。每个模块(无论是核心模块还是其他模块)都是通过 Ansible 的可插拔架构来实现的。
在 Ansible 的上下文中,模块提供了类似于编程库的功能。这些库可以通过 playbook 任务 或 直接的单行命令操作来调用。让我们快速了解一下这两种实现方式。
让我们从命令行调用一个 Ansible 模块:
ansible webservers -m service -a "name=httpd state=started"
ansible webservers -m ping
ansible webservers -m command -a "/sbin/reboot -t now"
让我们从 playbook 任务中调用一个 Ansible 模块:
- name: reboot the servers
 action: command /sbin/reboot -t now
从前面的示例中,我们可以看到两种不同的 Ansible 模块实现。现在,我们已经了解了 Ansible 模块如何执行(用于系统调用),接下来我们将看看 Ansible 模块如何为其他技术提供连接解决方案。
将 Ansible 与其他技术集成
Ansible 与其他技术的集成非常顺畅。模块系统的实现为我们提供了一组独特的挂钩,我们可以利用这些挂钩将 Ansible(在 API 层面)与流行的软件解决方案连接起来。在本节中,我们将更详细地了解如何实际使用 Ansible 与一些流行的技术解决方案进行连接,这些解决方案是面向 DevOps 思维的人员所常用的。
Ansible 与 JIRA
Ansible 为我们提供了一组方便的内置任务,可以用来 创建 工单。对于不熟悉 JIRA 的朋友们,JIRA 是由 Atlassian 创建并维护的一个广泛使用的工单系统 (www.atlassian.com)。在本节中,我们将探讨如何使用 Ansible 创建和操作 JIRA 工单。一般的实现使用了 Ansible 提供的 JIRA 模块。以下是通过 Ansible 任务创建工单的示例:
# Create a new JIRA ticket and add a comment to it:
- name: Create an issue
  jira:
  uri: "http://pathtojira"
  username: '{{ user }}'
  password: '{{ pass }}'
  project: ANSIBLE
  operation: create
  summary: Hello Ansible Jira Integration
  description: Created using Ansible JIRA module
  issuetype: Task
  register: issue
请注意,前面的实现使用 Jinja 为模块提供 JIRA 用户名和密码。最好的做法可能是使用 Ansible vault(我们在前一章中刚刚介绍过)。
除了简单的工单创建外,Ansible 还可以用来修改现有的工单。接下来让我们看看一个这样的示例:
# Transition an issue in JIRA by changing its status to done
- name: Mark the issue as resolved
 jira:
 uri: "http://pathtojira"
 username: '{{ user }}'
 password: '{{ pass }}'
 issue: '{{ issue.meta.key }}'
 operation: transition
 status: Resolved
有关如何使用 Ansible 操作 Jira 工单的完整文档,请访问 docs.ansible.com/ansible/jira_module.html#synopsis.
Ansible 与 Git
Ansible 提供了一个完整的模块来操作 Git 仓库。对于那些不熟悉 Git 的人来说,Git 是一种现代的分布式源代码控制解决方案,当前被大量软件公司使用。与传统的服务器/客户端导向的源代码控制解决方案不同,Git 提供了一种巧妙的分布式解决方案,它不需要中央服务器。Ansible 与 Git 的集成相当强大,提供了一整套集成任务。Ansible Git 模块的完整文档可以在 docs.ansible.com/ansible/git_module.html 查阅。
让我们看一下几个示例的 playbook 任务,看看如何将 Ansible 和 Git 集成。
Ansible 的 Git 集成的一个前提是目标机器应安装并正常运行 Git v1.7 或更高版本的命令行客户端。
这是一个示例 Git checkout 任务:
# An Ansible task example which checks out source code
- git:
 repo: 'https://mygitserver.com/the/path/to/repo.git'
 dest: /opt/sourcecode
 version: release-0.23
这是一个示例,我们将从 GitHub 仓库创建一个 ZIP 文件:
# Example of how to create a ZIP from a GitHub repository
- git:
  repo: https://github.com/foo/implementingdevops-examples.git
  dest: /src/implementingdevops-examples
  archive: /tmp/examples.zip
Ansible 和 Jenkins
Jenkins CI(由 CloudBees 开源)是一个现代化的持续集成和持续交付解决方案。它被各种规模的组织广泛使用,支持简单的自动化以及复杂的构建和交付流水线实现。它具有巨大的
Ansible 以几种不同的方式与 Jenkins 集成。对于本章的目的,我们将讨论的是模块特定的集成点。也就是说,我们将通过 Ansible 自动化控制 Jenkins 本身。在本节中,您将了解如何通过 Ansible 控制和管理 Jenkins 实例的一些有趣方法。Ansible Jenkins 模块的完整文档可以在 docs.ansible.com/ansible/jenkins_job_module.html 查阅。
Ansible 通过 Jenkins REST API 与 Jenkins 通信。因此,重要的是 Jenkins 实例必须提供 REST API,并且可以从将要连接到 REST API 的目标服务器访问。除了 REST API 可用性外,Ansible Jenkins 模块还需要安装以下 Python 软件包:
- 
Python-Jenkins 0.4.12 或更高版本 
- 
lxml 3.3.3 或更高版本 
这些软件包是必需的,因为它们提供了从 Python 直接连接 Jenkins 的 API。这是 Ansible 无法直接连接的,因此它们是使用该特定模块所必需的。
现在我们已经处理了先决条件,让我们看一下 Ansible 任务的一些示例:
# Jenkins REST API to create a jenkins job 
- jenkins_job:
 config: "{{ lookup('file', 'templates/test.xml') }}"
 name: test
 password: admin
 url: http://localhost:8080
 user: admin
除了基于模板创建作业外,我们还可以删除作业。以下示例演示了如何做到这一点:
# Delete a jenkins job using the Ansible Jenkins module
- jenkins_job:
    name: foo
    password: my_admin
    state: absent
    url: http://pathtojenkinsurl:8080
    user: admin
有关完整的 Jenkins REST API 文档,请访问以下网址:
www.cloudbees.com/blog/taming-jenkins-json-api-depth-and-tree
Ansible 和 Docker
Docker 在过去几年里已成为虚拟化领域的强大工具。Docker 具有独特的内核、文件系统和内存管理解决方案,使得 Docker 成为许多组织虚拟化的理想选择。Docker 基于容器化虚拟化的概念运行,这些容器是轻量级的虚拟化操作系统。在本节中,我们将了解 Ansible 如何与 Docker 集成。我们将发现如何使用 Ansible 2 控制 Docker 容器。完整的 Ansible Docker 模块参考资料可以在docs.ansible.com/ansible/docker_module.html找到。
为了使用 Docker 的 Ansible 模块,目标系统(运行 playbook 的系统)必须安装以下 Python 包:
- 
Python 2.6 或更高版本 
- 
Docker-py 0.3.0 或更高版本 
- 
Docker 服务器 0.10.0 或更高版本 
Ansible 提供了许多面向 docker 的模块,用于实现 Ansible 与 Docker 的互联互通。每个模块的完整列表和描述如下所示(docs.ansible.com/ansible/guide_docker.html):
- 
docker_service:使用现有的 Docker Compose 文件来编排单个 Docker 守护进程或 Swarm 中的容器。支持 Compose 版本 1 和 2
- 
docker_container:通过提供创建、更新、停止、启动和销毁容器的功能来管理容器生命周期
- 
docker_image:提供对镜像的完全控制,包括build、pull、push、tag和remove
- 
docker_image_facts:检查 Docker 主机镜像缓存中的一个或多个镜像,并将信息作为事实提供,以便在 playbook 中做出决策或断言
- 
docker_login:与 Docker Hub 或任何 Docker 注册表进行身份验证,并更新 Docker 引擎配置文件,从而提供无需密码即可将镜像推送到注册表或从注册表拉取镜像
- 
docker (动态清单):动态构建来自一个或多个 Docker 主机的所有可用容器的清单
需要注意的是,docker_container任务名称在 Ansible 2.2 中已被弃用。取而代之的是,Ansible 建议使用docker_container和docker_image。
让我们看一些简单的 playbook 任务示例,这些示例将 Ansible 与 Docker 进行集成。
这是我们如何使用 Ansible 构建 Docker 容器的方式:
# How to build a docker image using Ansible
- name: Build a docker image
  docker_image: >
  name=docker-image-created-by-ansible
  tag=ansibleexample1
  path=/tmp/site
  state=present
这是你如何使用命令行构建 Docker 容器的方式:
- name: How to build an image with the command line
  command: docker build -t build-container-using-ansible-command:ex2 /tmp/site
这是一个完整的示例:
---
- name: Build a docker container using the command line
  hosts: all
tasks:
 - name: build a docker container 
   command: docker build -t build-a-docker-container:ex2b ./site
- name: run a site within a docker container
  docker:
   name: mysite
   image: "build-a-docker-container:ex2b"
   state: reloaded
   publish_all_ports: yes
   use_tls: encrypt
总结
在这一章中,我们探讨了 Ansible 模块架构。我们讨论了 Ansible 如何按类别(核心模块和用户模块)组织模块。在你了解了 Ansible 如何管理模块之后,我们还讨论了 Ansible 模块可以通过两种不同的方式调用(命令行和 playbook 任务)。接着,我们继续查看了一些与 Ansible 集成的流行模块的示例。
在下一章,我们将继续讨论如何将 Ansible 与流行的 CICD 解决方案(如 Jenkins、Git 等)集成。我们将探索如何使用 Ansible 创建流水线,并发现一些技巧,帮助我们在组织内部鼓励重用 Ansible playbook。让我们开始吧,怎么样?
第九章:将 Ansible 与 CI 和 CD 解决方案集成
Ansible、DevOps、敏捷、持续集成(CI)和 持续交付(CD)的实现是密不可分的。每个阶段都顺利过渡到下一个。通过我们在上一章学习到的 Ansible 的全面模块实现,我们看到 Ansible 如何与许多行业标准的敏捷、持续集成和持续交付解决方案良好集成。一些 Ansible 集成的常见 CI->CD 解决方案包括 Jenkins、Artifactory、Maven、Bamboo 等。Ansible 与这些解决方案的集成非常紧密,实际上,Ansible 甚至提供了完整的 CI->CD 集成指南,网址为 www.ansible.com/continuous-delivery。
对于那些不熟悉持续集成和持续交付的人来说,这些解决方案已经在软件开发界掀起了巨大的变革。它们提出了一种现代化的标准化方式,通过软件自动化进行代码提交、构建、交付和测试。通过自动化和流程将代码推向生产的过程被称为流水线。实施 CI->CD 流水线的结果是一种高效的自动化软件项目发布方式,通过创建可重复的流程,为团队成员提供关于质量的自动反馈。
CI->CD 最佳实践旨在提供一种高度可靠的方式,以确保软件项目随时都可以发布。通过结合高度规范化的开发实践和自动化构建、交付与测试工具链来实现这一目标。通过将一些基本实践与自动化结合起来,组织可以在开发和交付代码方面变得高效。
本章中,我们将开始探讨持续集成和持续交付。我们将深入分析与每个概念相关的基本结构,并提供如何将 Ansible 与现有的流行 CI->CD 解决方案集成的详细信息。具体而言,本章将涵盖以下主题:
- 
持续集成概述 
- 
持续交付概述 
- 
Ansible 在面向 CI->CD 的组织中的作用 
- 
将 Ansible 与 Jenkins 集成 
- 
将 Ansible 与 Vagrant 集成 
让我们开始探索这个关键的集成部分。
持续集成概述
持续集成(CI)已经存在了一段时间。它的起源可以追溯到 Kent Beck、Martin Fowler 及其在九十年代中期的 Chrysler 公司工作。基本的想法是,组织通过频繁地将小规模代码合并到源代码管理的中央主线,而不是在发布前进行一次大规模且风险较大的合并,可以节省大量的时间和精力。
这种思维方式要求团队具备较好的自律性,并要求每个团队成员频繁提交代码。它不鼓励长时间独立开发特性,并鼓励更高水平的协作和沟通。这样实现的结果是,由于更少的复杂合并冲突和代码集成问题,发布的质量显著提高。
持续集成的概念已经成为多年来的热门话题。这是因为它需要更高水平的沟通,以确保其成为一种成功的实践。除了基本的提交阶段,CI 还包括自动化验证系统和通知反馈回路,以便利益相关者能够在提交和合并出现缺陷时收到通知。该通知系统提供了关于提交质量的即时反馈。
软件开发专业人员对于这种解决方案的有效性已经讨论了相当长的时间,并将在未来继续讨论。通常,CI 的实践可以通过以下图示进行描述:

从这个图示中我们可以看到,持续集成贯穿了软件项目的整个开发生命周期。具体包括以下阶段:
- 
代码开发协作 
- 
代码提交与合并 
- 
基于自动化的构建/开发环境的配置 
- 
基于自动化的构建 
- 
完成构建的基于自动化的测试(单元测试、代码覆盖率、代码规范和样式测试) 
- 
基于自动化的构建结果打包(可执行文件和交付物) 
持续集成的概念和实践
如我们之前提到的,持续集成的概念并不新鲜。实施持续集成的开发团队的普遍做法需要团队成员在思维方式上发生变化,团队之间应用一定程度的工程协作,并遵循一系列基本的实践。这些实践在此列表中概述:
- 
主干开发(没有基于版本控制的分支或高度频繁的分支合并)。 
- 
一个基于 CI 的自动化系统: - 
自动将代码检出到系统中 
- 
验证源代码的可编译性 
- 
通知用户任何失败 
 
- 
CI 的分支概念在一段时间内大致如下图所示:

从这个示例中我们可以看到,在持续集成环境中,开发人员需要频繁地从中央主干推送和拉取代码。每次推送都会通过自动化构建和测试系统进行验证。任何失败都会报告给更大的团队和利益相关者。
如果系统报告出现失败,所有的提交和推送应停止,直到错误被修复。这是因为系统处于非正常工作状态,必须修复该失败状态,以防止错误的累积。
虽然 CI 一般鼓励主干开发,但通过分支也可以实现持续集成。CI 的主要要求是开发者要与主干保持同步。如果采用分支系统,必须严格遵守纪律,确保分支开发不会长期孤立进行。
实现 CI 解决方案的结果是构建流水线中的提交阶段元素。现在我们已经理解了 CI 的概念以及它的工作方式,接下来让我们看看持续交付(Continuous Delivery)。
处理未完成的工作
在持续集成(CI)或持续交付(CD)开发环境中,常常存在一种误解,认为开发者需要通过源代码管理分支来隔离未完成的工作。这种基于分支的开发与持续集成模式相对立。因此,存在一些结构和开发实践,可以实施这些方式,让 CI 和 CD 在不需要在源代码管理中创建额外分支的情况下继续进行。让我们花几分钟看看,哪些选项可以帮助开发者在未完成工作的情况下仍然满足持续集成要求。
通过抽象进行分支
通过抽象进行分支为开发者提供了一种可靠的方式,可以在不需要创建新分支的情况下继续处理未完成的实现。通过抽象分支架构的思路很简单:
- 
对软件系统的架构进行模块化。 
- 
通过创建一个新的类或文件夹结构,将过时的模块替换,只需将其与旧模块并排放置。 
- 
替换模块的调用。 
之前文档中提到的基本实现也可以通过以下图示进行总结:

从架构中可以看出,新的组件在完成并被认为是可用后,直接替换旧组件。这允许组件的源代码管理级别进行提交和推送,而无需新建分支。
特性切换
特性切换是提供全面 CI 实现的另一种方式。特性切换以开关的形式存在。通过配置更改、UI 实现开关或其他可配置对象,可以开启或关闭这些开关。
软件编程语言本身提供了特性切换的能力。特性切换最简单的例子就是一个简单的 if/else 条件,如下所示:
# Simple Feature toggle
if x in y:
 # Do something
除了程序化实现外,特性切换的基本操作流程大概是如下图所示:

特性切换使我们能够将代码提交到主干,甚至在不需要创建分支的情况下将代码推送到生产环境。
A/B 测试模式
A/B 测试是软件领域中相对较新的一个概念。对于那些不熟悉它的人来说,用户群体会看到两个不同的数据实现。根据用户群体对哪种实现的喜好,最受欢迎的功能将推广到更广泛的用户,并成为永久功能。我们来看看一个简单的 A/B 测试示意图:

正如我们在前面的图示中所看到的,A/B 测试允许我们仅将一定比例的用户暴露于某个功能,从而使用这个控制组来测试该功能是否有用。这种实现方式有助于减少过度设计一个功能的风险,避免最终发现它实际上并没有价值。
一般而言,A/B 测试的目标是为组织节省时间和金钱。这些节省为业务提供了更好的敏捷性和更高的竞争力,帮助其变得更加竞争或保持竞争力。尽管示意图中显示了 50% 的用户展示 B 变体,但这不一定要完全是 50%。
举个例子,如果我们进行 A/B 测试时,我们可能只向 1% 的用户展示一个功能,其他 99% 的用户仍然使用稳定版本。然后,如果 1% 的用户普遍认可该功能,我们可能会扩大范围。
这个时候,你可能会问 Ansible 如何与 A/B 测试结合使用。这是一个非常好的问题。Ansible 为我们提供了一种通过我们的 playbook 定向目标主机的方式。因此,1% 的用户可能仅被分配到单个主机的部署。然而,在大多数情况下,你不希望仅将升级部署到单个主机。相反,你可能会考虑使用蓝绿部署之类的方式。
持续交付概述
持续交付由 Jez Humble 于 2012 年在他那本具有革命性的《持续交付》书中提出。Humble 在写这本书时的想法是将持续集成(CI)的概念扩展,以支持软件团队在发布之前进行交付和自动化测试的机制。这个概念彻底改变了软件组织在向客户发布软件解决方案时的方式,并旨在保持软件在任何时候都可以发布。
在以往的软件开发中,拥有并维护一个构建系统被认为是最佳实践。然而,一旦构建完成并且单元测试全部通过,仍然有许多手动流程需要维护,以确保软件解决方案实际上是可以发布的。
一些更受欢迎的构建后任务包括以下内容:
- 
安装验证 
- 
质量保证测试 
- 
部署环境配置 
- 
部署 
- 
部署后验证 
随着软件公司失败的案例越来越多,负责软件项目管理的人们意识到,手动执行这些任务可能会变得容易出错且耗时。这些任务的复杂性也随着他们构建的软件系统的规模增大和用户群体的扩展而增加。Jez 提出的解决方案是一个可重复且高度自动化的持续集成进阶。他将这一实现称为持续交付(Continuous Delivery,简称 CD)。
持续交付定义
如前所述,持续交付旨在扩展持续集成的实现。通过这种方式,软件系统始终保持在可发布的状态。这是通过结合主干开发实践、持续集成自动化、一套自动化的预生产环境配置和部署解决方案以及自动化测试来实现的。
在一个面向持续交付的组织中,部署流水线的创建有助于实施前述的解决方案。接下来展示的是一个高级别的部署流水线示例:

这里展示的部署流水线本质上非常简单。它仅展示了一个开发者的提交从开发到生产和发布的过程。在下图中,我们可以看到从稍微低一点的视角来看,部署流水线是怎样的:

从展示的图表中,我们可以看到在构建流水线中有更多可见的组件。这些组件包括:
- 
一个工件仓库 
- 
版本控制 
- 
一键部署 
- 
配置管理 
- 
冒烟测试 
- 
功能测试 
- 
容量测试 
- 
生产阶段 
持续交付的这些额外组件非常重要。让我们逐一回顾每个组件,看看它们的作用或目标:
- 
版本控制:版本控制旨在为开发者提供一个中心位置,以便与更大的团队沟通代码变更。一些现代版本控制系统的例子包括 Git、Mercurial、SVN 和 Perforce。版本控制系统不仅方便沟通,还允许恢复错误的代码。 
- 
工件仓库:为您的组织添加一个结构化的数字媒体库(DML)是实现持续交付的重要一步。这代表了一个明确的位置,在这个位置,构建系统的输出可以被版本控制并在发布临近时保留。它还允许同一内容的多个版本。 
- 
一键部署:一键部署是指可以通过按一个按钮自动部署的解决方案。 
解决方案可以通过以下方式部署:
- 
自动化从工件仓库拉取二进制文件。 
- 
自动化通过配置管理解决方案推送工件。 
- 
工件被解压,并执行其中的自动化过程以完成部署。 
让我们详细看一下其他组件:
- 
配置管理:在这一步,使用配置管理解决方案(例如 Ansible)将软件安装部署/配置到目标环境机器上。 
- 
冒烟测试:冒烟测试是高层次的功能测试,用于判断软件是否值得进一步测试。 
- 
功能测试:功能测试是验证测试(自动化测试),用于验证软件是否符合业务功能需求。功能测试解决方案中的每个测试套件应尽可能并行执行,以确保在执行这些测试时不会出现性能瓶颈。 
- 
容量测试/压力测试:此类测试帮助验证软件在正常用户流量负载下的操作和性能表现。通常,这类测试会被忽视,导致软件在高负载下崩溃,从而无法扩展。 
处理复杂且长时间运行的构建和部署
有时,构建或部署过程可能非常耗时(涉及多个不同组件或复杂的环境设置步骤)。遇到这种情况时,面向持续交付的组织同样可以应对。这最好通过将软件项目的架构模块化,拆分成独立可部署的实体来处理。一旦软件完成模块化,部署和自动化测试工具可以被拆解成多个以组件为导向的交付流水线。下图展示了一个多组件交付流水线的示例:

从图中可以看出,可以创建多个流水线,以简化部署过程。每个流水线都有自己的构建、打包、单元测试和相关工具。通过这些多个流水线,我们可以迅速且可靠地将各个组件版本部署到不同的环境中。
现在我们已经很好地掌握了基本流水线的架构,接下来让我们看看在通知和反馈方面,同类型的流水线会是什么样子。
CI->CD 反馈循环
CI 反馈循环是 CI->CD 的一个主要卖点。基本的理念是,用户和利益相关者可以几乎即时地获取 CD 反馈循环,从而了解提交代码的质量。这使得开发人员能够迅速解决自动化识别的问题,并有助于提高整个系统的质量。基本的反馈循环大致如下所示:

如您所见,通知循环会在流水线的每个阶段向利益相关者发送通知(电子邮件、即时消息、Slack、Hipchat 等)。
蓝绿部署
蓝绿部署代表了系统运维和工程团队的明智创新。蓝绿部署的基本概念,在许多方面类似于我们之前讨论的“抽象分支”概念。
蓝绿部署提供了一个理念,即部署一个并行实例的组件或应用程序,同时保持现有实例运行并服务于实时流量。当部署被认为成功时,流量将从旧版本切换到新版本。下面是一个简单的图示,说明了蓝绿部署:

如我们所见,新连接会转向绿色实例,而旧连接则保持与蓝色实例的持续连接。
CI->CD 反模式
既然讨论了 CD 反模式、抽象分支和功能开关,现在可能是时候查看一些组织常用的反模式,它们代表了 CI->CD 最佳实践的对立面:
- 
功能分支:这是因为持续集成的核心理念之一是将代码与主干合并。这也是缺陷最容易出现的地方。 
- 
让构建处于失败状态:将构建保持在已知的失败状态是持续集成中常见的反模式。这是一个反模式,因为它实际上给其他开发者留下了一个“地雷”。 
- 
长时间将代码保存在开发者工作站本地:在本地系统上编码软件更改而不与主干合并,本质上是隐藏更改。此模式带来的风险在于需要进行大规模合并。这样的合并通常发生在发布前,这会使发布质量面临风险。 
Ansible 在 CI->CD 中的角色
Ansible 适用于 CI->CD 实现中的多个领域。它可用于构建环境配置、本地工作站环境配置、部署服务器上的配置管理、管理物理部署等。
在《使用 Ansible 实现 DevOps》这一部分中,我们将看看 Ansible 在 CI->CD 流水线实现中的角色,以及与每个实现位置相关的一些最佳实践。在我们开始关注具体领域之前,先来识别一下 CI->CD 流水线中的常见步骤。
最初,一个交付流水线会很简单;它可能只包含一组非常基础的步骤。这些步骤可能包括以下内容:
- 
当更改被提交时,检查源代码控制。 
- 
执行构建或语法检查。 
- 
执行一些单元测试。 
- 
报告提交的质量。 
这些步骤在下图中有所展示:

根据初步描述的 CI 过程,我们可以考虑在以下 CI 步骤中使用 Ansible:
- 
帮助开发者使用 Ansible playbook 配置他们的开发环境 
- 
通过 Ansible playbooks 自动配置构建机并确保构建机的配置得到维护 
- 
作为执行构建和单元测试的自动化绑定 
正如我们从这些步骤中看到的那样,我们可以利用 Ansible 的方式远不止于执行配置管理任务和部署。
现在我们已经定义了持续集成,让我们看看 Ansible 如何在 CI 和 CD 的扩展中使用。看看以下的持续交付图示:

基于这个图示和流程图,我们可以看到 CI->CD 中有许多地方可以利用 Ansible。让我们来看看这些地方:
- 
在提供测试环境(冒烟测试、功能测试和单元测试)时。 
- 
在提供部署环境(DEV、QA、STG、PROD)时。 
- 
在执行部署时。 
- 
在应用程序部署后启动应用。 
- 
在出现故障时回滚环境。 
- 
在执行应用程序的分阶段/增量发布到生产环境时。 
Ansible 在 CI->CD 中的最佳实践
Ansible 可在持续集成和持续交付的环境中用于多种任务。在采用 Ansible 时,建议从小规模开始,并逐步扩展它,以承担越来越多的自动化执行任务。
在本节中,我们将花几分钟时间探索在 CI->CD 中与 Ansible 配合使用的一些最佳实践:
- 
始终将你的 Ansible playbooks 存储在源代码管理中。 
- 
将你的 Ansible playbooks 与构件一起交付(版本化所有内容!)。 
- 
为每个环境(DEV、QA 等)维护独立的清单文件。 
- 
尽量使用相同的 playbooks 部署到 DEV 和生产环境中。 
- 
利用 Ansible 的配置管理实现来帮助保持你的基础设施同步。 
- 
尽量保持你的 playbook 简单。 
- 
使用角色帮助定义可重用的自动化。 
- 
在可能的情况下,使用 Ansible 进行构建和部署自动化操作。 
- 
保持你的环境同步(苹果 | 苹果 | 苹果,开发 | QA | 生产)。 
集成 Ansible 与 Jenkins
在本节中,我们将讨论如何将 Ansible 与 Jenkins 集成。Jenkins 是由开源社区创建和发布的现代 CI 和自动化编排解决方案。Jenkins 起初是 Hudson,后来以新品牌名转型并发展成一个全面的免费开源构建和交付管道编排解决方案。你可以在 jenkins.io/ 下载 Jenkins。
将 Ansible 与 Jenkins 集成通常是一个直接的任务。为了实现这一目标,有几个众所周知的集成点可以利用 Ansible。这些包括 Jenkins Ansible 插件、直接在 Jenkins CI 服务器上安装 Ansible 并通过执行 shell 操作调用它,以及使用 Ansible 模块来控制 Jenkins。让我们花几分钟讨论每个选项,看看它们是如何工作的。
Jenkins Ansible 插件
Jenkins Ansible 插件提供了 Jenkins 与 Ansible 之间直接通信并执行 playbook 的能力。这个选项可能是 Jenkins 与 Ansible 之间最直接的集成方式。使用此解决方案时,Ansible 并不一定需要在远程服务器上运行 playbooks(但它确实可以)。在这个方案中,我们可以直接从 Jenkins 服务器上运行 playbooks,既可以在本地运行,也可以将其应用于目标基础设施。
为了方便通过 Jenkins 执行 playbooks,我们首先需要通过 Jenkins 插件管理器安装 Ansible 插件。接下来我们来看看如何操作。
本教程假设你已经安装并运行了一个 Jenkins 实例。
我们要做的第一件事是启动 Jenkins。初次加载 Jenkins 时,我们将看到类似如下的界面:

要安装 Ansible 插件,只需导航到插件管理器(作为 Jenkins 管理员)并从“可用插件”选项卡中选择 Ansible 插件,然后安装该插件。以下截图展示了这个过程:

搜索 Ansible 插件并选择它。现在,通过点击“安装而不重启”来安装插件:

接下来,我们需要进入希望通过 Ansible 利用的作业的配置页面,并启用该作业使用 Ansible。配置可能类似于以下截图所示:

从这张截图中,我们可以看到 Ansible 插件在 Jenkins 中可用的一些选项。完整文档可以在 wiki.jenkins-ci.org/display/JENKINS/Ansible+Plugin 查阅。
插件和作业配置完成后,运行 Jenkins 作业,查看它如何连接到 Ansible,并利用 Ansible 作为作业的自动化引擎。使用 Ansible 插件执行 Jenkins 作业时,输出结果可能类似于以下截图所示:

现在我们已经大致了解了如何利用 Jenkins 执行 Ansible playbook,接下来让我们看看如何通过 API 让 Ansible 控制 Jenkins。
在这种场景中,Ansible 的 playbooks 最好存储在源代码管理(SCM)中,并在 Jenkins 作业的 SCM 阶段进行检出。
Jenkins Ansible API 模块
Jenkins Ansible 模块提供了 Jenkins 与 Ansible 之间的直接 API 集成。通过这个解决方案,Ansible 可以通过其 REST API 管理和控制 Jenkins。Jenkins REST API 功能强大,能够创建作业、执行作业、管理用户等。在本节中,我们将看看 Ansible 模块提供的一些功能示例。
Ansible 与 Jenkins 的集成分为三个独立分类的模块。这些模块(如前所述)通过 API 与 Jenkins 通信,并提供对 Jenkins 解决方案的控制。这三个具体模块如下:
- 
jenkins_job:管理 Jenkins 作业
- 
jenkins_plugin:添加或移除 Jenkins 插件
- 
jenkins_script:在 Jenkins 实例中执行 Groovy 脚本
让我们从 jenkins_job Ansible 模块开始。
jenkins_job Ansible 模块
jenkins_job Ansible 模块提供了 Jenkins 作业与 Ansible 之间的互联互通。通过此模块,Ansible 可以创建 Jenkins 作业、执行它们、管理它们、删除它们等等。为了使用此模块,我们需要在 Ansible 控制服务器上安装以下软件包库:
- 
python-jenkins>= 0.4.12
- 
lxml>= 3.3.3
这些库可以通过 pip 或类似 apt-get 或 yum 的包管理系统安装。
在确保模块已安装后,我们可以开始使用 Ansible 模块的功能。让我们通过一些示例 playbook 来创建和控制 Jenkins 作业,使用 REST API 进行操作。此外,我们还将查看该模块支持的功能文档:
# Create a Jenkins Job
- jenkins_job: 
    config: "{{ lookup('file', 'templates/example.xml') }}"
    name: HelloJenkins
    password: admin
    url: "http://localhost:8080"
    user: admin
# Delete a jenkins job using the Ansible Jenkins_Job Module
 - jenkins_job:
    name: AnsibleExample
    password: admin
    state: absent
    url: http://localhost:8080
    user: admin
# Disable a Jenkins job using the Ansible Jenkins_Job module
- jenkins_job:
    name: AnsibleExample
    password: admin
    enabled: False
    url: http://localhost:8080
    user: admin
要创建 example.xml 模板,你需要使用 Jenkins UI 向导创建一个新模板。可以通过作业模板插件完成此操作。有关此插件的更多信息,请访问以下网址:www.cloudbees.com/products/cloudbees-jenkins-platform/enterprise-edition/features/templates-plugin
将 Ansible 与 Vagrant 集成
在本节中,我们将讨论如何将 Ansible 与 Vagrant 集成。Vagrant 是一种自由提供的基础设施虚拟化解决方案,当前被众多组织使用。它由 HashiCorp 提供免费支持。有关 Vagrant 的完整文档,请访问:www.vagrantup.com/intro/index.html
首先,我们假设你已经启动并运行了 Vagrant。如果没有,请参考 HashiCorp 的 Vagrant Up 网站上的说明:www.vagrantup.com/docs/cli/up.html 来完成初始设置。一旦完成 Vagrant 的初始设置,我们就可以开始了解如何在 Vagrant 中利用 Ansible。
利用 Ansible 进行 Vagrant 配置
Ansible 的 playbook 实现可以通过 Ansible 提供者来配置 Vagrant 虚拟机。Vagrant 中的提供者允许 Vagrant 用户指定一个配置管理解决方案,该解决方案将被用来自动化指定虚拟机的搭建。这些信息包含在一个 Ruby Vagrantfile 中。这里提供了一个简单 Vagrantfile 的例子:
# This is an example Vagrantfile which can be used with 
# Vagrant 1.7 and greater to provision an Ubuntu Box 
# using Ansible
Vagrant.require_version ">= 1.7.0"
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
 config.vm.provision "ansible" do |ansible|
 ansible.verbose = "v"
 ansible.playbook = "playbook.yml"
 end
end
从这个例子中,我们可以看到我们使用 Ansible 来配置我们的 Vagrant 环境。这将导致 Vagrant 执行 Ansible playbook。一旦 Vagrantfile 被更新,我们可以使用vagrant up命令来运行它。
摘要
在本章中,我们讨论了持续集成、持续交付和 Ansible。我们还讨论了 CI->CD 的组织需求,以及 CI->CD 如何使软件交付更加高效。你了解了一些使 CI->CD 实现有效的模式。
在深入讨论 CI->CD 并讨论相关模式后,我们谈到了 Ansible 在 CI->CD 组织中的作用。我们发现了 Ansible 等工具如何帮助连接不同环节,提升效率。通过标准化和利用像 Ansible 这样的现代工具,使软件开发组织更加高效,我们能够节省组织的时间和金钱。
在下一章中,我们将探索如何将 Ansible 与 Docker 配合使用。本章将教你如何使用 Ansible 来配置 Docker 容器,如何将 Ansible 与 Docker 服务集成,如何管理 Docker 镜像信息,以及如何完全控制 Docker 镜像。
第十章:Ansible 和 Docker
Ansible 在 DevOps 集成领域的应用不仅限于 CI 解决方案或配置管理部署实现。除了这些,它与云基础设施和虚拟化解决方案的集成被业内专家认为是无可比拟的。像 Docker 和 Vagrant 这样的虚拟化解决方案在云计算行业中掀起了风暴。因此,配置管理工具(包括 Ansible)与这些虚拟化解决方案的集成变得越来越强大。
本章将深入探讨 Docker 与 Ansible 之间可以构建的关系。我们将发现如何使用 Ansible 创建、维护和部署 Docker 镜像。我们将看看 Ansible 的 Docker 模块解决方案如何帮助自动化软件应用程序的交付。我们还将探索一些流行的集成 Ansible 与这一现代虚拟化解决方案的方式,并了解行业专家如何将这两个工具结合起来,打造水平可扩展且强大的基础设施交付解决方案。
完成本章后,我们将更好地理解如何将 Ansible 与 Docker 集成。我们将牢固掌握创建可扩展 Docker 环境所需的技术要求,您将学会如何更好地自动化持续集成和持续交付管道。
更具体来说,本章将涵盖以下主题:
- 
了解 Docker 的架构 
- 
使用 Ansible 管理 Docker 容器 
- 
使用 Ansible 创建 Docker 镜像 
- 
使用 Ansible 管理 Docker 镜像 
- 
使用 Ansible 收集 Docker 容器信息 
让我们开始吧!
了解 Docker 的架构
最初,将 Docker 和 Ansible 结合使用可能看起来与良好的配置管理策略相悖。然而,经过一些研究后,我们很快发现,这两种看似不同的技术结合起来时,实际上是非常强大和可扩展的。本章将重点介绍 Ansible 与 Docker 的集成。
Docker 无疑是虚拟化解决方案的领跑者。与市场上几乎所有其他虚拟化解决方案相比,它提供了巨大的优势。因此,Docker 在寻求为客户提供高质量、强大实施的组织中,受到了显著的关注和广泛应用。
在我们深入探讨集成点之前,先了解一下 Docker 的架构。这一点非常重要,因为它是 Docker 与竞争对手的最大区别。以下图表详细展示了 Docker 独特的架构:

正如我们从前面的图表中看到的,Docker 的架构为我们提供了一个共享内核,它位于主机操作系统之上。除了共享内核外,我们还有共享库和一组共享资源。
这一点很重要,因为在主机操作系统的情况下,Linux 的版本是无关紧要的。这种支持结构允许 Docker 在任何 Linux 版本之上运行,并从另一种 Linux 版本提供文件系统。例如,主机操作系统可以是 Ubuntu Linux,但容器可能有 Fedora 版本。
让我们看看这是如何通过下图实现的:

从上述图表中,我们可以看到各种 Linux 发行版如何通过 Docker 容器使用。在我们的示例中,我们有三种高度独特的 Linux 版本和不同 Linux 版本中的 Web 应用程序。很整洁,对吧?
将 Docker 容器视为环境的理解
Docker 的实现使其非常适合快速启动环境。在这里,环境表示应用主机、数据库层和 API,可以组合在一起提供软件解决方案的工作实例。对于较大的组织而言,这些环境可能是多个实例(开发、QA、预发布和生产)。
在下图中,我们可以看到使用 Docker 实现完整环境的架构:

在上述图表中,我们有多个容器提供多个环境。Ansible 的集成和编排解决方案可以帮助铺设和维护这些环境。在下一节中,我们将看到这是如何实现的。
使用 Ansible 管理 Docker 容器
Ansible 提供了一组独特的模块,允许 Ansible playbook 开发人员直接与 Docker 集成。Docker 模块为 Ansible playbook 开发人员提供了创建、启动、重启、修改和移除的能力。
在本节中,我们将看看可以应用于使用 Ansible 管理 Docker 容器的基本操作技术。具体来说,我们将涵盖:
- 
如何使用 Ansible 创建 Docker 容器 
- 
如何使用 Ansible 更新 Docker 容器 
- 
如何使用 Ansible 删除 Docker 容器 
- 
如何使用 Ansible 启动 Docker 容器 
最初,执行这些任务可能看起来有些令人畏惧。但一旦我们揭开这个解决方案的面纱,我们会发现在 playbook 中实现这些任务是相当简单的。让我们看看这些 playbook 模块如何帮助我们管理 Docker 容器。
创建 Docker 容器
通过 Ansible 创建 Docker 容器可能看起来是一个困难的任务;然而,实际上它非常简单。在这一节中,你将学习如何做到这一点。让我们开始吧:
- name: Create a data container
  docker_container:
    name: mydata
    image: busybox
    volumes:
      - /data
前面的 Ansible 任务使用busybox镜像创建了一个简单的数据容器,只包含一个简单的/data卷。虽然创建简单容器很容易,但显然我们需要更多内容来创建更有用的容器。让我们看看如何做到这一点。
移除 Docker 容器
删除 Docker 容器可以通过任务中提供的简单 Ansible 状态来实现。以下是如何从本地 Docker 注册表中删除 Docker 容器的示例:
- name: Remove MYSQL container
  docker_container:
    name: mysql
    state: absent
启动和停止 Docker 容器
使用 Ansible 启动 Docker 容器可以通过 Ansible Docker 任务来实现。在以下示例中,我们为 mysql 启动一个 Docker 容器。我们来看看:
# The following task launches a mysql docker container
- name: MySQL Database Container Launch
   docker:
   name: database
   image: mysql:1.0
   state: started
从前面的示例中,我们可以看到该任务启动了一个 MySQL Docker 容器(版本 1.0)。我们在这个任务中使用的主要参数是 image 和 name。这些参数定义了任务使用的镜像和标签,以及我们希望给正在管理的容器命名。
启动容器的关键是 started 状态。在这种情况下,状态提供了以下开关:
- 
absent
- 
present
- 
stopped
- 
started
所以,为了扩展这个内容,我们来看看同一个 Ansible 任务的示例,改为停止容器而不是启动它:
# The following task stops a mysql docker container
- name: MySQL Database Container Stop
   docker:
   name: database
   image: mysql:1.0
   state: stopped
对于经常更新容器的团队,可能需要在 Docker 容器启动操作中添加以下标志:
   pull: always
这些操作参数将迫使 Docker 每次执行时都拉取一个新的容器,并作为任务的一部分重新加载容器。
要了解 Ansible Docker 容器模块的完整文档,请访问 docs.ansible.com/ansible/docker_container_module.html。
除了使用 started 和 stopped 开关来启动和停止 Docker 容器,我们还可以启动一个容器并执行命令。我们来看看如何做到这一点:
- name: Starting a container and executing a command
 docker_container:
 name: sleepy
 image: ubuntu:14.04
 command: ["sleep", "infinity"]
管理 Docker 容器的网络访问
没有网络访问的孤立 Docker 容器有什么用?在本节中,我们将探讨如何将容器添加到网络中,反之亦然,如何从网络中移除容器。
为了管理 Docker 容器内的网络连接,Ansible 任务实现已经为主 Docker 容器任务提供了一组网络开关。我们来看看这些开关的实际应用示例,并了解如何在这种形式下利用 Ansible。
将容器添加到网络可以使用以下代码:
- name: Add container to CoprLAN and GuestLan networks
  docker_container:
  name: sleepy
  networks:
- name: CorpLan
  ipv4_address: 172.1.10.1
  links:
   - sleeper
   - name: GuestLan
  ipv4_address: 172.1.10.2
从网络中移除容器可以使用以下代码:
- name: Remove container from the CorpLan network
  docker_container:
  name: MySQL
  networks:
    - name: CorpLan
  purge_networks: yes
使用 Ansible 创建 Docker 镜像
Docker 提供了一种开箱即用的解决方案,通过 Docker 特定语言构建 Docker 镜像。Docker 文件用于提供启动指令,Docker 可以执行这些指令来构建镜像。在学习如何创建 Docker 文件后,可能会问:为什么我们要提倡结合使用 Dockerfile 和 Ansible 来创建 Docker 容器呢?答案很简单——幂等性。幂等操作是指一旦执行该操作后,可以重复执行而不会产生变化。这正是 Ansible 所做的。
一旦 Ansible 对给定系统进行了更改,如果该更改已经存在,它将自动跳过该更改。例如,如果一个 Ansible playbook 针对目标系统运行,并对该系统进行四项更改,它会自动跳过那些已经存在的更改,前提是 该更改已经存在或系统已经处于所需状态。
在使用 Ansible 创建 Docker 镜像方面,利用 Ansible 是一个不错的选择,因为其领域语言更易读,操作具有幂等性,而且更改可以同时应用于一个或一百个容器。这为该领域提供了很大的灵活性和可扩展性。在本节中,我们将探讨如何利用 Ansible 创建 Docker 镜像。让我们开始吧。
使用 Dockerfiles 启动 Ansible playbook
通过利用 Docker 文件在启动时调用 Ansible playbook,我们可以使 Docker 容器的实现相当健壮。这种实现方式有许多好处。最显著的有以下几点:
- 
如果已有基础设施正在使用 Ansible,那么保持自动化控制一致性是显而易见的选择 
- 
Ansible 的模块系统提供了与多种第三方工具和技术的集成 
- 
Ansible 实现了易于阅读的语法和幂等架构,为开发者入门 Ansible 提供了强大的功能。 
在本节中,我们将探讨如何最好地利用 Dockerfile 执行 Ansible playbook。让我们通过查看一个示例 Dockerfile 开始:
# This DOCKERFILE creates a docker image with Ubuntu 14.04 and Ansible installed
# It also executes a playbook upon startup
FROM ansible/ubuntu14.04-ansible:stable
# This Defines the location for Ansible playbooks as /srv/example
ADD ansible /srv/example
WORKDIR /srv/example
# Execute Ansible with the playbook's primary entry point as myplaybook.yml
RUN ansible-playbook myplaybook.yml -c local
CMD ["--help"] 
上面的示例应该是自解释的。然而,它基本上是基于 Ubuntu 14.04 创建 Docker 镜像,定义了一个用于 Ansible 的工作目录,然后使用 myplaybook.yml 作为源在本地运行 Ansible。简单吧?
使用 Ansible 管理 Docker 镜像
Docker 镜像与容器稍有不同。也就是说,镜像是容器的存储副本。Docker 镜像存储在通常所称的 注册中心 中。在 Docker 的上下文中,注册中心在许多方面与源代码控制解决方案类似。这个源代码控制解决方案在许多方面与 Git 相似。Docker 注册中心与 Git 有很多相似之处;最明显的是能够拥有分布式的注册中心。还搞不清楚吗?让我们看看以下图示:

从前面的示意图中,我们可以看到 Docker 注册中心 是一个远程位置,用于存储 Docker 镜像。Docker 镜像随后存储在本地注册中心(开发者本地),在这里他们可以操作并存储对存储在 Docker 注册中心 中的各个容器所做的更改。当一组更改完成时,开发者可以选择将镜像推送到远程注册中心,并传达他们的更改。
拉取、推送和标记镜像
Docker 开发的基本要求之一是开发人员能够从远程仓库拉取、推送和标记 Docker 镜像。如果你有一个协调基础设施变更并存储已构建镜像以供未来部署的构建系统,Ansible 在这种情况下可能会非常有用。这样的工作流可能大致如下:
- 
开发人员检出一组定义组织基础设施的 Ansible playbook 的源代码。 
- 
开发人员修改与环境中的 DB 层相关的 playbook,并将他们的代码更改提交到 Git。 
- 
Jenkins 提供了一个自动化的 CI 解决方案,它会监控提交并拉取 playbook 仓库进行验证。 
- 
Jenkins 执行 Ansible 运行 playbook,从而自动创建更新版的 Docker 镜像。 
- 
更新后的 Docker 镜像将被推送到远程仓库,以便部署到开发或 QA 环境。 
这种类型的工作流相当简单。它确保开发团队不会手动更改容器;它确保 Ansible 是用于开发、自动化和部署软件解决方案的工具;并确保构建的镜像在部署到生产环境之前经过测试。
在 Ansible 中自动化这种工作流需要能够拉取 Docker 镜像、推送 Docker 镜像并标记 Docker 镜像。让我们看看可以实现这一目标的 playbook 任务。
要拉取 Docker 镜像,可以使用以下代码:
- name: Pull a Docker Image
  docker_image:
  name: pacur/centos-7
要标记镜像并推送它,请使用以下代码:
- name: Tag a Docker Image and Push to docker hub
  docker_image:
  name: pacur/centos-7
  repository: myorg/myimage
  tag: 1.1
  push: yes
正如我们所看到的,这些任务的实现其实非常简单。但标记和推送到本地仓库怎么办?很高兴你问了。让我们看看如何做到这一点:
- name: Tag a Docker Image and push to the local registry
  docker_image:
  name: MyCentOS
  repository: localhost:5000/mycentos
  tag: 1.0
  push: yes
很简单,对吧?最后,让我们看看如何从 Dockerfile 构建镜像并将其推送到私有仓库。这应该很简单,对吧?
以下示例展示了如何从 Dockerfile 构建镜像:
- name: Build a Docker Image from a Dockerfile and push it to a private registry
  docker_image:
  path: ./test
  name: registry.myorg.com/foo/test
  tag: v1
  push: yes
该任务假设你在 ./test 目录下有一个 Dockerfile,而且这绝对不是火箭科学。
构建和归档 Docker 镜像
从 Dockerfile 构建 Docker 镜像是我们在上一节中简单提到的话题,但它值得深入探讨。在从 Dockerfile 构建 Docker 镜像时,我们可以稍后利用它。但首先,我们需要一个 Dockerfile 来实现这一点。让我们先看一个 Dockerfile 的示例,然后看看如何使用 Ansible 构建它:
# This DOCKERFILE creates a docker image with Ubuntu 14.04 and Ansible installed
# It also executes a playbook upon startup
FROM ansible/ubuntu14.04-ansible:stable
# This Defines the location for Ansible playbooks as /srv/example
ADD ansible /srv/example
WORKDIR /srv/example
# Execute Ansible with the playbook's primary entry point as myplaybook.yml
RUN ansible-playbook myplaybook.yml -c local
CMD ["--help"] 
这个 Dockerfile 看起来非常熟悉。实际上,它就是!它是我们之前利用的源代码,让它在启动时运行 Ansible playbook。你能看到我们从这个示例中想要表达的意思吗?将这个 Dockerfile 保存到 /opt/test 目录下,然后创建一个内容如下的 playbook:
---
- hosts: all
  remote_user: root
  tasks:
    - name: Build Docker Image
      docker_image:
      path: /opt/test
      name: myimage
现在,在 /srv/example/myplaybook.yml 中创建一个简单的 playbook,内容如下:
---
- hosts: all
  tasks:
   - name: Installs nginx web server on a Docker Image
     apt: pkg=nginx state=installed update_cache=true
     notify:
       - start nginx
handlers:
 - name: start nginx
   service: name=nginx state=started
很好。现在运行 /opt/test Ansible playbook,查看解决方案如何构建一个已经安装了 nginx 并且愉快地位于本地 Docker 注册表中的 Docker 容器。
保存和加载归档的 Docker 镜像
Docker 提供了一种独特的能力,通过 tarball 分享容器。这使得开发者可以传递 tarball 格式的基础设施副本,以便进行检查和操作。通常,归档 Docker 容器涉及两个 distinct 操作(导出/归档和加载归档);归档 Docker 镜像也是一个非常简单的任务。我们来看一个如何归档 Docker 镜像的示例代码:
Archiving an Image:
- name: Archive A Docker Image as a TAR file
 docker_image:
 name: registry.ansible.com/foo/sinatra
 tag: v1
 archive_path: sinatra.tar
可以使用以下代码加载已归档的镜像:
- name: Load a Docker Image from a TAR archive and push to a private registry
 docker_image:
 name: localhost:5000/foo/sinatra
 tag: v1
 push: yes
 load_path: sinatra.tar
收集有关 Docker 容器的事实
事实是 Ansible 工作原理和管理其控制/自动化系统信息的核心。事实代表了关于设备和当前状态的数据。Ansible 提供了一组可用于收集 Docker 镜像信息的 playbook 任务。我们来看几个如何实现这一目标的示例。
这是示例 1:
- name: Inspect a single Docker image
 docker_image_facts:
 name: foo/centos-7
这是示例 2:
- name: Inspect multiple Docker images
 docker_image_facts:
 name:
 - foo/centos-7
 - sinatra
前述 playbook 任务检查单个或多个 Docker 镜像的设置并报告事实。事实数据本身存储在返回值中。以下是输出的一个示例:
[{'Container': 'e83a452b8fb8ff43oj094j4050131ca5c863629a47639530d9ad2008d610', 'Name': 'registry:2', 'Author': '', 'GraphDriver': {'Data': None, 'Name': 'aufs'}, 'Architecture': 'amd64', 'VirtualSize': 165808884, 'ContainerConfig': {'Cmd': ['/bin/sh', '-c', '#(nop) CMD ["/etc/docker/registry/config.yml"]'], 'Env': ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], 'StdinOnce': False, 'Hostname': 'e5c68db50333', 'WorkingDir': '', 'Entrypoint': ['/bin/registry'], 'Volumes': {'/var/lib/registry': {}}, 'OnBuild': [], 'OpenStdin': False, 'Tty': False, 'Domainname': '', 'Image': 'c72dce2618dc8groeirgjeori444c2b1e64e0205ead5befc294f8111da23bd6a2c799', 'Labels': {}, 'ExposedPorts': {'5000/tcp': {}}, 'User': '', 'AttachStdin': False, 'AttachStderr': False, 'AttachStdout': False}, 'Os': 'linux', 'RepoTags': ['registry:2'], 'Comment': '', 'DockerVersion': '1.9.1', 'Parent': 'f0b1f729f784b755e7bf9c8c2e65d8a0a35a533769c2588f02895f6781ac0805', 'Config': {'Cmd': ['/etc/docker/registry/config.yml'], 'Env': ['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'], 'StdinOnce': False, 'Hostname': 'e5c68db50333', 'WorkingDir': '', 'Entrypoint': ['/bin/registry'], 'Volumes': {'/var/lib/registry': {}}, 'OnBuild': [], 'OpenStdin': False, 'Tty': False, 'Domainname': '', 'Image': 'c72dce2618dc409834095834jt4ggf5ead5befc294f8111da23bd6a2c799', 'Labels': {}, 'ExposedPorts': {'5000/tcp': {}}, 'User': '', 'AttachStdin': False, 'AttachStderr': False, 'AttachStdout': False}, 'Created': '2016-03-08T21:08:15.399680378Z', 'RepoDigests': [], 'Id': '53773d8552f07b7340958340fj32094jfd67b344141d965463a950a66e08', 'Size': 0}]
该命令的输出提供了一个漂亮的哈希数据集。这些数据可以进一步解析并在 playbook 中使用。
总结
在本章中,我们发现了一些有趣且独特的方式,将 Ansible 自动化系统与 Docker 集成。我们了解到,这两种看似冗余的技术可以结合起来提供一个强大的自动化实现,并且具有非常好的可扩展性。
我们还讨论了如何反向利用 Ansible playbook 任务来创建、更新、删除和管理容器。然后,我们介绍了如何为这些容器添加和移除网络。你会发现,尽管这些实现一开始看起来很难,但实际上它们非常简单。
在下一章中,我们将讨论如何扩展 Ansible 并创建自定义模块。我们将教你如何使用 Python 扩展 Ansible,并创建与特定技术栈集成的自定义模块。通过提供一系列教程,我们将教你如何编写和发布自定义 Ansible 模块。本章将教你如何读取输入、管理事实、执行自动化任务、与 REST API 交互以及生成文档。
第十一章:扩展 Ansible
多年来,Ansible 已成熟,支持各种技术、工具、操作系统和流程。其灵活的架构和模块化的实现使其成为 DevOps 导向团队的理想选择,能够满足不同或多样的需求。Ansible 所包含的可扩展架构旨在支持模块的创建,并将 Ansible 解决方案扩展以满足用户需求。因此,Ansible 本身及其许多现在成为核心的模块都源于曾经存在的插件。
多年来,Ansible 的创造者增加了许多 API 钩子和架构,以支持通过各种方式扩展 Ansible 本身。这项努力的最终成果是一个高度可扩展的系统,开发人员利用它创建了大量额外的核心功能——一个插件系统!
多年来,Ansible 插件和模块系统不断扩展,逐渐在 Ansible 核心架构中占据了更为中心的角色。曾经笨重的扩展系统已经被精炼为一个架构良好、实施成熟的插件解决方案。曾经不规范的扩展点经过改进,成为了一个强大且高效的模块 API。由于这些改进,插件和模块系统自最初阶段以来已经经历了显著的发展。
在本章中,我们将参观 Ansible 的模块和插件架构。我们将探索 Ansible 架构和 API 的内部工作原理。我们将深入 Python 开发,并利用它创建一些自定义模块和插件扩展,增强我们的 Ansible 实现,以支持定制化需求。具体来说,我们将覆盖以下主题:
- 
理解 Ansible 插件及其架构 
- 
设置 Ansible 模块开发环境 
- 
开发一个 HelloWorld Ansible 模块并扩展它 
- 
设置插件开发环境 
- 
理解不同类型的插件 
在我们开始学习 Ansible 插件系统的过程中,请仔细注意语法和格式,以确保代码保持清晰和无歧义。通过遵循这一通用规则,您将学会如何创建和交付高质量的 Ansible 扩展。让我们开始吧。
理解 Ansible 插件及其架构
Ansible 的实现具有高度的模块化。模块化架构提供了高水平的封装(将不同的关注点隔离开,避免它们相互影响)。Ansible 子系统中的插件解决方案经过精心设计,以保持添加内容的有序性和封装性。该架构被划分为不同的子系统。以下是定义 Ansible 插件和模块最关键的子系统:
- 
Ansible 核心模块 
- 
Ansible 配置 
- 
自定义模块 
- 
Ansible Python API 
为了更好地描述刚刚提供的模糊列表,以下图表提供了 Ansible 架构的示意图:

上面的图表突出了 Ansible 插件和模块开发中三个最关键的子系统:核心模块、自定义模块和 Ansible Python API。这个堆栈为扩展 Ansible 提供了一整套组件。
在 Ansible 中,扩展 Ansible 核心解决方案有两种不同的方式。具体描述如下:
- 
Ansible 插件:Ansible 插件扩展了主系统的核心功能,并为控制服务器提供附加功能。 
- 
Ansible 模块:Ansible 模块扩展了在目标系统上运行的 playbook 的功能。目标系统是 Ansible 执行 playbook 的系统。 
这两个区别非常重要,因为它决定了开发的范围。我们来看一个简单的 Ansible playbook,以便更好地理解 Ansible 模块在 Ansible 架构中的作用:
# This simple playbook installs / updates Apache
---
- hosts: all
 tasks:
   - name: Ensure Apache is installed and at the latest version
     yum: name=httpd state=latest
根据前面的 playbook,你能确定模块是如何使用的吗?不行?我们来仔细看看这个 playbook:
# This simple playbook installs / updates Apache
---
- hosts: all
 tasks:
   - name: Ensure Apache is installed and at the latest version
 <yum>: <param>=<value> <param>=<value>
如果你猜测模块名称是<yum>,那么你是正确的。以 playbook 形式表示的任务实际上就是模块调用。如果是这种情况,我们可以合理地问自己:“我应该在什么时候创建自己的模块?”
我们什么时候应该创建一个模块?
在此时,最明显的问题是:你什么时候以及为什么要开发自己的 Ansible 模块?答案是大多数时候你其实并不需要。但是也有一些情况下,你可能需要开发自己的模块。以下是一些例子:
- 
当与特定 API 的通信显得笨拙或困难时 
- 
做一些 Ansible 本身没有原生支持的定制操作 
- 
与没有现成 Ansible 模块的内部进程或软件进行通信 
通常,如果你想编写一个 Ansible 模块,而该软件解决方案是由第三方(如开源、商业等)创建的,最好在编写代码之前先检查 Ansible 模块是否已经提供了现成的支持。
在下一节中,我们将看看如何设置 Ansible 开发环境,Ansible 模块代码应该存储在哪里,以及如何组织这些代码。我们继续吧,好吗?
设置 Ansible 模块开发环境
在本节中,我们将讨论如何为 Ansible 模块开发设置本地 Linux 环境。在我们具体的实现中,我们将看看如何在 Ubuntu 中做到这一点。然而,其他 Linux 发行版下的相同配置选项也应该适用。作为新手 Ansible 模块开发者,我们首先要了解如何配置我们的系统以最好地支持 Ansible 开发,如何设置模块路径,以及如何配置环境进行测试。
启动开发环境的第一步是了解系统上的 Ansible 库路径。这个路径是 Ansible 用来搜索其他库的地方。库路径的默认值在主 Ansible 配置文件(/etc/ansible/ansible.cfg)中定义。该行项如下所示:
library = /usr/share/ansible
虽然默认路径在 Ansible 配置文件中定义,但也可以在运行时通过在启动 Ansible 时指定 --module-path 来修改该路径。
除了 --module-path 开关外,我们还可以通过系统级环境变量覆盖默认的模块路径。如何操作的示例如下:
#> export ANSIBLE_LIBRARY=/srv/modules/custom_modules:/srv/modules/vendor_modules
在开发和使用 Ansible 模块时,最常见的存储模块的位置是在 playbook 本身旁边的 ./library 目录中。这将涉及创建如下所示的目录结构:
#> foo.yml
 #> library/
 #> library/mymodule.py
在前述结构中,我们可以利用 playbook 中可用的任务。非常不错,对吧?这些就是使用 Ansible 设置开发环境的基础。从这里开始,我们可能还需要设置一些基本的调试和 lint 解决方案。
Ansible 模块测试工具设置
Ansible 开发者已经实现并发布了一个非常有用的工具,用于调试 Ansible 插件和模块中的语法和格式问题。这个 linter 确实非常有用。要安装 linter,请执行以下命令:
$ pip install git+https://github.com/sivel/ansible-testing.git#egg=ansible_testing
执行前面的命令后,我们应该看到以下输出:

现在我们已经安装了 lint 工具,让我们检查一下它是否安装成功。尝试执行以下命令:
#> ansible-validate-modules
执行前面的命令后,我们应该在控制台上看到以下输出:

很棒,对吧?接下来,我们将设置 Ansible 模块测试解决方案。让我们继续。
开发 Hello World Ansible 模块
现在我们已经设置了基本的开发环境,接下来我们将通过查看必备的 Hello World Ansible 模块实现来探索如何创建 Ansible 模块。通过创建一个 Hello World 模块,我们可以在 Ansible 模块开发中迈出第一步,并学习成功实现所需的基本结构。让我们开始吧!
为了开始我们的Hello World模块,让我们创建一个符合以下截图的目录结构:

一旦创建了这个结构,我们就可以开始填写我们的 Ansible Hello World 模块代码。为此,修改 helloworld.py 文件,使其包含以下 Python 代码:
#!/usr/bin/python
# The following Python code converts a simple "Hello Ansible" message into a json object 
# for use with an Ansible module call
import json
message = "Hello Ansible"
print(json.dumps({
 "Message" : message
}))
一旦实现了前面的代码,我们就需要一个高效的方式来测试其功能。接下来,让我们设置测试环境。
测试开发中的 Ansible 模块
一旦设置好了主要的 Ansible 模块开发环境,我们将需要设置 Ansible 模块测试环境。这个解决方案将使我们能够在不直接使用 Ansible 的情况下验证我们的 Python 代码。因此,它将使潜在模块的开发和验证更加高效。要设置此环境,请在开发模块目录中执行以下命令:
#> git clone git://github.com/ansible/ansible.git
#> source ansible/hacking/env-setup
执行这些命令后,你应该会看到类似以下输出:

一旦完成,我们应该能够访问一个新命令,这将使我们能够测试我们部分开发的 Ansible 模块。让我们来检查一下:
#> ansible/hacking/test-module -m ./library/helloworld.py
以下截图显示了前述命令的输出:

如果出现问题(如拼写错误或不可编译的脚本),我们将看到类似以下内容的提示:

读取输入参数
Ansible 提供的一个基本价值是它与 YAML 剧本的连接。只有当我们能够创建传递数据参数给模块的剧本时,开发模块才是有意义的。在本节中,你将学习如何扩展我们的 Hello World Ansible 模块,以接受并处理来自剧本的输入参数。我们还将了解如何构建我们的模块,使其符合 Ansible 为模块开发设计的模板系统。
为了让我们的 Hello World 程序能够从 Ansible 剧本中读取输入参数,我们需要稍作修改。让我们将 ./library/HelloWorld.py 文件更新为以下内容:
#!/usr/bin/python#!/usr/bin/python
import json
def main(): 
    module = AnsibleModule(argument_spec=dict( param1 = dict(required=True, type='str') ) )
    message = module.params['param1']
    print(json.dumps({
        "Message" : message
    }))
    module.exit_json(changed=True, keyword=value)
    module.exit_json(changed=False, msg='error message ', keyword=value)
from ansible.module_utils.basic import *
if __name__ = '__main__': 
    main() 
接下来,在库文件夹的父文件夹中创建一个简单的剧本 myplaybook.yml,内容如下所示:
- name: Hello World
  hosts: localhost
  connection: local
  tasks:
    - name: Tell the Ansible Community Hello
      helloworld: param1=hello
保存后,让我们执行它并查看输出。以下是运行命令和预期输出:
#> ansible-playbook myplaybook.yml -i localhost -v
输出将如以下截图所示:

很棒,对吧?接下来,让我们看看这些 Hello World 行的作用。以下是 helloworld.py 脚本的一个更为详细的文档版本:
#!/usr/bin/python
#Main Entry point for the module
def main():
    # Instantiate the message variable (this will contain our YAML param value)
    message = ''
    # Instantiate the Ansible Module which will retrieve the value of our param1 variable
    module = AnsibleModule(argument_spec=dict(param1 = dict(required=True, type='str')))
    # Set the value of Message to the value of module.params['param1']
    message = module.params['param1']
    # Display the content of the message in JSON format
    print(json.dumps({"Message": message{))
    # Exit the program SUCCESS/FAIL
    module.exit_json(changed=True, keyword=value)
    module.exit_json(changed=False, msg='error message', keyword=value)
# Import ansible functionality from Ansible.module
from ansible.module_utils.basic import *
# This line imports the functionality of JSON. It allows us to print the JSON formatted message
import json
# Call Main Function IF _main_ is defined
if __name__ = '__main__':
    main()
向模块添加自定义 facts
Ansible facts(我们在前面的章节中提到过)提供了有关运行剧本或任务的系统的数据信息。在某些情况下,我们可能需要设置一些 Ansible facts,并将其返回给 Ansible 控制服务器。在本节中,我们将讨论如何在我们的 Hello World 模块中设置 Ansible facts 以及 Ansible facts 的一些限制。
根据 Ansible 文档(docs.ansible.com/ansible/dev_guide/developing_modules_general.html):
setup - 收集有关远程主机的事实模块,它是与 Ansible 一起提供的,提供了许多关于系统的变量,这些变量可以在 playbook 和模板中使用。不过,也可以在不修改系统模块的情况下添加你自己的事实。为此,只需让模块返回一个 ansible_facts 键,像这样,连同其他返回数据。
在本节中,我们将介绍如何收集模块特定的自定义事实并将其返回给 Ansible 控制服务器。首先,我们需要定义一组格式化的事实。让我们来看一个实现此功能的代码示例:
ansible_facts_dict = {
 "changed" : true,
 "rc" : 5,
 "ansible_facts" : {
 "foo" : "bar",
 }
}
module.exit_json(changed=False, result="success",ansible_facts)
根据之前的代码,我们可以看到,Ansible 事实可以以 JSON 字典的形式设置,然后通过 module.exit_json 文件传递。然后可以在 playbook 中访问这些事实,但只有在设置事实的任务执行后才能访问。不错吧?
设置 Ansible 插件开发环境
正如我们之前提到的,Ansible 插件表示在主控服务器上执行的操作,而不是在目标主机上执行。这些插件使我们能够轻松地为 Ansible 解决方案添加额外的功能。一旦插件编写完成,操作便可以通过传统的 YAML playbook 操作进行调用。在开始编写操作插件之前,让我们先看看如何设置开发环境。
类似于模块开发环境,操作插件必须位于 ./<plugin 类型>_plugins 中,紧邻正在执行的 playbook 或 位于指定的文件夹之一。例如,你可能有如下的目录结构:
#> foo.yml
#> action_plugins/
#> action_plugin/mymodule.py
或者,你可能有如下所示:
#> foo.yml
#> callback_plugins/
#> callback_plugin/mymodule.py
或者,你可以考虑修改配置文件夹中的 <plugin 类型>_plugins 路径,如下所示:
#action_plugins = /usr/share/ansible/plugins/action
#callback_plugins = /usr/share/ansible/plugins/callback
#connection_plugins = /usr/share/ansible/plugins/connection
#lookup_plugins = /usr/share/ansible/plugins/lookup
#vars_plugins = /usr/share/ansible/plugins/vars
#filter_plugins = /usr/share/ansible/plugins/filter
#test_plugins = /usr/share/ansible/plugins/test
在配置文件中取消注释你希望在配置中使用的插件类型是很重要的。一旦为我们希望创建的插件类型创建了开发环境,就可以开始编写插件本身了。
了解不同类型的插件
Ansible 提供了创建多种类型插件的选项。每种插件类型与 Ansible 系统的交互方式不同。在本节中,我们将查看 Ansible 插件架构中可用的不同类型插件,并学习如何编写它们。可用的插件类型如下:
- 
操作插件 
- 
回调插件 
- 
连接插件 
- 
查找插件 
- 
Vars 插件 
- 
过滤器插件 
- 
测试插件 
在接下来的章节中,我们将了解如何编写每种插件类型的代码以及它们的功能。让我们开始吧。
操作插件
在本节中,我们将查看操作插件,您将了解操作插件的作用以及如何创建新的可在 Ansible 子系统中使用的操作的基本代码示例。在 Ansible 中,action_plugins 是一种特殊类型的模块,为现有模块提供额外的功能。正如我们之前提到的,action_plugins 在主机上运行,而不是在目标主机上运行。
例如,通过 Ansible playbook 表示的操作插件可能如下所示:
- name: Special Action to be run on the master
  action: myaction foo=bar
这样的操作插件代码可能看起来像以下内容:
#!/usr/bin/python
# Import the Ansible Runner return data lib 
from ansible.runner.return_data import ReturnData
# Define our ActionModule class (MUST BE NAMED ActionModule)
class ActionModule(object):
    # Define our Calss constructor method (Must be present)
    def __init__(self, runner):
        self.runner = runner
    # Define our run method (must be present)
    def run(self, conn, tmp, module_name, module_args, inject, complex_args=None, **kwargs):
        return ReturnData(conn=conn, comm_ok=True, result=dict(failed=False, changed=False, msg="Hello Ansible"))
正如我们所见,插件代码通过使用一组定义明确的结构化方法,简单地将功能添加到 playbook 中。
回调插件
Ansible 中的回调插件为 Ansible 提供了对系统内各种事件响应的附加功能。回调插件还控制在运行命令行程序时显示的大部分执行输出。在本节中,我们将查看回调插件,并学习如何在 Ansible 子系统中实现额外的回调功能。
回调插件的 Python 代码必须存储在 callback 文件夹中,正如我们之前提到的。需要由类重写的代码如下所示:
#!/usr/bin/python
# Import CallbackPlugin base class
from ansible.plugins.callback import CallbackBase
from ansible import constants as C
# Define the CallBackModule class
class CallbackModule(CallbackBase):
    pass
回调插件的工作方式类似于其他插件。它们使我们能够重写在初始 Ansible 实现中开发的各种功能。有关可用重写的详细信息,可以在 docs.ansible.com/ansible/dev_guide/developing_plugins.html 找到。
连接插件
类似于回调和操作插件,连接插件也可以添加以增强 Ansible 子系统的功能。开箱即用,Ansible 使用 paramiko SSH 和原生 SSH 协议连接方案。此外,还使用了一些其他小型库(例如 chroot、jail 等)。这些库可以通过 playbook 使用。可能需要使用替代的连接类型,如 SNMP,或者消息,供 Ansible 使用。对于具有 Python 和编程知识的人来说,使用连接插件选项是一个非常简单的过程。要做到这一点,只需将现有连接类型之一的格式复制到 plugins_connection 文件夹中,并根据需要进行修改。
该插件类型的文档不全面,而且 Ansible 开发者尚未公开发布。因此,建议您查看 Ansible 源代码中现有连接插件的实现示例。例如,这些示例可以在 github.com/ansible/ansible/tree/devel/lib/ansible/plugins/connection 中找到。
查找插件
在本节中,我们将更深入地了解查找插件,学习它们是什么,在哪里可以找到一些示例,以及如何利用它们。首先,让我们更好地理解查找插件的实际含义。查找插件旨在从外部数据源检索信息和数据集。例如,Ansible 迭代的概念就是使用查找插件开发的。更具体来说,with_fileglob 和 with_items 是通过查找插件构建的。
让我们来看一下如何基于官方 Ansible 源代码文档实现查找插件:
# This Code Example Comes from the Official Ansible Documentation Set (http://www.ansible.com/)
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins.lookup import LookupBase
try:
    from __main__ import display
except ImportError:
    from ansible.utils.display import Display
    display = Display()
# This is the standard class for the LookupModule implementation it is required to be this name 
class LookupModule(LookupBase):
    # As with all our other plugins, the run method MUST be there 
    def run(self, terms, variables=None, **kwargs):
        ret = []
        # Perform iteration
        for term in terms:
            display.debug("File lookup term: %s" % term)
            # Find the file in the expected search path
            lookupfile = self.find_file_in_search_path(variables, 'files', term)
            display.vvvv(u"File lookup using %s as file" % lookupfile)
            try:
                if lookupfile:
                    contents, show_data = self._loader._get_file_contents(lookupfile)
                    ret.append(contents.rstrip())
                else:
                    raise AnsibleParserError()
            except AnsibleParserError:
                raise AnsibleError("could not locate file in lookup: %s" % term)
return ret
这是如何调用这个查找插件的示例:
---
- hosts: all
 vars:
 contents: "{{ lookup('file', '/etc/foo.txt') }}"
 tasks:
  - debug: msg="the value of foo.txt is {{ contents }} as seen today {{ lookup('pipe', 'date +"%Y-%m-%d"') }}"
分发 Ansible 插件
我们在本章的每一节中都详细讨论了这一点,但它值得再次强调。启用和分发 Ansible 插件的最有效方式是,在 playbook 所在目录旁创建一个子目录,利用该插件,也就是说,在你的 playbook 旁创建以下目录之一:
- 
./action_plugins
- 
./lookup_plugins
- 
./callback_plugins
- 
./connection_plugins
- 
./filter_plugins
- 
./strategy_plugins
- 
./cache_plugins
- 
./test_plugins
- 
./shell_plugins
除了这种插件分发方法,我们还可以使用 RPM 或 PIP 来打包插件并将其分发到适当的 Ansible 安装目录。传统的安装位置设置在 /etc/ansible/ansible.cfg 文件中,并可以根据需要进行更改。
总结
在本章中,我们讨论了如何扩展 Ansible。你了解到 Ansible 有两种类型的扩展。第一种是 Ansible 模块,第二种是 Ansible 插件。Ansible 模块为开发者提供了向运行在目标主机上的 Ansible 添加功能的能力,而插件则扩展了控制服务器的功能。
你学习了如何为 Ansible 模块和 Ansible 插件设置本地开发环境。一旦我们处理好了开发环境,我们就探讨了如何通过一个 Hello World 示例编写模块,以及如何通过新的插件扩展 Ansible,从而覆盖核心 Ansible 插件解决方案中的功能。
之后,我们探讨了插件架构,并学习了可以利用的各种扩展点。这包括操作插件、控制插件、变量插件等。
在接下来的最后一章中,我们将看看 Ansible Galaxy。Ansible Galaxy 是一个由用户管理的 Ansible playbook 分发点。它是 Ansible 开发者的一个关键实现,使得创建用于执行常见任务的 playbook 变得轻松。让我们开始吧。
第十二章:Ansible Galaxy
在本书的这个阶段,我们应该已经对 DevOps 和 Ansible 有了比较扎实的理解,并且知道如何有效地使用 Ansible 2 来实施 DevOps 模式和组织策略。从这里开始,我们将看看 Ansible 提供的社区资源(Ansible Galaxy),并了解如何利用丰富的开源社区提供的 Ansible 角色和剧本。
Ansible 的实现多年来已成功实现标准化,并成为市场上领先的 DevOps 配置管理、自动化和交付解决方案之一。Ansible 成功的主要原因在于其内在的模块化、可扩展性、Ansible Tower 以及社区支持的 Ansible Galaxy 解决方案。
对于那些不熟悉 Ansible Galaxy 的人,它是一个强大且高度适应的解决方案,通过它,社区成员和剧本开发者可以创建和共享 Ansible 角色。这个由社区开发的共享解决方案为社区提供了丰富的自动化解决方案。
在最后一章中,我们将介绍 Ansible 的旗舰剧本和角色分发解决方案:Ansible Galaxy。我们将探索如何利用这一创新实现来检索和提交角色开发。我们将探索这个解决方案是如何工作的,并学习如何最大限度地利用它。
我们将要探索的具体主题包括以下内容:
- 
Ansible Galaxy 基础知识 
- 
了解 Ansible Galaxy 中可用的命令行选项 
- 
了解如何使用 Ansible Galaxy 将角色安装到您的系统中 
- 
描述如何创建和与 Ansible Galaxy 共享 
Ansible Galaxy 基础知识
在本节中,我们将了解 Ansible Galaxy。我们将探索 Ansible Galaxy 是什么以及它是如何工作的。首先,让我们定义一下什么是 Ansible Galaxy。Ansible Galaxy 是一个由命令行界面支持的网站,为角色和剧本开发者提供了一个共享和使用其创作的空间。每个进入 Ansible Galaxy 的入口都可以独立使用,允许社区根据需要加以利用。
Ansible Galaxy 网站
Ansible Galaxy 网站是一个由 Ansible 拥有并由社区支持的角色和剧本共享解决方案。Ansible Galaxy 网站托管着成千上万的社区创建的角色和剧本。这些角色的开发者创建它们是为了让其他人能够从他们的努力中受益,并利用它们执行自动化部署和配置管理任务。
到目前为止,最好查看一下 Ansible Galaxy 网站。Ansible Galaxy 网站和社区位于 galaxy.ansible.com/。
Ansible Galaxy 网站应该看起来类似于以下截图所示:

正如我们所看到的,Ansible Galaxy 网站被分为几个不同的菜单选项。以下是这些子部分(在网站顶部突出显示)的内容:
- 
关于 
- 
探索 
- 
浏览角色 
- 
浏览作者 
让我们花一点时间简要描述一下这些部分及其在 Ansible Galaxy 中的作用:
- 
关于:该网站部分提供了与 Ansible Galaxy 相关的重要信息。包括如何逐个下载角色、如何一次下载多个角色、高级下载选项、如何创建和共享角色、最佳实践、自动化测试技巧,以及遇到问题时该去哪里寻求帮助。 
- 
探索:探索部分提供了一组带标签的标准,供我们在浏览角色时使用。这些标签标准允许我们通过作者名称、最多下载次数、最多观看次数等方式查看 Ansible Galaxy 中的可用角色。 
- 
浏览角色:浏览角色正如其名,是一个角色浏览器和搜索工具。这可能是网站上最常用的部分,因为它允许我们查找并获取 Ansible 角色。 
- 
浏览作者:浏览作者是我们在 Ansible Galaxy 中搜索并找到由特定人物创建的角色的方式。这对于寻找你认识的人或知名作者创建的角色特别有用。 
除了 Ansible Galaxy 的主网站外,你还可以参考 www.ansible.com/ 官方文档,获取关于 Ansible Galaxy 的帮助。该文档的链接是 docs.ansible.com/ansible/galaxy.html。
除了连接到中央的 Ansible Galaxy 网站,你还可以运行自己专用的私有 Ansible Galaxy 服务器。这是因为,像 Ansible 一样,Ansible Galaxy 也是开源的。关于如何搭建个人 Ansible Galaxy 服务器的更多信息,可以在 github.com/ansible/galaxy 找到。
在你浏览 Ansible Galaxy 网站时,使用 Ansible 角色时有一些重要事项需要注意。这些事项包括以下内容:
- 
角色名称 
- 
兼容的平台 
- 
安装命令 
Ansible Galaxy 网站为我们提供了每个角色的命令行解决方案。例如,以下截图展示了 Ansible Galaxy 网站如何列出 MySQL 角色的信息:

一旦你点击了首选的角色(在我们的案例中是 mysql),我们应该看到如下截图所示的内容:

Ansible Galaxy 命令行界面
Ansible Galaxy 的命令行界面提供给用户,以便他们可以从 Ansible Galaxy 网站自动化地获取 Ansible 角色。这是一个非常有用的命令行工具,我们将在本章的其余部分使用它。首先,我们需要验证 Ansible Galaxy 命令行工具是否已正确安装并正常工作。我们可以尝试以下命令:
#> ansible-galaxy --help
该命令的输出应该类似于以下内容:

现在我们已经了解了如何基本地调用 Ansible Galaxy,我们可以开始查看 Ansible Galaxy 解决方案提供的命令行开关。
ansible-galaxy 安装的完整语法(用于在系统上安装角色、登录以及更多操作)可能类似于以下内容:
#> ansible-galaxy [ACTION] [options] [-r FILE | role_name(s)[,version] | tar_file(s)]
如果这看起来稍微有点困惑,没关系;很快你就会明白。接下来,我们将详细介绍更重要的 ansible-galaxy 命令行选项,并且你将学会如何更好地使用它们。
解释 Ansible Galaxy 命令行选项
在本节中,我们将更深入地研究 Ansible Galaxy 的命令行选项和可用的参数。目标是深入了解命令行,更好地理解 Ansible Galaxy 的命令行实现如何让 Ansible 管理员的工作更轻松。我们刚刚看到了(前面)ansible-galaxy 命令的完整语法示例。在使用 Ansible Galaxy 时,ansible-galaxy 命令中的格式为 <OPTION> 后跟 [PARAMS],如下所示:
#> ansible-galaxy <OPTION> [PARAMS]
所以实际上,<OPTION> 标签可以是以下任一选项:
[delete|import|info|init|install|list|login|remove|search|setup] [--help]
另一方面,[PARAMS] 将是任何支持的子参数选项。让我们来看一下 ansible-galaxy 命令的主要选项,并查看这些选项的作用,以及每个选项可用的子参数。
install 子命令
install 子命令用于 Ansible 将角色安装到控制服务器上。该命令的一般用法如下:
ansible-galaxy install [options] [-r FILE | role_name(s)[,version] | tar_file(s)]
这个命令在实际应用中的更真实示例如下:
ansible-galaxy install ANXS.postgresql
正如我们之前看到的,Ansible Galaxy 网站将为我们提供安装给定角色的命令行语法,正如我们所见,这通常是相当直接的。
install 选项提供了多个安装角色的选项,接下来将描述这些选项:
- 
使用 username.role[,version]:此解决方案为我们提供了安装在 Ansible Galaxy 网站上找到的给定角色的能力。根据我们之前的install命令示例,该解决方案的语法允许我们指定,比如ansible-galaxy install ANXS.postgresql,其中ANXS是用户名,postgresql是角色。
- 
使用 filename -r-选项:此方案为我们提供了安装文本文件中提供的多个角色的能力。文本文件中每行包含一个角色,并且每一行的格式要求与前面选项的格式要求相同:
username.role[,version]
- 
使用 tarball:此选项允许我们从其他来源(如 GitHub)获取角色,并通过指向 tar.gz文件来安装该角色。
- 
可用选项: - 
-f,--force: 强制覆盖系统中已有的角色。
- 
-i,--ignore-errors: 此选项忽略错误,并允许 Ansible Galaxy 继续处理下一个指定的角色。
- 
-n,--no-deps: 此选项从ansible-galaxy命令的上下文中移除依赖项。这意味着不会与指定角色一起安装依赖项。
- 
-p ROLES_PATH,--roles-path=ROLES_PATH: 这个可选路径参数允许我们覆盖包含角色的目录。具体来说,它允许我们为 Galaxy 指定一个替代位置以下载角色。roles_path的默认实现配置在ansible.cfg文件中(如果未配置,则为/etc/ansible/roles)。
- 
-r ROLE_FILE,--role-file=ROLE_FILE: 包含待导入角色列表的文件,如之前所指定。如果已指定角色名或.tar.gz文件,则无法使用此选项。
 
- 
delete 子命令
Ansible Galaxy 中的 delete 命令选项将从 galaxy.ansible.com 注册表中移除一个角色。需要注意的是,为了有效移除角色,首先需要使用登录选项进行身份验证。以下是如何使用 DELETE 选项的一些示例:
#> ansible-galaxy delete USER.ROLE
可用的参数如下:
- 
-c,--ignore-certs: 这个特定选项告诉 Ansible Galaxy 忽略 TLS 证书错误。
- 
-s,--server: 此选项覆盖默认服务器galaxy.ansible.com。在设置自己的 Ansible Galaxy 服务器时,这个选项特别有用。
import 子命令
这个 ansible-galaxy 选项允许我们从 GitHub 导入角色到 galaxy.ansible.com 库中。为了使该导入功能正常工作,需要使用登录子命令进行 galaxy.ansible.com 的用户身份验证。让我们来看一下如何使用 import 子命令导入角色。
$> ansible-galaxy import [options] github_user github_repo 
可用的参数如下:
- 
-c,--ignore-certs: 此命令行选项告诉 Ansible Galaxy 忽略 SSL 证书。该选项还会忽略 TLS 证书错误。
- 
-s,--server: 覆盖默认服务器galaxy.ansible.com。
- 
--branch: 此选项允许我们指定要导入到 Ansible Galaxy 的特定分支。如果未定义特定分支,则使用meta/main.yml中找到的分支。
ifo 子命令
ansible-galaxy info 子命令提供了与特定角色相关的详细信息集。返回的角色信息包括来自远程 Ansible Galaxy 副本 和 本地副本 的信息。接下来提供了如何使用 info 子命令的示例:
$> ansible-galaxy info [options] role_name[, version] 
可用选项如下:
- 
-p ROLES_PATH,--roles-path=ROLES_PATH:roles-path选项允许我们指定包含 Ansible 角色的目录路径。角色的默认位置是ansible.cfg文件中指定的ROLES_PATH(如果未配置,默认路径为/etc/ansible/roles)。
- 
-c,--ignore-certs:此选项忽略 TLS 证书错误。
- 
-s,--server:此选项覆盖默认服务器galaxy.ansible.com,允许我们指定一个备用服务器。
初始化命令
init 命令用于初始化一个空的角色结构,该结构可以上传到 galaxy.ansible.com/。这是开始 Ansible Galaxy 角色开发的好方法,也是开始创建所需结构的首选方式。以下是如何使用 init 命令的语法示例:
$> ansible-galaxy init [options] role_name
可用选项如下:
- 
-f,--force:此选项强制init结构自动覆盖路径中现有的任何角色。
- 
-p INIT_PATH,--init-path=INIT_PATH:此选项允许我们指定将创建新角色骨架的路径。默认值为当前工作目录。
- 
--offline:此选项告知init子参数在创建角色时不要查询galaxyAPI。
列出子命令
list 子命令指示 Ansible Galaxy 显示当前系统上已安装的角色。通过此命令,我们还可以只指定角色名称,如果该角色确实已安装,则只显示该角色。让我们看一下如何使用此子命令的示例:
$> ansible-galaxy list [role_name]
可用选项为 -p ROLES_PATH, --roles-path=ROLES_PATH;该路径允许我们指定包含角色的目录路径。此选项的默认值为 roles_path,通常在 /etc/ansible/roles 目录下的 ansible.cfg 文件中进行配置。
登录子命令
Ansible Galaxy 的 login 子命令提供了在 ansible-galaxy 和本地命令行客户端之间进行身份验证的功能。此身份验证在将角色上传到 Ansible Galaxy 时尤其有用。它对于从 GitHub 导入角色到 Ansible Galaxy 也非常有用。在这些情况(以及其他一些情况)中,在执行操作之前需要使用 login 命令。让我们看一下 login 子命令的命令行语法:
$> ansible-galaxy login [options]
可用选项如下:
- 
-c,--ignore-certs:此选项忽略可能发生的任何 TLS 证书错误。
- 
-s,--server:此选项告诉 Ansible Galaxy 覆盖默认的服务器galaxy.ansible.com。
- 
--github-token:如果我们不想使用 GitHub 密码,或者如果我们的 GitHub 帐户启用了双因素认证,我们可以选择使用--github-token参数传递一个个人访问令牌来进行 GitHub 登录。需要特别注意的是,只有在 GitHub 帐户启用了双因素认证时,才应该使用此选项。
删除子命令
在本小节中,我们将介绍 Ansible Galaxy 的 remove 子命令。此特定子命令用于删除一个或多个角色。让我们快速看一下此命令的语法示例:
$> ansible-galaxy remove rolea roleb ...
可用的选项有 -p ROLES_PATH, --roles-path=ROLES_PATH;此路径允许我们指定包含角色的目录的路径。此选项的默认值为 ROLES_PATH,该路径通常配置在 /etc/ansible/roles 目录下的 ansible.cfg 文件中。
搜索子命令
Ansible Galaxy 解决方案有一个非常实用的 search 子命令。此子命令为我们提供了在 Ansible Galaxy 服务器上搜索特定角色的能力。除了基本的搜索功能外,我们还可以搜索并筛选结果。让我们花一点时间来看一下这个有用功能的语法:
$> ansible-galaxy search [options] [searchtermZ searchtermA]
可用的选项如下:
- 
--galaxy-tags:tags选项提供了一个逗号分隔值(CSV)列表,列出了我们希望 Galaxy 服务器筛选的标签。
- 
--platforms:此选项允许我们根据支持的平台筛选角色。要使用它,我们需要提供一个逗号分隔的平台列表来进行筛选。
- 
--author:此选项允许我们指定要筛选的用户名。
- 
-c,--ignore-certs:此选项忽略 TLS 证书错误。
- 
-s,--server:此选项允许我们更改 Ansible Galaxy 的服务器 URL。例如,如果我们运行自己的 Ansible Galaxy 服务器,这可能会很有用。
设置子命令
setup 操作允许 Ansible Galaxy 与 Travis CI 集成。这样,Ansible Galaxy 就可以在构建完成后接收来自 Travis CI 的通知。需要注意的是,在能够使用此集成之前,Ansible Galaxy 必须首先通过 galaxy.ansible.com 库进行身份验证。
接下来提供了如何使用 setup 命令的示例:
$ ansible-galaxy setup [options] source github_user github_repo secret 
帮助命令
此命令行选项显示简短的帮助信息并退出。接下来提供了 help 命令的输出示例:

摘要
在最后一章中,我们探讨了如何使用 Ansible Galaxy。Ansible Galaxy 是一个非常有用的解决方案,可以帮助分享和沟通 Ansible 角色。这个解决方案可以消除许多 Ansible 的猜测工作。通常,Ansible Galaxy 社区有一句话:“为此总有一个 Galaxy 角色”,事实上,确实可能有。
在我们结束 DevOps 和 Ansible 的旅程时,重要的是要记住,实施 DevOps 对任何组织来说都是一个艰巨的挑战。许多人需要共同合作,制定统一的流程,一套严格的标准,同时保持技术要求的灵活氛围。
在你实施 DevOps 与 Ansible 2 的过程中,Ansible 提供了一个急需的自动化平台和组织内部的粘合剂,可以作为未来面向 DevOps 改进的基础点。我作为本书的作者,希望书中的知识对你的组织有所帮助,并祝你的 DevOps 实施成功。

 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号