资源编排服务 ROS(Resource Orchestration Service)通过 Terraform 模板,一次性自动创建完整的云网络基础设施、ECS 云服务器,并在 ECS 上自动安装和启动 Nginx 服务。适用于需要快速、可重复地搭建 Web 服务器环境的场景。
方案概述
本方案通过阿里云资源编排服务(ROS)执行 Terraform 模板,一次性自动创建完整的云网络基础设施与 ECS 云服务器,并在 ECS 上自动安装并启动 Nginx 服务。适用于需要快速、可重复地搭建 Web 服务器环境的场景。
业务场景
在阿里云专有网络 VPC 中创建一台 ECS 实例,配置安全组开放 SSH(22 端口)和 HTTP/HTTPS(80/443 端口)访问,挂载数据盘,并通过 user_data 初始化脚本自动安装和启动 Nginx 服务。
创建的资源清单
序号 | 资源名称 | Terraform 资源类型 | 用途说明 |
1 | 专有网络 VPC |
| 提供隔离的网络环境 |
2 | 交换机 VSwitch |
| 在 VPC 中划分子网网段,指定可用区 |
3 | 安全组 |
| 控制 ECS 的网络访问规则 |
4 | 安全组规则(SSH) |
| 开放 22 端口入站访问 |
5 | 安全组规则(Web) |
| 开放 80~443 端口入站访问 |
6 | 安全组规则(出站) |
| 允许所有出站流量 |
7 | ECS 云服务器实例 |
| 运行 Nginx 的计算节点,含数据盘 |
部署完成后获得的结果
模板成功执行后,可获得:
一个完整的 VPC 网络环境(包含 VPC、交换机、安全组及规则)
一台 ECS 云服务器实例(带 100 GB 数据盘,已分配公网 IP)
ECS 上已自动安装并启动 Nginx 服务
通过输出的公网 IP 地址即可访问 Nginx 默认页面
架构图

前提条件
在使用本模板前,请确认以下条件:
账号权限:阿里云账号需要具有 ECS、VPC 产品的创建权限。
基础知识:了解 Terraform 模板的基本语法和结构。更多信息,请参见Terraform类型模板结构。
查看资源类型:可通过 Terraform Provider alicloud 资源索引 查看 Terraform 资源属性详情。每个属性标注了
Optional(可选)或Required(必须)。
模板编写教程
本教程分两个阶段演示 Terraform 模板编写过程:
阶段一(基础版):使用固定参数值,专注于理解 Terraform 资源定义和依赖关系。
阶段二(进阶版):添加
AssociationProperty动态参数筛选、参数分组和参数约束,提升控制台体验。
阶段一:基础模板——定义资源及其依赖关系
Step 1:定义基础网络资源
基础网络资源包括 alicloud_vpc、alicloud_vswitch 和 alicloud_security_group,是所有其他资源的基础。Terraform 通过资源间属性引用自动建立依赖关系。
# ============================================================
# 基础网络层:VPC → VSwitch → SecurityGroup
# ============================================================
resource "alicloud_vpc" "vpc" {
cidr_block = "192.168.0.0/16" # VPC 地址段,可容纳 65534 个内网 IP
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id # 引用 VPC 的 ID,Terraform 自动建立依赖关系
cidr_block = "192.168.0.0/24" # 子网地址段,必须是 VPC 网段的子集
zone_id = "cn-beijing-h" # 此场景以北京 H 可用区为示例
}
resource "alicloud_security_group" "security_group" {
name = "sg-for-nginx" # 安全组名称
vpc_id = alicloud_vpc.vpc.id # 安全组绑定到同一 VPC
}关键知识点:
隐式关联 vs 显式依赖关联(
depends_on):alicloud_vswitch.vsw的vpc_id属性引用了alicloud_vpc.vpc.id,Terraform 会自动识别这个依赖关系,确保先创建 VPC 再创建 VSwitch。当两个资源之间没有直接引用关系,但业务上要求先后顺序时,才需要使用
depends_on(显式关联)。
Step 2:定义安全组规则
alicloud_security_group_rule 控制进出 ECS 的网络流量。本场景需要开放 SSH 远程连接和 Web 访问端口。
# ============================================================
# 安全组规则:入站 SSH + 入站 Web + 出站全放行
# ============================================================
# 入站规则 1:开放 SSH 22 端口(用于远程管理)
resource "alicloud_security_group_rule" "allow_ssh" {
security_group_id = alicloud_security_group.security_group.id # 关联到安全组
type = "ingress" # 入站方向
cidr_ip = "0.0.0.0/0" # 允许所有 IP 访问(生产环境建议限制 IP 范围)
policy = "accept" # 放行策略
ip_protocol = "tcp" # TCP 协议
port_range = "22/22" # 端口范围:仅 22 端口
priority = 1 # 优先级:1(最高)
}
# 入站规则 2:开放 Web 端口 80~443(HTTP/HTTPS 访问 Nginx)
resource "alicloud_security_group_rule" "allow_web" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "80/443" # 端口范围:80 到 443
priority = 1
}
# 出站规则:允许所有出站流量(ECS 可访问外部网络,如 yum 源)
resource "alicloud_security_group_rule" "allow_egress" {
security_group_id = alicloud_security_group.security_group.id
type = "egress" # 出站方向
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "1/65535" # 所有端口
priority = 1
}关键知识点:
Terraform 中端口范围格式为
"起始端口/结束端口",如"80/443"表示 80 到 443 的所有端口;单个端口写法为"22/22"。生产环境建议将 SSH 的
cidr_ip限制为办公 IP,避免被恶意扫描。如需开放其他端口(如 8080),可新增一条
alicloud_security_group_rule资源。
Step 3:定义 ECS 实例与初始化脚本
alicloud_instance 是运行 Nginx 的计算节点。通过 user_data 属性指定首次启动时执行的初始化脚本。
user_data 初始化脚本(user-data.sh):
#!/bin/bash -v
# ---- 数据盘格式化与挂载 ----
cat >> /root/InitDataDisk.sh << "EOF"
#!/bin/bash
echo "p
n
p
w
" | fdisk -u /dev/vdb
EOF
/bin/bash /root/InitDataDisk.sh
rm -f /root/InitDataDisk.sh
mkfs -t ext4 /dev/vdb1
cp /etc/fstab /etc/fstab.bak
mkdir /disk1
echo `blkid /dev/vdb1 | awk '{print $2}' | sed 's/\"//g'` /disk1 ext4 defaults 0 0 >> /etc/fstab
mount -a
# ---- 安装并启动 Nginx ----
yum install -y nginx
/usr/sbin/nginxECS 实例资源定义:
# ============================================================
# 计算层:ECS 实例(含数据盘 + Nginx 初始化)
# ============================================================
resource "alicloud_instance" "instance" {
availability_zone = "cn-beijing-h"
security_groups = [alicloud_security_group.security_group.id] # 数组格式绑定安全组
host_name = "app-for-nginx"
instance_type = "ecs.c6e.large" # 实例规格(计算增强型,2vCPU 4GiB)
system_disk_size = 100 # 系统盘大小
system_disk_category = "cloud_essd" # 系统盘类型
image_id = "centos_7_9_x64_20G_alibase_20210318.vhd" # CentOS 7.9 镜像
vswitch_id = alicloud_vswitch.vsw.id # 绑定到交换机
password = "<YourPassword>" # 部署时请替换为实际密码(需含大小写字母 + 数字 + 特殊字符)
internet_charge_type = "PayByTraffic" # 公网按流量计费
internet_max_bandwidth_out = 30 # 公网出带宽 30 Mbps
instance_charge_type = "PostPaid" # 付费类型(按量 / 包年包月)
user_data = file("${path.cwd}/user-data.sh") # 加载初始化脚本
data_disks { # 挂载数据盘配置
size = 100 # 数据盘大小
category = "cloud_essd" # 数据盘类型
}
}
# 输出 Nginx 访问地址
output "nginx_ip" {
value = "http://${alicloud_instance.instance.public_ip}:80"
}关键知识点:Terraform 变量引用方式
引用方式 | 语法 | 说明 |
变量引用 |
| 获取 |
资源属性引用 |
| 获取其他资源的输出属性(如 |
文件加载 |
| 加载当前工作目录下的文件内容 |
模板扩展与变体:
user_data通过file()函数加载外部脚本文件,便于维护和复用。internet_max_bandwidth_out = 30设置了 30 Mbps 出带宽,ECS 自动获得公网 IP。data_disks块可添加多个数据盘,也可省略不挂载数据盘。模板创建完资源后,可以通过模板的
output信息直接查询对应资源内部属性信息,因此可以通过定义output内容输出 ECS 公网访问地址(Nginx 服务访问地址)。
基础版完整模板
Terraform 模板结构如下所示。
main.tf 文件内容:
resource "alicloud_vpc" "vpc" {
cidr_block = "192.168.0.0/16"
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "192.168.0.0/24"
zone_id = "cn-beijing-h"
}
# 安全组基本信息配置
resource "alicloud_security_group" "security_group" {
name = "sg-for-nginx"
description = "nginx scg"
vpc_id = alicloud_vpc.vpc.id
}
# 安全组入口端 1:SSH
resource "alicloud_security_group_rule" "allow_ssh" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "22/22"
priority = 1
}
# 安全组入口端 2:Web
resource "alicloud_security_group_rule" "allow_web" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "80/443"
priority = 1
}
# 安全组出口端:全放行
resource "alicloud_security_group_rule" "allow_egress" {
security_group_id = alicloud_security_group.security_group.id
type = "egress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "1/65535"
priority = 1
}
# 实例基本配置
resource "alicloud_instance" "instance" {
availability_zone = "cn-beijing-h"
security_groups = [alicloud_security_group.security_group.id]
host_name = "app-for-nginx"
instance_type = "ecs.c6e.large"
system_disk_size = 100
system_disk_category = "cloud_essd"
image_id = "centos_7_9_x64_20G_alibase_20210318.vhd"
vswitch_id = alicloud_vswitch.vsw.id
password = "<YourPassword>" # 部署时请替换为实际密码
internet_charge_type = "PayByTraffic"
internet_max_bandwidth_out = 30
instance_charge_type = "PostPaid"
user_data = file("${path.cwd}/user-data.sh")
data_disks {
size = 100
category = "cloud_essd"
}
}
# 返回 Nginx 的访问地址
output "nginx_ip" {
value = "http://${alicloud_instance.instance.public_ip}:80"
}user-data.sh 文件内容:
#!/bin/bash -v
# 挂盘到 /disk1
cat >> /root/InitDataDisk.sh << "EOF"
#!/bin/bash
echo "p
n
p
w
" | fdisk -u /dev/vdb
EOF
/bin/bash /root/InitDataDisk.sh
rm -f /root/InitDataDisk.sh
mkfs -t ext4 /dev/vdb1
cp /etc/fstab /etc/fstab.bak
mkdir /disk1
echo `blkid /dev/vdb1 | awk '{print $2}' | sed 's/\"//g'` /disk1 ext4 defaults 0 0 >> /etc/fstab
mount -a
# 安装 Nginx
yum install -y nginx
# 启动 Nginx
/usr/sbin/nginx以下是将上述三个步骤合并后的完整可用模板。使用 ROS 部署 Terraform 模板时,需要将其包装为 ROS Workspace 格式。
完整 ROS Workspace 格式模板:
ROSTemplateFormatVersion: '2015-09-01'
Transform: Aliyun::Terraform-v1.2
Workspace:
main.tf: |-
resource "alicloud_vpc" "vpc" {
cidr_block = "192.168.0.0/16"
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "192.168.0.0/24"
zone_id = "cn-beijing-h"
}
# 安全组基本信息配置
resource "alicloud_security_group" "security_group" {
name = "sg-for-nginx"
description = "nginx scg"
vpc_id = alicloud_vpc.vpc.id
}
# 安全组入口端 1:SSH
resource "alicloud_security_group_rule" "allow_ssh" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "22/22"
priority = 1
}
# 安全组入口端 2:Web
resource "alicloud_security_group_rule" "allow_web" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "80/443"
priority = 1
}
# 安全组出口端:全放行
resource "alicloud_security_group_rule" "allow_egress" {
security_group_id = alicloud_security_group.security_group.id
type = "egress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "1/65535"
priority = 1
}
# 实例基本配置
resource "alicloud_instance" "instance" {
availability_zone = "cn-beijing-h"
security_groups = [alicloud_security_group.security_group.id]
host_name = "app-for-nginx"
instance_type = "ecs.c6e.large"
system_disk_size = 100
system_disk_category = "cloud_essd"
image_id = "centos_7_9_x64_20G_alibase_20210318.vhd"
vswitch_id = alicloud_vswitch.vsw.id
password = "<YourPassword>"
internet_charge_type = "PayByTraffic"
internet_max_bandwidth_out = 30
instance_charge_type = "PostPaid"
user_data = file("${path.cwd}/user-data.sh")
data_disks {
size = 100
category = "cloud_essd"
}
}
# 返回 Nginx 的访问地址
output "nginx_ip" {
value = "http://${alicloud_instance.instance.public_ip}:80"
}
user-data.sh: |-
#!/bin/bash -v
# 挂盘到 /disk1
cat >> /root/InitDataDisk.sh << "EOF"
#!/bin/bash
echo "p
n
p
w
" | fdisk -u /dev/vdb
EOF
/bin/bash /root/InitDataDisk.sh
rm -f /root/InitDataDisk.sh
mkfs -t ext4 /dev/vdb1
cp /etc/fstab /etc/fstab.bak
mkdir /disk1
echo `blkid /dev/vdb1 | awk '{print $2}' | sed 's/\"//g'` /disk1 ext4 defaults 0 0 >> /etc/fstab
mount -a
# 安装 Nginx
yum install -y nginx
# 启动 Nginx
/usr/sbin/nginx阶段二:进阶模板——参数化与动态配置
基础版模板中未定义 variable 和 variable 支持的 description、default 等动态筛选配置。在 ROS 控制台使用此模板时,参数之间缺乏联动,可能选择到无效的组合(如在某可用区选择了没有库存的实例规格)。
通过以下优化,可以大幅提高控制台使用体验和模板复用性:
动态参数筛选(AssociationProperty):在
variable的description中配置 JSON 格式的AssociationProperty,让控制台自动过滤可用选项。条件显示(
Visible.Condition):按付费类型动态显示 / 隐藏购买时长参数。参数分组(.metadata 文件):通过 .metadata 文件配置 ParameterGroups,控制台按逻辑分组展示。
参数约束(参数约束查询):在 .metadata 中定义资源约束,确保参数组合有效。
参数依赖关系图
pay_type(付费类型)
├── pay_period_unit(周期单位,仅 PrePaid 时显示)
└── pay_period(购买时长,仅 PrePaid 时显示)
zone_id(可用区,核心过滤参数)
├── instance_type(实例规格,按可用区 + 付费类型过滤)
│ ├── system_disk_category(系统盘类型,按可用区 + 实例规格过滤)
│ └── data_disk_category(数据盘类型,按可用区 + 实例规格过滤)核心思路:先选可用区和 ECS 付费类型,再据此筛选 ECS 规格(当选择预付费类型时需要选择付费周期和购买时长),最后根据可用区 + ECS 规格筛选磁盘类型。每一步都只展示当前条件下可用的选项,避免无效组合。
参数 AssociationProperty 原理详解
1. zone_id — 可用区(基础参数)
定义 AssociationProperty 为 ALIYUN::ECS::ZoneId 列出当前地域下所有可用区。示例:
variable "zone_id" {
type = string
description = <<EOT
{
# AssociationProperty 指定参数关联的资源属性类型
# ROS 控制台会根据此配置自动列出可选值
"AssociationProperty": "ALIYUN::ECS::ZoneId",
"Label": {
"zh-cn": "交换机可用区",
"en": "VSwitch Availability Zone"
},
"Description": {
"zh-cn": "选择可用区,后续参数会根据此可用区自动过滤",
"en": "Select availability zone for resource filtering"
}
}
EOT
}此参数是后续所有参数的筛选基础,选择后其他参数的选项列表会随之更新。
2. pay_type — ECS 付费类型
定义 pay_type 参数,使用 AssociationPropertyMetadata.Visible.Condition 实现参数的条件动态切换——当选择"包年包月(PrePaid)"时才显示购买时长相关参数。示例:
variable "pay_type" {
type = string
default = "PostPaid"
description = <<EOT
{
"Label": { "en": "ECS Instance Charge Type", "zh-cn": "付费类型" },
"AllowedValues": ["PostPaid", "PrePaid"],
"AssociationProperty": "ChargeType"
}
EOT
}
variable "pay_period_unit" {
type = string
default = "Month"
description = <<EOT
{
"Label": { "zh-cn": "购买资源时长周期" },
"AllowedValues": ["Month", "Year"],
"AssociationProperty": "PayPeriodUnit",
"AssociationPropertyMetadata": {
"Visible": {
"Condition": {
"Fn::Not": {
"Fn::Equals": ["$${pay_type}", "PostPaid"]
}
}
}
}
}
EOT
}
variable "pay_period" {
type = number
default = 1
description = <<EOT
{
"Label": { "en": "Period", "zh-cn": "购买资源时长" },
"AllowedValues": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"AssociationProperty": "PayPeriod",
"AssociationPropertyMetadata": {
"Visible": {
"Condition": {
"Fn::Not": { "Fn::Equals": ["$${pay_type}", "PostPaid"] }
}
}
}
}
EOT
}在 ROS 控制台选择"按量付费(PostPaid)"时,购买时长相关参数自动隐藏;选择"包年包月(PrePaid)"时才显示这些参数。
3. instance_type — ECS 实例规格
定义 AssociationProperty 为 ALIYUN::ECS::Instance::InstanceType 列出可用的 ECS 实例规格,并通过关联 ${zone_id} 和 ${pay_type} 筛选对应可用区下对应付费类型支持的可用实例规格。示例:
variable "instance_type" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "实例规格", "en": "Instance Type" },
"AssociationProperty": "ALIYUN::ECS::Instance::InstanceType",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceChargeType": "$${pay_type}"
}
}
EOT
}不同可用区的实例规格库存不同。如果不过滤,可能选到该可用区无货的规格,导致创建失败。
4. system_disk_category — 系统盘类型
定义 AssociationProperty 为 ALIYUN::ECS::Disk::SystemDiskCategory 列出可选的系统盘类型,并通过关联 ${zone_id} 和 ${instance_type} 筛选对应可用区与 ECS 实例规格支持使用的 ECS 系统盘类型。示例:
variable "system_disk_category" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "系统盘类型", "en": "System Disk Type" },
"Description": {
"zh-cn": "可选值:cloud_efficiency(高效云盘)、cloud_ssd(SSD 云盘)、cloud_essd(ESSD 云盘)",
"en": "Options: cloud_efficiency, cloud_ssd, cloud_essd"
},
"AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceType": "$${instance_type}"
}
}
EOT
}系统盘类型的可用性同时取决于可用区(某些可用区不支持 ESSD)和实例规格(某些规格仅支持特定磁盘类型)。
5. data_disk_category — 数据盘类型
定义 AssociationProperty 为 ALIYUN::ECS::Disk::DataDiskCategory 列出可选的数据盘类型。过滤原理与系统盘类型完全一致,同样通过关联 ${zone_id} 和 ${instance_type} 筛选当前可用区与实例规格共同支持的数据盘类型。示例:
variable "data_disk_category" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "数据盘类型", "en": "Data Disk Type" },
"Description": {
"zh-cn": "可选值:cloud_efficiency(高效云盘)、cloud_ssd(SSD 云盘)、cloud_essd(ESSD 云盘)",
"en": "Options: cloud_efficiency, cloud_ssd, cloud_essd"
},
"AssociationProperty": "ALIYUN::ECS::Disk::DataDiskCategory",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceType": "$${instance_type}"
}
}
EOT
}动态参数筛选模板示例
variable "zone_id" {
type = string
description = <<EOT
{
"AssociationProperty": "ALIYUN::ECS::ZoneId",
"Label": {
"zh-cn": "交换机可用区",
"en": "VSwitch Availability Zone"
},
"Description": {
"zh-cn": "选择可用区,后续参数会根据此可用区自动过滤",
"en": "Select availability zone for resource filtering"
}
}
EOT
}
variable "pay_type" {
type = string
default = "PostPaid"
description = <<EOT
{
"Label": { "en": "ECS Instance Charge Type", "zh-cn": "付费类型" },
"AllowedValues": ["PostPaid", "PrePaid"],
"AssociationProperty": "ChargeType"
}
EOT
}
variable "pay_period_unit" {
type = string
default = "Month"
description = <<EOT
{
"Label": { "zh-cn": "购买资源时长周期" },
"AllowedValues": ["Month", "Year"],
"AssociationProperty": "PayPeriodUnit",
"AssociationPropertyMetadata": {
"Visible": {
"Condition": {
"Fn::Not": {
"Fn::Equals": ["$${pay_type}", "PostPaid"]
}
}
}
}
}
EOT
}
variable "pay_period" {
type = number
default = 1
description = <<EOT
{
"Label": { "en": "Period", "zh-cn": "购买资源时长" },
"AllowedValues": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"AssociationProperty": "PayPeriod",
"AssociationPropertyMetadata": {
"Visible": {
"Condition": {
"Fn::Not": { "Fn::Equals": ["$${pay_type}", "PostPaid"] }
}
}
}
}
EOT
}
variable "instance_type" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "实例规格", "en": "Instance Type" },
"AssociationProperty": "ALIYUN::ECS::Instance::InstanceType",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceChargeType": "$${pay_type}"
}
}
EOT
}
variable "system_disk_category" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "系统盘类型", "en": "System Disk Type" },
"Description": {
"zh-cn": "可选值:cloud_efficiency(高效云盘)、cloud_ssd(SSD 云盘)、cloud_essd(ESSD 云盘)",
"en": "Options: cloud_efficiency, cloud_ssd, cloud_essd"
},
"AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceType": "$${instance_type}"
}
}
EOT
}
variable "data_disk_category" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "数据盘类型", "en": "Data Disk Type" },
"Description": {
"zh-cn": "可选值:cloud_efficiency(高效云盘)、cloud_ssd(SSD 云盘)、cloud_essd(ESSD 云盘)",
"en": "Options: cloud_efficiency, cloud_ssd, cloud_essd"
},
"AssociationProperty": "ALIYUN::ECS::Disk::DataDiskCategory",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceType": "$${instance_type}"
}
}
EOT
}参数 AssociationProperty 工作原理总结
在 ROS 控制台创建资源栈时,参数的选择过程是级联触发的:
选择可用区(
zone_id)。选择 ECS 付费类型(
pay_type)。ECS 实例规格下拉框自动刷新,仅显示该可用区下所支持对应付费类型且具备库存的规格。
选择 ECS 实例规格后,系统盘类型和数据盘类型下拉框自动刷新,仅显示该可用区 + 该规格共同支持的磁盘类型。
这样,每一步选择都只展示当前条件下可用的选项,从源头避免因参数组合无效导致的部署失败。
Metadata 参数分组
通过 Metadata 中的 ALIYUN::ROS::Interface 配置,可以将参数按逻辑分成多个组,每组带一个标题,控制台上会分组展示,填写体验大幅提升。
Metadata 语法结构
在 Terraform 标签下新建 .metadata 文件,配置参数分组(ParameterGroups)和参数约束(参数约束查询):
{
"ALIYUN::ROS::Interface": {
"ResourcesForParameterConstraints": {
"resource": {
"Type": "ALIYUN::ECS::Instance",
"Properties": {
}
}
},
"ParameterGroups": [
{
"Parameters": ["参数名 1", "参数名 2", "参数名 3"],
"Label": { "default": { "zh-cn": "XXXX 配置", "en": "Configuration" } }
}
]
}
}Metadata 语法规则:
Parameters、Label都是必填项。Parameters中列出的参数名必须与variable参数名完全一致。未出现在任何分组中的参数,会在控制台显示在未分组区域。
本场景的分组设计
本模板将 12 个参数按资源类型分成 3 个组:
┌─────────────────────────────────────────────┐
│ 基础网络配置 │
│ ├── 交换机可用区 (zone_id) │
│ ├── VPC 网段 (vpc_cidr_block) │
│ └── 交换机网段 (vswitch_cidr_block) │
├─────────────────────────────────────────────┤
│ 付费模式配置 │
│ ├── ECS 实例付费类型 (pay_type) │
│ ├── ECS 预付费购买资源周期 (pay_period_unit) │
│ └── ECS 预付费购买资源时长 (pay_period) │
├─────────────────────────────────────────────┤
│ ECS 实例配置 │
│ ├── ECS 实例规格 (instance_type) │
│ ├── 系统盘类型 (system_disk_category) │
│ ├── 系统盘大小 (system_disk_size) │
│ ├── 数据盘类型 (data_disk_category) │
│ ├── 数据盘大小 (data_disk_size) │
│ └── ECS 实例密码 (instance_password) │
└─────────────────────────────────────────────┘.metadata 文件配置
在 Terraform 标签下新建 .metadata 文件,配置参数分组和参数约束:
{
"ALIYUN::ROS::Interface": {
"ResourcesForParameterConstraints": {
"instance": {
"Type": "ALIYUN::ECS::Instance",
"Properties": {
"InstanceType": { "Ref": "instance_type" },
"ImageId": "centos_7_9_x64_20G_alibase_20210318.vhd",
"ZoneId": { "Ref": "zone_id" },
"SystemDiskCategory": { "Ref": "system_disk_category" },
"SystemDiskSize": { "Ref": "system_disk_size" },
"DataDiskCategory": { "Ref": "data_disk_category" },
"DataDiskSize": { "Ref": "data_disk_size" }
}
}
},
"ParameterGroups": [
{
"Parameters": ["vpc_cidr_block", "zone_id", "vswitch_cidr_block"],
"Label": { "default": { "zh-cn": "基础网络配置", "en": "Network Configuration" } }
},
{
"Parameters": ["pay_type", "pay_period_unit", "pay_period"],
"Label": { "default": { "zh-cn": "付费模式配置", "en": "Payment Configuration" } }
},
{
"Parameters": ["instance_type", "system_disk_category", "system_disk_size", "data_disk_category", "data_disk_size", "instance_password"],
"Label": { "default": { "zh-cn": "ECS 实例配置", "en": "ECS Instance Configuration" } }
}
]
}
}关键知识点:
定义了一个虚拟的
ALIYUN::ECS::Instance资源,将模板中的参数与 ECS 资源属性关联。ROS 控制台据此验证参数组合的有效性(如指定可用区是否支持所选实例规格 + 磁盘类型)。
Ref语法引用 Terraform variable 名称(不带var.前缀)。
进阶版完整模板
以下是包装为 ROS Workspace 格式的完整进阶版模板,可直接在 ROS 控制台部署:
ROSTemplateFormatVersion: '2015-09-01'
Transform: Aliyun::Terraform-v1.2
Workspace:
.metadata: |-
{
"ALIYUN::ROS::Interface": {
"ResourcesForParameterConstraints": {
"instance": {
"Type": "ALIYUN::ECS::Instance",
"Properties": {
"InstanceType": { "Ref": "instance_type" },
"ImageId": "centos_7_9_x64_20G_alibase_20210318.vhd",
"ZoneId": { "Ref": "zone_id" },
"SystemDiskCategory": { "Ref": "system_disk_category" },
"SystemDiskSize": { "Ref": "system_disk_size" },
"DataDiskCategory": { "Ref": "data_disk_category" },
"DataDiskSize": { "Ref": "data_disk_size" }
}
}
},
"ParameterGroups": [
{
"Parameters": ["vpc_cidr_block", "zone_id", "vswitch_cidr_block"],
"Label": { "default": { "zh-cn": "基础网络配置", "en": "Network Configuration" } }
},
{
"Parameters": ["pay_type", "pay_period_unit", "pay_period"],
"Label": { "default": { "zh-cn": "付费模式配置", "en": "Payment Configuration" } }
},
{
"Parameters": ["instance_type", "system_disk_category", "system_disk_size", "data_disk_category", "data_disk_size", "instance_password"],
"Label": { "default": { "zh-cn": "ECS 实例配置", "en": "ECS Instance Configuration" } }
}
]
}
}
main.tf: |-
variable "pay_type" {
type = string
default = "PostPaid"
description = <<EOT
{
"Label": { "en": "ECS Instance Charge Type", "zh-cn": "付费类型" },
"AllowedValues": ["PostPaid", "PrePaid"],
"AssociationProperty": "ChargeType",
"AssociationPropertyMetadata": { "LocaleKey": "InstanceChargeType" }
}
EOT
}
variable "pay_period_unit" {
type = string
default = "Month"
description = <<EOT
{
"Label": { "en": "Pay Period Unit", "zh-cn": "购买资源时长周期" },
"AllowedValues": ["Month", "Year"],
"AssociationProperty": "PayPeriodUnit",
"AssociationPropertyMetadata": {
"Visible": {
"Condition": {
"Fn::Not": { "Fn::Equals": ["$${pay_type}", "PostPaid"] }
}
}
}
}
EOT
}
variable "pay_period" {
type = number
default = 1
description = <<EOT
{
"Label": { "en": "Period", "zh-cn": "购买资源时长" },
"AllowedValues": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"AssociationProperty": "PayPeriod",
"AssociationPropertyMetadata": {
"Visible": {
"Condition": {
"Fn::Not": { "Fn::Equals": ["$${pay_type}", "PostPaid"] }
}
}
}
}
EOT
}
variable "zone_id" {
type = string
description = <<EOT
{
"AssociationProperty": "ALIYUN::ECS::ZoneId",
"Label": { "zh-cn": "交换机可用区", "en": "VSwitch Availability Zone" },
"Description": {
"zh-cn": "选择可用区,后续 ECS 实例规格和磁盘类型将根据此可用区自动过滤可用选项",
"en": "Select availability zone. Instance types and disk categories will be filtered accordingly."
}
}
EOT
}
variable "vpc_cidr_block" {
type = string
default = "192.168.0.0/16"
description = <<EOT
{
"Label": { "zh-cn": "专有网络网段", "en": "VPC CIDR Block" },
"Description": {
"zh-cn": "新建 VPC 的 IP 地址段范围。推荐使用 10.0.0.0/8、172.16.0.0/12 或 192.168.0.0/16",
"en": "VPC IP range. Recommended: 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16"
}
}
EOT
}
variable "vswitch_cidr_block" {
type = string
default = "192.168.0.0/24"
description = <<EOT
{
"Label": { "zh-cn": "交换机网段", "en": "VSwitch CIDR Block" },
"Description": {
"zh-cn": "必须是 VPC 网段的子网段,且不能与同 VPC 下其他交换机网段重叠",
"en": "Must be a subnet of VPC CIDR and not overlap with other VSwitches"
}
}
EOT
}
variable "instance_type" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "实例规格", "en": "Instance Type" },
"AssociationProperty": "ALIYUN::ECS::Instance::InstanceType",
"AssociationPropertyMetadata": {
"InstanceChargeType": "$${pay_type}",
"ZoneId": "$${zone_id}"
}
}
EOT
}
variable "system_disk_category" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "系统盘类型", "en": "System Disk Type" },
"Description": {
"zh-cn": "可选值:cloud_efficiency(高效云盘)、cloud_ssd(SSD 云盘)、cloud_essd(ESSD 云盘)",
"en": "Options: cloud_efficiency, cloud_ssd, cloud_essd"
},
"AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceType": "$${instance_type}"
}
}
EOT
}
variable "system_disk_size" {
type = number
default = 40
description = <<EOT
{
"Label": { "zh-cn": "系统盘空间", "en": "System Disk Size" },
"Description": {
"zh-cn": "系统盘大小,取值范围:40~500,单位:GB",
"en": "System disk size, range: 40~500 GB"
}
}
EOT
}
variable "data_disk_category" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "数据盘类型", "en": "Data Disk Type" },
"Description": {
"zh-cn": "可选值:cloud_efficiency(高效云盘)、cloud_ssd(SSD 云盘)、cloud_essd(ESSD 云盘)",
"en": "Options: cloud_efficiency, cloud_ssd, cloud_essd"
},
"AssociationProperty": "ALIYUN::ECS::Disk::DataDiskCategory",
"AssociationPropertyMetadata": {
"ZoneId": "$${zone_id}",
"InstanceType": "$${instance_type}"
}
}
EOT
}
variable "data_disk_size" {
type = number
default = 100
description = <<EOT
{
"Label": { "zh-cn": "数据盘空间", "en": "Data Disk Space" },
"Description": {
"zh-cn": "ECS 实例数据盘大小,取值范围:20~32768,单位:GiB",
"en": "ECS data disk size, range: 20~32768 GiB"
},
"MaxValue": 32768,
"MinValue": 20
}
EOT
}
variable "instance_password" {
type = string
sensitive = true
description = <<EOT
{
"Label": { "zh-cn": "登录密码", "en": "Instance Password" },
"Description": {
"zh-cn": "长度 8~30,必须包含大写字母、小写字母、数字、特殊符号中的至少三种",
"en": "Length 8-30, must contain at least 3 of: uppercase, lowercase, digits, special characters"
},
"AssociationProperty": "ALIYUN::ECS::Instance::Password",
"MinLength": 8,
"MaxLength": 30
}
EOT
}
# 默认资源名称
locals {
production_name = "nginx"
new_scg_name = "sg-for-${local.production_name}"
new_host_name = "app-for-${local.production_name}"
}
resource "alicloud_vpc" "vpc" {
cidr_block = var.vpc_cidr_block
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = var.vswitch_cidr_block
zone_id = var.zone_id
}
resource "alicloud_security_group" "security_group" {
name = local.new_scg_name
description = "nginx scg"
vpc_id = alicloud_vpc.vpc.id
}
resource "alicloud_security_group_rule" "allow_ssh" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "22/22"
priority = 1
}
resource "alicloud_security_group_rule" "allow_web" {
security_group_id = alicloud_security_group.security_group.id
type = "ingress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "80/443"
priority = 1
}
resource "alicloud_security_group_rule" "allow_egress" {
security_group_id = alicloud_security_group.security_group.id
type = "egress"
cidr_ip = "0.0.0.0/0"
policy = "accept"
ip_protocol = "tcp"
port_range = "1/65535"
priority = 1
}
resource "alicloud_instance" "instance" {
availability_zone = var.zone_id
security_groups = [alicloud_security_group.security_group.id]
host_name = local.new_host_name
instance_type = var.instance_type
system_disk_size = var.system_disk_size
system_disk_category = var.system_disk_category
image_id = "centos_7_9_x64_20G_alibase_20210318.vhd"
vswitch_id = alicloud_vswitch.vsw.id
password = var.instance_password
internet_charge_type = "PayByTraffic"
internet_max_bandwidth_out = 30
instance_charge_type = var.pay_type
period = var.pay_period
period_unit = var.pay_period_unit
user_data = file("${path.cwd}/user-data.sh")
data_disks {
size = var.data_disk_size
category = var.data_disk_category
}
}
output "nginx_ip" {
value = "http://${alicloud_instance.instance.public_ip}:80"
}
user-data.sh: |-
#!/bin/bash -v
# 挂盘到 /disk1
cat >> /root/InitDataDisk.sh << "EOF"
#!/bin/bash
echo "p
n
p
w
" | fdisk -u /dev/vdb
EOF
/bin/bash /root/InitDataDisk.sh
rm -f /root/InitDataDisk.sh
mkfs -t ext4 /dev/vdb1
cp /etc/fstab /etc/fstab.bak
mkdir /disk1
echo `blkid /dev/vdb1 | awk '{print $2}' | sed 's/\"//g'` /disk1 ext4 defaults 0 0 >> /etc/fstab
mount -a
# 安装 Nginx
yum install -y nginx
# 启动 Nginx
/usr/sbin/nginxTerraform 模板语法速查
语法 | 示例 | 本模板中的用途 |
|
| 引用 variable 定义的输入参数值 |
|
| 引用 locals 块定义的本地变量 |
|
| 引用其他资源的输出属性,自动建立依赖 |
|
| 加载外部文件内容作为属性值 |
|
| 字符串插值,在字符串中嵌入变量值 |
|
| 多行字符串,用于嵌入 JSON 配置 |
|
| 在 description 的 JSON 中引用其他 variable 的当前值 |
部署环节
部署参数
必需输入参数(部署时需填写)
参数名 | 类型 | 说明 | 约束 |
| String | 交换机可用区 | 控制台动态选择 |
| String | ECS 实例规格 | 根据可用区自动过滤 |
| String | 系统盘类型 | 根据可用区 + 规格自动过滤 |
| String | 数据盘类型 | 根据可用区 + 规格自动过滤 |
| String | ECS 登录密码 | 长度 8~30,含大小写 + 数字 + 特殊字符 |
可选配置参数(有默认值,可不填)
参数名 | 默认值 | 说明 |
|
| 付费类型( |
| Month | 购买时长单位(仅 |
| 1 | 购买时长(仅 |
| 192.168.0.0/16 | VPC 网段地址范围 |
| 192.168.0.0/24 | 交换机子网网段 |
| 40 | 系统盘大小(GB),范围 40~500 |
| 100 | 数据盘大小(GiB),范围 20~32768 |
部署方式
方式一:通过 ROS 控制台部署
-
登录资源编排控制台。
在左侧导航栏选择模板 > 我的模板 > 创建模板。
在模板类型下选择 Terraform模板。
新建
main.tf文件,粘贴进阶版模板中的 main.tf 内容。新建
user-data.sh文件,粘贴 user-data.sh 脚本内容。新建
.metadata文件,粘贴 .metadata 的 JSON 配置。保存模板后,前往 资源栈 > 创建资源栈 > 选择已有模板 > 我的模板,选择该模板。
按参数分组填写配置参数,确认后单击 创建。
等待资源栈状态变为
CREATE_COMPLETE,在 输出 页签查看 Nginx 访问地址。
方式二:通过 ROS IaC Code 部署
IaC Code 是面向云基础设施的 AI 基础设施即代码助手,通过自然语言提示词生成、部署和管理基础设施模板。架构设计面向多云工作流。以下为本方案的参考提示词:
# Prompt
帮我用 Terraform 模板创建一台 ECS 并部署 Nginx 服务。
要求:
1. 新建 VPC 和 VSwitch 作为基础网络。
2. 安全组开放入方向 22 端口(SSH)和 80~443 端口(Web 访问)。
3. ECS 需要分配公网 IP,出带宽 30 Mbps。
4. 挂载一块 100 GB 数据盘,格式化并挂载到 /disk1。
5. 通过 user_data 脚本自动安装并启动 Nginx。
6. zone_id、instance_type、system_disk_category、data_disk_category 都需要配置 AssociationProperty 动态筛选。
7. 付费类型参数需要配置条件显示(包年包月时才显示购买时长)。常见问题
Q1:部署失败,报错 "The specified InstanceType is not available in the zone"
原因:所选 ECS 实例规格在指定可用区中没有库存。
解决方案:
在进阶版模板中,
AssociationProperty配合ResourcesForParameterConstraints会自动过滤可用规格,避免此问题。如使用基础版模板,请在云服务器ECS定价确认目标可用区的规格库存。
Q2:ECS 创建成功但无法访问 Nginx 页面
排查步骤:
确认安全组规则是否已开放 80 端口入站访问(检查
allow_web规则)。登录 ECS 实例,执行
systemctl status nginx确认 Nginx 是否正常运行。检查
user_data脚本是否执行成功:查看cat /var/log/messages | grep cloud-init。确认 ECS 有公网 IP(
internet_max_bandwidth_out > 0时自动分配)。
Q3:user_data 脚本执行失败,数据盘未挂载
原因:可能是镜像不兼容或数据盘设备名不匹配。
解决方案:
user_data脚本中使用的/dev/vdb是数据盘的默认设备名,确保模板中配置了data_disks块。如使用非 CentOS 镜像,可能需要调整分区命令(如使用
parted替代fdisk)。若不需要数据盘,可移除
data_disks块和user_data中的挂盘脚本部分。
Q4:Terraform 模板如何转换为 ROS 模板?
方式一:控制台转换
操作步骤:
在 ROS 控制台选择 模板 > 我的模板 > 创建模板。
先在 Terraform模板 标签下编写完模板。
切换到 ROS模板 标签,并在右上角选择 YAML,系统自动将 Terraform 模板转换为如下格式:
ROSTemplateFormatVersion: '2015-09-01'
Transform: Aliyun::Terraform-v1.2
Workspace:
.metadata: |-
{ ... } # 参数分组和约束配置
main.tf: |-
... # Terraform 模板内容
user-data.sh: |-
... # 初始化脚本内容方式二:使用模板转换工具
执行转换命令:
rostran transform templates/terraform/alicloud/main.tf --target-format jsonQ5:如何改为部署其他 Web 服务(如 Apache、Tomcat)?
改造要点:
修改 user-data.sh 中的安装和启动命令:
Apache:
yum install -y httpd && systemctl start httpdTomcat:安装 JDK 后下载 Tomcat 并启动。
调整安全组规则的端口范围(如 Tomcat 默认 8080 端口)。
修改 output 输出中的端口号。
Q6:如何使用已有 VPC 而非新建?
方案:将 VPC 和 VSwitch 改为从已有资源中选择:
variable "vpc_id" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "已有 VPC" },
"AssociationProperty": "ALIYUN::ECS::VPC::VPCId"
}
EOT
}
variable "vswitch_id" {
type = string
description = <<EOT
{
"Label": { "zh-cn": "已有交换机" },
"AssociationProperty": "ALIYUN::VPC::VSwitch::VSwitchId",
"AssociationPropertyMetadata": {
"VpcId": "$${vpc_id}"
}
}
EOT
}然后将 alicloud_instance 中的 vswitch_id 改为 var.vswitch_id,并删除 alicloud_vpc 和 alicloud_vswitch 资源块。
想要了解更多资源部署问题,可以查询 常见问题。