改进你的 Ansible 剧本的 4 行代码
只要付出一点点努力，你就可以帮助下一个人，不只是绘制出安全路径，还可以留下危险的警告。
Ansible
只要付出一点点努力，你就可以帮助下一个人，不只是绘制出安全路径，还可以留下危险的警告。

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


![](/data/attachment/album/202101/23/225612fowskoyyk1gtwtog.jpg "A person programming")


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


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


### 要解决的问题


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


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


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


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



```
- 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` 标识符是不可用的，所以即使把它移到<ruby> 清单文件 <rt>  inventory </rt></ruby>或额外的变量中也没有什么帮助。<ruby> 剧本 <rt>  playbook </rt></ruby>的用户需要对系统有一些更深入的理解，才能找出正确的引用 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](https://www.ansible.com/products/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](https://opensource.com/users/jeffwarncia) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出

