Day 03-Terraform State 之你的 Local State 不是我的 State

Terraform State 之你的 Local State 不是我的 State

State 是初学 Terraform 的核心概念,本章节会讲解基本的 State 原理。

课程内容与代码会放在 Github 上: https://github.com/chechiachang/terraform-30-days

赛後文章会整理放到个人的部落格上 http://chechia.net/

追踪粉专可以收到文章的主动推播

https://ithelp.ithome.com.tw/upload/images/20210901/20120327NvpHVr2QC0.jpg


上一讲 Day 02-是在 Hello?什麽都要 Hello 一下之 Hello Terraform,我们操作 terraform 指令,来 create / update / destroy 远端的资源。在执行完成 terraform apply 後,本地资料夹会产生一个 terraform.tfstate 档案。

初探 .tfstate

首先,我们看一下 terraform.tfstate 档案的内容。你可以使用文字编辑器,或是透过 shell 与 jq 工具来检视 State

cat terraform.tfstate
cat terraform.tfstate | jq keys
[
  "lineage",
  "outputs",
  "resources",
  "serial",
  "terraform_version",
  "version"
]

其中 .resources 是纪录 .tf 档案产生的资源,在远端的 instance 的实际资料。换句话说,Terraform apply 後产生资源满足 .tf 的描述,而实际在远端的实体是哪一个,有哪些资料,纪录在 State 中。

再举个例,如果我们想要产生多个不同的 _poc/foundation/*.tf 中的 resource,复制 .tf 档案在 apply,会获得另一组 foundation resource,如另一组 resource group 与 storage account,有相同的参数,但有不同的远端 id。

我们还可以比较 .tfstate 的内容,与 azure console 上看到的内容,更能理解两者个关系。

State 设计

根据官方文件 描述 State 的设计与功能,这边简述几个重点,底下会有范例详述

  • Mapping to the Real World:只有 .tf 档案的话,并不能明确的对应到远端的 resource,Terraform 托管需要的 metadata 并存放在 State 中
  • Metadata:除了 mapping 必须资料外,State 也而外存放 Terraform runtime 需要的资料(之後章节描述)
  • Performance:Terraform plan 与 apply 需要先 refresh 远端的状态,State 作为一层资料 Cache,可以加速工作流程
  • Syncing:多人协作

State 与 Backend

厘清名词 State

  • State 是一个 terraform 的核心架构,在原始码中是一个抽象层,意思是 terraform 支援许多不同的 State 实作,不同实作的机制不同,但都能满足上面描述的 State 概念,满足 Terraform 执行时的需求
  • 本地的 .tfstate 是使用 Local Backend 这个 Backend 实作来管理 State 时,於本地储存的 State 档案。换句话说,如果不使用 Local Backend,本地就不会产生这个 .tfstate

Issues

使用 Local State 有许多好处

然而使用 Local State 也有以下几个问题

没有 .tfstate 档案就无法使用 Terraform

Q: 没有 .tfstate 档案就无法使用 Terraform 吗?可是我的 .tf 档案里 name 写得很清楚,这样 Terraform 抓不到远端相同名字的资源吗?

我们可以做个实验,切换到另外一个 foundation cloned 资料夹,内容与 foundation 完全一致。

cd ../foundation_cloned
terraform init && terraform plan

plan 的结果是什麽?

是否令人有些疑惑?

  • 内容完全一样的 .tf 档案,plan 出来的结果却是要重新产生所有资源
  • 实际执行 terraform apply,provider 会回覆错误 'A resource with the ID ".../resourceGroups/terraform-30-days-poc" already exists"'

我们原先预想透过相同的 .tf 档案来管理 resourceGroup/terraform-30-days-poc,然而 Terraform 无法追踪远端已经存在的 resource group,而是选择 create 一个新的 resource group,provider API 送出後,远端的 Azure 回传错误

更明确的说明:没有 State 便无法 mapping .tf resource,与远端 resource

我们可以进一步检视,.tfstate 档案的哪些内容,使得一边可以正常使用 Terraform,一边何谓产生错误。使用 jq 工具来检视 State 中与 resource gropu 有关的资料。

cat terraform.tfstate | jq '.resources[0]'

{
  "mode": "managed",
  "type": "azurerm_resource_group",
  "name": "rg",
  "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
  "instances": [
    {
      "schema_version": 0,
      "attributes": {
        "id": "/subscriptions/6fce7237-7e8e-4053-8e7d-ecf8a7c392ce/resourceGroups/terraform-30-days-poc",
        "location": "southeastasia",
        "name": "terraform-30-days-poc",
        "tags": null,
        "timeouts": null
      },
      "sensitive_attributes": [],
      "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo1NDAwMDAwMDAwMDAwLCJkZWxldGUiOjU0MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjo1NDAwMDAwMDAwMDAwfX0="
    }
  ]
}

cat terraform.tfstate | jq '.resources[0].instances[0].attributes'

{
  "id": "/subscriptions/6fce7237-7e8e-4053-8e7d-ecf8a7c392ce/resourceGroups/terraform-30-days-poc",
  "location": "southeastasia",
  "name": "terraform-30-days-poc",
  "tags": {},
  "timeouts": null
}

Terraform 产生 resource group 後,将 azure API 回覆的 resources 各项参数纪录在 .tfstate 中。下次要再进行编辑时,我们编辑 .tf 档案,Terraform 则会依据 .tfstate 档案去 mapping 远端的 resource group,进行 plan 与 apply。

熟悉 RESTful API 的朋友可以这样想:使用 POST API 产生物件时,後端 server 会回传 id 在 response body 中,下次要编辑这个相同物件,则需要使用 id 作为辨识。Terraform 其实是相同的原理,只是在这个例子中,Terraform 协助托管了 id 这个 metadata。

协作问题

既然使用 terraform 时,必须仰赖 State .tf 档案,那是否一起协作的团队成员就必须要取得 .tfstate 档案,才能正确的操作 Terraform?

是的,这也是使用 Local Backend 的 .tfstate 档案,最大的问题,会造成多人协作十分困难。过往旧版的 Terraform 有几个妥协的做法:

将 .tfstate 档案与 .tf 档案一起纳入版本控制系统,具体流程可能是这样

  • 编辑完 .tf 档案後,commit
  • 依据新的 .tf 档案,执行 terraform plan / apply,产生新的 .tfstate 档案後,commit .tfstate 档案
  • 推上版本控制系统 (ex. Github) 其他团队成员只要 git pull 最新的 .tf 与 .tfstate 档案,就可以正确使用 Terraform

理想上是这样,但实务上还是非常不便

  • 必须要确定 state 以一直最新的,团队成员如果有没 commit 的 state,马上会造成 conflicts 与开发 blocking
  • .tf 有事需要 review 与测试
  • State 中可能包含 sensitive 资料,sensitive 资料不宜放到版本控制系统

本课程不建议把 .tfstate 加入到版本控制,本 repository 已经将.tfstate 加入到 .gitignore 中。

sensitive 资料与安全性

所有在 terraform plan / apply 中产生的 data 与 meta-data,都会纪录在 tfstate 中,那是否有一些 State 内容是敏感资料,不希望让他人看到?

我们可以使用 _poc/user/ 为例子。首先检视一下内容 .tf 档案,这个档案使用 Terraform random password 来产生一组随机密码,然後使用这组密码作为新的 User 的登入密码。

# _poc/user/ad_user.tf
resource "random_password" "terraform" {
  length           = 16
  special          = true
  override_special = "_%@"
}

resource "azuread_user" "terraform" {
  user_principal_name = "[email protected]" # Need valified domain on Azure AD
  display_name        = "Terraform Runner"
  mail_nickname       = "terraform"
  password            = random_password.terraform.result
}

产生 user terraform 资源

terraform apply

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + password            = (sensitive value)
  + user_principal_name = "[email protected]"
...


Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

password = <sensitive>
user_principal_name = "[email protected]"

Apply 完成後,我们可以看一下 _poc/user 产生的资料,这边有点有趣

  • 我们 .tf 档案中有 output.tf,将产生的资料输出,让外部可以使用。例如,使用者可取得这组密码来进行登入
  • 然而 terraform output 却输出 password =

我们可以在用其他方法印出 sensitive 资料:

terraform output

password = <sensitive>
user_principal_name = "[email protected]"

terraform output -json

{
  "password": {
    "sensitive": true,
    "type": "string",
    "value": "QfgdxMwVRKr41nHG"
  },
  "user_principal_name": {
    "sensitive": false,
    "type": "string",
    "value": "[email protected]"
  }
}

cat terraform.tfstate | jq '.resources[1].instances[0]'
{
  "schema_version": 0,
  "attributes": {
    "id": "none",
    "keepers": null,
    "length": 16,
    "lower": true,
    "min_lower": 0,
    "min_numeric": 0,
    "min_special": 0,
    "min_upper": 0,
    "number": true,
    "override_special": "_%@",
    "result": "QfgdxMwVRKr41nHG",
    "special": true,
    "upper": true
  },
  "sensitive_attributes": [],
  "private": "bnVsbA=="
}

我们可以看到,所有密码还是明码的印出来。

  • 就算使用 sensitive 关键字,本地产出的资料,Terraform 还是需要透过 State 管理
  • Local Backend 的 .tfstate 是没有任何加密的,取得档案就取得密码,取得 State 就可以取得隐私资料
  • 如果使用版本控制,这个密码也会存在 Git objejct 中

更好的方式是

  • 使用其他 Backend 来管理 State
  • 使用有加密功能的 Backend

下堂,我们要来使用远端的 Backend 与 State

Homework

尝试使用 terraform state 的几个指令

terraform state --help
terraform state list
terraform state show

terraform destroy 删除所有本课程的产物

阅读以下官方文件

References


<<:  30天轻松学会unity自制游戏-了解unity基础操作

>>:  【Day1】简介 and 30天大致的内容

6. STM32-NVIC USART

USART介绍 USART全名为通用同步/非同步收发传输器(universal synchronou...

[Day 52] 留言板後台及前台(八) - 加入图片上传

在正文之前要说一下, 其实我觉得在留言板用文字编辑器不是个好主意, 反而应该放在心情随笔的地方, (...

追求JS小姊姊系列 Day24 -- 工具人、姐妹不只身份的差别(下):从记忆体看宣告变数的可变性

前情提要 被第一人视角的我打断了对话,现在要继续讲完: 在D22的时候,我们知道了识别字、保留字,其...

Day 28 Realm的练习-使用者注册系统(2/3)

昨天我们把一资料库建里好之後,今天我们学习怎麽把资料写进去吧~ 在MainVC里面先建立一个空阵列,...

EP 11 - [TDD] 建立 Gateway

Youtube 频道:https://www.youtube.com/c/kaochenlong ...