Terraform版ECS单机部署Nginx服务

本文以Terraform版ECS单机部署Nginx服务为例,由简入难的向您介绍如何编辑Terraform模板。

前提条件

请您提前了解模板语法和结构。更多信息,请参见模板快速入门

场景示例

在阿里云专有网络VPC中创建ECS实例,并在ECS实例中部署Nginx服务。

2023-04-26_17-59-47..png

使用须知

您可以访问对应的资源类型查看属性详情。具体操作,请参见查看资源类型

资源类型文档为每个属性定义了Optional(可选)、Required(必须)等信息。如果将属性定义为Required(必须),则要求必须在模板资源中声明该属性;反之,则为非必须。

编辑模板

您可以通过资源类型索引文档查找所需的资源类型。更多信息,请参见资源类型索引

例如:当前场景中需要创建专有网络VPC(alicloud_vpc)、ECS实例(alicloud_instance)、交换机(alicloud_vswitch)、安全组(alicloud_security_group)和安全组规则(alicloud_security_group_rule),在创建ECS实例的同时会安装Nginx服务。

定义模板多种资源及其依赖关系

定义基础网络资源

您可以通过模板定义基础网络资源vpcvswsecurity_group

  • 使用alicloud_vpc.vpc.id获取资源alicloud_vpc的输出ID属性值。

  • 使用var.***获取自定义变量,即variable中的参数值。例如var.zone_id获取的是variablezone_id的值。

resource "alicloud_vpc" "vpc" {
  cidr_block = "10.1.0.0/21"
}
resource "alicloud_vswitch" "vsw" {
  vpc_id     = alicloud_vpc.vpc.id
  cidr_block = "172.16.0.0/21"
  zone_id    = var.zone_id
}
resource "alicloud_security_group" "security_group" {
  name       = "new-group"
  vpc_id     = alicloud_vpc.vpc.id
}

定义安全组规则

您可以通过模板定义安全组规则allow_sshallow_weballow_egress

使用alicloud_security_group.security_group.id获取安全组security_group资源的输出ID属性值。

# 安全组入口端1
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
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
}

定义ECS实例

您可以通过模板定义ECS实例instance

  • 使用var.*** 获取自定义变量,即variable中的参数值。例如:var.instance_type获取的是variableinstance_type的值。

  • 使用local.***获取本地变量,即locals中的参数值。例如:local.new_host_name获取的是localsnew_host_name的值。

  • 使用user_data中的${path.cwd}获取当前工作目录。

  • user_data.sh文件中包含云服务器ECS需要执行的初始化脚本。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
# 这里配置安装脚本
yum install -y nginx
# 配置启动脚本
/usr/sbin/nginx

定义ECS实例模板如下所示。

resource "alicloud_instance" "instance" {
      availability_zone					 = var.zone_id
      security_groups 					 = [alicloud_security_group.security_group.id]
      # series III
      host_name 								 = local.new_host_name
      instance_type              = var.instance_type
      system_disk_size           = 500
      system_disk_category       = "cloud_essd"
      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        						 = 100
        category  						   = "cloud_essd"
      }
    }

整模板示例

variable "pay_type" {
	type                       = string
}
variable "pay_period_unit" {
	type                       = string
}
variable "pay_period" {
	type                       = number
}
variable "zone_id" {
	type                       = string
}
variable "vpc_cidr_block" {
	type                       = string
}
variable "vswitch_cidr_block" {
	type                       = string
} 
variable "instance_type" {
	type                       = string
}
variable "system_disk_category" {
	type                       = string
}
variable "system_disk_size" {
	type                       = number
}
variable "data_disk_category" {
	type                       = string
}
variable "data_disk_size" {
	type                       = number
} 
variable "instance_password" {
	type                       = string
}
# 默认资源名称
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
}

# 安全组入口端1
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
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]
  # series III
  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
  }
}
# 返回Nginx的IP地址
output "nginx_ip" {
  value										 	 = "http://${alicloud_instance.instance.public_ip}:8080"
}

Terraform模板类型转换

您可以通过资源编排控制台将Terraform模板转换成ROS模板,使用ROS资源实现添加模板参数分组与动态获取参数配置。

  1. 登录资源编排控制台

  2. 在左侧导航栏,选择模板>我的模板

  3. 我的模板页面,单击创建模板

  4. 编辑模板内容页签的下拉页表中,选择Terraform

  5. 编辑Terraform模板。

    1. 新建main.tf文件,输入完整模板示例中创建的Terraform模板。

    2. 新建user-data.sh文件,输入user_data.sh初始化脚本内容。

  6. 编辑模板内容页签的下拉页表中,选择ROS

    您已成功将Terraform模板转换为ROS模板,ROS模板示例如下所示。

    ROSTemplateFormatVersion: '2015-09-01'
    Transform: Aliyun::Terraform-v1.2
    Workspace:
      main.tf: |-
            variable "pay_type" {
              type 										   = string
            }
            variable "pay_period_unit" {
              type											 = string
            }
            variable "pay_period" {
              type											 = number
            }
            variable "zone_id" {
              type											 = string
            }
            variable "instance_type" {
              type											 = string
            }
            variable "vpc_cidr_block" {
              type											 = string
            }
            variable "vsw_cidr_block" {
              type											 = string
            }
            variable "instance_password" {
              type											 = string
            }
            # 默认资源名称
            locals {
              production_name = "nginx"
              new_vpc_name  					   = "vpc-for-${local.production_name}"
              new_vsw_name  			 		   = "vsw-for-${local.production_name}"
              new_scg_name  						 = "sg-for-${local.production_name}"
              new_host_name   					 = "app-for-${local.production_name}"
            }
    
            resource "alicloud_vpc" "vpc" {
              vpc_name    					     = local.new_vpc_name
              cidr_block 								 = var.vpc_cidr_block
            }
    
    
            resource "alicloud_vswitch" "vsw" {
              vpc_id   								   = alicloud_vpc.vpc.id
              cidr_block								 = var.vsw_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
            }
            
            # 安全组入口端1
            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
            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]
              # series III
              host_name									 = local.new_host_name
              instance_type              = var.instance_type
              system_disk_size           = 500
              system_disk_category       = "cloud_essd"
              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       							 = 100
                category  						   = "cloud_essd"
              }
            }
            # 返回Nginx的IP地址
            output "nginx_ip" {
              value											 = "http://${alicloud_instance.instance.public_ip}:8080"
            }
            
      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
        # 这里配置安装脚本
        yum install -y nginx
        # 配置启动脚本
        /usr/sbin/nginx
    

添加模板参数分组与动态获取参数配置

在以上模板中您已经完成对多种资源及其依赖关系的定义。其中instance资源的system_disk_category属性值为固定值,当您在不同地域创建资源栈时,需要调整模板内容和变更资源属性值以达到部署资源栈的目的。

您可以对模板添加Parameters,从而提高模板的灵活性和可复用性。

添加模板参数分组

您可以在模板中使用元数据(Metadata)对Parameters中定义的参数进行分组,并定义参数分组标签。更多信息,请参见元数据(Metadata)

您可以根据不同资源与资源对应的参数进行参数分组。以当前模板为例,您可以将资源按照如下结果划分。

资源参数分类

资源名称

参数名称

基础网络配置

vpcvswsecurity_group

zone_idvpc_cidr_blockvswitch_cidr_block

ECS实例资源配置

instance

instance_typesystem_disk_categorysystem_disk_sizedata_disk_categorydata_disk_sizeinstance_password

在Terraform标签下,新建.metadata文件,输入以下内容。

{
  "ALIYUN::ROS::Interface": {
    "ParameterGroups": [
      {
        "Parameters": [
          "pay_type",
          "pay_period_unit",
          "pay_period"
        ],
        "Label": {
          "default": {
            "en": "Payment mode Configuration",
            "zh-cn": "付费模式配置"
          }
        }
      },
      {
        "Parameters": [
          "zone_id"
        ],
        "Label": {
          "default": {
            "zh-cn": "可用区配置",
            "en": "Zone Configuration"
          }
        }
      },
      {
        "Parameters": [
          "vpc_cidr_block",
          "vswitch_cidr_block"
        ],
        "Label": {
          "default": {
            "zh-cn": "选择已有基础资源配置",
            "en": "Choose existing Infrastructure Configuration"
          }
        }
      },
      {
        "Parameters": [
          "instance_type",
          "system_disk_category",
          "system_disk_size",
          "data_disk_category",
          "data_disk_size",
          "instance_password"
        ],
        "Label": {
          "default": {
            "en": "Instance",
            "zh-cn": "ECS实例配置"
          }
        }
      }
    ]
  }
}

动态获取参数配置

以instance参数为例,当您需要在ROS控制台上对参数设置筛选条件并动态选择参数配置时,可以按照参数对应的资源类型(ALIYUN::ECS::Instance)在AssociationProperty和AssociationPropertyMetadata文档中查询到该参数支持的AssociationProperty取值 (ALIYUN::ECS::Instance::InstanceType),然后查看对筛选到的AssociationProperty设置过滤条件为ZoneIdAssociationPropertyMetadata取值。更多信息,请参见AssociationProperty和AssociationPropertyMetadata

在Terraform标签下的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::Or": [
            {
              "Fn::Equals": [
                "$${pay_type}",
                "PrePaid"
              ]
            },
            {
              "Fn::Equals": [
                "$${pay_type}",
                "undefined"
              ]
            }
          ]
        }
      }
    }
  }
  EOT
}
variable "zone_id" {
  type			  = string
  description = <<EOT
  {
    "AssociationProperty": "ALIYUN::ECS::Instance:ZoneId",
    "Description": {
      "zh-cn": "可用区ID。<br><b>注: <font color='blue'>选择前请确认该可用区是否支持创建ECS资源的规格,建议与其他交换机可用区不同。</font></b>",
      "en": "Availability Zone ID.<br><b>notex:<font color='blue'>before selecting, please confirm that the Availability Zone supports the specification of creating ECS resources,which is recommended to be different from other VSwitch Availability Zone.</font></b>"
    },
    "Label": {
      "zh-cn": "交换机可用区",
      "en": "VSwitch Availability Zone"
    }
  }
  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": "新建专有网络IP地址段范围,推荐使用以下的IP地址段<br><font color='green'>[10.0.0.0/8]</font><br><font color='green'>[172.16.0.0/12]</font><br><font color='green'>[192.168.0.0/16]。</font>",
      "en": "New proprietary network IP address segment range, recommended use of the following IP address segments<br><font color='green'>[10.0.0.0/8]</font><br><font color='green'>[172.16.0.0/12]</font><br><font color='green'>[192.168.0.0/16].</font>"
    }
  }
  EOT
}
variable "vswitch_cidr_block" {
  type			  = string
  default     = "192.168.0.0/24"
  description = <<EOT
  {
    "Description": {
      "zh-cn": "必须是所属专有网络的子网段,并且没有被其他交换机占用。",
      "en": "Must be a sub-network segment of the proprietary network and is not occupied by other VSwitches."
    },
    "Label": {
      "zh-cn": "交换机网段",
      "en": "VSwitch CIDR Block"
    }
  }
  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
  {
    "Description": {
      "en": "<font color='blue'><b>Optional values:</b></font><br>[cloud_efficiency: <font color='green'>Efficient Cloud Disk</font>]<br>[cloud_ssd: <font color='green'>SSD Cloud Disk</font>]<br>[cloud_essd: <font color='green'>ESSD Cloud Disk</font>]<br>[cloud: <font color='green'>Cloud Disk</font>]<br>[ephemeral_ssd: <font color='green'>Local SSD Cloud Disk</font>]",
      "zh-cn": "<font color='blue'><b>可选值:</b></font><br>[cloud_efficiency: <font color='green'>高效云盘</font>]<br>[cloud_ssd: <font color='green'>SSD云盘</font>]<br>[cloud_essd: <font color='green'>ESSD云盘</font>]<br>[cloud: <font color='green'>普通云盘</font>]<br>[ephemeral_ssd: <font color='green'>本地SSD盘</font>]"
    },
    "AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
    "AssociationPropertyMetadata": {
      "ZoneId": "$${zone_id}",
      "InstanceType": "$${instance_type}"
    },
    "Label": {
      "en": "System Disk Type",
      "zh-cn": "系统盘类型"
    }
  }
  EOT
}
variable "system_disk_size" {
  type			  = number
  default     = 40
  description = <<EOT
  {
    "Description": {
      "zh-cn": "系统盘大小,取值范围:40~500,单位:GB。",
      "en": "System disk size, range of values: 40~500, units: GB."
    },
    "Label": {
      "zh-cn": "系统盘空间",
      "en": "System Disk Space"
    }
  }
  EOT
}
variable "data_disk_category" {
  type			  = string
  description = <<EOT
  {
    "Label": {
      "zh-cn": "数据盘类型",
      "en": "Data Disk Type"
    },
    "Description": {
      "zh-cn": "<font color='blue'><b>可选值:</b></font><br>[cloud_efficiency: <font color='green'>高效云盘</font>]<br>[cloud_ssd: <font color='green'>SSD云盘</font>]<br>[cloud_essd: <font color='green'>ESSD云盘</font>]<br>[cloud: <font color='green'>普通云盘</font>]",
      "en": "<font color='blue'><b>Optional values:</b></font><br>[cloud_efficiency: <font color='green'>Efficient Cloud Disk</font>]<br>[cloud_ssd: <font color='green'>SSD Cloud Disk</font>]<br>[cloud_essd: <font color='green'>ESSD Cloud Disk</font>]<br>[cloud: <font color='green'>Cloud Disk</font>]"
    },      
    "AssociationProperty": "ALIYUN::ECS::Disk::DataDiskCategory",
    "AssociationPropertyMetadata": {
      "ZoneId": "$${zone_id}",
      "InstanceType": "$${instance_type}"
    }
  }
  EOT
}
variable "data_disk_size" {
  type			  = number
  default     = 100
  description = <<EOT
  {
    "Description": {
      "zh-cn": "ECS实例数据盘大小,单位为GiB。取值范围:20~32768",
      "en": "ECS Instance disk size, range of values: 20~32768, units: GB"
    },
    "MaxValue": 32768,
    "MinValue": 20,
    "Label": {
      "zh-cn": "数据盘空间",
      "en": "Data Disk Space"
    }
  }
  EOT
} 
variable "instance_password" {
  type			  = string
  sensitive   = true
  description = <<EOT
  {
    "Description": {
      "en": "Server login password, Length 8~30, must contain three(Capital letters, lowercase letters, numbers, ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ Special symbol in).",
      "zh-cn": "登录密码,长度8~30,必须包含三项(大写字母、小写字母、数字、 ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ 中的特殊符号)。"
    },
    "Label": {
      "en": "Instance Password",
      "zh-cn": "登录密码"
    },
    "ConstraintDescription": {
      "en": "Length 8~30, must contain three(Capital letters, lowercase letters, numbers, ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ Special symbol in).",
      "zh-cn": "长度8~30,必须包含三项(大写字母、小写字母、数字、 ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ 中的特殊符号)。"
    },
    "AssociationProperty": "ALIYUN::ECS::Instance::Password",
    "AllowedPattern": "^[a-zA-Z0-9-\\(\\)\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\_\\-\\+\\=\\|\\{\\}\\[\\]\\:\\;\\<\\>\\,\\.\\?\\/]*$",
    "MinLength": 8,
    "MaxLength": 30
  }
  EOT
}

添加模板参数关联与约束

Terraform模板中定义了ECS实例,其中需要约束的参数(zone_idinstance_typevswitch_idsystem_disk_categorysystem_disk_sizedata_disk_categorydata_disk_size)都与alicloud_instance有关。

在ResourcesForParameterConstraints字段中只需要定义资源类型ALIYUN::ECS::Instance(alicloud_instance),并关联image_idinstance_typezone_id参数就可以实现对instance_typezone_id的约束。

{
  "ALIYUN::ROS::Interface": {
    "ResourcesForParameterConstraints": {
      "instance": {
        "Type": "ALIYUN::ECS::Instance",
        "Properties": {
          "InstanceType": {
            "Ref": "instance_type"
          },
          "ImageId": "centos_7_9_x64_20G_alibase_20210318.vhd",
          "VSwitchId": {
            "Ref": "vswitch_id"
          },
          "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": [
          "pay_type",
          "pay_period_unit",
          "pay_period"
        ],
        "Label": {
          "default": {
            "en": "Payment mode Configuration",
            "zh-cn": "付费模式配置"
          }
        }
      },
      {
        "Parameters": [
          "zone_id"
        ],
        "Label": {
          "default": {
            "zh-cn": "可用区配置",
            "en": "Zone Configuration"
          }
        }
      },
      {
        "Parameters": [
          "vpc_cidr_block",
          "vswitch_cidr_block"
        ],
        "Label": {
          "default": {
            "zh-cn": "选择已有基础资源配置",
            "en": "Choose existing Infrastructure Configuration"
          }
        }
      },
      {
        "Parameters": [
          "instance_type",
          "system_disk_category",
          "system_disk_size",
          "data_disk_category",
          "data_disk_size",
          "instance_password"
        ],
        "Label": {
          "default": {
            "en": "Instance",
            "zh-cn": "ECS实例配置"
          }
        }
      }
    ]
  }
}

完整模板示例

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",
              "VSwitchId": {
                "Ref": "vswitch_id"
              },
              "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": [
              "pay_type",
              "pay_period_unit",
              "pay_period"
            ],
            "Label": {
              "default": {
                "en": "Payment mode Configuration",
                "zh-cn": "付费模式配置"
              }
            }
          },
          {
            "Parameters": [
              "zone_id"
            ],
            "Label": {
              "default": {
                "zh-cn": "可用区配置",
                "en": "Zone Configuration"
              }
            }
          },
          {
            "Parameters": [
              "vpc_cidr_block",
              "vswitch_cidr_block"
            ],
            "Label": {
              "default": {
                "zh-cn": "选择已有基础资源配置",
                "en": "Choose existing Infrastructure Configuration"
              }
            }
          },
          {
            "Parameters": [
              "instance_type",
              "system_disk_category",
              "system_disk_size",
              "data_disk_category",
              "data_disk_size",
              "instance_password"
            ],
            "Label": {
              "default": {
                "en": "Instance",
                "zh-cn": "ECS实例配置"
              }
            }
          }
        ]
      }
    }
  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::Or": [
                {
                  "Fn::Equals": [
                    "$${pay_type}",
                    "PrePaid"
                  ]
                },
                {
                  "Fn::Equals": [
                    "$${pay_type}",
                    "undefined"
                  ]
                }
              ]
            }
          }
        }
      }
      EOT
    }
    variable "zone_id" {
      type			  = string
      description = <<EOT
      {
        "AssociationProperty": "ALIYUN::ECS::Instance:ZoneId",
        "Description": {
          "zh-cn": "可用区ID。<br><b>注: <font color='blue'>选择前请确认该可用区是否支持创建ECS资源的规格,建议与其他交换机可用区不同。</font></b>",
          "en": "Availability Zone ID.<br><b>notex:<font color='blue'>before selecting, please confirm that the Availability Zone supports the specification of creating ECS resources,which is recommended to be different from other VSwitch Availability Zone.</font></b>"
        },
        "Label": {
          "zh-cn": "交换机可用区",
          "en": "VSwitch Availability Zone"
        }
      }
      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": "新建专有网络IP地址段范围,推荐使用以下的IP地址段<br><font color='green'>[10.0.0.0/8]</font><br><font color='green'>[172.16.0.0/12]</font><br><font color='green'>[192.168.0.0/16]。</font>",
          "en": "New proprietary network IP address segment range, recommended use of the following IP address segments<br><font color='green'>[10.0.0.0/8]</font><br><font color='green'>[172.16.0.0/12]</font><br><font color='green'>[192.168.0.0/16].</font>"
        }
      }
      EOT
    }
    variable "vswitch_cidr_block" {
      type			  = string
      default     = "192.168.0.0/24"
      description = <<EOT
      {
        "Description": {
          "zh-cn": "必须是所属专有网络的子网段,并且没有被其他交换机占用。",
          "en": "Must be a sub-network segment of the proprietary network and is not occupied by other VSwitches."
        },
        "Label": {
          "zh-cn": "交换机网段",
          "en": "VSwitch CIDR Block"
        }
      }
      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
      {
        "Description": {
          "en": "<font color='blue'><b>Optional values:</b></font><br>[cloud_efficiency: <font color='green'>Efficient Cloud Disk</font>]<br>[cloud_ssd: <font color='green'>SSD Cloud Disk</font>]<br>[cloud_essd: <font color='green'>ESSD Cloud Disk</font>]<br>[cloud: <font color='green'>Cloud Disk</font>]<br>[ephemeral_ssd: <font color='green'>Local SSD Cloud Disk</font>]",
          "zh-cn": "<font color='blue'><b>可选值:</b></font><br>[cloud_efficiency: <font color='green'>高效云盘</font>]<br>[cloud_ssd: <font color='green'>SSD云盘</font>]<br>[cloud_essd: <font color='green'>ESSD云盘</font>]<br>[cloud: <font color='green'>普通云盘</font>]<br>[ephemeral_ssd: <font color='green'>本地SSD盘</font>]"
        },
        "AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
        "AssociationPropertyMetadata": {
          "ZoneId": "$${zone_id}",
          "InstanceType": "$${instance_type}"
        },
        "Label": {
          "en": "System Disk Type",
          "zh-cn": "系统盘类型"
        }
      }
      EOT
    }
    variable "system_disk_size" {
      type			  = number
      default     = 40
      description = <<EOT
      {
        "Description": {
          "zh-cn": "系统盘大小,取值范围:40~500,单位:GB。",
          "en": "System disk size, range of values: 40~500, units: GB."
        },
        "Label": {
          "zh-cn": "系统盘空间",
          "en": "System Disk Space"
        }
      }
      EOT
    }
    variable "data_disk_category" {
      type			  = string
      description = <<EOT
      {
        "Label": {
          "zh-cn": "数据盘类型",
          "en": "Data Disk Type"
        },
        "Description": {
          "zh-cn": "<font color='blue'><b>可选值:</b></font><br>[cloud_efficiency: <font color='green'>高效云盘</font>]<br>[cloud_ssd: <font color='green'>SSD云盘</font>]<br>[cloud_essd: <font color='green'>ESSD云盘</font>]<br>[cloud: <font color='green'>普通云盘</font>]",
          "en": "<font color='blue'><b>Optional values:</b></font><br>[cloud_efficiency: <font color='green'>Efficient Cloud Disk</font>]<br>[cloud_ssd: <font color='green'>SSD Cloud Disk</font>]<br>[cloud_essd: <font color='green'>ESSD Cloud Disk</font>]<br>[cloud: <font color='green'>Cloud Disk</font>]"
        },      
        "AssociationProperty": "ALIYUN::ECS::Disk::DataDiskCategory",
        "AssociationPropertyMetadata": {
          "ZoneId": "$${zone_id}",
          "InstanceType": "$${instance_type}"
        }
      }
      EOT
    }
    variable "data_disk_size" {
      type			  = number
      default     = 100
      description = <<EOT
      {
        "Description": {
          "zh-cn": "ECS实例数据盘大小,单位为GiB。取值范围:20~32768。",
          "en": "ECS Instance disk size, range of values: 20~32768, units: GB."
        },
        "MaxValue": 32768,
        "MinValue": 20,
        "Label": {
          "zh-cn": "数据盘空间",
          "en": "Data Disk Space"
        }
      }
      EOT
    } 
    variable "instance_password" {
      type			  = string
      sensitive   = true
      description = <<EOT
      {
        "Description": {
          "en": "Server login password, Length 8~30, must contain three(Capital letters, lowercase letters, numbers, ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ Special symbol in).",
          "zh-cn": "登录密码,长度8~30,必须包含三项(大写字母、小写字母、数字、 ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ 中的特殊符号)。"
        },
        "Label": {
          "en": "Instance Password",
          "zh-cn": "登录密码"
        },
        "ConstraintDescription": {
          "en": "Length 8~30, must contain three(Capital letters, lowercase letters, numbers, ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ Special symbol in).",
          "zh-cn": "长度8~30,必须包含三项(大写字母、小写字母、数字、 ()`~!@#$%^&*_-+=|{}[]:;<>,.?/ 中的特殊符号)。"
        },
        "AssociationProperty": "ALIYUN::ECS::Instance::Password",
        "AllowedPattern": "^[a-zA-Z0-9-\\(\\)\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\_\\-\\+\\=\\|\\{\\}\\[\\]\\:\\;\\<\\>\\,\\.\\?\\/]*$",
        "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
    }

    # 安全组入口端1
    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
    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]
      # series III
      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
      }
    }
    # 返回Nginx的IP地址
    output "nginx_ip" {
      value											 = "http://${alicloud_instance.instance.public_ip}:8080"
    }
  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
    # 这里配置安装脚本
    yum install -y nginx
    # 配置启动脚本
    /usr/sbin/nginx