背景

为满足 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有趣味的同学可自行学习理解。

参考

  1. Mikael Krief. Terraform Cookbook: Efficiently define, launch, and manage Infrastructure as Code across various cloud platforms. Packt Publishing 2020
  2. Scott Winkler. Terraform in Action. Manning 2021
  3. Yevgeniy Brikman. Terraform: Up & Running: Writing Infrastructure as Code. O'Reilly Media 2019
  4. Terraform