背景
为满足 GrowingIO 客户多样性的需要,在私有云设施上应用 Terraform 作资源管理。采取 Terrform 具备以下相干劣势:
- 多云反对,支流云厂商均提供对应的Provider反对。
- 自动化治理根底构造,可反复对资源进行编排应用。
- 基础架构即代码(Infrastructure as Code),容许保留基础设施状态,便于追踪治理。
- 对立的语法来治理不同的云服务,实现标准化治理。
Terraform 介绍
概念
Terraform是一个开源 IAS 工具,提供统一的 CLI工作流,可治理数百个云服务。 Terraform通过将云厂商提供的 API 编写为申明式配置文件,通过Terraform的命令行接口,可将资源调度配置利用到任意反对的云上,并实现版本控制。更多详情请参见HashiCorp Terraform。
Terraform通过不同的Provider来反对不同云平台。国外云服务商如 Azure, AWS, GoogleCloud, DigtalOcean,国内云服务商如 Aliyun, TencentCloud, Ucloud, BaiduCloud均有提供官网的 Provider。
架构
Terraform通过解析用户书写的HCL(HashiCorp Configuration Language)格局的DSL文件,而后通过Terraform core与各云厂商提供的Providers进行交互,从而进行相干资源的调度。各云厂商依HCL代码格调,将自家资源调用API从新封装,以生成对应的 Providers。
我的项目实际
我的项目设计
客户我的项目存在多个同构环境,环境交付须要统一。
每个环境中存在中多个我的项目,各我的项目对资源调度需要各异。
每个我的项目须要应用EC2、ELB、EBS、 EMR等多种资源。
我的项目实现
我的项目构造
# tree -L 3 .├── README.MD ├── module│ ├── app1│ │ ├── config.tf│ │ ├── locals.tf│ │ ├── main.tf│ │ ├── outputs.tf│ │ └── variables.tf│ ├── global│ │ └── config.tf└── dev ├── main.tf └── output.tf
目录构造拆解:
- module中依我的项目名进行封装。其中 app1为我的项目名。
- global为全局参数设置。
- dev为各环境对 module的援用封装。
module细节: - config.tf为配置的相干参数,如 Providers的相干参数设定。
- locals.tf为变量的计算生成与其它变量援用,如 module 的援用与一些简单变量的重生成。
- main.tf为 resource 与 data 相干的资源编排调用。
- outputs.tf为我的项目输入值,如创立 ecs之后主机的 ip 等。
variables.tf 为传参的相干设置,如需创立 ecs的数量。
module 封装
module 的资源援用
在对资源调度的施行过程中,往往须要多次重复作业,故需将多个原子操作,对立封装成module,后续在内部援用module并传入对应参数即可。
apply构建文件代码:
# dev/main.tf...module "app1" { source = "../module/app1" aws_ec2_create_number = 3 ...}
module 的条件判断
在 Terraform 中,往往将循环与判断联合应用,次要应用场景有两种:
- 确认变量模式
- 确认资源是否创立
如创立 ecs时的主机名设置,当创立多台主机时,主机名需数字后缀以辨别,而只有一台主机时,不须要数字后缀。
相干实例代码如下:
# module/app1/main.tf...resource "aws_instance" "aws-ec2-create" { count = var.aws_ec2_create_number ... tags = { Name = var.aws_ec2_create_number < 2 ? "${var.env}-${var.aws_ec2_name}" : "${var.env}-${var.aws_ec2_name}-${count.index + 1}" ... }...
如在ECS的创立之中,无奈判断用户是否须要数据盘。
相干实例代码如下:
# module/app1/main.tf...resource "aws_instance" "aws-ec2-create" { count = var.aws_ec2_create_number ... dynamic "ebs_block_device" { for_each = var.aws_ebs_block_device_volume_size != 0 ? [1] : [] content { delete_on_termination = true device_name = var.aws_ebs_block_device_name volume_size = var.aws_ebs_block_device_volume_size volume_type = var.aws_ebs_block_device_volume_type } } ...}...
当用户传入var.aws_ebs_block_device_volume_size的值为0时,即循环一个空列表,即不创立该资源,亦即不创立数据盘。
module 的简单循环
在 Terraform中,循环次要依赖于count与for_each,这两种办法均只反对简略的循环,而for循环更多的是参加计算,并不会间接在resource中间接进行应用。
如在app1我的项目中,须要创立5台实例,同时实例需散布在不同的subnet之中,但subnet只有3个。在该状况下,咱们无奈简略的以subnet的id作循环,更为重要的是,如果前期 subnet的数量也可能会变动,所以无奈固定循环列表。
对于简单的循环需要,个别将其置于locals中作相干计算,其后在resource中进行援用 。
在locals中的计算,相干代码如下:
# module/app1/locals.tf...//case for the rc2 number is more than the number of zone for subnetlocals { times = ceil(var.aws_ec2_create_number / length(var.aws_subnet_id_list))}locals { // loop two list to generate a new list subnet_list_combine = flatten([ for p in range(local.times) : [ for q in var.aws_subnet_id_list : [join(",", [q])] ] ] )}...
通过在locals中的计算,咱们能够失去一个名为subnet_list_combine的list,其后在resource中进行援用即可。
resource相干代码如下:
# module/app1/main.tf...resource "aws_network_interface" "aws-network-interface" { count = var.aws_ec2_create_number subnet_id = local.subnet_list_combine[count.index]...
全局变量
在Terraform中,官网为了层级的简洁,默认不举荐应用全局变量,因为全局变量的设置,会呈现所见非所得的景象,详见Terraform global variables
但在理论生产中,却有相干需要,如 aws_profile_name在每个我的项目中均统一,同时前期因为用户的 profile 设置不统一而须要对立变更。
咱们能够将此类参数写入一个 module之中。
# module/global/config.tf...output "aws_profile_name" { value = "default"}...在各我的项目中的module,再次对global的module作援用。# module/application/locals.tf...// In order to make global variables --beginningmodule "global" { source = "../global"}locals { ... aws_profile_name = module.global.aws_profile_name}// In order to make global variables --end...
最初在我的项目中,作对应本身module的locals值作相干的援用。
# module/application/config.tf...Provider "aws" { profile = var.aws_profile_name == "" ? local.aws_profile_name : var.aws_profile_name ...}...
环境隔离
在Terraform中,隔离个别有两种:
- workspace隔离
- 目录隔离
在 workspace隔离中,须要应用 terraform workspace子命令,与 git branch相似,然而 terraform workspace 中的隔离,并不直观,在生产中容易呈现误操作,所以对于不同环境的 module调用,本我的项目中采纳了目录隔离。
# dev/main.tf...module "app1" { source = "../module/app1" aws_ec2_create_number = 3 ...}# stage/main.tf...module "app1" { source = "../module/app1" aws_ec2_create_number = 3 ...}
我的项目心得
Terraform 在根底资源编排中,使用方便,语法简洁,但因为各云厂商提供 Provider的格调并不齐全对立,肯定水平上减少了多云混合应用的老本。特地是对于国内非 AWS用户而言,国内局部云厂商提供的Provider,反对的资源品种相较于 AWS 偏少,局部场景可能无奈实现。
同时也因为 Terraform的语法对于一些高级个性的反对欠缺,导致在局部简单的场景中,有些顾此失彼,而更多须要 Provider去提供对应性能。尽管有 module的设计,能够进行代码复用,但也有因局部参数无奈动静辨别,而不得不创立多个 module以辨别,这点在国内云厂商提供的 Provider中尤为显著。
小结
本文简略介绍了Terraform的基本概念以及采纳Terraform的原由。
同时例举了在生产实践中Terraform的目录构造编排与环境隔离,具体阐明如何通过传参来动静调整资源编排的理论传参加调度,如何通过屡次组合计算以动静生成新的参数来躲避Terraform对高级个性反对的欠缺,以及如何构建全局变量以解决全局动静传参。基于篇幅限度, Terraform的应用无奈逐个阐明,对Terraform有趣味的同学可自行学习理解。
参考
- Mikael Krief. Terraform Cookbook: Efficiently define, launch, and manage Infrastructure as Code across various cloud platforms. Packt Publishing 2020
- Scott Winkler. Terraform in Action. Manning 2021
- Yevgeniy Brikman. Terraform: Up & Running: Writing Infrastructure as Code. O'Reilly Media 2019
- Terraform