改进你的 Ansible 剧本的 4 行代码
创始人
2024-03-02 06:27:54
0

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

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

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

要解决的问题

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

如果我们的目标是一个超紧凑的、直接的、笨拙的宏,那么它可能用 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"

不幸的是,除了通过 API,vlan_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中国 荣誉推出

相关内容

ansible完全攻略
ansible完全攻略 ansible安装: apt安装: apt...
2025-05-31 03:08:02
不在运行中的EC2实例上运...
是的,可以在不运行中的EC2实例上运行Terraform/Ansi...
2025-01-11 16:31:42
不使用Vagrant的情况...
要在不使用Vagrant的情况下使用Ansible和Virtual...
2024-12-29 12:31:06
不使用ansible_ho...
在Ansible中,可以使用inventory_hostname来...
2024-12-28 10:00:32
不使用 -u 标志的Ans...
要在不使用 -u 标志的情况下实现Ansible特权升级,可以使用...
2024-12-28 07:01:52
不明确的ansible变量...
在Ansible中,可以使用default过滤器来处理不明确的变量...
2024-12-26 22:31:12

热门资讯

Helix:高级 Linux ... 说到 基于终端的文本编辑器,通常 Vim、Emacs 和 Nano 受到了关注。这并不意味着没有其他...
使用 KRAWL 扫描 Kub... 用 KRAWL 脚本来识别 Kubernetes Pod 和容器中的错误。当你使用 Kubernet...
JStock:Linux 上不... 如果你在股票市场做投资,那么你可能非常清楚投资组合管理计划有多重要。管理投资组合的目标是依据你能承受...
Epic 游戏商店现在可在 S... 现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。...
《Apex 英雄》正式可在 S... 《Apex 英雄》现已通过 Steam Deck 验证,这使其成为支持 Linux 的顶级多人游戏之...
从 Yum 更新中排除特定/某... 作为系统更新的一部分,你也许需要在基于 Red Hat 系统中由于应用依赖排除一些软件包。如果是,如...
通过 SaltStack 管理... 我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)...
如何在 Github 上创建一... 学习如何复刻一个仓库,进行更改,并要求维护人员审查并合并它。你知道如何使用 git 了,你有一个 G...
Opera 浏览器内置的 VP... 昨天我们报道过 Opera 浏览器内置了 VPN 服务,用户打开它可以防止他们的在线活动被窥视。不过...
如何检查你的 Linux 系统... 不知道在使用哪个初始化系统?以下是方法。每个主流 Linux 发行版(包括 Ubuntu、Fedor...