关于sre:GrowingIO-Terraform-实践

50次阅读

共计 4788 个字符,预计需要花费 12 分钟才能阅读完成。

背景

为满足 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 subnet
locals {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 --beginning
module "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

正文完
 0