terraform学习

1 概述

terraform学习。
语法查询
https://developer.hashicorp.com/terraform/language
provider地址
https://registry.terraform.io/browse/modules
最佳实践 https://www.cloudbolt.io/eguides/terraform-best-practices/

2 terraform 命令参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
命令       用途
terraform version 查看 Terraform 版本
terraform init 初始化 Terraform
terraform plan Terraform 执行计划
terraform apply 应用 Terraform
terraform show 检查 Terraform 状态
terraform output 查看输出变量的值
terraform graph 生成资源依赖图
terraform destroy 销毁资源
terraform workspace 管理 Terraform 工作区
terraform workspace new 新建工作区
terraform workspace list 列出工作区
terraform workspace select 切换工作区
terraform workspace delete 删除工作区
terraform get 下载或更新 Terraform 模块
terraform fmt 格式化 Terraform 代码
terraform validate 检查 Terraform 语法
terraform console Terraform 控制台

3 terraform使用

3.1 编写Terraform配置

3.1.1 alias的使用

  • 多个 Azure 订阅管理: 如果你有多个 Azure 订阅,每个订阅可能有不同的资源组和资源,你可以通过不同的 alias 配置来管理它们
  • 跨环境管理: 在开发、测试和生产环境中可能有不同的 Azure 订阅或账户,使用 alias 可以在同一个 Terraform 配置文件中管理这些环境的资源。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    terraform {
    required_version = "~> 1.1"
    required_providers {
    azurerm = {
    version = "~> 3.9.0"
    }
    }
    }

    provider "azurerm" {
    # 订阅id
    subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    alias = "sub1"
    features {}
    }

    provider "azurerm" {
    subscription_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
    alias = "sub2"
    features {}
    }

    resource "azurerm_resource_group" "rg" {
    provider = azurerm.sub1 # 使用别名为 sub1 的 Azure provider
    name = "rg-sub1"
    location = "westeurope"
    }

    resource "azurerm_resource_group" "rg2" {
    provider = azurerm.sub2 # 使用别名为 sub2 的 Azure provider
    name = "rg-sub2"
    location = "westeurope"
    }

3.1.2 使用check来验证基础设施

1
2
3
4
5
6
7
8
9
10
11
12
check "response" {
data "http" "webapp" {
url = "https://${azurerm_linux_web_app.app.default_hostname}"
insecure = true
}

#通过检查http url地址访问状态码是否为200
assert {
condition = data.http.webapp.status_code == 200
error_message = "Web app response is ${data.http.webapp.status_code}"
}
}

3.1.3 depends_on的使用

https://developer.hashicorp.com/terraform/language/meta-arguments/depends_on

1
2
3
4
5
6
7
8
9
10
11
12
13
14
resource "azurerm_resource_group" "rg" {
name = "rgdep"
location = "westeurope"
}

resource "azurerm_virtual_network" "vnet" {
name = "vnet"
location = "westeurope"
resource_group_name = "rgdep"
address_space = ["10.0.0.0/16"]

depends_on = [azurerm_resource_group.rg]

}

3.1.4 函数upper的使用

函数upper的使用,将给定字符串中的所有大小写字母转换为大写
更多函数的使用参考:https://developer.hashicorp.com/terraform/language/functions/chomp

1
2
3
4
5
#FOR CONDITION
resource "azurerm_resource_group" "rg-app" {
name = var.environment == "Production" ? upper(format("RG-%s", var.app_name)) : upper(format("RG-%s-%s", var.app_name, var.environment))
location = "westeurope"
}

3.1.5 环境变量local块的使用

示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
resource "azurerm_public_ip" "pip" {
name = "IP-${local.resource_name}"
location = "westeurope"
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
domain_name_label = "mydomain"
}


locals {
resource_name = "${var.application_name}-${var.environment_name}-${var.country_code}"
}

示例2

动态配置生成:适用于需要根据外部数据源(如 YAML 文件)动态生成资源配置的场景,避免硬编码每个子网的配置。
模块化和复用:通过 dynamic 块可以实现资源配置的模块化和复用,尤其是在处理变化和扩展性较高的情况下。

1
2
3
4
5
6
7
8

vnet: "myvnet"
address_space: "10.0.0.0/16"
subnets:
- name: subnet1
iprange: "10.0.1.0/24"
- name: subnet2
iprange: "10.0.2.0/24"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
locals {
network = yamldecode(file("network.yaml"))
}
resource "azurerm_virtual_network" "vnet" {
name = local.network.vnet
address_space = [local.network.address_space]
dynamic "subnet" { #动态生成子网
for_each = local.network.subnets #遍历列表
content { #content 块:content 块内部的配置会根据 for_each 迭代生成多个实例,每个实例代表一个子网。
name = subnet.value.name
address_prefix = subnet.value.iprange
}
}
}

3.1.6 output块的使用

1
2
3
4
output "webapp_name" {
description = "output Name of the webapp"
value = azurerm_app_service.app.name
}

3.1.7 随机数的使用

https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
resource "random_password" "password" {
length = 16
special = true
override_special = "_%@"
}

# Create virtual machine
resource "azurerm_linux_virtual_machine" "myterraformvm" {
name = "myVM"
location = "westeurope"
resource_group_name = azurerm_resource_group.myterraformgroup.name
network_interface_ids = [azurerm_network_interface.myterraformnic.id]
disable_password_authentication = false
computer_name = "vmdemo"
admin_username = "uservm"
admin_password = random_password.password.result
size = "Standard_DS1_v2"

os_disk {
name = "myOsDisk"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
1
2
3
4
5
6
7
8
resource "random_string" "random" {
length = 4
special = false
upper = false
}
resource "azurerm_resource_group" "rg-app" {
name = "${random_string.random.result}"
}

3.1.8 terraform_remote_state的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
provider "azurerm" {
skip_provider_registration = true # skip registration
features {} # features
}

# data "terraform_remote_state" "service_plan_tfstate" 用于从远程存储中获取 Terraform 状态信息。
data "terraform_remote_state" "service_plan_tfstate" {
backend = "azurerm" #远程后端
config = { # config 区块包含了连接到远程状态存储所需的详细信息:
resource_group_name = "rg_tfstate"
storage_account_name = "storstate"
container_name = "tfbackends"
key = "serviceplan.tfstate"
}
}

3.1.9 variables的使用

1
2
# 定义的location变量要符合validation中的要求
location = "westeurope"
1
2
3
4
5
6
7
8
9
variable "location" {
description = "The name of the Azure location"
default = "westeurope"
type = string
validation {
condition = contains(["westeurope", "westus"], var.location)
error_message = "The location must be westeurope or westus."
}
}

3.2 使用Terraform扩展基础设施

1
2
3
4
5
6
7
8
9
10
11
12
13
14
variable "nsg_rules" {
description = "List of NSG rules"
type = list(object({
name = string
priority = number
direction = string
access = string
protocol = string
source_port_range = string
destination_port_range = string
source_address_prefix = string
destination_address_prefix = string
}))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
nsg_rules = [
{
name = "rule1"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
},
{
name = "rule"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resource "azurerm_network_security_group" "example" {
name = "acceptanceTestSecurityGroup1"
dynamic "security_rule" {
for_each = var.nsg_rules
content {
name = security_rule.value["name"]
priority = security_rule.value["priority"]
direction = security_rule.value["direction"]
access = security_rule.value["access"]
protocol = security_rule.value["protocol"]
source_port_range = security_rule.value["source_port_range"]
destination_port_range = security_rule.value["destination_port_range"]
source_address_prefix = security_rule.value["source_address_prefix"]
destination_address_prefix = security_rule.value["destination_address_prefix"]
}
}
}

1
2
3
4
5
6
7
# 1.count
# 2.dynamics
# 3.filter_array
# 4.list_map
# 5.map
# 6.map_merge

3.3

4 terraform之provisioner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
resource "aws_instance" "app_server" {
count = var.instance_count

ami = data.aws_ami.amazon_linux.id
instance_type = var.ec2_instance_type
key_name = "galendu-key"
user_data_base64 = "IyEvYmluL2Jhc2gNCnN1ZG8geXVtIHVwZGF0ZSAteQ0Kc3VkbyB5dW0gaW5zdGFsbCAteSBhbWF6b24tbGludXgtZXh0cmFzDQpzdWRvIHl1bSBpbnN0YWxsIG5naW54IC15DQpzdWRvIHN5c3RlbWN0bCBlbmFibGUgbmdpbngNCnN1ZG8gc3lzdGVtY3RsIHN0YXJ0IG5naW54DQpFT0Y="

tags = var.resource_tags

# provisioner "file" {
# source = "./conf/nginx.conf"
# destination = "/etc/nginx/nginx.conf"

# connection {
# type = "ssh"
# host = self.public_ip
# user = "ec2_user"
# private_key = file("./.keys/galendu-key.pem")
# timeout = "4m"
# }
# }

# provisioner "remote-exec" {
# inline = [
# "sudo systemctl restart nginx",
# "sudo ls /usr/share/nginx/html/",
# ]
# }

provisioner "local-exec" {
command = "ps -ef"
}

}

5 terraform之modules

https://developer.hashicorp.com/terraform/tutorials/modules

示例代码

1
2
3
4
5
6
7
8
9
10
git clone https://github.com/hashicorp/learn-terraform-modules-create.git

cd learn-terraform-modules-create

terraform init
terraform plan
terraform apply
aws s3 cp modules/aws-s3-static-website-bucket/www/ s3://$(terraform output -raw website_bucket_name)/ --recursive
aws s3 rm s3://$(terraform output -raw website_bucket_name)/ --recursive
terraform destroy

5 terraform最佳实践

5.1 terraform调试方法

  • 配置调试日志
    1
    2
    3
    4
    5
    6
    7
    8
    9
    开启详细日志输出(环境变量 TF_LOG)
    Terraform 使用环境变量 TF_LOG 来控制日志级别。你可以设置不同的日志级别来获得不同程度的调试信息。

    日志级别:
    TRACE:最高级别的日志,提供最详细的信息。
    DEBUG:包含调试信息。
    INFO:默认的日志级别,提供较少的详细信息。
    WARN:仅记录警告信息。
    ERROR:仅记录错误信息。
  • 检查语法是否正确
    terraform validate
  • 检查格式是否正确
    terraform fmt
  • 查看可视化依赖,生成依赖图
    terraform graph | dot -Tpng > graph.png
  • 交互式命令行
    1
    2
    3
    terraform console
    > var.my_variable
    > aws_instance.example
  • 模块调试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 1.在模块中定义输出
    output "instance_id" {
    value = aws_instance.example.id
    }

    # 2.在根模块中引用这个输出
    module "my_module" {
    source = "./module"
    }

    output "module_instance_id" {
    value = module.my_module.instance_id
    }
    # terraform apply -autoapprove -target="moudle.my_module"
  • terraform state 调试状态文件
    terraform state show aws_instance.example
  • terraform taint 标记资源重新创建
    terraform taint aws_instance.example
  • terraform refresh 刷新状态
    terraform refresh

5.2 alicloud配置

1
2
3
# 配置aliyun到git 目录 /c/Program Files/Git/usr/bin/aliyun.exe
# 配置阿里云ak/sk
aliyun configure --profile AkProfile

5.3 通过terragrunt管理terraform

https://github.com/gruntwork-io/terragrunt
https://terragrunt.gruntwork.io/

5.3.1 安装

1
2
3
4
5
6
7
8
9
10
11
12
wget https://https://ghp.ci/https://github.com/gruntwork-io/terragrunt/releases/download/v0.68.4/terragrunt_windows_amd64.exe 

#Windows: You can install Terragrunt on Windows using Chocolatey:
choco install terragrunt

#macOS: You can install Terragrunt on macOS using Homebrew:
brew install terragrunt

#FreeBSD: You can install Terragrunt on FreeBSD using Pkg:
pkg install terragrunt

terragrunt --install-autocomplete

5.3.2 使用

  1. 生成依赖图
    terragrunt graph-dependencies | dot -Tsvg > graph.svg
  2. 限制并行数 terragrunt run-all apply --terragrunt-parallelism 4
  3. 保存输出terragrunt run-all plan --terragrunt-out-dir /tmp/tfplan
  4. 通过输出的文件进行部署terragrunt run-all apply --terragrunt-out-dir /tmp/tfplan
  5. 删除输出文件terragrunt run-all plan -destroy --terragrunt-out-dir /tmp/tfplan
  6. 输出二进制文件和json文件terragrunt run-all plan --terragrunt-out-dir /tmp/all --terragrunt-json-out-dir /tmp/all
  7. 常用命令
    1
    2
    3
    4
    terragrunt run-all apply
    terragrunt run-all destroy
    terragrunt run-all output
    terragrunt run-all plan

5.3.3 常用函数

1
2
3

#获取当前目录的基本名称
basename(get_terragrunt_dir())

6 terraform常见问题处理

6.1 lock锁

terraform force-unlock <ID>

6.2 for_each、appends_on、count不能使用

删除modules中的versions.tf的provider配置

作者

Galen Du

发布于

2024-09-03

更新于

2025-02-17

许可协议

评论