对于每一个应用来说都有存储数据的需求,阿里云ECS针对不同的用户需求提供了三种类型的数据云盘,普通云盘,高效云盘和SSD云盘。通过ECS控制台可以为每个实例创建最多4块空数据盘或者根据已有数据盘的snapshot产生4块含有数据的云盘。但是根据上面的方式产生的数据云盘,不能直接使用,需用户登录ECS实例手动配置。对于空数据盘,用户登录ECS的实例手动分区,格式化,挂载;对于通过snapshot产生的数据盘,用户必须自己手动mount。这对于用户部署使用多台ECS实例,是一个繁重的体力活而且容易出错。
本文将为大家提供一个基于资源编排一键创建数据盘并通过UserData自动分区,格式化,化挂载数据盘的便捷方法。
创建数据盘
创建数据盘有两种方式:
- 作为ECS实例的属性
DiskMappings
DiskMappings是一个列表类型的属性,列表的每一个项就代表一个数据盘的定义,用户可以指定数据盘名称,大小,类型,以及源snapshotId。
- 作为stack的资源,再把disk资源关联到对应的ECS
ALIYUN::ECS::Disk
在模版指定这样的资源就说明需要创建一块数据盘,用户指定数据盘名称,大小,域,源snapshotId等等
ALIYUN::ECS::DiskAttachment
指定哪一块数据盘要和关联到哪一个ECS实例。必须指定的属性包括ALIYUN::ECS::Disk的Id和ECS实例的Id
不管是上面那种方式创建带有源数据的云盘,源数据盘snapshot必须和将要创建的数据盘在同一个域。
利用UserData处理数据盘
ROS给用户提供了UserData机制,这样用户就可以在创建ECS的时候,通过指定UserData脚本给ECS在第一次启动的时候做一些配置和初始化的工作。用户可以根据自己的需要自由的编写UserData脚本。下面我将提供两个简单的例子说明到底如何利用ROS和UserData机制实现一键创建数据盘并自动分区,格式化和mount。
UserData脚处理空数据盘
这个例子创建一个ECS实例,并给实例创建2个空数据盘,让实例处于阿里云的专有网络,并且给这个实例分配弹性IP以便后续访问。我们使用的是DiskMappings实例属性来说明ROS怎么创建数据盘,这个例子中定义了数据盘的名称,大小。给UserData指定了一个简单的脚本,脚本中首先是通过fdisk命令分区数据盘,然后以ext4方式格式化数据盘,最后挂载数据盘,最后配置/etc/fstab保证每次启动ECS都能正确挂载数据盘。在这个例子中用户可以通过TotalDataDisk说明有多少块数据盘,MountPoint指定数据盘的挂载点。我也通过指定ROS的WaitCondition资源来得到UserData脚本的执行结果。WaitCondition的使用可以参考这里。下面是最终模版:
{
"ROSTemplateFormatVersion": "2015-09-01",
"Parameters": {
"VpcName": {
"MaxLength": 128,
"Description": "VPC 名称",
"Type": "String",
"ConstraintDescription": "[2, 128] 英文或中文字符",
"MinLength": 2
},
"SecurityGroupName": {
"Description": "安全组名称",
"Type": "String"
},
"VpcCidrBlock": {
"Default": "10.0.0.0/8",
"AllowedValues": [
"192.168.0.0/16",
"172.16.0.0/12",
"10.0.0.0/8"
],
"Type": "String"
},
"Password": {
"NoEcho": true,
"MaxLength": 30,
"Description": "ECS登录密码.",
"Type": "String",
"ConstraintDescription": "8-30个字符,可以包含大、小写字母和特殊字符",
"MinLength": 8
},
"DiskSize": {
"Default": 40,
"Type": "Number"
},
"TotalDataDisk": {
"Description": "实例挂在数据盘的数量",
"Type": "String"
},
"ZoneId": {
"Description": "可用区 Id, <a href='#/product/cn-shenzhen/list/zoneList' target='_blank'>查看可用区</a>",
"Type": "String"
},
"DiskName": {
"Type": "String"
},
"SystemDiskCategory": {
"Default": "cloud",
"AllowedValues": [
"cloud",
"cloud_efficiency",
"cloud_ssd"
],
"Description": "系统盘的磁盘种类, 普通云盘(cloud)、高效云盘(cloud_efficiency)或SSD云盘(cloud_ssd)",
"Type": "String"
},
"MountPoint": {
"Description": "数据盘的挂在点",
"Type": "String"
},
"DestinationCidrBlock": {
"Default": "192.168.1.0",
"Description": "Route的目标网段,例如192.168.1.0/24或192.168.1.0",
"Type": "String"
},
"VSwitchCidrBlock": {
"Default": "10.0.10.0/24",
"Description": "VSwitch网段,此网段必须属于VPC",
"Type": "String"
}
},
"Resources": {
"VSwitch": {
"Type": "ALIYUN::ECS::VSwitch",
"Properties": {
"CidrBlock": {
"Ref": "VSwitchCidrBlock"
},
"ZoneId": {
"Ref": "ZoneId"
},
"VpcId": {
"Fn::GetAtt": [
"Vpc",
"VpcId"
]
}
}
},
"Vpc": {
"Type": "ALIYUN::ECS::VPC",
"Properties": {
"CidrBlock": {
"Ref": "VpcCidrBlock"
},
"VpcName": {
"Ref": "VpcName"
}
}
},
"WaitCondition": {
"Type": "ALIYUN::ROS::WaitCondition",
"Properties": {
"Handle": {
"Ref": "WaitConHandle"
},
"Timeout": 200,
"Count": 1
}
},
"SecurityGroup": {
"Type": "ALIYUN::ECS::SecurityGroup",
"Properties": {
"SecurityGroupName": {
"Ref": "SecurityGroupName"
},
"VpcId": {
"Ref": "Vpc"
}
}
},
"NewEip": {
"Type": "ALIYUN::ECS::EIP",
"Properties": {
"InternetChargeType": "PayByTraffic",
"Bandwidth": 1
}
},
"SecurityGroupIngress": {
"Type": "ALIYUN::ECS::SecurityGroupIngress",
"Properties": {
"SourceCidrIp": "0.0.0.0/0",
"SecurityGroupId": {
"Ref": "SecurityGroup"
},
"IpProtocol": "all",
"NicType": "intranet",
"PortRange": "-1/-1"
}
},
"WebServer": {
"Type": "ALIYUN::ECS::Instance",
"Properties": {
"IoOptimized": "optimized",
"ImageId": "centos6u5_64_40G_cloudinit_20160427.raw",
"SecurityGroupId": {
"Ref": "SecurityGroup"
},
"Password": {
"Ref": "Password"
},
"DiskMappings": [
{
"DiskName": {
"Ref": "DiskName"
},
"Size": {
"Ref": "DiskSize"
}
},
{
"Size": {
"Ref": "DiskSize"
}
}
],
"SystemDiskCategory": {
"Ref": "SystemDiskCategory"
},
"UserData": {
"Fn::Join": [
"",
[
"#!/bin/sh\n",
"logs=~/mount_logs\n",
"i=1\n",
"total=",
{
"Ref": "TotalDataDisk"
},
"\n",
"mountpoint=",
{
"Ref": "MountPoint"
},
"\n",
"while [ $i -le $total ]\n",
"do\n",
" j=`echo $i|awk '{printf \"%c\", 97+$i}'`\n",
"fdisk -S 56 /dev/vd$j <<ESXU\n",
"n\n",
"p\n",
"1\n",
"\n",
"\n",
"w\n",
"ESXU\n",
" echo \"/dev/vd$j is fdisked!\" >> $logs\n",
" mkfs.ext4 /dev/vd${j}1\n",
" if [ $? -eq 0 ];then\n",
" echo \"/dev/vd${j}1 is formated!\" >> $logs\n",
" fi\n",
" touch ~/test_ftab\n",
" mkdir $mountpoint$i\n",
"cat << ESXU > ~/test_ftab\n",
"/dev/vd${j}1 $mountpoint$i ext4 defaults 0 0\n",
"ESXU\n",
" cat ~/test_ftab >> /etc/fstab\n",
" mount -a\n",
" chmod -R 777 $mountpoint$i\n",
" rm -rf ~/test_ftab\n",
" echo \"/dev/vd${j}1 is mounted!\" >> $logs\n",
" let i+=1\n",
"done\n",
"\n",
{
"Fn::GetAtt": [
"WaitConHandle",
"CurlCli"
]
},
" -d '{\"id\" : \"webserver\", \"data\" : \"mount disk\"}'\n"
]
]
},
"VSwitchId": {
"Ref": "VSwitch"
},
"VpcId": {
"Ref": "Vpc"
},
"InstanceType": "ecs.n1.small"
}
},
"WaitConHandle": {
"Type": "ALIYUN::ROS::WaitConditionHandle"
},
"EIPBind": {
"Type": "ALIYUN::ECS::EIPAssociation",
"Properties": {
"InstanceId": {
"Ref": "WebServer"
},
"AllocationId": {
"Ref": "NewEip"
}
}
},
"SecurityGroupEgress": {
"Type": "ALIYUN::ECS::SecurityGroupEgress",
"Properties": {
"SecurityGroupId": {
"Ref": "SecurityGroup"
},
"IpProtocol": "all",
"DestCidrIp": "0.0.0.0/0",
"NicType": "intranet",
"PortRange": "-1/-1"
}
}
},
"Outputs": {
"Data": {
"Value": {
"Fn::GetAtt": [
"WaitCondition",
"Data"
]
}
},
"PublicIp": {
"Value": {
"Fn::GetAtt": [
"WebServer",
"PublicIp"
]
}
},
"InstanceId": {
"Value": {
"Fn::GetAtt": [
"WebServer",
"InstanceId"
]
}
},
"CurlCli": {
"Value": {
"Fn::GetAtt": [
"WaitConHandle",
"CurlCli"
]
}
}
}
}
UserData脚本处理带有源数据的数据盘
这个例子是根据源数据的snapshot创建数据盘,其它的都和上面的例子类似。这时候新的ECS实例的数据盘是已经分好区和格式化过的,我们只需要挂载数据盘到合适的挂载点就行。通过TotalDataDisk说明有多少块数据盘,MountPoint指定数据盘的挂载点,使用ROS的WaitCondition资源来得到UserData脚本的执行结果。下面是最终模版:
{
"ROSTemplateFormatVersion": "2015-09-01",
"Parameters": {
"VpcName": {
"MaxLength": 128,
"Description": "VPC 名称",
"Type": "String",
"ConstraintDescription": "[2, 128] 英文或中文字符",
"MinLength": 2
},
"DataDisk2SnapshotId": {
"Description": "根据此快照创建数据盘2",
"Type": "String"
},
"Password": {
"NoEcho": true,
"MaxLength": 30,
"Description": "ECS登录密码.",
"Type": "String",
"ConstraintDescription": "8-30个字符,可以包含大、小写字母和特殊字符",
"MinLength": 8
},
"DiskName": {
"Type": "String"
},
"ZoneId": {
"Description": "可用区 Id, <a href='#/product/cn-shenzhen/list/zoneList' target='_blank'>查看可用区</a>",
"Type": "String"
},
"DestinationCidrBlock": {
"Default": "192.168.1.0",
"Description": "Route的目标网段,例如192.168.1.0/24或192.168.1.0",
"Type": "String"
},
"MountPoint": {
"Description": "数据盘的挂在点",
"Type": "String"
},
"VSwitchCidrBlock": {
"Default": "10.0.10.0/24",
"Description": "VSwitch网段,此网段必须属于VPC",
"Type": "String"
},
"DataDisk1SnapshotId": {
"Description": "根据此快照创建数据盘1",
"Type": "String"
},
"SecurityGroupName": {
"Description": "安全组名称",
"Type": "String"
},
"VpcCidrBlock": {
"Default": "10.0.0.0/8",
"AllowedValues": [
"192.168.0.0/16",
"172.16.0.0/12",
"10.0.0.0/8"
],
"Type": "String"
},
"DiskSize": {
"Default": 40,
"Type": "Number"
},
"TotalDataDisk": {
"Description": "实例挂在数据盘的数量",
"Type": "String"
},
"SystemDiskCategory": {
"Default": "cloud",
"AllowedValues": [
"cloud",
"cloud_efficiency",
"cloud_ssd"
],
"Description": "系统盘的磁盘种类, 普通云盘(cloud)、高效云盘(cloud_efficiency)或SSD云盘(cloud_ssd)",
"Type": "String"
}
},
"Resources": {
"VSwitch": {
"Type": "ALIYUN::ECS::VSwitch",
"Properties": {
"CidrBlock": {
"Ref": "VSwitchCidrBlock"
},
"ZoneId": {
"Ref": "ZoneId"
},
"VpcId": {
"Fn::GetAtt": [
"Vpc",
"VpcId"
]
}
}
},
"Vpc": {
"Type": "ALIYUN::ECS::VPC",
"Properties": {
"CidrBlock": {
"Ref": "VpcCidrBlock"
},
"VpcName": {
"Ref": "VpcName"
}
}
},
"WaitCondition": {
"Type": "ALIYUN::ROS::WaitCondition",
"Properties": {
"Handle": {
"Ref": "WaitConHandle"
},
"Timeout": 200,
"Count": 1
}
},
"SecurityGroup": {
"Type": "ALIYUN::ECS::SecurityGroup",
"Properties": {
"SecurityGroupName": {
"Ref": "SecurityGroupName"
},
"VpcId": {
"Ref": "Vpc"
}
}
},
"NewEip": {
"Type": "ALIYUN::ECS::EIP",
"Properties": {
"InternetChargeType": "PayByTraffic",
"Bandwidth": 1
}
},
"SecurityGroupIngress": {
"Type": "ALIYUN::ECS::SecurityGroupIngress",
"Properties": {
"SourceCidrIp": "0.0.0.0/0",
"SecurityGroupId": {
"Ref": "SecurityGroup"
},
"IpProtocol": "all",
"NicType": "intranet",
"PortRange": "-1/-1"
}
},
"WebServer": {
"Type": "ALIYUN::ECS::Instance",
"Properties": {
"IoOptimized": "optimized",
"ImageId": "centos6u5_64_40G_cloudinit_20160427.raw",
"SecurityGroupId": {
"Ref": "SecurityGroup"
},
"Password": {
"Ref": "Password"
},
"DiskMappings": [
{
"SnapshotId": {
"Ref": "DataDisk1SnapshotId"
},
"DiskName": {
"Ref": "DiskName"
},
"Size": {
"Ref": "DiskSize"
}
},
{
"SnapshotId": {
"Ref": "DataDisk2SnapshotId"
},
"Size": {
"Ref": "DiskSize"
}
}
],
"SystemDiskCategory": {
"Ref": "SystemDiskCategory"
},
"UserData": {
"Fn::Join": [
"",
[
"#!/bin/sh\n",
"logs=~/mount_logs\n",
"i=1\n",
"total=",
{
"Ref": "TotalDataDisk"
},
"\n",
"mountpoint=",
{
"Ref": "MountPoint"
},
"\n",
"while [ $i -le $total ]\n",
"do\n",
" j=`echo $i|awk '{printf \"%c\", 97+$i}'`\n",
" touch ~/test_ftab\n",
" mkdir $mountpoint$i\n",
"cat << ESXU > ~/test_ftab\n",
"/dev/vd${j}1 $mountpoint$i ext4 defaults 0 0\n",
"ESXU\n",
" cat ~/test_ftab >> /etc/fstab\n",
" mount -a\n",
" chmod -R 777 $mountpoint$i\n",
" rm -rf ~/test_ftab\n",
" echo \"/dev/vd${j}1 is mounted!\" >> $logs\n",
" let i+=1\n",
"done\n",
"\n",
{
"Fn::GetAtt": [
"WaitConHandle",
"CurlCli"
]
},
" -d '{\"id\" : \"webserver\", \"data\" : \"mount disk\"}'\n"
]
]
},
"VSwitchId": {
"Ref": "VSwitch"
},
"VpcId": {
"Ref": "Vpc"
},
"InstanceType": "ecs.n1.small"
}
},
"WaitConHandle": {
"Type": "ALIYUN::ROS::WaitConditionHandle"
},
"EIPBind": {
"Type": "ALIYUN::ECS::EIPAssociation",
"Properties": {
"InstanceId": {
"Ref": "WebServer"
},
"AllocationId": {
"Ref": "NewEip"
}
}
},
"SecurityGroupEgress": {
"Type": "ALIYUN::ECS::SecurityGroupEgress",
"Properties": {
"SecurityGroupId": {
"Ref": "SecurityGroup"
},
"IpProtocol": "all",
"DestCidrIp": "0.0.0.0/0",
"NicType": "intranet",
"PortRange": "-1/-1"
}
}
},
"Outputs": {
"Data": {
"Value": {
"Fn::GetAtt": [
"WaitCondition",
"Data"
]
}
},
"PublicIp": {
"Value": {
"Fn::GetAtt": [
"WebServer",
"PublicIp"
]
}
},
"InstanceId": {
"Value": {
"Fn::GetAtt": [
"WebServer",
"InstanceId"
]
}
},
"CurlCli": {
"Value": {
"Fn::GetAtt": [
"WaitConHandle",
"CurlCli"
]
}
}
}
}
注意
我们注意到以上两个例子都是使用DiskMappings属性,而不是使用ROS栈资源的方式创建数据盘,这是因为DiskMappings是ECS资源的属性,所以在创建启动ECS的时候,数据盘已经创建好了,并且和ECS实例做了关联。那么执行UserData就能完成分区,格式化和挂载。但是如果以ROS stack资源的方式创建数据盘,首先是创建ECS资源和Disk资源,最后才关联disk和ECS实例。那么当ECS实例启动的时候,数据盘还没有真正关联到相应的ECS,所以这种情况下执行UserData脚本就会找不到数据盘,分区,格式化,挂载也就无从谈起。如果大家要使用数据盘并想通过UserData自动挂载数据盘,则建议使用DiskMappings这中方式创建数据盘。