TT Lab
开始
学习 学习路径 课程

Terraform/OpenTofu 基础

创建的块与读取的块

在 TT Lab 中继续学习

一句话总结

resource 拥有对象,data 只读取对象。这一字之差,决定了状态的形态、计划的时点和破坏的范围。

为什么需要这种区分

早期的配置语言里只有 resource。然而现实中的基础设施并不是由一个团队全部创建的。网络已经由基础设施团队建好,证书由安全团队签发,账号 ID 全公司只有一个。要使用这些东西就需要它们的值,但如果为了获取值而写成 resource,工具就会误以为它是自己的。一旦它相信自己拥有了,destroy 就会想去删除别人的网络,漂移检测也会想去改回别的团队所做的修改。

于是出现了单独的“只读取的块”。data 块既不创建、不修改,也不删除对象。状态中虽然会多出一个条目,但它不是所有权凭证,而是最近一次读取的值的副本。所以即使执行 destroy,原对象也会保留,计划中出现的不是 create 或 destroy,而只有 read。

工作原理

打开状态文件,每个条目都有 mode。它的值只有两种:managed 或 data。tofu state list 输出地址时是否带上 data. 前缀,也取决于此。

tofu state list
# data.local_file.seed      ← mode = "data"
# local_file.copy           ← mode = "managed"

读取何时发生,是第二个分岔点。默认发生在计划阶段。如果参数都是已知的值,工具会在生成计划时读取,并把读到的值直接写进计划。所以评审者只看计划就能知道会放进什么。

读取被推迟到应用时点,有两种情形:参数依赖于尚未创建的资源的属性;以及数据源上带有 depends_on。后者即使原对象已经在磁盘上也会被推迟——因为它声明的是“等依赖完成之后再读取”。这时计划中会这样显示。

  # data.local_file.back will be read during apply
      + content = (known after apply)

值变成未知之后,使用该值的下游资源的属性也会全部变成未知。计划评审的价值相应下降,所以给数据源加 depends_on 时,必须再确认一次是否真的需要。

第三个分岔点是会重新读取。managed 资源会比较状态中记录的值和实物来发现漂移,而数据源没有可比较的东西。它在每次计划时都重新读取,并把这个值传给下游。如果原对象发生变化,捕捉到的不是“状态不一致”,而是“输入变了”;如果该值是会引发重新创建的属性,下游就会被整个替换。

读取不到时的失败,时点也不同。如果文件不存在,就走不到应用,计划会在那里停住。

Error: Read local file data source error
+Original Error: open ./absent.txt: no such file or directory

还有一个名字容易混淆的兄弟。terraform_data 不是 data 块,而是内置 provider 提供的 managed 资源。它在状态中的 mode 是 managed,修改值时计划中会出现 update 或 replace,执行 destroy 就会消失。由于名字的缘故,很多人把它当成数据源来读,但这个块的用途不是读取值,而是在状态中留下“一旦变化就让某件事重做的标记”。

在现场相遇的样子

最常见的事故是把别人创建的东西写成 resource。如果把已经存在的对象声明为 resource,工具就会想重新创建它;名称冲突时应用会失败,名称不冲突时则会多出一个完全一样的东西。后者更糟糕——没有人看到失败,重复的资源却不断堆积。只需要读取的东西写成 data;如果确实需要拥有已有的东西,就用 import 把它纳入管理。

第二常见的是计划被未知值刷满。评审文化扎实的团队往往有“计划中所有值都看得到才批准”的规则,但只因为给一个数据源加了 depends_on,就出现了无法批准的计划。这种时候,把依赖从数据源移到使用其值的资源一侧会更好。

第三种是相反的方向:原对象悄悄变化,导致下游被替换。配置文件一个字符都没有变,计划中却出现了替换。如果在代码里找原因,永远找不到。应先查看数据源读取的原对象是什么,以及谁可以修改它。

下一项实验要做什么

使用 Pod 中的 OpenTofu 和 local·random·null provider,走完八个步骤。先用 data 读取一个文件并复制,在状态中用 mode 区分两个条目,确认 destroy 之后原对象仍然保留。接着查看:读取名称在应用时才确定的文件时,计划会如何不同;给已经存在的文件加上 depends_on 会推迟什么;原对象不存在时,计划会在哪里停住。最后两步,把 terraform_data 的 mode 与数据源并排比较,并修改原对象,确认下游被替换。