TranslateProject/published/202101/20210105 4 lines of code to improve your Ansible play.md
2021-02-01 10:26:42 +08:00

12 KiB
Raw Permalink Blame History

改进你的 Ansible 剧本的 4 行代码

只要付出一点点努力,你就可以帮助下一个人,不只是绘制出安全路径,还可以留下危险的警告。

在博客圈里,人们对基础架构即代码、持续集成/持续交付CI/CD管道、代码审查和测试制度赞不绝口但人们很容易忘记这种精心设计的象牙塔只是一种理想而不是现实。虽然不完美的系统困扰着我们但我们必须交付一些东西。

在系统自动化的过程中,很少有比那些通过粘合 API 创建的象牙塔更脆弱的塔。这是一个脆弱的世界。要让它“工作起来”,交付它,然后继续前进,压力巨大。

要解决的问题

想象一个简单的功能请求:编写一些 Ansible 代码,在外部系统中创建几条记录,以记录一个 VLAN 的一些详细信息。我最近很想做一些实验室的管理工作来完成这个任务。这个外部系统是一个常见的互联网协议地址管理Internet Protocol Address ManagementIPAM工具但对于一个更抽象的配置管理数据库Configuration Management DataBaseCMDB或一个与网络无关的记录来说困难是一样的。在这个例子中我创建一个记录的直接愿望就是让系统保存记录而已。

如果我们的目标是一个超紧凑的、直接的、笨拙的宏,那么它可能用 100 行代码就能写出来。如果我记得 API我也许能在一个小时内把它敲出来该代码的作用不会超过预期除了确切的成品之外什么也没留下。对它的目的而言是完美的但是对未来的扩展毫无用处。

如今,我希望几乎每个人都能从一个角色role和几个任务task文件开始这项任务准备扩展到十几个创建、读取、更新和删除CRUD操作。因为我不了解这个 API我可能会花上几个小时到几天的时间仅仅是摆弄它弄清楚它的内部模式和工艺弥和它的功能和我用代码编写出来的意图之间的差距。

在研究 API 的时候,我发现创建一个 VLAN 记录需要一个父对象引用 vlan_view_ref。这看起来像一个路径片段,里面有随机字符。也许它是一个哈希,也许它真的是随机的,我不确定。我猜想,许多在泥泞中挣扎的人,在迫在眉睫的截止日期前,可能会把这个任意的字符串复制粘贴到 Ansible 中,然后继续混下去。忽略这个角色role的实现细节,显而易见这个剧本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"

不幸的是,除了通过 APIvlan_view_ref 标识符是不可用的,所以即使把它移到清单文件inventory或额外的变量中也没有什么帮助。剧本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 }}"

但有时 _otherthing_search_result[0] 是未定义的,所以 _thatthig_vlan_view_ref 也将是未定义的。很有可能是因为代码运行在不同的真实环境中,而有人忘记了在清单中或在命令行中更新 {{ vlan_parent_view_name }}。或者无论公平与否也许有人进入了工具的图形用户界面GUI删除了记录或更改了它的名称什么的。

我知道你在想什么。

“好吧,不要这样做。这是一个没有哑巴的场所。不要那么笨。”

也许我对这种情况还算满意反驳道“Ansible 会很正确的告诉你错误是list 对象没有元素 0,甚至会带个行号。你还想怎样?”作为开发者,我当然知道这句话的意思 —— 我刚写的代码。我刚从三天的和 API 斗智斗勇中走出来,我的脑子很清醒。

明天是另一个故事

但是到了明天,我可能会忘记什么是父对象引用,我肯定会忘记第 30 行上的内容。如果一个月后出了问题,就算你能找到我,我也得花一个下午的时间重新解读 API 指南,才能搞清楚到底出了什么问题。

而如果我出门了呢?如果我把代码交给了一个运维团队,也许是一个实习生通过 Tower 来运行,把 vlan_view_name 手动输入到表单之类的东西呢?那第 30 行出的问题是对他们没有帮助的。

你说,加注释吧! 嗯,是的。我可以在代码中写一些梗概,以帮助下周或下个月的开发人员。这对运行代码的人没有帮助,他的“工作”刚刚失败,当然对于企业也无济于事。

记住,我们此刻无所不能。在写代码或者跳过写代码的时候,我们是站在实力和知识的立场上进行的。我们花了几个小时,甚至几天的时间,研究了文档、现实、其他 bug、其他问题我们留下了代码、注释甚至可能还有文档。我们写的代码是分享成功的而成功正是我们用户想要的。但是在这种学习中也有很多失败的地方我们也可以留下这些。

在代码中留言

“第 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 允许重复的元组,尽管在概念上讲永远不可能。

在我的实验室里,我很乐意尝试并记住不要这样做。在企业生产环境中,我可能会写一个策略。不管是哪种方式,经验告诉我,系统会被破坏,会在倒霉的时候被破坏,而且可能需要很长时间才能让这些问题发酵成,嗯,一个问题。

对于 “第 30 行有错误”,一个本来有丰富经验的 Ansible 开发者可能会认识到这是“记录没有找到”,而不用知道其他的事情就足以解决这个问题。但如果 _otherthing_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、代码审查和验收测试的工作代码是处理安全和所需路径的代码。只要付出一点点努力你就可以帮助下一个人不仅仅是绘制安全路径还可以对你发现的危险留下警告。


via: https://opensource.com/article/21/1/improve-ansible-play

作者:Jeff Warncia 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出