Terraform解说
跳转到导航
跳转到搜索
main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# --------------------------------------------------
# VPC
# --------------------------------------------------
resource "aws_vpc" "lab" {
cidr_block = "10.10.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "ssh-port-lab-vpc"
}
}
# --------------------------------------------------
# Public Subnet
# --------------------------------------------------
resource "aws_subnet" "public" {
vpc_id = aws_vpc.lab.id
cidr_block = "10.10.1.0/24"
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "ssh-port-lab-public"
}
}
# --------------------------------------------------
# Internet Gateway
# --------------------------------------------------
resource "aws_internet_gateway" "lab" {
vpc_id = aws_vpc.lab.id
tags = {
Name = "ssh-port-lab-igw"
}
}
# --------------------------------------------------
# Route Table
# --------------------------------------------------
resource "aws_route_table" "public" {
vpc_id = aws_vpc.lab.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.lab.id
}
tags = {
Name = "ssh-port-lab-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# --------------------------------------------------
# Security Group
# --------------------------------------------------
resource "aws_security_group" "ssh" {
name = "ssh-port-lab-sg"
description = "Allow SSH for SSH port migration lab"
vpc_id = aws_vpc.lab.id
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.my_ip]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "ssh-port-lab-sg"
}
}
# --------------------------------------------------
# EC2 Key Pair
# --------------------------------------------------
resource "aws_key_pair" "lab" {
key_name = var.key_name
public_key = file(var.public_key_path)
}
# --------------------------------------------------
# Amazon Linux 2023 AMI
# --------------------------------------------------
data "aws_ssm_parameter" "al2023" {
name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}
# --------------------------------------------------
# Launch Template
# --------------------------------------------------
resource "aws_launch_template" "lab" {
name_prefix = "ssh-port-lab-"
image_id = data.aws_ssm_parameter.al2023.value
instance_type = "t3.micro"
key_name = aws_key_pair.lab.key_name
vpc_security_group_ids = [
aws_security_group.ssh.id
]
tag_specifications {
resource_type = "instance"
tags = {
Name = "ssh-port-lab-ec2"
}
}
}
# --------------------------------------------------
# Auto Scaling Group
# --------------------------------------------------
resource "aws_autoscaling_group" "lab" {
name = "ssh-port-lab-asg"
min_size = 1
max_size = 1
desired_capacity = 1
vpc_zone_identifier = [
aws_subnet.public.id
]
launch_template {
id = aws_launch_template.lab.id
version = "$Latest"
}
health_check_type = "EC2"
tag {
key = "Name"
value = "ssh-port-lab-asg-instance"
propagate_at_launch = true
}
}
这个 `main.tf` 很适合拿来理解 Terraform,因为它基本把 AWS Infra 里最常见的几个概念串起来了:
**VPC → Subnet → Internet Gateway → Route Table → Security Group → Key Pair → AMI → Launch Template → ASG**
我按你现在这个实验的实际运行路径,从上到下解释。
---
# 1. Terraform 本身的版本和 Provider
```hcl
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
```
这里**不是创建 AWS 资源**,而是在告诉 Terraform:
> “这个项目需要什么 Terraform 版本,以及什么 Provider。”
---
### `required_version`
```hcl
required_version = ">= 1.5.0"
```
意思:
```text
Terraform >= 1.5.0
```
比如:
```text
Terraform 1.4 ❌
Terraform 1.5 ✅
Terraform 1.6 ✅
Terraform 1.13 ✅
```
---
### `required_providers`
```hcl
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
```
Terraform 自己并不知道:
```hcl
resource "aws_instance" ...
```
是什么意思。
真正知道 AWS API 怎么调用的是:
**AWS Provider**
所以:
```text
Terraform
│
▼
AWS Provider
│
▼
AWS API
│
▼
VPC / EC2 / ASG / SG ...
```
---
### `source`
```hcl
source = "hashicorp/aws"
```
就是:
> 使用 HashiCorp 官方的 AWS Provider。
---
### `version`
```hcl
version = "~> 6.0"
```
表示使用 6.x 系列的 provider,避免 Terraform 随便升级到不兼容的大版本。
---
# 2. Provider
```hcl
provider "aws" {
region = var.aws_region
}
```
这个才是告诉 AWS Provider:
> “我们操作哪个 AWS Region?”
你在 `variables.tf` 中定义了:
```hcl
variable "aws_region" {
default = "ap-east-1"
}
```
所以最终相当于:
```hcl
provider "aws" {
region = "ap-east-1"
}
```
也就是:
```text
Hong Kong
ap-east-1
```
---
# 3. VPC
```hcl
resource "aws_vpc" "lab" {
cidr_block = "10.10.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "ssh-port-lab-vpc"
}
}
```
这是我们创建的第一个 AWS 资源。
Terraform 的基本语法:
```hcl
resource "TYPE" "NAME" {
...
}
```
这里:
```hcl
resource "aws_vpc" "lab"
```
可以理解成:
```text
resource type = aws_vpc
resource name = lab
```
Terraform 内部引用它的时候:
```hcl
aws_vpc.lab
```
---
### CIDR
```hcl
cidr_block = "10.10.0.0/16"
```
我们的 VPC:
```text
10.10.0.0/16
```
范围比较大:
```text
10.10.0.0
↓
10.10.255.255
```
然后我们后面从里面划一个 subnet:
```text
VPC
10.10.0.0/16
│
└── subnet
10.10.1.0/24
```
---
### DNS
```hcl
enable_dns_hostnames = true
enable_dns_support = true
```
让 VPC 支持 AWS DNS 功能以及实例 DNS hostname。
对于 EC2 / AWS Infra 环境,一般建议开启。
---
# 4. Public Subnet
```hcl
resource "aws_subnet" "public" {
vpc_id = aws_vpc.lab.id
cidr_block = "10.10.1.0/24"
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "ssh-port-lab-public"
}
}
```
这里开始出现一个非常重要的 Terraform 概念:
```hcl
vpc_id = aws_vpc.lab.id
```
这不是手写:
```hcl
vpc_id = "vpc-0123456789"
```
而是:
> 把刚才创建的 VPC 的 ID 自动传给 Subnet。
---
## `aws_vpc.lab.id`
可以拆成:
```text
aws_vpc
│
└── lab
│
└── id
```
也就是:
```text
资源类型 → 资源名称 → 属性
```
Terraform 会自动建立依赖关系:
```text
aws_vpc.lab
│
▼
aws_subnet.public
```
所以 Terraform 知道:
> 先创建 VPC,再创建 Subnet。
这就是 Terraform 非常核心的:
**Dependency Graph**
---
## Subnet CIDR
```hcl
cidr_block = "10.10.1.0/24"
```
这个 subnet 位于:
```text
10.10.0.0/16
```
里面。
所以:
```text
VPC
10.10.0.0/16
└── Public Subnet
10.10.1.0/24
```
---
## Availability Zone
```hcl
availability_zone = "${var.aws_region}a"
```
如果:
```hcl
var.aws_region = "ap-east-1"
```
Terraform 会得到:
```text
ap-east-1a
```
---
## `map_public_ip_on_launch`
```hcl
map_public_ip_on_launch = true
```
意思是:
> 在这个 subnet 中启动的 EC2,默认分配 Public IPv4。
所以我们后面才能:
```bash
ssh [email protected]
```
---
# 5. Internet Gateway
```hcl
resource "aws_internet_gateway" "lab" {
vpc_id = aws_vpc.lab.id
tags = {
Name = "ssh-port-lab-igw"
}
}
```
Internet Gateway:
**IGW**
它负责让 VPC 和 Internet 之间进行 Internet connectivity。
关系:
```text
Internet
│
▼
Internet Gateway
│
▼
VPC
│
▼
Subnet
│
▼
EC2
```
注意:
**仅仅创建 IGW 并不能让 EC2 上网。**
还需要 Route Table。
---
# 6. Route Table
```hcl
resource "aws_route_table" "public" {
vpc_id = aws_vpc.lab.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.lab.id
}
tags = {
Name = "ssh-port-lab-public-rt"
}
}
```
这里非常重要。
```hcl
cidr_block = "0.0.0.0/0"
```
意思:
> 所有不是本地 VPC 的 IPv4 流量。
然后:
```hcl
gateway_id = aws_internet_gateway.lab.id
```
意思:
> 把这些流量送到 Internet Gateway。
所以:
```text
0.0.0.0/0
│
▼
Internet Gateway
```
---
# 7. Route Table Association
前面只是创建了 Route Table。
现在要告诉 AWS:
> “这个 Route Table 应该用于哪个 Subnet?”
```hcl
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
```
最终:
```text
Public Subnet
10.10.1.0/24
│
│ association
▼
Public Route Table
│
└── 0.0.0.0/0 → IGW
```
于是这个 subnet 才真正成为 Public Subnet。
---
# 8. Security Group
```hcl
resource "aws_security_group" "ssh" {
name = "ssh-port-lab-sg"
description = "Allow SSH for SSH port migration lab"
vpc_id = aws_vpc.lab.id
```
Security Group 可以理解成:
> EC2 的虚拟防火墙。
我们这个实验第一阶段只允许 SSH:
```text
Internet
│
│ TCP 22
▼
Security Group
│
▼
EC2
```
---
## Inbound
```hcl
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.my_ip]
}
```
这里非常重要。
```hcl
from_port = 22
to_port = 22
```
允许:
```text
TCP/22
```
---
### `var.my_ip`
比如:
```hcl
my_ip = "1.2.3.4/32"
```
那么只有:
```text
1.2.3.4
```
可以访问:
```text
EC2:22
```
而不是:
```hcl
0.0.0.0/0
```
这样实验环境安全很多。
---
# 9. Egress
```hcl
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
```
这是:
> EC2 出去的流量全部允许。
例如:
```text
EC2
│
├── yum
├── curl
├── AWS API
└── Internet
```
都可以出去。
---
# 10. Key Pair
```hcl
resource "aws_key_pair" "lab" {
key_name = var.key_name
public_key = file(var.public_key_path)
}
```
这个资源非常值得理解。
你本地:
```text
~/.ssh/id_ed25519
~/.ssh/id_ed25519.pub
```
Terraform:
```hcl
public_key = file(var.public_key_path)
```
读取:
```text
id_ed25519.pub
```
然后上传到 AWS:
```text
AWS EC2 Key Pair
```
---
## 注意一个非常重要的事情
Terraform **没有把你的 private key 上传 AWS**。
流程是:
```text
你的电脑
id_ed25519 ← Private Key
│
│ 不上传
X
id_ed25519.pub ← Public Key
│
▼
Terraform
│
▼
AWS Key Pair
```
所以你执行:
```bash
ssh -i ~/.ssh/id_ed25519 [email protected]
```
的时候:
```text
你的 private key
│
▼
EC2 sshd
│
▼
验证 AWS 中对应的 public key
```
---
# 11. AL2023 AMI
这里是整个代码里一个很典型的 **data source**:
```hcl
data "aws_ssm_parameter" "al2023" {
name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}
```
注意:
```text
resource
```
和:
```text
data
```
是不一样的。
---
## `resource`
例如:
```hcl
resource "aws_vpc" "lab"
```
意思:
> Terraform **创建/管理**这个 AWS 资源。
---
## `data`
```hcl
data "aws_ssm_parameter" "al2023"
```
意思:
> Terraform **查询已有的信息**。
这里 Terraform 查询 AWS Systems Manager Parameter Store:
```text
/aws/service/ami-amazon-linux-latest/
al2023-ami-kernel-default-x86_64
```
AWS 会告诉 Terraform 当前 Amazon Linux 2023 的 AMI ID。
例如可能类似:
```text
ami-xxxxxxxxxxxxxxxxx
```
这样我们就不用硬编码:
```hcl
image_id = "ami-123456789"
```
因为 Amazon Linux AMI 会更新。
---
# 12. Launch Template
```hcl
resource "aws_launch_template" "lab" {
name_prefix = "ssh-port-lab-"
image_id = data.aws_ssm_parameter.al2023.value
instance_type = "t3.micro"
key_name = aws_key_pair.lab.key_name
vpc_security_group_ids = [
aws_security_group.ssh.id
]
```
这个资源非常重要。
**Launch Template 可以理解成 EC2 的“启动模板”。**
它定义:
```text
EC2 应该长什么样?
```
包括:
```text
AMI
Instance Type
SSH Key
Security Group
User Data
Disk
Network
...
```
---
## AMI
```hcl
image_id = data.aws_ssm_parameter.al2023.value
```
刚才:
```text
data.aws_ssm_parameter.al2023
```
查到的是 SSM Parameter。
它的:
```text
.value
```
就是实际 AMI ID。
所以:
```text
SSM Parameter
│
▼
AMI ID
│
▼
Launch Template
│
▼
EC2
```
---
# 13. Instance Type
```hcl
instance_type = "t3.micro"
```
就是:
```text
EC2 = t3.micro
```
我们的实验不需要很大的机器。
---
# 14. Key Name
```hcl
key_name = aws_key_pair.lab.key_name
```
又出现了 Terraform dependency。
它不是:
```hcl
key_name = "ssh-port-lab-key"
```
而是:
```text
aws_key_pair.lab
│
└── key_name
```
所以 Terraform 自动知道:
```text
Key Pair
↓
Launch Template
↓
EC2
```
---
# 15. Security Group
```hcl
vpc_security_group_ids = [
aws_security_group.ssh.id
]
```
就是把刚才创建的:
```text
ssh-port-lab-sg
```
绑定到 EC2。
所以最终:
```text
EC2
│
└── Security Group
│
└── TCP 22
```
---
# 16. Tag Specification
```hcl
tag_specifications {
resource_type = "instance"
tags = {
Name = "ssh-port-lab-ec2"
}
}
```
给 EC2 打 Tag:
```text
Name = ssh-port-lab-ec2
```
这样 AWS Console 里面很容易找到。
---
# 17. ASG
最后是:
```hcl
resource "aws_autoscaling_group" "lab" {
name = "ssh-port-lab-asg"
min_size = 1
max_size = 1
desired_capacity = 1
```
这就是我们真正要实验的 **Auto Scaling Group**。
---
## 三个数字
```hcl
min_size = 1
max_size = 1
desired_capacity = 1
```
现在:
```text
最少 1
最多 1
期望 1
```
所以:
```text
ASG
│
└── EC2 × 1
```
如果这台 EC2 被人为 terminate:
```text
EC2
↓
terminated
↓
ASG 发现少了一台
↓
重新创建 EC2
```
这就是 ASG 的核心价值。
---
# 18. ASG 使用哪个 Subnet?
```hcl
vpc_zone_identifier = [
aws_subnet.public.id
]
```
意思:
> ASG 创建的 EC2 放进这个 subnet。
所以:
```text
ASG
│
└── Public Subnet
│
└── EC2
```
---
# 19. ASG 使用哪个 Launch Template?
```hcl
launch_template {
id = aws_launch_template.lab.id
version = "$Latest"
}
```
这个特别重要。
意思:
> ASG 创建 EC2 时,按照这个 Launch Template 来创建。
完整关系:
```text
┌── Security Group
│
├── Key Pair
│
└── AL2023 AMI
│
▼
Launch Template
│
▼
ASG
│
▼
EC2
```
这就是为什么我们没有直接写:
```hcl
resource "aws_instance"
```
而是:
```text
Launch Template + ASG
```
因为我们的最终实验就是要研究:
**ASG 中 EC2 的 SSH 配置发生变化以后,如何保持 SFTP 用户侧连接不变。**
---
# 20. Health Check
```hcl
health_check_type = "EC2"
```
意思 ASG 使用 EC2 状态来判断实例是否健康。
如果 EC2:
```text
running → unhealthy/terminated
```
ASG 可以重新补一台。
---
# 21. ASG Tag
```hcl
tag {
key = "Name"
value = "ssh-port-lab-asg-instance"
propagate_at_launch = true
}
```
这里:
```hcl
propagate_at_launch = true
```
非常重要。
意思:
> ASG 创建 EC2 的时候,把这个 Tag 自动传给 EC2。
所以:
```text
ASG
│
└── Name=ssh-port-lab-asg-instance
│
▼
EC2 Instance
```
---
# 最后把整个 `main.tf` 串起来
你现在不要把这些 Terraform resource 当成孤立的代码。
真正应该理解成这个依赖图:
```text
AWS Provider
│
▼
VPC
10.10.0.0/16
│
┌───────────┴───────────┐
▼ ▼
Internet Gateway Subnet
│ 10.10.1.0/24
│ │
│ ▼
│ Route Table
│ │
│ 0.0.0.0/0
│ │
└───────────────────────┘
Security Group
│
│ TCP 22
▼
Key Pair
│
│
▼
AL2023 AMI
│
▼
Launch Template
│
┌─────┴──────┐
│ │
▼ ▼
Key Pair Security Group
│ │
└─────┬──────┘
▼
ASG
│
min=1 max=1
│
▼
EC2 × 1
│
▼
SSH :22
```
### 你现在最应该记住的 5 个 Terraform 概念
结合你现在做 AWS Infra,我建议先把这五个吃透:
| Terraform | 你这个实验中的例子 | 含义 |
| ----------- | --------------------------------- | ------------------ |
| `resource` | `aws_vpc.lab` | **创建/管理** AWS 资源 |
| `data` | `aws_ssm_parameter.al2023` | **查询**已有信息 |
| `variable` | `var.aws_region` | 输入参数 |
| `attribute` | `aws_vpc.lab.id` | 获取资源的属性 |
| dependency | `aws_subnet.public → aws_vpc.lab` | Terraform 自动建立资源依赖 |
尤其是:
```hcl
vpc_id = aws_vpc.lab.id
```
```hcl
key_name = aws_key_pair.lab.key_name
```
```hcl
vpc_security_group_ids = [
aws_security_group.ssh.id
]
```
```hcl
image_id = data.aws_ssm_parameter.al2023.value
```
这四种写法是你以后阅读公司 Terraform `.tf` 文件时**非常常见的模式**。
另外,你这次 `terraform plan` 显示 **9 to add**,本质上就是 Terraform 根据这些资源和依赖关系,计算出它需要创建的 AWS 对象。