使用ROS一键创建,分区,格式化和挂载数据盘

对于每一个应用来说都有存储数据的需求,阿里云ECS针对不同的用户需求提供了三种类型的数据云盘,普通云盘,高效云盘和SSD云盘。通过ECS控制台可以为每个实例创建最多4块空数据盘或者根据已有数据盘的snapshot产生4块含有数据的云盘。但是根据上面的方式产生的数据云盘,不能直接使用,需用户登录ECS实例手动配置。对于空数据盘,用户登录ECS的实例手动分区,格式化,挂载;对于通过snapshot产生的数据盘,用户必须自己手动mount。这对于用户部署使用多台ECS实例,是一个繁重的体力活而且容易出错。

本文将为大家提供一个基于资源编排一键创建数据盘并通过UserData自动分区,格式化,化挂载数据盘的便捷方法。

创建数据盘

创建数据盘有两种方式:

  1. 作为ECS实例的属性

    DiskMappings

    DiskMappings是一个列表类型的属性,列表的每一个项就代表一个数据盘的定义,用户可以指定数据盘名称,大小,类型,以及源snapshotId。

  2. 作为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这中方式创建数据盘。

上一篇:《DNS攻击防范科普系列4》--遭遇DNS缓存投毒该怎么办?


下一篇:《DNS稳定保障系列1--服务双保障“辅助DNS”产品介绍》