这些年,云计算发展迅速,越来越多的企业,把业务迁移到了云上。但是,云基础设施的管理,也变得越来越复杂。手动创建和管理云资源,不仅效率低,而且容易出错,也很难做版本控制和审计。

基础设施即代码(Infrastructure as Code,IaC),就是为了解决这个问题而生的。通过代码来定义和管理基础设施,既可以版本控制,又可以自动化部署,还可以保证环境的一致性。

在众多的IaC工具中,Terraform,是目前最流行的一个。它由HashiCorp公司开发,支持AWS、Azure、GCP、阿里云等几乎所有主流云平台,也支持VMware、OpenStack等私有云平台。

今天,我就从基础到高级,详细讲解一下Terraform的配置方法。无论你是Terraform新手,还是有一定经验的开发者,都能从本文中获得收获。

一、Terraform基础

在开始配置之前,先简单介绍一下Terraform的基本概念。

1. 什么是Terraform

Terraform,是一个开源的基础设施即代码工具。它使用一种叫做HCL(HashiCorp Configuration Language)的配置语言,来定义基础设施资源。然后,通过Terraform的命令,来创建、修改、删除这些资源。

Terraform的核心思想,是"声明式"的。你只需要描述,你想要什么样的基础设施,Terraform就会帮你实现。你不需要告诉它,具体怎么一步步做,它会自己计算,需要创建哪些资源,修改哪些资源,删除哪些资源。

2. Terraform的工作流程

Terraform的工作流程,通常包括以下几个步骤:

  • Write(编写):用HCL编写配置文件,定义你想要的基础设施。
  • Plan(计划):运行terraform plan,Terraform会根据配置文件,计算出需要执行的操作,展示给你看。你可以检查,这些操作是不是你想要的。
  • Apply(应用):运行terraform apply,Terraform会执行plan中计算出的操作,创建、修改或删除资源。
  • Destroy(销毁):运行terraform destroy,Terraform会销毁配置文件中定义的所有资源。

这个工作流程,非常清晰,也非常安全。因为,在apply之前,你可以通过plan,先看一下,Terraform会做什么,确认无误后,再执行。

3. 安装Terraform

安装Terraform很简单。你只需要去官网,下载对应操作系统的二进制文件,然后,把它放到PATH环境变量包含的目录里,就可以了。

安装完成后,运行terraform version,如果能看到版本号,就说明安装成功了。

二、基本语法

Terraform的配置文件,使用HCL语言,后缀名是.tf。下面,介绍一下HCL的基本语法。

1. 资源(Resource)

资源,是Terraform中最基本的概念。一个资源,代表一个基础设施对象,比如一台EC2实例,一个S3存储桶,一个VPC网络等。

资源的定义语法如下:

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = "example-instance"
  }
}

上面的代码,定义了一个AWS EC2实例。其中,aws_instance是资源类型,example是资源名称。资源类型和资源名称,组合起来,唯一标识一个资源。花括号里面,是资源的参数,比如AMI ID、实例类型、标签等。

2. 提供者(Provider)

提供者,是Terraform和云平台之间的桥梁。每个云平台,都有对应的提供者。比如,AWS的提供者是aws,Azure的提供者是azurerm,GCP的提供者是google

提供者的配置,通常包括认证信息、区域等。比如:

provider "aws" {
  region     = "us-west-2"
  access_key = "your-access-key"
  secret_key = "your-secret-key"
}

不过,不建议把accesskey和secretkey直接写在配置文件里。更好的做法是,使用环境变量,或者共享凭证文件,或者IAM角色。

3. 变量(Variable)

变量,可以让你的配置更加灵活,更加可复用。你可以把一些会变化的值,定义成变量,然后,在不同的环境中,传入不同的值。

变量的定义语法如下:

variable "instance_type" {
  description = "EC2实例类型"
  type        = string
  default     = "t2.micro"
}

使用变量的时候,用var.变量名的方式:

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type
}

变量的值,可以通过多种方式传入。比如,命令行参数-var,变量文件-var-file,环境变量TFVAR变量名,或者交互式输入。

4. 输出(Output)

输出,可以把资源的某些属性,输出出来,供你查看,或者供其他配置使用。

输出的定义语法如下:

output "instance_public_ip" {
  description = "EC2实例的公网IP"
  value       = aws_instance.example.public_ip
}

运行terraform apply之后,你就可以在输出中,看到EC2实例的公网IP了。

5. 数据源(Data Source)

数据源,可以让你从外部获取数据,然后,在配置中使用。比如,你可以通过数据源,获取一个已经存在的VPC的信息,获取最新的AMI ID,获取当前账户的信息等。

数据源的定义语法如下:

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }
}

使用数据源的时候,用data.数据源类型.数据源名称.属性的方式:

resource "aws_instance" "example" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
}

三、进阶用法

掌握了基本语法之后,我们来看一些进阶用法。

1. 模块(Module)

模块,是Terraform中代码复用的主要方式。你可以把一组相关的资源,封装成一个模块,然后,在不同的配置中,调用这个模块。

比如,你可以封装一个"VPC模块",包含VPC、子网、路由表、网关等资源。然后,在不同的环境中,调用这个模块,传入不同的参数,就可以创建出不同的VPC。

模块的目录结构,通常如下:

modules/
  vpc/
    main.tf
    variables.tf
    outputs.tf

调用模块的语法如下:

module "vpc" {
  source = "./modules/vpc"

  vpc_cidr = "10.0.0.0/16"
  public_subnets = [
    "10.0.1.0/24",
    "10.0.2.0/24",
  ]
  private_subnets = [
    "10.0.10.0/24",
    "10.0.11.0/24",
  ]
}

模块,是Terraform中非常重要的概念。合理使用模块,可以让你的配置,更加清晰,更加可复用,也更容易维护。

2. 状态管理(State)

Terraform通过状态文件,来记录它管理的资源的信息。默认情况下,状态文件保存在本地,文件名是terraform.tfstate

状态文件,非常重要。它记录了资源和配置之间的映射关系。Terraform在执行plan和apply的时候,都会读取状态文件,来判断,哪些资源需要创建,哪些需要修改,哪些需要删除。

如果状态文件丢失了,Terraform就不知道,它管理了哪些资源,就可能会重复创建,或者无法修改。所以,一定要妥善保管状态文件。

对于团队协作来说,本地状态文件,是不够的。因为,每个人的本地状态,可能不一样,会导致冲突。所以,通常会使用远程状态,把状态文件,保存在共享的存储中,比如S3、GCS、Azure Blob Storage等。

配置远程状态的语法如下:

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "us-west-2"
  }
}

使用远程状态,不仅可以实现团队协作,还可以启用状态锁定,防止多人同时修改,导致状态冲突。

3. 工作区(Workspace)

工作区,可以让你在同一个配置下,管理多个环境。比如,开发环境、测试环境、生产环境。每个工作区,都有自己独立的状态文件。

创建工作区的命令:

terraform workspace new dev

切换工作区的命令:

terraform workspace select dev

查看当前工作区的命令:

terraform workspace show

在配置中,可以通过terraform.workspace,获取当前工作区的名称,然后,根据不同的工作区,使用不同的配置。比如:

variable "instance_type" {
  type = map(string)
  default = {
    dev  = "t2.micro"
    test = "t2.small"
    prod = "t2.large"
  }
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type[terraform.workspace]
}

工作区,是管理多环境的一个简单有效的方式。但是,对于复杂的多环境管理,还是建议使用模块,配合不同的配置目录。

4. 条件表达式和循环

Terraform 0.12之后,引入了条件表达式和循环,让配置更加灵活。

条件表达式的语法,和很多编程语言类似:

condition ? true_value : false_value

比如,根据环境,决定是否创建资源:

resource "aws_instance" "example" {
  count         = terraform.workspace == "prod" ? 1 : 0
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.large"
}

上面的代码,只有在prod工作区,才会创建EC2实例。

循环,可以用for_each,来遍历一个集合,创建多个资源:

variable "subnets" {
  type = map(string)
  default = {
    "subnet-1" = "10.0.1.0/24"
    "subnet-2" = "10.0.2.0/24"
    "subnet-3" = "10.0.3.0/24"
  }
}

resource "aws_subnet" "example" {
  for_each          = var.subnets
  vpc_id            = aws_vpc.example.id
  cidr_block        = each.value
  availability_zone = "us-west-2a"

  tags = {
    Name = each.key
  }
}

上面的代码,会根据subnets变量,创建三个子网。

四、最佳实践

最后,分享一些Terraform的最佳实践。

1. 目录结构要清晰

一个好的Terraform项目,目录结构应该清晰。通常,根目录放主配置,模块放在modules目录,变量文件按环境分开。比如:

.
├── main.tf
├── variables.tf
├── outputs.tf
├── provider.tf
├── terraform.tfvars
├── modules/
│   ├── vpc/
│   ├── ec2/
│   └── rds/
└── environments/
    ├── dev/
    ├── test/
    └── prod/

2. 使用模块,避免重复代码

不要把所有的资源,都写在一个大文件里。应该把相关的资源,封装成模块,然后,在主配置中调用。这样,既减少了重复代码,又让配置更加清晰,更容易维护。

3. 变量和输出要规范

变量,应该有明确的描述、类型和默认值。不要使用未定义的变量,也不要在配置中硬编码值。

输出,应该只输出必要的信息。不要输出敏感信息,比如密码、密钥等。

4. 使用远程状态,启用状态锁定

对于团队协作,一定要使用远程状态,并且启用状态锁定。这样,可以避免状态冲突,也可以保证状态文件的安全。

5. 执行plan,确认后再apply

在执行apply之前,一定要先执行plan,仔细检查,Terraform会做哪些操作。确认无误后,再执行apply。不要直接执行apply,那样,可能会导致意外的资源修改或删除。

6. 版本控制

Terraform配置文件,一定要用Git等版本控制工具管理。这样,可以追踪配置的变更历史,也可以在出问题的时候,回滚到之前的版本。

但是,状态文件,不要提交到版本控制。因为,状态文件中,可能包含敏感信息,而且,状态文件是动态变化的,不适合版本控制。

五、写在最后

Terraform,是一个非常强大的基础设施即代码工具。掌握了它,你就可以用代码,来管理你的云基础设施,实现自动化部署,保证环境一致性,提升工作效率。

本文,从基础到高级,介绍了Terraform的配置方法,包括基本语法、资源管理、变量和输出、模块、状态管理、工作区、以及最佳实践。希望能给你带来一些帮助。

当然,Terraform的内容,远不止这些。还有很多高级特性,比如自定义提供者、Terraform Cloud、Terraform Enterprise等,值得深入学习。

如果你对Terraform感兴趣,建议你动手实践一下。找一个云平台的免费账号,跟着教程,一步步配置,你会发现,Terraform其实并不难。

希望这篇文章,能帮你入门Terraform。如果你有任何问题,欢迎在评论区留言,我们一起交流。