Day 05-撰文在疫苗发作时,之module 是 terraform 执行与调用的基本单位

module 是 terraform 执行与调用的基本单位。本章简单介绍 module 的内容与使用。

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

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

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

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


Terraform Module

module 在 Terraform 中的定义很简单,就是一个 container,里头有一组一起使用的 resource .tf。然而除了容纳一组 resource 以外,module 还有须多额外的功能。

  • directory 中至少一个包含一个 root module
  • 可以调用其他的 module
  • 可以透过 module input / output 传值

Example

我们看一下 _poc/container_registry_module 这边的范例

module "test" {
  source = "../../..//azure/modules/container_registry"

  registry_name                 = "chechiatest"
  resource_group_name           = local.resource_group_name
  location                      = local.location
  public_network_access_enabled = true
}

module "registry" {
  for_each = toset(local.environments) # convert tuple to set of string

  source = "../../..//azure/modules/container_registry"

  registry_name                 = "chechia${each.value}"
...
}

这里使用 module block,来宣告一组 child module

  • module 的程序码来源,使用 source argument
  • 其他的 input argument (例如 registry name),则在底下宣告
  • 由於有重复的 arguments,使用 locals block 宣告参数,然後使用 local. reference 到 local variable

Module meta-argument

使用另外一组 module block,来宣告另一组 child module

  • 使用 for_each meta-argument,来宣告这组 module 里面,由多个 instance 组成
  • 使用 ${each.value},将 registry name eval 成为 "chechiadev", "chechiastag", "chechiaprod" 三个名称,给三个 instance 使用

Module Init

执行 Terraform init,会在 init

  • 扫描 module 与 source 内容,检查有无 module 设定错误(ex. 路径错误找不到 source),
  • 初始化 Backend,我们这边是 remote Backend,会对远端进行初始化
  • 根据 modules 内部的内容,计算 module 需要的 providers 与 dependency
    • 由於我们这边全部都是使用 azurerm provider,.terraform/providers 中只有 azurerm
    • 如果有多组 provider,会一并下载到 .terraform/providers
terraform init

Initializing modules...
- registry in ../../../azure/modules/container_registry
- test in ../../../azure/modules/container_registry

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Installing hashicorp/azurerm v2.65.0...
- Installed hashicorp/azurerm v2.65.0 (signed by HashiCorp)
...

我们可以看一下 .terraform 内容

  • 多了一个 .terraform/modules 资料夹
  • 多了一个 .terraform/modules/modules.json
cat .terraform/modules/modules.json  | jq

{
  "Modules": [
    {
      "Key": "",
      "Source": "",
      "Dir": "."
    },
    {
      "Key": "registry",
      "Source": "../../..//azure/modules/container_registry",
      "Dir": "../../../azure/modules/container_registry"
    },
    {
      "Key": "test",
      "Source": "../../..//azure/modules/container_registry",
      "Dir": "../../../azure/modules/container_registry"
    }
  ]
}
  • 第1个 module 是 "Dir": ".",也就是 _poc/container_registry_module root module 本身
  • 第2, 3个 module 是 ../../../azure//azure/modules/container_registry module
    • 附上解析出来的相对路径

如果 module 有任何新增删除,或是设定改动,都需要重新 init,因为 module 的初始化在 init 步骤处理。

初始化完成,进行 plan,看看是否如我们预期

terraform plan

 # module.registry["dev"].azurerm_container_registry.acr will be created
  + resource "azurerm_container_registry" "acr" {
      + location                      = "southeastasia"
      + name                          = "chechiadev"

  # module.registry["prod"].azurerm_container_registry.acr will be created
  + resource "azurerm_container_registry" "acr" {
      + location                      = "southeastasia"
      + name                          = "chechiaprod"

  # module.registry["stag"].azurerm_container_registry.acr will be created
  + resource "azurerm_container_registry" "acr" {
      + location                      = "southeastasia"
      + name                          = "chechiastag"

  # module.test.azurerm_container_registry.acr will be created
  + resource "azurerm_container_registry" "acr" {
      + location                      = "southeastasia"
      + name                          = "chechiatest"

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

plan 结果

  • 产生一组 module.test.azurerm_container_registry.acr
  • 产生一组 module.registry object,内容为三个 instance,依字母排序分别为
    • module.registry["dev"]
    • module.registry["prod"]
    • module.registry["stag"]

Terraform apply,terraform 便会平行化处理产生这四个 registry

terraform apply

...
module.registry["stag"].azurerm_container_registry.acr: Creating...
module.registry["dev"].azurerm_container_registry.acr: Creating...
module.registry["prod"].azurerm_container_registry.acr: Creating...
module.test.azurerm_container_registry.acr: Creating...
...

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

root module

Terraform 官方文件 对 module 的说明

  • 每组资料夹中的 terraform configuration 至少有一个 module,意思是在 _poc/container_registry 中操作,本身便是一组 root module。

换句话说,当资料夹中只有一组,便不会产生 .terraform/modules/ 资料夹及档案。这里跟程序码的实作比较有关,使用上不用额外注意。

为何需要 Module

随着 Terraform 使用越久,我们会开始产生越来越复杂的 .tf 档案,以满足我们的需求,而这些需求有可能是类似的功能,不断重复。例如:

_poc/container_registry 中,我们设定了一组 container registry。在现实的应用中,我们可能会产生多组 Registry,给不同服务使用。一个实际的例子:开发流程中产生多组功能相同的 infrastructure resources,作为测试环境与生产环境,除了 poc-registry 外,我们可能会产生 dev-registry,stag-registry,prod-registry,内容完全相同,承载开发流程中不同用途的软件环境。

使用截至目前所学,可能是把 .tf 档案复制多份,更改成名称等参数,apply 上去就获得多组不同的 registry。

resource "azurerm_container_registry" "acr" {
  name                     = "chechia-poc"
  resource_group_name      = "terraform-30-days-poc"
  location                 = "southeastasia"
}

resource "azurerm_container_registry" "acr" {
  name                     = "chechia-dev"
  resource_group_name      = "terraform-30-days-poc"
  location                 = "southeastasia"
}

resource "azurerm_container_registry" "acr" {
  name                     = "chechia-stag"
  resource_group_name      = "terraform-30-days-poc"
  location                 = "southeastasia"
}

resource "azurerm_container_registry" "acr" {
  name                     = "chechia-prod"
  resource_group_name      = "terraform-30-days-poc"
  location                 = "southeastasia"
}

这个做法十分直观,而且确实能用。Terraform 对相同 directory 中的 .tf 数量也没有限制,也就是说我们可以使用无限的复制贴上,来解决。

可以尝试更改 _poc/container_registry 的资料夹,试着 apply。完全没问题,是吧?

Don't Repeat Yourself

我们很快发现

  • 上面这组 registry 除了档案名称以外,其他部分的内容都相同,导致 .tf 内容都是重复的
  • 如果未来想要修改(ex. azure 发布新功能)我必须重复修改多次,浪费时间
  • 另外一个 repository 也想使用相同的 resource block,复制过去等於是 hard fork,不会跟上这边的更新

Don't Repeat Yourself (DRY),是软件工程中不同领域共通的最佳实践。不断重复的代码,本身就代表而外的维护成本。那在 Terraform 中我们有没有可能重复使用 .tf 中的内容?

Issues

使用的本地 module 会有一些问题

  • module 程序码重用
  • module 版本锁定

Issues: module sharing

开头提到 module 可以方便程序码重用,可以使用社群维护的 module,然而到目前我们还是无法使用社群维护的 terraform module,例如:

我想使用 Azure 维护的 AKS module,可以让我直接建立 Azure Kubernetes Service
目前使用本地 module 的话,我就要把整个 AKS module 里面的 .tf copy 到本地 repository 中使用。如此确实可以运作,但 copy 等於失去远端的 reference,如果後续远端有更新,本地也很难使用

另外,本地的 module 很难给另外一个 repository 使用,例如:

我又开一个 github.com/chechiachang/terraform-30-weeks 的 repository,那要如何使用 terraform-30-days 中的 module?

我们写成 module 是希望尽可能量重复使用相同程序码,但希望是类似 git submodule 的方式,保留对远端的 reference,有更新可以 git pull 下来使用

Issues: module version locking

module 会随着使用持续修改,如果我今天希望修改 module 内容,但 module 已经在使用中了,该如何处理?例如:

开发 //azure/modules/container_registry 中的新功能

  • 希望在 module "test" 中测试,但
  • 不要影响 module "registry" 上面的功能,特别是 stag / prod 环境,可能有其他团队在测试
module "test" {
  source = "../../..//azure/modules/container_registry"
  ...
}

module "registry" {
  for_each = toset(local.environments) # convert tuple to set of string
  source = "../../..//azure/modules/container_registry"
  ...
}

以上面的 .tf 档案,改了 //azure/modules/container_registr 的话,所有使用到的内容都会一起改变。实务上会希望可以把现有的程序码锁在旧的版本,新的程序码使用本地 module 继续开发新功能。例如打 version tag:

module "registry" {
  for_each = toset(local.environments) # convert tuple to set of string
  source = "../../..//azure/modules/container_registry?ref=v0.1.0"
  ...
}

事实上,Terraform 不只支援本地的 module,还有许多类型的 module 可以解决上述问题(请见下章)

Source code

对於 terraform init module 的程序码,有兴趣请见 Terraform Github

Homework

  • 阅读 Module 官方文件
  • 修改 _poc/container_registry_module,使用其他的 module argument count,取代 for_each,达成一样的效果
  • 完成一组 module,push 到 Github 上,使用 ssh 方式使用 remote module

Remote module

至此,我们已经学会使用 module 来重用(reuse) .tf 档案,这边只完成课程目标的一半。熟悉开源专案,我们很自然会想到,有没有可以使用社群维护的 module,来让我们使用。下堂课将分享,module 各种不同的 ㄩodule remote source,以及如何分享及使用远端的 module?


<<:  每个人都该学的30个Python技巧|技巧 5:各种运算子(上)(字幕、衬乐、练习)

>>:  [Day5]Count on Cantor

【心得】Sublime TexT 3 即时连线

一开始学习时用Sublime,久了之後也成为一种习惯(,,・ω・,,) 但是初学者很喜欢写一步骤就要...

Day17 React-Router(二)Route设置进阶

讲完最基础的Route设置之後, 来学习如何更准确的经由path来渲染画面上的元件。 Route标签...

Angular 深入浅出三十天:表单与测试 Day09 - 整合测试实作 - 登入系统 by Reactive Forms

昨天帮我们用 Reactive Forms 所撰写的登入系统写完单元测试之後,今天则是要来为它写整...

如何定期备份 MySQL 及删除旧有档案-适用 Windows

MySQL 是免费的关联式资料库,具有轻量级速度快的优点,适合小型网站架设使用。 目前最流行的 Wo...

【DAY 14】问卷、测验样样行 – Microsoft Form 让您搜集资料事半功倍!

哈罗大家好~ 今天要跟大家介绍 Microsoft Form 问卷调查工具,不过相信目前云端问卷工具...