在博客圈中,到处歌颂基础设施即代码、持续集成/持续交付 (CI/CD) 管道、代码审查和测试制度的优点,人们很容易忘记,这样一个精心设计的象牙塔只是一种理想,而不是现实。不完善的系统困扰着我们,但我们必须交付一些东西。
很少有比在系统自动化过程中将 API 粘合在一起创建的塔更不象牙塔的了。这是一个脆弱的世界。将它“运行起来”、交付并继续前进的压力巨大。
要解决的问题
想象一个简单的功能请求:编写一些 Ansible 代码,在外部系统中创建多个记录,以记录 VLAN 的一些详细信息。我最近突然想做一些实验室管理工作来完成这项任务。外部系统是一个知名的互联网协议地址管理 (IPAM) 工具,但对于更抽象的配置管理数据库 (CMDB) 或与网络无关的记录来说,障碍是相同的。在本例中,我创建记录的直接目的是为了记录——该系统仅用于记录保存。
如果目标是一个超紧凑、直接且简单的宏,它可能会达到 100 行代码。如果我记住了 API,我可能可以在一个小时内把它敲出来,代码除了按预期执行之外什么也不做,并且只留下精确的、完成的工件。完全符合其目的,并且对于未来的任何扩展都完全无用。
如今,我期望几乎每个人都以角色和几个任务文件开始这项任务,准备扩展到十几个左右的创建、读取、更新和删除 (CRUD) 操作。因为我不是那些了解这个 API 的人之一,所以我可能会花费几个小时到几天的时间来摆弄它,弄清楚它的内部模式和工艺,弥合它的功能和我编码在代码中的愿望之间的差距。
在研究 API 时,我发现创建 VLAN 记录需要父对象引用。这看起来像一个路径片段,其中包含随机字符。也许它是一个哈希值,或者它真的是随机的;我不知道。我可以想象,许多身处泥潭并面临最后期限的人可能会直接将这个任意字符串复制并粘贴到 Ansible 中,然后继续他们的生活。忽略角色的实现细节,显而易见的 playbook 级别任务将是
- name: "Create VLAN"
include_role:
name: otherthing
tasks_from: vlan_create.yml
vars:
vlan_name: "lab-infra"
vlan_tag: 100
vlan_view_ref: "vlan_view/747f602d-0381"
不幸的是,除非通过 API,否则 vlan_view_ref
标识符不可用,因此即使将其移动到清单或额外变量中也没有太大帮助。playbook 的用户需要对系统有一定程度的非常规理解才能找出正确的引用 ID。
在我的实验室构建环境中,我将频繁地重新部署这个记录系统。因此,父 ID 将每天都在变化,我不想每次都手动找出它。所以我肯定必须按名称搜索引用。没问题
- name: Get Lab vlan view reference
include_role:
name: otherthing
tasks_from: search_for.yml
vars:
_resource: vlan_view
_query: "name={{ vlan_parent_view_name }}"
最终,它会进行 REST 调用。这“返回”JSON,我按照约定将其塞入 _otherthing_search_result
中,以便在角色外部轻松访问。search_for.yml
的实现是抽象的,它总是返回零个或多个结果的字典。
大多数 Ansible 开发人员,正如我读过的几乎所有真实世界的 Ansible 代码所证明的那样,会像一切都很棒一样继续进行,并直接访问预期的单个结果
- name: Remember our default vlan view ref
set_fact:
_thatthig_vlan_view_ref: "{{ _otherthing_search_result[0]._ref }}"
- name: "Create VLAN"
include_role:
name: otherthing
tasks_from: vlan_create.yml
vars:
vlan_name: "lab-infra"
vlan_tag: 100
vlan_view_ref: "{{ vlan_parent_view_name }}"
但有时 _thatthing_search_result[0]
将是未定义的,因此 _thatthig_vlan_view_ref
将是未定义的。最有可能的是,这是因为代码在不同的真实世界环境中运行,并且有人忘记更新清单或命令行中的 {{ vlan_parent_view_name }}
。或者,不管公平与否,可能有人进入工具的图形用户界面 (GUI) 并删除了记录或更改了其名称或类似的东西。
我知道你在想什么。
“好吧,别那样做。这不是一个允许犯傻的地方。少犯点傻。”
也许我可以接受这种情况并反驳:“Ansible 会非常正确地告诉你 The error was: list object has no element 0
,甚至还会提示行号。你还想要什么?” 作为开发人员,我当然知道这意味着什么——我刚刚写的。我刚刚花了三天时间摆弄 API。我的思路很清晰。
明天又是另一回事了
但到了明天,我可能会忘记 vlan 视图引用是什么,而且我肯定会忘记第 30 行是什么。如果在一个月后出现问题,即使你设法找到我,我也将不得不花一个下午的时间再次解码 API 指南,以弄清楚哪里出了问题。
如果我离职了呢?如果我已经将代码移交给运营团队,也许是一个实习生通过 Tower 运行它,手动将 vlan_view_name
输入到调查问卷中或类似的操作?那么第 30 行是问题所在对他们来说毫无帮助。
你说添加注释!嗯,是的。我可以在代码中写一些简洁的文字来帮助下周或下个月的开发人员。但这并不能帮助运行代码并且工作刚刚失败的人,当然也不能帮助企业完成任何需要完成的工作。
记住,我们在当下是全能的。在编写代码或跳过编写代码时,我们都是从优势和知识的角度出发的。我们花费了数小时甚至数天的时间来理解文档、现实、其他错误、其他问题,并且我们留下了代码、注释,甚至可能是文档。我们编写的代码分享了成功,而成功是我们的用户想要的。但在学习过程中有很多失败;我们也可以把失败抛在脑后。
在代码中留下消息
Error on line 30
对任何人都没有帮助。至少,我可以处理明显的错误情况,并提供更好的错误消息
- name: Fail if zero vlan views returned
fail:
msg: "Got 0 results from searching for VLAN view {{ vlan_parent_view_name }}. Please verify exists in otherthing, and is accessible by the service account."
when: _otherthing_search_result | length == 0
在四行代码(以及零额外思考)中,我对下一个接手的人——那个倒霉的运营团队成员,或者更可能是一个月后的我——提供了具体的、有用的建议,关于真正与代码无关的现实世界问题。这条消息允许任何人发现简单的复制/粘贴错误,或者记录系统已更改。无需 Ansible 知识,无需凌晨 3 点呼叫开发人员“查看第 30 行”。
但是等等!还有更多!
在了解 otherthing
的过程中,我了解到它在一个关键方面有点傻。它的许多(如果不是全部)记录类型都没有唯一性约束,并且可能存在多个相同的记录。vlan 视图被定义为具有名称、起始 ID 和结束 ID;其他记录类型也类似地简单,并且显然应该是一个唯一的元组——基于现实和数据库规范化的抽象概念。但是 otherthing
允许重复的元组,尽管从概念上来说这是不可能的。
在我的实验室中,我很乐意尝试记住不要这样做。在企业生产环境中,我可能会编写一个策略。无论哪种方式,经验都告诉我,系统会被破坏,它会在糟糕的时间被破坏,并且这些问题可能需要很长时间才能成为真正的问题。
对于 Error on line 30
,一个经验丰富的 Ansible 开发人员可能会将其识别为“未找到记录”,而无需了解其他任何信息,这足以解决问题。但是,_thatthing_search_result[0]
有时才是正确的 vlan_view_ref
要糟糕得多——它允许世界在静默中被破坏。并且错误可能在完全不同的地方显现出来;也许六个月后的安全审计会将此标记为记录保存不一致,并且如果有多个工具和手动访问,则可能需要数天或数周才能追踪到是这段特定代码的错误。
在摆弄 API 的几天里,我了解到了这一点。我不是在寻找问题;如果它被记录在案,我也没有看到它。所以我想到了这篇文章的重点。与其因为这是一个实验室而忽略这种不可能的情况,修复它,然后继续前进,不如花两分钟时间留下代码——不是注释,不是笔记,不是文档——而是始终会运行的代码,它涵盖了这种不可能的情况
- name: Fail if >1 views returned
fail:
msg: "Got {{ _otherthing_search_result | length }} results from searching for VLAN view {{ vlan_parent_view_name }}. Otherthing allows this, but is not handled by this code."
when: _otherthing_search_result | length > 1
我手动创建了故障条件,所以我可以手动测试这种情况。我希望它永远不会在实际使用中运行,但我确信它会运行。
如果(当)该错误在生产环境中发生时,那么有人可以决定该怎么做。我希望他们修复错误的数据。如果它经常发生,我希望他们追踪到另一个损坏的系统。如果他们要求删除这段代码,而这段代码做了未定义和错误的事情,那是他们的特权,也是我不想工作的地方。代码是不完美的,但它是完整的。这是工匠的工作。
真实世界中的自动化是一个迭代过程,它与不完善的系统作斗争并平等地使用它们。它永远无法处理所有异常情况。它甚至可能无法处理所有正常情况。通过 lint、代码审查和验收测试的工作代码是处理安全和必要路径的代码。只需付出一点努力,你就可以通过不仅标出安全路径,还留下你发现的危险警告来帮助后来者。
评论已关闭。