理解 Terraform 中的 Provider 安装处理(2021年2月版)

首先

在Terraform中采用了基于插件的架构,用户可以通过使用插件自由扩展Terraform的功能。作为插件,有两种类型的支持,即用于控制基础设施提供者如AWS的资源的提供者(Provider)和用于设置创建的资源的设置工具(Provisioner)。

Provisioner 1 包含在 Terraform 二进制文件中,但 Provider 不包含。因此,在初始化 Terraform 时,根据需要可以从 HashiCorp 提供的 Terraform Registry 或其他外部注册表中安装 Provider。

当执行terraform init作为具体的处理流程时,首先Terraform会读取工作目录中的配置文件,并判断需要安装的Provider以进行资源控制。然后,检查已安装的Provider,并从外部注册表下载并安装缺失的Provider。最后,当再次执行terraform init时,会将版本和哈希值写入名为.terraform.lock.hcl的锁文件中,以便使用相同的Provider,完成整个流程。

通常情况下,按照上述流程进行,但如果执行Terraform的主机受到组织或地区的防火墙限制无法访问外部注册表,则需要预先安装提供程序,或者使用执行主机网络可达的私有注册表。

考虑到这种规格,我们将介绍本次关于 Terraform Provider 安装过程的规格。需要说明的是,这篇文章是根据截至2021年2月24日的最新版本v0.14.7整理而成的。

判断如何安装 Provider

在中国的官方文档中,有一条指南为:Provider的安装将根据.tf文件中的required_providers代码块中定义的Provider源地址和版本进行判断。如果没有定义required_providers代码块,则将根据.tf文件内部的定义进行判断。

如果是以下所述的设置情况,需要安装 registry.terraform.io 注册表中的 hashicorp 命名空间下的 aws 供应商版本 3.28.0。如果是官方注册表的源地址,可以省略注册表名称,如 hashicorp/aws。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 3.28.0"
    }
  }
}

如果不存在锁文件,上述指定将适用;如果Terraform已经初始化且存在锁文件,则不仅考虑在required_providers区块中记录的信息,还会考虑记录在锁文件中的信息,以确定要安装的提供程序。详见 https://github.com/hashicorp/terraform/blob/v0.14.7/website/docs/language/dependency-lock.html.md#dependency-installation-behavior。

提供者的安装方法

供应商的安装方法可以在CLI的配置文件中的provider_installation块中指定。你可以定义多个安装方法,并在其中使用include和exclude参数来为每个供应商单独指定安装方法。

provider_installation {
  filesystem_mirror {
    path    = "/usr/share/terraform/providers"
    include = ["example.com/*/*"]
  }
  direct {
    exclude = ["example.com/*/*"]
  }
}

以下是对CLI配置文件的简要介绍。配置文件的路径取决于执行Terraform的操作系统。在Windows上,它会被放置在执行用户的%APPDATA%目录下,并被命名为terraform.rc文件。在其他操作系统中,它会被放置在执行用户的主目录下,并被命名为.terraformrc文件。您也可以通过环境变量TF_CLI_CONFIG_FILE来指定任意的文件路径。

接下来,我们将介绍在provider_installation模块中支持的提供者安装方式。

直接

以下是在Terraform中默认的提供商安装方法。它通过向注册表请求提供商信息,并通过网络安装提供商从注册表指定的位置。下面是通过网络安装来自example.com注册表的提供商的配置示例。

provider_installation {
  direct {
    include = ["example.com/*/*"]
  }
}

文件系统镜像

可以从本地文件系统中的文件安装 Provider。还可以使用 terraform init 的 -plugin-dir 选项进行替代。以下是一个从名为 example.com 的注册表中安装 Provider 到 /usr/share/terraform/providers 的配置示例。

provider_installation {
  filesystem_mirror {
    path    = "/usr/share/terraform/providers"
    include = ["example.com/*/*"]
  }
}

根据设置示例可知,我们通过参数指定了 Provider 二进制文件的搜索目录。但事先需要根据二进制文件的形式来准备指定路径下的目录结构,具体方式会有所不同。

使用terrafrom providers mirror命令从外部注册表中获取ZIP压缩的二进制文件,并将其放置在///terraform-provider-__.zip的目录结构中,如果您需要解压缩ZIP文件并放置二进制文件本身,则需准备////目录结构。其中,TARGET是指将操作系统和CPU架构连接在一起的字符串,如darwin_amd64、linux_arm、windows_amd64等。

配置するファイルディレクトリ構造ZIP ファイル<HOSTNAME>/<NAMESPACE>/<TYPE>/terraform-provider-<TYPE>_<VERSION>_<TARGET>.zipバイナリ本体<HOSTNAME>/<NAMESPACE>/<TYPE>/<VERSION>/<TARGET>

如果存在一个压缩为ZIP格式的二进制文件,那么在安装Provider时,解压ZIP文件的结果会被输出到工作目录下的.terraform目录中。如果二进制文件本身已经放置好了,那么会在.terraform目录中创建一个符号链接指向设置的目录。因此,根据Teraform的操作方式选择适合的方案应该是可以的。

由于只有说明往往较难理解,所以以下是针对MacOS的具体安装示例。

将ZIP压缩的Provider二进制文件放置并安装的示例。

$ cat main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 3.28.0"
    }
  }
}

# ZIP 圧縮した Provider バイナリをダウンロードする
$ terraform providers mirror ./plugins
- Mirroring hashicorp/aws...
  - Selected v3.28.0 to meet constraints 3.28.0
  - Downloading package for darwin_amd64...
  - Package authenticated: signed by HashiCorp

# デフォルトで仕様に準拠したディレクトリ構造が作成される
# <HOSTNAME>/<NAMESPACE>/<TYPE>/terraform-provider-<TYPE>_<VERSION>_<TARGET>.zip
$ tree plugins
plugins
└── registry.terraform.io
    └── hashicorp
        └── aws
            ├── 3.28.0.json
            ├── index.json
            └── terraform-provider-aws_3.28.0_darwin_amd64.zip

# .terraformrc を用意せず -plugin-dir オプションを利用して
# filesystem_mirror 方式で Provider をインストールする
$ terraform init -plugin-dir plugins

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "3.28.0"...
- Installing hashicorp/aws v3.28.0...
- Installed hashicorp/aws v3.28.0 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

# 利用した Provider のバージョンやハッシュ値がロックファイルに保存される
$ cat .terraform.lock.hcl
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "3.28.0"
  constraints = "3.28.0"
  hashes = [
    "h1:0cCqlVoOAj4YOi61kVpqoxu1bdAmB67z6uZf+lsHJOw=",
  ]
}

# ZIP ファイルを解凍した結果がワーキングディレクトリ直下の .terraform ディレクトリに出力される
$ tree .terraform
.terraform
├── plugin_path
└── providers
    └── registry.terraform.io
        └── hashicorp
            └── aws
                └── 3.28.0
                    └── darwin_amd64
                        └── terraform-provider-aws_v3.28.0_x5

# 次の例のために不要なファイルを削除する
$ rm -rf .terraform .terraform.lock.hcl

安装示例:将提供者二进制文件放置的位置

在此示例中,我们将继续使用之前提供的 main.tf 和 plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.28.0_darwin_amd64.zip。

# <HOSTNAME>/<NAMESPACE>/<TYPE>/<VERSION>/<TARGET> に準拠したディレクトリを作成する
$ mkdir -p ./unpacked-plugins/registry.terraform.io/hashicorp/aws/3.28.0/darwin_amd64

# ZIP ファイルを解凍して作成したディレクトリ配下にバイナリ本体を配置する
$ unzip plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.28.0_darwin_amd64.zip \
  -d ./unpacked-plugins/registry.terraform.io/hashicorp/aws/3.28.0/darwin_amd64
Archive:  plugins/registry.terraform.io/hashicorp/aws/terraform-provider-aws_3.28.0_darwin_amd64.zip
  inflating: ./unpacked-plugins/registry.terraform.io/hashicorp/aws/3.28.0/darwin_amd64/terraform-provider-aws_v3.28.0_x5

# バイナリ本体が配置されたことを確認する
$ tree unpacked-plugins
unpacked-plugins
└── registry.terraform.io
    └── hashicorp
        └── aws
            └── 3.28.0
                └── darwin_amd64
                    └── terraform-provider-aws_v3.28.0_x5

# .terraformrc を用意せず -plugin-dir オプションを利用して
# filesystem_mirror 方式で Provider をインストールする
$ terraform init -plugin-dir unpacked-plugins

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "3.28.0"...
- Installing hashicorp/aws v3.28.0...
- Installed hashicorp/aws v3.28.0 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

# 利用した Provider のバージョンやハッシュ値がロックファイルに保存される
$ cat .terraform.lock.hcl
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "3.28.0"
  constraints = "3.28.0"
  hashes = [
    "h1:0cCqlVoOAj4YOi61kVpqoxu1bdAmB67z6uZf+lsHJOw=",
  ]
}

# .terraform ディレクトリから設定されたディレクトリにシンボリックリンクが作成される
$ tree .terraform
.terraform
├── plugin_path
└── providers
    └── registry.terraform.io
        └── hashicorp
            └── aws
                └── 3.28.0
                    └── darwin_amd64 -> /Users/ryumyosh/Desktop/terraform/unpacked-plugins/registry.terraform.io/hashicorp/aws/3.28.0/darwin_amd64

网络镜像

以下是从在运行Terraform的主机网络中可以访问的私有注册表安装Provider的方法。下面是一个从名为example.com的注册表中安装Provider的配置示例,该注册表可以从私有注册表https://terraform.example.com/providers中获取。

provider_installation {
  network_mirror {
    url     = "https://terraform.example.com/providers"
    include = ["example.com/*/*"]
  }
}

根据设定示例,通过参数指定了私有注册表的URL,但请注意私有注册表必须实现Provider Network Mirror Protocol。

如果未指定供应商安装方式的规范。

如果在 CLI 的配置文件中的 provider_installation 块或 terraform init 选项中没有指定提供程序的安装方法,则会使用由 filesystem_mirror 块和 direct 块组成的隐式配置。

文件系统镜像块的提供程序二进制文件的搜索目录取决于执行Terraform的操作系统,并且遵循如下规范。

Windows (operating system) in Chinese can be paraphrased as:

Windows 操作系统 (Windows

    • %APPDATA%/terraform.d/plugins

 

    %APPDATA%/HashiCorp/Terraform/plugins

苹果操作系统

    • $HOME/.terraform.d/plugins/

 

    • $HOME/Library/Application\ Support/io.terraform/plugins

 

    /Library/Application\ Support/io.terraform/plugins

Linux 6 是一种操作系统。

    • $PWD/terraform.d/plugins

 

    • $HOME/.terraform.d/plugins

 

    • $HOME/.local/share/terraform/plugins

 

    • /usr/local/share/terraform/plugins

 

    /usr/share/terraform/plugins

文件系统镜像无法从本地安装的提供商将通过直接块从外部注册表进行安装。

有关提供者的缓存。

Terraform的默认行为是在初始化时在工作目录的根目录下创建.terraform/providers目录,并在该目录下安装提供者。但如果使用了direct方式或network_mirror方式,按照这个规范,在Terraform初始化时会每次下载提供者二进制文件,这可能会浪费时间。

为了解决这个问题,Terraform 提供了一个能够缓存 Provider 资源的功能。通过在 CLI 配置文件中使用 plugin_cache_dir 参数指定缓存目录,可以启用缓存功能。你也可以通过设置环境变量 TF_PLUGIN_CACHE_DIR 来指定目录,从而启用功能。

plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"

如果启用了缓存功能,CLI 将在执行指定于配置文件的安装方法之前检查缓存目录,并且根据规格7,如果存在缓存,先前下载的提供者二进制文件将被重新使用。然而,请注意,不建议将缓存目录用作filesystem_mirror的目录。

如果在同一个主机上有多个使用相同 Provider 的 .tf 文件,则通过在本地共享缓存拥有共享的初始处理,可以跳过下载多余 Provider 二进制文件的过程。我认为这是一个非常有效的功能。

总结了

根据之前解释的规范,整理了Provider安装处理的流程。虽然我没有彻底地阅读Terraform的源代码,所以可能在细节上存在错误,但如果能够帮助那些想要了解概述的人,我将感到幸运。

步骤1:识别需要安装的供应商。

如果在.tf文件中定义了required_providers块,则从那里确定要安装的Provider。如果没有定义,将从.tf文件中的每个配置中确定。如果存在.terraform.lock.hcl,还将考虑记录在锁定文件中的信息来进行判断。

步骤2:确认已安装的提供商

参考.`terraform/providers`目录位于当前工作目录下,检查已安装的Provider。如果所有Provider都已安装,则处理在此结束,否则将开始下一步。

步骤3:确定 Provider 的安装方式

如果在CLI的配置文件中指定了provider_installation块或terraform init的选项等,则从中确定安装提供者的方法。如果未指定安装方法,则使用由filesystem_mirror块和direct块组成的隐式配置。

步骤4:检查已缓存的提供者资源

如果启用了缓存功能,并且Provider资源已经被缓存了,那么将使用该缓存来安装Provider。如果未启用缓存功能,则此步骤将被跳过。

步骤5:安装提供者

按照指定的安装方式安装Provider,并将安装的Provider信息写入到锁定文件中,完成安装过程。

最後

这篇文章介绍了Terraform中提供者安装处理的规范。最初开始调查时,我对Terraform的规范理解不够准确,浪费了很多时间,但最终能够加深对Terraform的理解,非常好。如果这篇文章能对某人有所帮助,我会很高兴。

以下是可供參考的資料:

    • https://www.terraform.io/docs/extend/index.html

https://www.terraform.io/docs/cli/config/config-file.html#provider-installation

https://github.com/hashicorp/terraform/blob/v0.14.7/website/docs/cli/config/config-file.html.md#provider-installation

在 https://www.terraform.io/docs/language/resources/provisioners/index.html 下可以查看到支持的官方提供者,包括 Generic Provisioners 和 Vendor Provisioners。您可以在 https://github.com/hashicorp/terraform/tree/v0.14.7/builtin/provisioners 查看源代码。虽然提供者未包含在 Terraform 二进制文件中,但严格来说,只有 terraform_remote_state 数据源作为提供者包含在 Terraform 二进制文件中。请参阅 https://github.com/hashicorp/terraform/blob/v0.14.7/website/docs/language/providers/requirements.html.md#built-in-providers 获取详细信息。

提供者二进制文件作为另一个进程启动,并通过 RPC 接口与 Terraform 二进制文件通信。

由于此次侧重于提供者的安装过程,因此省略了其他处理(如后端初始化)的说明。

如果要指定多个要搜索的目录,则需要定义多个 filesystem_mirror 块。

通过使用 docker run -it –rm –name tf –entrypoint sh hashicorp/terraform:0.14.7 命令来启动 Terraform 容器,然后准备一个适当的 .tf 文件,最后通过执行 TF_LOG=DEBUG terraform init 命令可以从调试日志中查看在 Linux 环境中搜索的目录。

记住,缓存的提供者二进制文件不会被 Terraform 自动删除,并且随着时间的推移,未使用的版本会积累下来,因此您需要定期手动删除。