Terraform 教程:基础设施即代码工具详解

1. 什么是 Terraform?

Terraform 是一个开源的基础设施即代码(Infrastructure as Code,IaC)工具,由 HashiCorp 开发。它允许用户使用声明式配置语言定义和管理基础设施,支持多种云服务提供商,如 AWS、Azure、Google Cloud、阿里云、腾讯云等。

1.1 核心概念

  • 资源(Resource):基础设施的基本构建块,如虚拟机、网络、存储等。
  • 模块(Module):可重用的资源配置集合。
  • 状态(State):记录基础设施当前状态的文件,用于跟踪资源的变更。
  • 提供者(Provider):与特定云服务提供商交互的插件。
  • 变量(Variable):用于参数化配置。
  • 输出(Output):用于暴露资源的属性值。
  • 数据源(Data Source):用于查询现有基础设施的信息。

1.2 核心特性

  • 多云支持:支持多种云服务提供商,实现多云部署。
  • 声明式配置:使用 HCL(HashiCorp Configuration Language)定义基础设施。
  • 状态管理:通过状态文件跟踪基础设施的变更。
  • 模块系统:支持模块化和代码重用。
  • 计划和执行:在执行前预览变更,确保操作的安全性。
  • 资源依赖管理:自动处理资源之间的依赖关系。
  • 插件系统:通过提供者插件支持新的云服务和功能。

1.3 适用场景

  • 云基础设施管理:创建和管理云资源。
  • 多环境部署:在开发、测试和生产环境中一致部署基础设施。
  • 基础设施自动化:自动化基础设施的创建、更新和销毁。
  • 多云战略:在多个云提供商之间管理资源。
  • 基础设施即代码:将基础设施配置版本控制,实现可追溯性和可重复性。

2. 安装和配置

2.1 安装 Terraform

2.1.1 Windows 安装

  1. 访问 Terraform 官方下载页面:https://www.terraform.io/downloads
  2. 下载适用于 Windows 的 64 位版本
  3. 解压下载的 zip 文件,将 terraform.exe 复制到系统路径中的目录,如 C:\Windows\System32
  4. 打开命令提示符,验证安装:
terraform --version

2.1.2 macOS 安装

使用 Homebrew 安装:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# 验证安装
terraform --version

2.1.3 Linux 安装

# 下载 Terraform
wget https://releases.hashicorp.com/terraform/1.5.0/terraform_1.5.0_linux_amd64.zip

# 解压
unzip terraform_1.5.0_linux_amd64.zip

# 移动到系统路径
sudo mv terraform /usr/local/bin/

# 验证安装
terraform --version

2.2 配置云提供商凭证

2.2.1 AWS 配置

# 安装 AWS CLI
pip install awscli

# 配置 AWS 凭证
aws configure

按照提示输入 AWS Access Key ID、AWS Secret Access Key、默认区域和输出格式。

2.2.2 Azure 配置

# 安装 Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# 登录 Azure
az login

# 设置默认订阅
az account set --subscription "your-subscription-id"

2.2.3 Google Cloud 配置

# 安装 Google Cloud SDK
curl https://sdk.cloud.google.com | bash

# 初始化 Google Cloud SDK
gcloud init

# 登录 Google Cloud
gcloud auth login

# 设置默认项目
gcloud config set project "your-project-id"

3. 基本使用

3.1 初始化项目

# 创建项目目录
mkdir terraform-demo
cd terraform-demo

# 创建主配置文件
vim main.tf

3.2 编写基本配置

创建一个 main.tf 文件,定义一个 AWS EC2 实例:

provider "aws" {
  region = "us-west-2"
}

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

  tags = {
    Name = "ExampleInstance"
  }
}

3.3 初始化和执行

# 初始化 Terraform 项目(下载提供者插件)
terraform init

# 预览变更
terraform plan

# 执行变更
terraform apply

# 查看状态
terraform show

# 销毁资源
terraform destroy

3.4 使用变量和输出

创建 variables.tf 文件:

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "ami" {
  description = "EC2 AMI ID"
  type        = string
  default     = "ami-0c55b159cbfafe1f0"
}

创建 outputs.tf 文件:

output "instance_id" {
  description = "EC2 instance ID"
  value       = aws_instance.example.id
}

output "public_ip" {
  description = "EC2 public IP address"
  value       = aws_instance.example.public_ip
}

output "public_dns" {
  description = "EC2 public DNS name"
  value       = aws_instance.example.public_dns
}

更新 main.tf 文件:

provider "aws" {
  region = var.region
}

resource "aws_instance" "example" {
  ami           = var.ami
  instance_type = var.instance_type

  tags = {
    Name = "ExampleInstance"
  }
}

3.5 使用模块

创建一个 modules 目录和 modules/ec2 子目录,在其中创建 main.tfvariables.tfoutputs.tf 文件:

modules/ec2/main.tf

resource "aws_instance" "instance" {
  ami           = var.ami
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}

modules/ec2/variables.tf

variable "ami" {
  description = "EC2 AMI ID"
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "instance_name" {
  description = "EC2 instance name"
  type        = string
}

modules/ec2/outputs.tf

output "instance_id" {
  description = "EC2 instance ID"
  value       = aws_instance.instance.id
}

output "public_ip" {
  description = "EC2 public IP address"
  value       = aws_instance.instance.public_ip
}

在根目录的 main.tf 文件中使用模块:

provider "aws" {
  region = var.region
}

module "ec2_instance" {
  source        = "./modules/ec2"
  ami           = var.ami
  instance_type = var.instance_type
  instance_name = "ExampleInstance"
}

4. 高级功能

4.1 状态管理

4.1.1 远程状态

使用 S3 存储远程状态:

terraform {
  backend "s3" {
    bucket         = "terraform-state-bucket"
    key            = "example/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

4.1.2 状态锁定

使用 DynamoDB 实现状态锁定,防止并发操作:

# 创建 DynamoDB 表
aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

4.2 工作空间

工作空间用于在同一配置下管理多个环境:

# 创建工作空间
terraform workspace new development

# 切换工作空间
terraform workspace select production

# 查看工作空间
terraform workspace list

# 在当前工作空间中执行操作
terraform apply

4.3 数据源

使用数据源查询现有资源:

data "aws_ami" "ubuntu" {
  most_recent = true

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

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

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

  tags = {
    Name = "ExampleInstance"
  }
}

4.4 资源依赖

Terraform 自动处理资源依赖,但也可以显式指定:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "main" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "us-west-2a"
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.main.id

  # 显式依赖
  depends_on = [aws_subnet.main]

  tags = {
    Name = "ExampleInstance"
  }
}

4.5 条件表达式和循环

4.5.1 条件表达式

resource "aws_instance" "example" {
  ami           = var.ami
  instance_type = var.environment == "production" ? "t2.large" : "t2.micro"

  tags = {
    Name        = "ExampleInstance"
    Environment = var.environment
  }
}

4.5.2 循环

variable "instance_count" {
  type    = number
  default = 3
}

resource "aws_instance" "example" {
  count         = var.instance_count
  ami           = var.ami
  instance_type = "t2.micro"

  tags = {
    Name = "ExampleInstance-${count.index}"
  }
}

5. 实用案例

5.1 创建 AWS EC2 实例

场景:使用 Terraform 创建一个 AWS EC2 实例。

步骤

  1. 创建项目目录
mkdir terraform-aws-ec2 && cd terraform-aws-ec2
  1. 创建配置文件

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

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

  tags = {
    Name = "ExampleInstance"
  }
}
  1. 初始化和执行
# 初始化
terraform init

# 预览
terraform plan

# 执行
terraform apply

# 查看输出
terraform output

# 销毁
terraform destroy

5.2 创建完整的网络基础设施

场景:使用 Terraform 创建一个包含 VPC、子网、安全组和 EC2 实例的完整网络基础设施。

步骤

  1. 创建项目目录
mkdir terraform-aws-network && cd terraform-aws-network
  1. 创建配置文件

variables.tf

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

variable "subnet_cidr" {
  description = "Subnet CIDR block"
  type        = string
  default     = "10.0.1.0/24"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "ami" {
  description = "EC2 AMI ID"
  type        = string
  default     = "ami-0c55b159cbfafe1f0"
}

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = var.region
}

# 创建 VPC
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = "MainVPC"
  }
}

# 创建子网
resource "aws_subnet" "main" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.subnet_cidr
  availability_zone = "${var.region}a"

  tags = {
    Name = "MainSubnet"
  }
}

# 创建 Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "MainIGW"
  }
}

# 创建路由表
resource "aws_route_table" "main" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "MainRouteTable"
  }
}

# 关联路由表
resource "aws_route_table_association" "main" {
  subnet_id      = aws_subnet.main.id
  route_table_id = aws_route_table.main.id
}

# 创建安全组
resource "aws_security_group" "main" {
  name        = "MainSecurityGroup"
  description = "Allow HTTP and SSH traffic"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "MainSecurityGroup"
  }
}

# 创建 EC2 实例
resource "aws_instance" "example" {
  ami           = var.ami
  instance_type = var.instance_type
  subnet_id     = aws_subnet.main.id
  vpc_security_group_ids = [aws_security_group.main.id]

  tags = {
    Name = "ExampleInstance"
  }
}

outputs.tf

output "instance_id" {
  description = "EC2 instance ID"
  value       = aws_instance.example.id
}

output "public_ip" {
  description = "EC2 public IP address"
  value       = aws_instance.example.public_ip
}

output "public_dns" {
  description = "EC2 public DNS name"
  value       = aws_instance.example.public_dns
}

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "subnet_id" {
  description = "Subnet ID"
  value       = aws_subnet.main.id
}
  1. 初始化和执行
# 初始化
terraform init

# 预览
terraform plan

# 执行
terraform apply

# 查看输出
terraform output

# 销毁
terraform destroy

5.2 创建 Azure 虚拟机

场景:使用 Terraform 创建一个 Azure 虚拟机。

步骤

  1. 创建项目目录
mkdir terraform-azure-vm && cd terraform-azure-vm
  1. 创建配置文件

main.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "example" {
  name     = "example-resources"
  location = "East US"
}

resource "azurerm_virtual_network" "example" {
  name                = "example-network"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_subnet" "example" {
  name                 = "example-subnet"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_network_interface" "example" {
  name                = "example-nic"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.example.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.example.id
  }
}

resource "azurerm_public_ip" "example" {
  name                = "example-pip"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  allocation_method   = "Static"
}

resource "azurerm_network_security_group" "example" {
  name                = "example-nsg"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "HTTP"
    priority                   = 1002
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet_network_security_group_association" "example" {
  subnet_id                 = azurerm_subnet.example.id
  network_security_group_id = azurerm_network_security_group.example.id
}

resource "azurerm_linux_virtual_machine" "example" {
  name                = "example-vm"
  resource_group_name = azurerm_resource_group.example.name
  location            = azurerm_resource_group.example.location
  size                = "Standard_B2s"
  admin_username      = "adminuser"
  network_interface_ids = [azurerm_network_interface.example.id]

  admin_ssh_key {
    username   = "adminuser"
    public_key = file("~/.ssh/id_rsa.pub")
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }
}
  1. 初始化和执行
# 初始化
terraform init

# 预览
terraform plan

# 执行
terraform apply

# 查看输出
terraform output

# 销毁
terraform destroy

6. 代码示例

6.1 AWS VPC 完整配置

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "MainVPC"
  }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.region}a"
  map_public_ip_on_launch = true

  tags = {
    Name = "PublicSubnet"
  }
}

resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "${var.region}a"

  tags = {
    Name = "PrivateSubnet"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "MainIGW"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "PublicRouteTable"
  }
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

resource "aws_security_group" "web" {
  name        = "WebSecurityGroup"
  description = "Allow HTTP and SSH traffic"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "WebSecurityGroup"
  }
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]

  tags = {
    Name = "WebServer"
  }
}

output "web_instance_public_ip" {
  value = aws_instance.web.public_ip
}

output "vpc_id" {
  value = aws_vpc.main.id
}

执行命令

# 初始化
terraform init

# 预览
terraform plan

# 执行
terraform apply

# 查看输出
terraform output

# 销毁
terraform destroy

6.2 Google Cloud 虚拟机配置

main.tf

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

provider "google" {
  project = "your-project-id"
  region  = "us-central1"
  zone    = "us-central1-a"
}

resource "google_compute_network" "vpc_network" {
  name = "terraform-network"
}

resource "google_compute_subnetwork" "subnetwork" {
  name          = "terraform-subnetwork"
  network       = google_compute_network.vpc_network.name
  region        = "us-central1"
  ip_cidr_range = "10.2.0.0/16"
}

resource "google_compute_firewall" "firewall" {
  name    = "terraform-firewall"
  network = google_compute_network.vpc_network.name

  allow {
    protocol = "tcp"
    ports    = ["22", "80"]
  }

  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_instance" "vm_instance" {
  name         = "terraform-instance"
  machine_type = "e2-micro"
  zone         = "us-central1-a"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-10"
    }
  }

  network_interface {
    network = google_compute_network.vpc_network.name
    subnetwork = google_compute_subnetwork.subnetwork.name

    access_config {
      // Ephemeral public IP
    }
  }

  metadata = {
    ssh-keys = "admin:${file("~/.ssh/id_rsa.pub"}"
  }
}

output "instance_ip_addr" {
  value       = google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip
  description = "The public IP address of the VM instance"
}

执行命令

# 初始化
terraform init

# 预览
terraform plan

# 执行
terraform apply

# 查看输出
terraform output

# 销毁
terraform destroy

7. 常见问题和解决方案

7.1 提供者插件安装失败

问题:初始化时提供者插件安装失败。

解决方案

  • 检查网络连接
  • 检查 Terraform 版本和提供者版本兼容性
  • 使用代理服务器:export HTTPS_PROXY=http://proxy.example.com:8080
  • 手动下载提供者插件并放置到相应目录

7.2 状态文件锁定

问题:执行 terraform apply 时提示状态文件被锁定。

解决方案

  • 等待其他操作完成
  • 如果是因为之前的操作异常终止,使用 terraform force-unlock LOCK_ID 解锁
  • 使用远程状态和状态锁定机制

7.3 资源创建失败

问题:资源创建失败,提示权限不足或资源不存在。

解决方案

  • 检查云提供商凭证是否正确
  • 检查用户权限是否足够
  • 检查资源配置是否正确
  • 检查云提供商的服务限制

7.4 循环依赖

问题:Terraform 提示循环依赖错误。

解决方案

  • 重新设计资源的依赖关系
  • 使用数据源查询现有资源
  • 避免不必要的 depends_on 声明

7.5 变量验证失败

问题:变量验证失败,提示类型不匹配或值无效。

解决方案

  • 检查变量定义和使用是否一致
  • 检查变量值是否符合验证规则
  • 使用正确的类型转换

8. 总结

Terraform 是一个强大的基础设施即代码工具,它通过声明式配置语言和状态管理,实现了基础设施的自动化管理和版本控制。Terraform 支持多种云服务提供商,为多云战略和混合云部署提供了统一的管理界面。

8.1 优点

  • 多云支持:支持多种云服务提供商,实现多云部署。
  • 声明式配置:使用 HCL 定义基础设施,配置清晰易读。
  • 状态管理:通过状态文件跟踪基础设施的变更,确保操作的一致性。
  • 模块系统:支持模块化和代码重用,提高配置的可维护性。
  • 计划和执行:在执行前预览变更,确保操作的安全性。
  • 资源依赖管理:自动处理资源之间的依赖关系。
  • 版本控制:基础设施配置可以纳入版本控制系统,实现可追溯性。

8.2 局限性

  • 学习曲线:对于初学者来说,Terraform 有一定的学习曲线。
  • 状态管理复杂性:在团队协作中,状态管理可能变得复杂。
  • 性能:对于大规模基础设施,Terraform 的执行速度可能较慢。
  • 错误处理:错误消息有时不够清晰,需要一定的调试经验。
  • 云提供商特定功能:某些云提供商的特定功能可能需要使用 provider-specific 的配置。

8.3 适用场景

  • 云基础设施管理:创建和管理云资源。
  • 多环境部署:在开发、测试和生产环境中一致部署基础设施。
  • 多云战略:在多个云提供商之间管理资源。
  • 基础设施自动化:自动化基础设施的创建、更新和销毁。
  • 灾难恢复:快速重建基础设施。
  • 成本优化:通过代码管理资源,避免资源浪费。

通过本教程,你应该已经掌握了 Terraform 的基本概念、安装配置、基本使用和常见场景的应用。在实际开发和运维中,Terraform 可以大大简化基础设施的管理工作,提高工作效率和基础设施的可靠性。

« 上一篇 Kubernetes 教程:容器编排平台详解 下一篇 » Ansible 教程:自动化配置管理工具详解