思考 Terraform – 考慮提仓库结构和利用范围
首先
开始学习 Terraform 后,虽然创建一个简单的 VPC 相对容易,但是一旦考虑到实际运维方面的 terraform.tfstate 管理方式或者高效的目录结构时,就会陷入停滞。
我已经完成了初学阶段,现在正在阅读《Pragmatic Terraform on AWS》这本书,重新学习有关 Terraform 的最佳实践。为了整理我所获得的知识,我打算写一篇文章。
写东西
-
- Terraform の使い所
- Terraform で実装したリポジトリの例とサンプルコード(一部)
不写书
- Terraform の使い方・インストール方法
Terraform 使用指南
只需使用Terraform构建云环境非常简单,但是考虑到“在构建的环境上运行应用程序的生命周期”和“从创建AWS账户到第一次使用Terraform在目标环境中执行”等因素,我们会有很多事情需要考虑。
实际的构建顺序
根据本次文章以”不用Terraform构建全部”为前提,考虑到”已获得AWS账号并完成基本设置”,总结出以下3个步骤需求:从”部署应用程序”开始。
-
- 设置账户基本配置和tfstate存储区
-
- 使用Terraform构建基础设施
- 构建Terraform不支持的基础设施(图形界面、命令行界面)
其中,仅有选项2通过执行Terraform来构建环境的阶段。

1. 設定帳戶基本設定並確保tfstate存儲空間
我想获取一个AWS账户,然后根据以下网站进行设置。
- AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ – Qiita
完成此设定后,我们将进行以下配置以使用Terraform进行工作。
-
- 创建任务用户
-
- 发放access_key, secret_key
- 创建后端用的S3存储桶
此外,根据情况,在运行 Terraform 之前可能还需要做以下工作。
-
- Elastic IP の発行(外部IF として固定が必要なものなど)
-
- ドメインの取得 と Route 53 の設定
- ACM を使用した SSL証明書の登録
参考:HashiCorp推出的Terraform多账户AWS架构。
理想情况下,由Terraform使用的基础设施应该存在于Terraform管理的基础设施之外。
用Terraform构建基础设施。
关于详细内容,请参阅《整理 Module 的规范》一节以后的部分进行了解。
3. 进行了Terraform不支持的基础设施建设(图形用户界面,命令行界面)
我们将在由Terraform构建的系统基础设施上进行应用程序的部署,以及部署依赖于该基础设施的资源。
-
- Elastic Beanstalk アプリケーション
-
- AWS Lambda アプリケーション
- API Gateway アプリケーション
然而,关于这第3点,我内心也感到很苦恼,对于如何分担角色还感到迷茫。
整理Module的使用规范
首先,在使用Terraform构建基础架构时,我们需要澄清一下无法回避的模块。
标准模块结构
在Terraform的官方网站上,介绍了两种标准模块结构,称为Standard Module Structure(标准模块结构)。
参考:Terraform by HashiCorp – 创建模块
-
- minimal recommended module
- complete example of a module
構造的规则 de
规则构造中包含了以下内容。
Root module : リポジトリのルートディレクトリに「Terraform files」を配置しなければならない。これがモジュールの primary entrypoint となる。
README : Root module と ネストされた moduleは README を配置した方が良い。input や output はツールで自動生成できるため、記載する必要はない。
LICENSE : Public にモジュール公開するなら、あった方が良い。
main.tf, variables.tf, outputs.tf : 最小モジュールとして推奨されるファイル名。空でもあったほうが良い。
Variables and outputs should have descriptions. : 全ての variable と output に1~2行の説明を記載しましょう。
Nested modules. : module は modules/配下に配置しましょう。module は可能な限り複雑さを排除した振る舞いを記載し、README に用法を記載しましょう。Root module が Nested modules を呼ぶ場合、個別のリポジトリにせずに全て1つのリポジトリで管理するようにしましょう。
Examples. : module の使用方法を記載した example は examples/ 配下に配置しましょう。example には README を配置し、用法のゴールを記載しましょう。
一个最低推荐的模块
$ tree minimal-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
一个完整的模块的例子
$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│ ├── nestedA/
│ │ ├── README.md
│ │ ├── variables.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ ├── nestedB/
│ ├── .../
├── examples/
│ ├── exampleA/
│ │ ├── main.tf
│ ├── exampleB/
│ ├── .../
考虑地形形状库的结构
我们将讨论在使用Terraform构建系统基础架构时实施的仓库结构。
基本概念
考虑时的基本理念如下。
-
- 组件拆分
-
- 环境分割
-
- 本地模块
- 后端在AWS S3上管理
1. 组件拆分
在《Pragmatic Terraform on AWS》这本书中,有一节叫做「17.4 组件分割」。
这里有以下这样的描述。
环境是一个明确的界限。对于每个环境,只有一个tfstate文件的想法是直接的。然而,这种想法有一些缺点。如果将一个环境的所有资源都在一个tfstate文件中管理,一个错误就会对整体产生影响。此外,执行Terraform也需要时间。因此,让我们将环境分解为多个组件。
另外,作为具体示例,以下是一些指导准则所提供的。
-
- 安定度が高いコンポーネントとそれ以外の分離
-
- ステートフルなリソース(ストレージやデータストア)の隔離
-
- (エンドユーザへの)影響範囲が異なるものの分割
-
- 組織のライフサイクルに関わるリソースの分離
- 関心事の分離
根据这些记录,我们可以发现在一个 tfstate 文件中保存具有不同角色的资源,例如“VPC”、“RDS”、“EC2”和“IAM”,是不可取的。
因此,我决定参考文章「如何在Terraform中进行组件拆分 – Qiita」来进行组件的分割。
2. 环境划分
在这里所说的环境是指Terraform所构建的服务在”生产”、”验证”或”开发”用途上的使用方式。
对于这个环境的定义有两种方法。
-
- ディレクトリ分割型
- workspace型
在互联网上,有很多关于使用“目录分割型”来定义环境的例子。文章《Terraform运营最佳实践2019〜摒弃workspace等种种〜 – 长生村本乡工程师博客》中提到了由于“workspace型”存在的缺点,而回归到了“目录分割型”。
另外,在書籍《Pragmatic Terraform on AWS》中提到,在2018年9月的HashiCorp Japan Meetup活动中,使用workspace类型的用户只占少数,大多数用户选择进行目录分割。
在另一方面上,工作区类型的应用实例确实存在,由于对那些实例更具吸引力,因此我们决定采用工作区类型。
3. 本地模块
根据「HashiCorp 的 Terraform 模块资源」,module块作为源可以指定以下方法。
-
- Local paths
-
- Terraform Registry
-
- GitHub
-
- Bitbucket
-
- Generic Git, Mercurial repositories
-
- HTTP URLs
-
- S3 buckets
- GCS buckets
这次我们将会使用的是”本地路径”。
4. 后端在 AWS S3 上进行管理。
这是一个理所当然的选择,我们采用将 AWS S3 指定为后端,并管理 terraform.tfstate 的方式。
项目结构
基于标准模块结构,并考虑了上述基本概念,设计的目录结构如下。
$ tree sample-project/
.
├── README.md
├── LICENSE
├── .secret
├── .gitignore
├── ...
├── bin <--- リポジトリの初期化
│ ├── init_components.sh
│ ├── init_s3.sh
│ ├── init_s3.bat
│ ├── config/
├── environments/ <--- 環境ごとの変数を定義
│ ├── common
│ │ ├── terraform.tfvars
│ ├── product
│ │ ├── terraform.tfvars
│ ├── staging
│ ├── develop
│ ├── default
├── modules/ <--- Local Module
│ ├── nestedA/
│ │ ├── README.md
│ │ ├── variables.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ ├── nestedB/
│ ├── .../
├── components/ <--- コンポーネント分割した設定群
│ ├── network/
│ │ ├── README.md
│ │ ├── main.tf <--- 基本となる処理を記載
│ │ ├── variables.tf <--- variables を記載
│ │ ├── outputs.tf <--- output ブロックを記載
│ │ ├── backend.tf <--- リモートステートを記載(terraformブロック、dataブロック)
│ │ ├── provider.tf <--- provider を記載
│ │ ├── .terraform
│ ├── firewall/
│ ├── iam/
│ ├── s3/
│ ├── datastore/
│ ├── application/
│ ├── operation/
│ ├── .../
在执行 Terraform 时,实际上是在 components 文件夹下的任意组件目录中进行操作。
粗略的结构图如下所示。

示例代码
以防火墙组件为例,以下是组件内代码的示例。
后端
以下是定义远程状态信息的 components/firewall/backend.tf 文件的示例:
terraform {
required_version = "0.12.3"
backend "s3" {
region = "ap-northeast-1"
encrypt = true
bucket = "<unique-bucket-name>"
key = "firewall/terraform.tfstate"
profile = "profile-name"
}
}
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "<unique-bucket-name>"
key = "env:/${terraform.workspace}/network/terraform.tfstate"
region = "ap-northeast-1"
profile = "profile-name"
}
}
如果指定terraform模块中的backend块,远程状态将在以下路径下创建。
唯一的桶名称/env:/${terraform.workspace}/防火墙/terraform.tfstate
变量
定义组件/防火墙/variables.tf 中的变量信息的示例如下:
variable "common" {
type = map(string)
default = {
"default.region" = "ap-northeast-1"
"default.project" = "project-name"
}
}
主要的
定义基本处理的组件是components/firewall/main.tf文件的示例如下。
module "firewall" {
source = "../../modules/nestedB"
common = var.common
vpc = data.terraform_remote_state.network.outputs.vpc
}
供应商
下面是定义provider的组件文件”components/firewall/provider.tf”的示例。
variable "aws_access_key" {
}
variable "aws_secret_key" {
}
provider "aws" {
version = "= 2.18.0"
access_key = var.aws_access_key
secret_key = var.aws_secret_key
region = "ap-northeast-1"
}
terraform.tfvars的意思是Terraform变量文件。
根据环境来定义变量文件 environments/common/terraform.tfvars 的示例如下。
region = "ap-northeast-1"
cidrs = [ "10.0.0.0/16", "10.1.0.0/16" ]
amis = {
"ap-northeast-1a" = "ami-abc123"
"ap-northeast-1c" = "ami-def456"
}
工作空间
在每个组件目录下创建以下四个工作空间。
terraform workspace list
* default
develop
product
staging
AWS CLI配置文件
设置用于访问在后端指定的 AWS S3 的配置文件信息。
[profile-name]
aws_access_key_id = AKIAXXXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
使用AWS S3存储桶来创建后端脚本。
使用AWS CLI创建S3 Bucket。
@echo off
set profile_name=%1
set bucket_name=%2
aws s3 mb s3://%bucket_name% ^
--profile %profile_name%
aws s3api put-bucket-versioning ^
--profile %profile_name% ^
--bucket %bucket_name% ^
--versioning-configuration Status=Enabled
aws s3api put-bucket-encryption ^
--profile %profile_name% ^
--bucket %bucket_name% ^
--server-side-encryption-configuration file://config/config-public-access-block.json
aws s3api put-public-access-block ^
--profile %profile_name% ^
--bucket %bucket_name% ^
--public-access-block-configuration file://config/config-public-access-block.json
{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}]
}
{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}
进行
在执行terraform命令时,请按以下方式进行操作。
请注意选择正确的命令实施文件夹。
// 環境変数の設定を確認
export TF_VAR_aws_access_key="AKIAXXXXXXXXXXXXXXXXXX"
export TF_VAR_aws_secret_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
echo $TF_VAR_aws_access_key
echo $TF_VAR_aws_secret_key
// workspace の選択
terraform workspace select [ default / product / staging / develop ]
// 初期化
terraform init
// Dry run
terraform plan \
-var-file="../../environments/common/terraform.tfvars" \
-var-file="../../environments/$(terraform workspace show)/terraform.tfvars"
// 環境変数の設定を確認
$env:TF_VAR_aws_access_key="AKIAXXXXXXXXXXXXXXXXXX"
$env:TF_VAR_aws_secret_key="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$env:TF_VAR_public_key_path=".\.secret\public"
$env:TF_VAR_aws_access_key;
$env:TF_VAR_aws_secret_key;
$env:TF_VAR_public_key_path;
Get-ChildItem env:
// workspace の選択
terraform workspace select [ default / product / staging / develop ]
// 初期化
terraform init
// Dry run
terraform plan `
-var-file="../../environments/common/terraform.tfvars" `
-var-file="../../environments/$(terraform workspace show)/terraform.tfvars"
总结
以下是这篇文章的总结。
我取得了成功。
-
- 通过组件分割,将要在tfstate文件中管理的资源进行分割
-
- 使用工作区将环境(生产、验证、开发)进行分割
-
- 使用本地模块定义资源
- 使用AWS S3来管理和版本控制terraform.tfstate文件
问题或挑战
-
- 在一个仓库中管理多个组件,并且每次运行”terraform init”时,提供程序工具会被安装到执行目录中。
-
- 不太了解模块仓库。
- 整体上都是摸索着进行的。
解决方案 ‘àn)
-
- 或许最好为每个组件设计独立的存储库。
-
- 唯有实际使用才能得知。
- (让人犹豫不决)
最后
在写文章的过程中,我升级了Terraform的版本,从v0.11.x升级到了v0.12.x,结果我之前写的代码完全无法运行,我感到非常着急。我尝试执行了terraform 0.12upgrade命令,但似乎并不是百分百有效。
确实,由于主要版本仍然是0系列,所以会有破坏性的变更。
terraform的核心功能非常出色,而且很容易理解,但是当您实际动手操作时,需要掌握HCL语法和各个构建目标服务的规范,您可能会感到在克服初期难关时困难重重。
另外,由于工具本身仍处于发展阶段,不断出现新的语法和规范,您需要了解并跟进这些信息,这可能会增加一些额外的工作。
然而,由于Terraform带来的好处远大于其成本,我希望能够更多地了解Terraform。
请提供中文指南。
在撰写这篇文章时所参考的信息。
目录结构
-
- Terraform Best Practices in 2017 – Qiita
-
- Terraformにおけるディレクトリ構造のベストプラクティス – Developers.IO
-
- shogomuranushi/oreno-terraform – GitHub
-
- DevOpsを支える今話題のHashiCorpツール群についてに登壇してきました – てっくぼっと!
-
- Terraform workspaceを利用する。環境ごとのリソース名の分岐など – Goldstine研究所
- TerraformのModuleソースとしてTerraform Enterprise’s Private Module Registryを利用する – GMOメディア エンジニアブログ
Terraform 的分割单位
-
- terraformはどの単位で分割すべきか – Qiita
-
- Terraformと変数(variable)の話 – CUPSULE CLOUD
-
- Feature: Conditionally load tfvars/tf file based on Workspace #15966 – GitHub
- Variable Definitions (.tfvars) Files – Input Variables – Configuration Language – Terraform by HashiCorp
国家的管理
- Backend の S3 や DynamoDB 自体を terraform で管理するセットアップ方法 – Qiita
样本
-
- cloudposse/terraform-aws-elastic-beanstalk-application – GitHub
- terraform-aws-modules/terraform-aws-vpc – GitHub
Terraform 版本 v0.12.x
- terraform v0.12 アップデート terraform 0.12upgrade,terraform 0.12checklistサブコマンド実行結果と、ファイルの変更例 – Qiita
使用AWS S3作为terraform.tfstate的存储。
-
- 独り Terraform 研究所 (1) 〜 Backend についてドキュメントを読んだり, チュートリアルしたり 〜 – ようへいの日々精進XP
- S3の暗号化についてまとめてみた(2018年6月版) – 本日も乙