学习Terraform入门(3)之创建VPC
这是Terraform入门系列的第三节,我们将通过实践来学习。
在上一节的基础教程中,我们先创建了一个VPC,而这一次我们将再次创建VPC相关的资源,包括子网和路由表等。
边创作边学习的Terraform入门系列
-
- 安装和初始设置
-
- 基础篇
VPC篇 => 这次是这个
EC2篇
Route53 + ACM篇
ELB篇
RDS篇
这次的学习重点如下。
-
- 変数の使い方
-
- リソース間の参照方法
-
- 関数の使い方
- Stateファイルの管理
创建VPC
首先,我们将重新创建虚拟私有云(VPC)。
将network.tf文件的内容修改如下。
################################
# VPC
################################
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.prefix}-vpc"
}
}
以下有关DNS的两个参数,我认为基本上使用时都需要启用。由于这是相当重要的设置,所以无论是true还是false,都应明确地进行描述。
-
- DNS解決: enable_dns_support
- DNSホスト名: enable_dns_hostnames
如省略,则默认情况下,enable_dns_support将为true,enable_dns_hostnames将为false。有关参数含义,请参考官方文档中关于VPC中DNS使用的部分。
默认情况下会设置默认值,但省略了instance_tenancy的配置。
您可以通过在tags中使用KeyValue的组合来指定标签,以便在控制台上进行识别。如果不指定,将使用Name标签来识别资源。
变量的使用方法
请将多次出现的字符串或环境变量化。使用variable块来定义变量。虽然在现有的network.tf文件中编写也能运行,但通常将其划分为另一个文件variables.tf来进行描述。
# Common
variable "prefix" {
description = "Project name given as a prefix"
type = string
default = "cloud02"
}
# VPC
variable "vpc_cidr" {
description = "The CIDR block of the VPC"
type = string
default = "10.0.0.0/16"
}
变量定义以及变量名,并用花括号{}将其包围,描述、类型、默认值等等都会在花括号内描述。尽管可以省略描述和类型,但建议不要省略并加以描述。
在network.tf中,我们以以下方式编写和调用定义的变量。
-
- cidr_block = var.vpc_cidr
- Name = “${var.prefix}-vpc”
您可以使用变量名调用变量,但如果要在字符串中引用变量,则需要使用”${var.变量名}-vpc”的形式将变量用${}括起来,而整个字符串需要用双引号括起来。
如果在variables.tf中没有指定默认值,那么在执行plan或apply时会显示提示并要求输入。这意味着变量已经被声明,但却无法知道其值。
terraform plan
var.prefix
Enter a value:
也可以通过使用-var参数来传递参数,例如:terraform plan -var=”prefix=cloud02″。
terraform plan -var="prefix=cloud02" -var="vpc_cidr=10.0.0.0/16"
当您在名为 terraform.tfvars 的文件中编写变量值时,它将自动加载这些值。
prefix = "cloud02"
vpc_cidr = "10.0.0.0/16"
也可以通过环境变量进行指定。以TF_VAR_<变量名>=<值>的格式进行声明。
export TF_VAR_prefix=cloud02
export TF_VAR_vpc_cidr=10.0.0.0/16
整理後,如果未写出变量的默认值,
-
- コマンド実行時にプロンプトで入力する
-
- コマンド実行時に-varオプションで指定する
terraform.tfvarsファイルで指定する
環境変数で指定する
如果同时指定了-var选项、terraform.tfvars和环境变量,那么优先级将按顺序为-var选项、terraform.tfvars和环境变量,并且如果都没有指定的话,则需要通过提示输入。
最初可能会难以理解 variables.tf 和 terraform.tfvars 的使用区别,
variables.tfには変数の宣言を書く
terraform.tfvarsには変数の値を書く
儘管variables.tf中指定所有變數的默認值時,terraform.tfvars是不必要的,但是當定義環境中會變動的值(如開發/測試/正式等)或者包含敏感信息(如密碼),並且在.gitignore中將其排除於版本控制之外時,terraform.tfvars仍然很有用。
如果您想使用除了terraform.tfvars之外的文件名来定义变量,例如,使用一个名为dev.tfvars的文件来描述开发环境的变量,您可以使用-var-file参数来指定并读取该文件。
terraform plan -var-file="dev.tfvars"
以上是关于变量的基本使用方法。
还有一种声明局部变量的方式是使用 locals 块,但基本使用方法是相同的。
接下来我们将继续处理 variables.tf 和 terraform.tfvars 文件。
# Common
variable "prefix" {}
# VPC
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
prefix = "cloud02"
你可以在这个阶段先申请并确认VPC的创建,也可以直接继续编写后续代码并最后进行申请,都可以。
创建互联网门户
接下来我们将创建互联网网关。
在network.tf文件中添加以下代码。
################################
# Internet Gateway
################################
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.prefix}-igw"
}
}
在aws_vpc.vpc.id中,vpc_id引用了VPC的ID。
当将本次的IGW与VPC或其他资源关联时,可以使用”<资源类型>.<资源的标识名称>.<属性>”的形式来指定关联资源。
在本例中,属性是id。
通过参考”Attributes Reference”,您可以了解有哪些属性,但在大多数情况下,会使用用于唯一标识资源的id或arn等。
创建子网
接下来我们将创建子网。
我们将在network.tf文件中追加以下代码。
################################
# Subnet
################################
resource "aws_subnet" "public_subnet_1a" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-1a"
cidr_block = cidrsubnet(var.vpc_cidr, 8, 11) # 10.0.11.0/24
map_public_ip_on_launch = true
tags = {
"Name" = "${var.prefix}-public-subnet-1a"
}
}
resource "aws_subnet" "public_subnet_1c" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-1c"
cidr_block = cidrsubnet(var.vpc_cidr, 8, 12) # 10.0.12.0/24
map_public_ip_on_launch = true
tags = {
"Name" = "${var.prefix}-public-subnet-1c"
}
}
resource "aws_subnet" "private_subnet_1a" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-1a"
cidr_block = cidrsubnet(var.vpc_cidr, 8, 21) # 10.0.21.0/24
map_public_ip_on_launch = false
tags = {
"Name" = "${var.prefix}-private-subnet-1a"
}
}
resource "aws_subnet" "private_subnet_1c" {
vpc_id = aws_vpc.vpc.id
availability_zone = "ap-northeast-1c"
cidr_block = cidrsubnet(var.vpc_cidr, 8, 22) # 10.0.22.0/24
map_public_ip_on_launch = false
tags = {
"Name" = "${var.prefix}-private-subnet-1c"
}
}
查看系统配置,您会很容易理解:我们在AZ-1a和AZ-1c分别创建了公共子网和私有子网(共计4个)。
公共子网使用”map_public_ip_on_launch”选项将公共IP自动分配设置为true。此外,”cidr_block”通过使用名为”cidrsubnet”的函数来根据VPC的CIDR”10.0.0.0/16″来计算得出。
在terraform console中查看每个函数部分后,cidrsubnet(prefix, newbits, netnum)这个句子就变得很明显了。
terraform console
> cidrsubnet("10.0.0.0/16", 8, 11)
"10.0.11.0/24"
> cidrsubnet("10.0.0.0/16", 8, 12)
"10.0.12.0/24"
> cidrsubnet("10.0.0.0/16", 8, 21)
"10.0.21.0/24"
> cidrsubnet("10.0.0.0/16", 8, 22)
"10.0.22.0/24"
Terraform中有许多其他内置函数,但要进行操作确认,terraform控制台非常方便。
创建路由表
在结束时,创建路由表并将其关联到子网上。
在network.tf文件中追加以下代码。
################################
# Public Route Table
################################
resource "aws_route_table" "public_rt" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.prefix}-public-route"
}
}
resource "aws_route" "to_internet" {
route_table_id = aws_route_table.public_rt.id
gateway_id = aws_internet_gateway.igw.id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route_table_association" "public_rt_1a" {
subnet_id = aws_subnet.public_subnet_1a.id
route_table_id = aws_route_table.public_rt.id
}
resource "aws_route_table_association" "public_rt_1c" {
subnet_id = aws_subnet.public_subnet_1c.id
route_table_id = aws_route_table.public_rt.id
}
################################
# Private Route Table
################################
resource "aws_route_table" "private_rt" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.prefix}-private-route"
}
}
resource "aws_route_table_association" "private_rt_1a" {
subnet_id = aws_subnet.private_subnet_1a.id
route_table_id = aws_route_table.private_rt.id
}
resource "aws_route_table_association" "private_rt_1c" {
subnet_id = aws_subnet.private_subnet_1c.id
route_table_id = aws_route_table.private_rt.id
}
使用aws_route_table创建空的路由表,
使用aws_route添加路由,
使用aws_route_table_association将路由表与子网关联。
请注意,以上为一种可能的汉语翻译选项。
对于公共子网的路由,将其与Internet Gateway相关联,作为指向互联网的路由。对于私有子网,本次不需要添加额外的路由,但如果存在NAT网关等情况,可以以类似的方式添加路由。
执行terraform apply命令,确认VPC、互联网网关、子网和路由表已经创建并关联。
将State文件迁移到S3。
现在,正如在Terraform执行计划中所解释的那样,Terraform可以很方便地了解差异,这是通过比较将要执行的代码和名为terraform.tfstate的文件实现的。
terraform.tfstate是一个由Terraform自动创建和更新的文件,它记录了由Terraform管理的基础设施的状态。文件内容采用JSON格式编写。
如果意外删除了terraform.tfstate文件,所有资源都将被视为新资源,因此执行apply命令后,之前创建的VPC相关资源将被再次全部创建。也就是说,对于维护和管理基础架构的状态来说,这是一个非常重要的文件。
在团队开发中,由于需要进行多人共享,因此通常使用像S3这样的存储来管理terraform.tfstate。在学习范围内,即使terraform.tfstate保存在本地也没有问题,但本文想要尝试将terraform.tfstate迁移到S3中。
在Terraform中,用于定义保存terraform.tfstate等文件的位置的术语称为”backend”,我们需要在provider.tf文件中添加backend块。请根据需要修改S3存储桶的名称。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "cloud02-tf-remote-state-bucket"
key = "ap-northeast-1/dev/terraform.tfstate"
region = "ap-northeast-1"
}
}
provider "aws" {
region = "ap-northeast-1"
}
通过写入后端 “s3” 来声明将存储位置设为S3。
同时,如果写入后端 “remote”,则表示将使用 Terraform Cloud。
在bucket key region中,分别指定桶名、tfstate路径(文件名)和地区。
请注意,正如Backend Configuration所述,backend块中无法使用变量。
后端程序块不能引用命名值(例如输入变量、局部变量或数据源属性)。
S3桶在后端被指定,事先通过控制台界面创建。
虽然可以用Terraform创建S3桶本身,但在这种情况下,最好单独创建,不包括在同一管理下。
如果可以进行修改,我会尝试执行terraform plan。
terraform plan
Error: Backend initialization required, please run "terraform init"
│
│ Reason: Initial configuration of the requested backend "s3"
│
│ The "backend" is the interface that Terraform uses to store state,
│ perform operations, etc. If this message is showing up, it means that the
│ Terraform configuration you're using is using a custom configuration for
│ the Terraform backend.
│
│ Changes to backend configurations require reinitialization. This allows
│ Terraform to set up the new configuration, copy existing state, etc. Please run
│ "terraform init" with either the "-reconfigure" or "-migrate-state" flags to
│ use the current configuration.
│
│ If the change reason above is incorrect, please verify your configuration
│ hasn't changed and try again. At this point, no changes to your existing
│ configuration or state have been made.
在这种情况下,由于后端配置变更,您将被要求执行terraform init。
terraform init
Initializing the backend...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "s3" backend. No existing state was found in the newly
configured "s3" backend. Do you want to copy this state to the new "s3"
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value: yes
执行 terraform init 后,会询问是否要将本地的 tfstate 复制到 S3 上,请输入 “yes” 继续。这样一来,本地的 terraform.tfstate 将被清除,并在 S3 存储桶上创建文件。
我将在这种情况下重新运行terraform plan。
terraform plan
─────────────────────────────────────────────────────────────────────────────────────────────────
No changes. Your infrastructure matches the configuration.
Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a refresh-only plan:
terraform apply -refresh-only
由于显示为”No changes”,我们可以确定已经参考了S3的terraform.tfstate进行差异检查。从此以后,每次进行更改时,S3中的terraform.tfstate将被更新。
通过将数据保存在S3上,可以实现多人共享,但严格来说,在更新期间需要一个机制来锁定以防止同时进行更改。使用DynamoDB可以轻松实现这一点,就像在DynamoDB State Locking中所示,但由于我还没有尝试过,所以这次将省略。
这次就到这里吧。下次我们来讲讲EC2,试着建立两个Web服务器!
请参考以下链接
-
- VPC での DNS の使用
-
- Input Variables
Local Values
cidrsubnet Function
S3 Backend