Deploy NGINX on an ECS instance with Terraform

更新时间:
复制 MD 格式

This guide demonstrates how to write a Terraform template to deploy NGINX on a single ECS instance, starting with a basic configuration and evolving it into an advanced, reusable template.

Prerequisites

Familiarize yourself with the template syntax and structure. For more information, see Quick Start.

Example scenario

Create an ECS instance in a VPC and deploy NGINX on the instance.

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

Usage notes

View the property details for each resource type. For more information, see View resource types.

The documentation for each resource type specifies whether properties are Required or Optional. You must declare all Required properties in your template's resource definition.

Author the template

You can find the required resource types in the Resource type index.

This scenario involves creating a VPC (alicloud_vpc), an ECS instance (alicloud_instance), a VSwitch (alicloud_vswitch), a security group (alicloud_security_group), and a security group rule (alicloud_security_group_rule). NGINX is installed when the ECS instance is created.

Define resources and dependencies

Define basic network resources

You can define the following basic network resources in the template: vpc, vsw, and security_group.

  • Use alicloud_vpc.vpc.id to get the output ID of the alicloud_vpc resource.

  • Use var.*** to access custom variables, that is, the values of parameters in a variable block. For example, var.zone_id accesses the value of zone_id in the variable block.

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
}

Define security group rules

Define the security group rules allow_ssh, allow_web, and allow_egress.

Use alicloud_security_group.security_group.id to get the ID of the security_group resource.

# Allow SSH ingress
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
}

# Allow web ingress
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
}

# Allow all egress traffic
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
}

Define the ECS instance

Define the ECS instance in the template.

  • Use var.*** to retrieve custom variables, that is, the parameter values in variable. For example, var.instance_type retrieves the value of instance_type from variable.

  • Use local.*** to retrieve a local variable, which is the value of a parameter in locals. For example, local.new_host_name retrieves the value of new_host_name in locals.

  • Use ${path.cwd} in the user_data argument to get the current working directory.

  • The user-data.sh file is an initialization script that runs on the ECS instance at boot. The script's content is as follows:

#!/bin/bash -v
# Mount the data disk to /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
# Configure the installation script.
yum install -y nginx
# Configure the startup script.
/usr/sbin/nginx

The following code defines the ECS instance.

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"
      }
    }

Complete template example

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
}
# Define default resource names.
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
}

# Configure basic information for the security group.
resource "alicloud_security_group" "security_group" {
  name        							 = local.new_scg_name
  description 							 = "nginx scg"
  vpc_id 										 =  alicloud_vpc.vpc.id
}

# Allow SSH ingress.
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
}

# Allow web ingress.
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
}

# Allow all egress traffic.
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
}

# Configure basic settings for the instance.
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
  }
}
# Output the IP address of the NGINX server.
output "nginx_ip" {
  value										 	 = "http://${alicloud_instance.instance.public_ip}"
}

Convert the Terraform template

You can convert a Terraform template to a ROS template in the ROS console to use features like parameter grouping and dynamic lookups.

  1. Log on to the ROS console.

  2. In the navigation pane on the left, choose Templates > My Templates.

  3. On the My Templates page, click Create Template.

  4. Under Template Type, select Terraform.

  5. Edit the Terraform template.

    1. Create a main.tf file and add the content from the complete template example.

    2. Create a user-data.sh file and add the content of the initialization script.

  6. Under Template Type, select ROS to convert the Terraform template into a ROS template. The following code provides an example of the converted ROS template.

    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
            }
            # Define default resource names.
            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
            }
    
            # Configure basic information for the security group.
            resource "alicloud_security_group" "security_group" {
              name      							   = local.new_scg_name
              description							   = "nginx scg"
              vpc_id									   =  alicloud_vpc.vpc.id
            }
            
            # Allow SSH ingress.
            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
            }
            
            # Allow web ingress.
            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
            }
            
            # Allow all egress traffic.
            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
            }
            
            # Configure basic settings for the instance.
            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       = "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       							 = var.data_disk_size
                category  						   = "cloud_essd"
              }
            }
            # Output the IP address of the NGINX server.
            output "nginx_ip" {
              value											 = "http://${alicloud_instance.instance.public_ip}"
            }
            
      user-data.sh: |-
        #!/bin/bash -v
        # Mount the data disk to /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
        # Configure the installation script.
        yum install -y nginx
        # Configure the startup script.
        /usr/sbin/nginx
    

Group parameters and add dynamic configurations

This template hardcodes some property values, such as system_disk_category. As a result, you must modify the template for deployments with different settings or in different regions.

To make the template more flexible and reusable, you can add parameters.

Group template parameters

You can use Metadata to group parameters and define labels for the parameter groups that appear in the console. For more information, see Metadata.

You can group parameters based on their corresponding resources. For this example, you can divide the resources as shown in the following table.

Parameter group

Resource name

Parameter

Basic network configuration

vpc, vsw, security_group

zone_id, vpc_cidr_block, vswitch_cidr_block

ECS instance configuration

instance

instance_type, system_disk_category, system_disk_size, data_disk_category, data_disk_size, instance_password

Under the Terraform tab, create a new .metadata file and enter the following content.

{
  "ALIYUN::ROS::Interface": {
    "ParameterGroups": [
      {
        "Parameters": [
          "pay_type",
          "pay_period_unit",
          "pay_period"
        ],
        "Label": {
          "default": {
            "en": "Payment mode"
          }
        }
      },
      {
        "Parameters": [
          "zone_id"
        ],
        "Label": {
          "default": {
            "en": "Zone"
          }
        }
      },
      {
        "Parameters": [
          "vpc_cidr_block",
          "vswitch_cidr_block"
        ],
        "Label": {
          "default": {
            "en": "Existing infrastructure"
          }
        }
      },
      {
        "Parameters": [
          "instance_type",
          "system_disk_category",
          "system_disk_size",
          "data_disk_category",
          "data_disk_size",
          "instance_password"
        ],
        "Label": {
          "default": {
            "en": "Instance configuration"
          }
        }
      }
    ]
  }
}

Dynamically configure parameters

Take the instance parameter as an example. When you need to set filter conditions for a parameter and dynamically select a configuration in the ROS console, you can find the supported AssociationProperty value for the parameter, such as ALIYUN::ECS::Instance::InstanceType, in the AssociationProperty and AssociationPropertyMetadata documentation based on the parameter's resource type (ALIYUN::ECS::Instance). Then, view the AssociationPropertyMetadata value that uses ZoneId as a filter condition for the retrieved AssociationProperty. For more information, see AssociationProperty and AssociationPropertyMetadata.

In the main.tf file on the Terraform tab, enter the following content.

variable "pay_type" {
  type 				= string
  default     = "PostPaid"
  description = <<EOT
  {
    "Label": {
      "en": "Charge type"
    },
    "AllowedValues": [
      "PostPaid",
      "PrePaid"
    ],
    "AssociationProperty": "ChargeType",
    "AssociationPropertyMetadata": {
      "LocaleKey": "InstanceChargeType"
    }
  }
  EOT
}
variable "pay_period_unit" {
  type 				= string
  default     = "Month"
  description = <<EOT
  {
    "Label": {
      "en": "Pay period unit"
    },
    "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"
    },
    "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": {
      "en": "The ID of the Availability Zone.<br><b>Note: <font color='blue'>Before you select an Availability Zone, confirm that it supports the instance types you plan to create. We recommend selecting a different Availability Zone from your other VSwitches.</font></b>"
    },
    "Label": {
      "en": "VSwitch availability zone"
    }
  }
  EOT
}
variable "vpc_cidr_block" {
  type			  = string
  default     = "192.168.0.0/16"
  description = <<EOT
  {
    "Label": {
      "en": "VPC CIDR block"
    },
    "Description": {
      "en": "The IP address range for the new VPC. We recommend using one of the following standard private network ranges:<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": {
      "en": "Must be a subnet of the VPC and not be in use by another VSwitch."
    },
    "Label": {
      "en": "VSwitch CIDR block"
    }
  }
  EOT
} 
variable "instance_type" {
  type			  = string
  description = <<EOT
  {
    "Label": {
      "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>Valid 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'>Basic Cloud Disk</font><br>ephemeral_ssd: <font color='green'>Local SSD</font>"
    },
    "AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
    "AssociationPropertyMetadata": {
      "ZoneId": "$${zone_id}",
      "InstanceType": "$${instance_type}"
    },
    "Label": {
      "en": "System disk type"
    }
  }
  EOT
}
variable "system_disk_size" {
  type			  = number
  default     = 40
  description = <<EOT
  {
    "Description": {
      "en": "The size of the system disk, in GB. Valid values: 40 to 500."
    },
    "Label": {
      "en": "System disk size"
    }
  }
  EOT
}
variable "data_disk_category" {
  type			  = string
  description = <<EOT
  {
    "Label": {
      "en": "Data disk type"
    },
    "Description": {
      "en": "<font color='blue'><b>Valid 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'>Basic Cloud Disk</font>"
    },      
    "AssociationProperty": "ALIYUN::ECS::Disk::DataDiskCategory",
    "AssociationPropertyMetadata": {
      "ZoneId": "$${zone_id}",
      "InstanceType": "$${instance_type}"
    },
    "Label": {
      "en": "Data disk type"
    }
  }
  EOT
}
variable "data_disk_size" {
  type			  = number
  default     = 100
  description = <<EOT
  {
    "Description": {
      "en": "The size of the data disk, in GiB. Valid values: 20 to 32768."
    },
    "MaxValue": 32768,
    "MinValue": 20,
    "Label": {
      "en": "Data disk size"
    }
  }
  EOT
} 
variable "instance_password" {
  type			  = string
  sensitive   = true
  description = <<EOT
  {
    "Description": {
      "en": "The login password for the instance. The password must be 8 to 30 characters in length and contain at least three of the following character types: uppercase letters, lowercase letters, digits, and special characters, which include ()`~!@#$%^&*_-+=|{}[]:;<>,.?/"
    },
    "Label": {
      "en": "Instance password"
    },
    "ConstraintDescription": {
      "en": "The password must be 8 to 30 characters long and contain at least three of the following character types: uppercase letters, lowercase letters, digits, and special characters from the set ()`~!@#$%^&*_-+=|{}[]:;<>,.?/"
    },
    "AssociationProperty": "ALIYUN::ECS::Instance::Password",
    "AllowedPattern": "^[a-zA-Z0-9-\\(\\)\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\_\\-\\+\\=\\|\\{\\}\\[\\]\\:\\;\\<\\>\\,\\.\\?\\/]*$",
    "MinLength": 8,
    "MaxLength": 30
  }
  EOT
}

Add parameter associations and constraints

The Terraform template defines an ECS instance, and the parameters to be constrained (zone_id, instance_type, vswitch_id, system_disk_category, system_disk_size, data_disk_category, and data_disk_size) are all related to alicloud_instance.

In the ResourcesForParameterConstraints field, you can apply constraints to instance_type and zone_id by defining the ALIYUN::ECS::Instance (alicloud_instance) resource type and associating the image_id, instance_type, and zone_id parameters.

{
  "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"
          }
        }
      },
      {
        "Parameters": [
          "zone_id"
        ],
        "Label": {
          "default": {
            "en": "Zone"
          }
        }
      },
      {
        "Parameters": [
          "vpc_cidr_block",
          "vswitch_cidr_block"
        ],
        "Label": {
          "default": {
            "en": "Existing infrastructure"
          }
        }
      },
      {
        "Parameters": [
          "instance_type",
          "system_disk_category",
          "system_disk_size",
          "data_disk_category",
          "data_disk_size",
          "instance_password"
        ],
        "Label": {
          "default": {
            "en": "Instance configuration"
          }
        }
      }
    ]
  }
}

Complete template example

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"
              }
            }
          },
          {
            "Parameters": [
              "zone_id"
            ],
            "Label": {
              "default": {
                "en": "Zone"
              }
            }
          },
          {
            "Parameters": [
              "vpc_cidr_block",
              "vswitch_cidr_block"
            ],
            "Label": {
              "default": {
                "en": "Existing infrastructure"
              }
            }
          },
          {
            "Parameters": [
              "instance_type",
              "system_disk_category",
              "system_disk_size",
              "data_disk_category",
              "data_disk_size",
              "instance_password"
            ],
            "Label": {
              "default": {
                "en": "Instance configuration"
              }
            }
          }
        ]
      }
    }
  main.tf: |-
    variable "pay_type" {
      type			  = string
      default     = "PostPaid"
      description = <<EOT
      {
        "Label": {
          "en": "Charge type"
        },
        "AllowedValues": [
          "PostPaid",
          "PrePaid"
        ],
        "AssociationProperty": "ChargeType",
        "AssociationPropertyMetadata": {
          "LocaleKey": "InstanceChargeType"
        }
      }
      EOT
    }
    variable "pay_period_unit" {
      type			  = string
      default     = "Month"
      description = <<EOT
      {
        "Label": {
          "en": "Pay period unit"
        },
        "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"
        },
        "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": {
          "en": "The ID of the Availability Zone.<br><b>Note: <font color='blue'>Before you select an Availability Zone, confirm that it supports the instance types you plan to create. We recommend selecting a different Availability Zone from your other VSwitches.</font></b>"
        },
        "Label": {
          "en": "VSwitch availability zone"
        }
      }
      EOT
    }
    variable "vpc_cidr_block" {
      type			  = string
      default     = "192.168.0.0/16"
      description = <<EOT
      {
        "Label": {
          "en": "VPC CIDR block"
        },
        "Description": {
          "en": "The IP address range for the new VPC. We recommend using one of the following standard private network ranges:<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": {
          "en": "Must be a subnet of the VPC and not be in use by another VSwitch."
        },
        "Label": {
          "en": "VSwitch CIDR block"
        }
      }
      EOT
    } 
    variable "instance_type" {
      type			  = string
      description = <<EOT
      {
        "Label": {
          "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>Valid 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'>Basic Cloud Disk</font><br>ephemeral_ssd: <font color='green'>Local SSD</font>"
        },
        "AssociationProperty": "ALIYUN::ECS::Disk::SystemDiskCategory",
        "AssociationPropertyMetadata": {
          "ZoneId": "$${zone_id}",
          "InstanceType": "$${instance_type}"
        },
        "Label": {
          "en": "System disk type"
        }
      }
      EOT
    }
    variable "system_disk_size" {
      type			  = number
      default     = 40
      description = <<EOT
      {
        "Description": {
          "en": "The size of the system disk, in GB. Valid values: 40 to 500."
        },
        "Label": {
          "en": "System disk size"
        }
      }
      EOT
    }
    variable "data_disk_category" {
      type			  = string
      description = <<EOT
      {
        "Label": {
          "en": "Data disk type"
        },
        "Description": {
          "en": "<font color='blue'><b>Valid 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'>Basic 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": {
          "en": "The size of the data disk, in GiB. Valid values: 20 to 32768."
        },
        "MaxValue": 32768,
        "MinValue": 20,
        "Label": {
          "en": "Data disk size"
        }
      }
      EOT
    } 
    variable "instance_password" {
      type			  = string
      sensitive   = true
      description = <<EOT
      {
        "Description": {
          "en": "The login password for the instance. The password must be 8 to 30 characters in length and contain at least three of the following character types: uppercase letters, lowercase letters, digits, and special characters, which include ()`~!@#$%^&*_-+=|{}[]:;<>,.?/"
        },
        "Label": {
          "en": "Instance password"
        },
        "ConstraintDescription": {
          "en": "The password must be 8 to 30 characters long and contain at least three of the following character types: uppercase letters, lowercase letters, digits, and special characters from the set ()`~!@#$%^&*_-+=|{}[]:;<>,.?/"
        },
        "AssociationProperty": "ALIYUN::ECS::Instance::Password",
        "AllowedPattern": "^[a-zA-Z0-9-\\(\\)\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\_\\-\\+\\=\\|\\{\\}\\[\\]\\:\\;\\<\\>\\,\\.\\?\\/]*$",
        "MinLength": 8,
        "MaxLength": 30
      }
      EOT
    }
    # Define default resource names.
    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
    }

    # Configure basic information for the security group.
    resource "alicloud_security_group" "security_group" {
      name      							   = local.new_scg_name
      description								 = "nginx scg"
      vpc_id										 =  alicloud_vpc.vpc.id
    }

    # Allow SSH ingress.
    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
    }

    # Allow web ingress.
    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
    }

    # Allow all egress traffic.
    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
    }

    # Configure basic settings for the instance.
    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
      }
    }
    # Output the IP address of the NGINX server.
    output "nginx_ip" {
      value											 = "http://${alicloud_instance.instance.public_ip}"
    }
  user-data.sh: |-
    #!/bin/bash -v
    # Mount the data disk to /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
    # Configure the installation script.
    yum install -y nginx
    # Configure the startup script.
    /usr/sbin/nginx