文档

使用ROS CDK生成创建ECS实例的ROS模板

阿里云ROS CDK(Cloud Development Toolkit)是资源编排(ROS)提供的一种命令行工具,帮助您使用多种编程语言定义云资源。您无需使用繁琐的JSON或YAML模板语法,即可使用ROS CDK完成资源的创建和配置,实现自动化部署及运维。本文以创建ECS实例模板为例生成CDK代码。

配置CDK开发环境

  1. 安装ROS CDK,执行以下命令创建一个工程目录并进行初始化。

    mkdir demo
    cd demo
    ros-cdk init --language=python --generate-only=true #初始化环境
  2. 执行以下命令,创建一个属于当前工程的虚拟环境。

    python3 -m venv .venv  # 创建一个属于当前工程的虚拟环境
    source .venv/bin/activate  # 进入虚拟环境
  3. 执行以下命令,配置阿里云凭证信息。

    ros-cdk config

    根据界面提示输入配置信息。

    endpoint(optional, default:https://ros.aliyuncs.com):
    defaultRegionId(optional, default:cn-hangzhou):cn-beijing
    
    [1] AK
    [2] StsToken
    [3] RamRoleArn
    [4] EcsRamRole
    [0] CANCEL
    
    Authenticate mode [1...4 / 0]: 1
    accessKeyId:************************
    accessKeySecret:******************************
    
     ✅ Your cdk configuration has been saved successfully!

编写CDK代码

  1. 分析模板可能会用到的资源,安装对应的资源依赖包并引入。

    pip install ros-cdk-core==1.0.16     # 使用指定版本
    pip install ros-cdk-ecs              # 不加版本默认下载最新版
  2. 根据CDK语法与ROS模板属性参数的对应关系并编写代码。

    ROS模板属性与CDK属性对应关系如下表所示。

    ROS常用模板属性

    CDK属性

    Parameters

    core.RosParameter

    Metadata

    core.RosInfo.metadata

    Resources

    Conditions

    core.RosCondition

    OutPuts

    core.RosOutput

    1. 创建参数。

      参数AssociationProperty字段需要对照文档将类型转化为core.RosParameter.AssociationProperty属性。

      zone_id = core.RosParameter(self, 'ZoneId',
                                  type=core.RosParameterType.STRING,
                                  label='可用区',
                                  association_property=core.RosParameter.AssociationProperty.ECS_ZONE_ID
                                 )
    2. 创建资源。

      /cdk/cdk_stack.py中引入资源包并编写对应的代码。

      import ros_cdk_core as core
      import ros_cdk_ecs as ecs
      
      
      class CdkStack(core.Stack):
      
          def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
              super().__init__(scope, construct_id, **kwargs)
      
              core.RosInfo(self, core.RosInfo.metadata, {
                  'ALIYUN::ROS::Interface': {
                      'ParameterGroups': [
                          {'Parameters': ['ZoneId'], 'Label': {'default': {'zh-cn': '可用区配置'}}},
                          {'Parameters': ['VpcId', 'VSwitchId'], 'Label': {'default': {'zh-cn': '选择已有基础资源配置'}}},
                          {'Parameters': ['PayType', 'PayPeriodUnit', 'PayPeriod'],
                           'Label': {'default': {'zh-cn': '付费类型配置'}}},
                          {'Parameters': ['EcsInstanceType', 'InstancePassword'],
                           'Label': {'default': {'zh-cn': 'ECS实例配置'}}}
                      ]
                  }
              })
      
              # The code that defines your stack goes here
      
              pay_type = core.RosParameter(self, 'PayType',
                                           type=core.RosParameterType.STRING,
                                           label='付费类型',
                                           default_value='PostPaid',
                                           allowed_values=['PostPaid', 'PrePaid'],
                                           association_property=core.RosParameter.AssociationProperty.CHARGE_TYPE,
                                           association_property_metadata={
                                               'LocaleKey': 'InstanceChargeType'
                                           })
      
              pay_period_unit = core.RosParameter(self, 'PayPeriodUnit',
                                                  type=core.RosParameterType.STRING,
                                                  label='购买资源时长周期',
                                                  default_value='Month',
                                                  allowed_values=['Month', 'Year'],
                                                  association_property='PayPeriodUnit',
                                                  association_property_metadata={
                                                      "Visible": {
                                                          "Condition": {
                                                              "Fn::Not": {
                                                                  "Fn::Equals": ["${PayType}", "PostPaid"]
                                                              }
                                                          }
                                                      }
                                                  }
                                                  )
      
              pay_period = core.RosParameter(self, 'PayPeriod',
                                             type=core.RosParameterType.NUMBER,
                                             label='购买资源时长',
                                             default_value=1,
                                             allowed_values=[1, 2, 3, 4, 5, 6, 7, 8, 9],
                                             association_property='PayPeriod',
                                             association_property_metadata={
                                                 "Visible": {
                                                     "Condition": {
                                                         "Fn::Not": {
                                                             "Fn::Equals": ["${PayType}", "PostPaid"]
                                                         }
                                                     }
                                                 }
                                             }
                                             )
      
              zone_id = core.RosParameter(self, 'ZoneId',
                                          type=core.RosParameterType.STRING,
                                          label='可用区',
                                          association_property=core.RosParameter.AssociationProperty.ECS_ZONE_ID
                                          )
      
              vpc_id = core.RosParameter(self, 'VpcId',
                                         type=core.RosParameterType.STRING,
                                         label='VPC实例ID',
                                         association_property=core.RosParameter.AssociationProperty.ECS_VPC_ID)
      
              vswitch_id = core.RosParameter(self, 'VSwitchId',
                                             type=core.RosParameterType.STRING,
                                             label='VSwitch实例ID',
                                             association_property=core.RosParameter.AssociationProperty.ECS_VSWITCH_ID,
                                             association_property_metadata={
                                                 'VpcId': '${VpcId}',
                                                 'ZoneId': '${ZoneId}'
                                             })
      
              instance_type = core.RosParameter(self, 'EcsInstanceType',
                                                type=core.RosParameterType.STRING,
                                                label='实例类型',
                                                association_property=core.RosParameter.AssociationProperty.ECS_INSTANCE_TYPE,
                                                )
      
              instance_password = core.RosParameter(self, 'InstancePassword',
                                                    no_echo=True,
                                                    type=core.RosParameterType.STRING,
                                                    label='实例密码',
                                                    association_property=core.RosParameter.AssociationProperty.ECS_INSTANCE_PASSWORD,
                                                    min_length=8,
                                                    max_length=30,
                                                    allowed_pattern='^[a-zA-Z0-9-\(\)\`\~\!\@\#\$\%\^\&\*\_\-\+\=\|\{\}\[\]\:\;\<\>\,\.\?\/]*$')
      
              security_group = ecs.SecurityGroup(self, 'EcsSecurityGroup', props=ecs.SecurityGroupProps(
                  security_group_name=core.FnJoin('-', [core.Fn.ref('ALIYUN::StackName'), '[1,4]']),
                  security_group_ingress=[
                      {'priority': 1, 'portRange': '80/80', 'nicType': 'intranet', 'sourceCidrIp': '0.0.0.0/0',
                       'ipProtocol': 'tcp'}
                  ],
                  security_group_egress=[
                      {'priority': 1, 'portRange': '-1/-1', 'nicType': 'intranet', 'destCidrIp': '0.0.0.0/0',
                       'ipProtocol': 'all'}
                  ]
              ))
      
              instance_group = ecs.InstanceGroup(self, "EcsInstanceGroup", props=ecs.InstanceGroupProps(
                  zone_id=zone_id,
                  vpc_id=vpc_id,
                  v_switch_id=vswitch_id,
                  security_group_id=security_group.attr_security_group_id,
                  instance_name=core.FnJoin('-', [core.Fn.ref('ALIYUN::StackName'), '[1,4]']),
                  io_optimized='optimized',
                  instance_charge_type=pay_type,
                  period_unit=pay_period_unit,
                  period=pay_period,
                  system_disk_category='cloud_essd',
                  system_disk_size=200,
                  image_id='centos_7',
                  max_amount=2,
                  instance_type=instance_type,
                  password=instance_password,
                  allocate_public_ip=False,
                  internet_max_bandwidth_out=0
              ))
      
              core.RosOutput(self, 'InstanceId', value=instance_group.attr_instance_ids)

生成ROS模板

CDK代码编写完成后,运行命令生成对应的JSON模板。

ros-cdk synth --json

上文CDK代码示例生成的模板如下。

{
  "Metadata": {
    "ALIYUN::ROS::Interface": {
      "ParameterGroups": [
        {
          "Parameters": [
            "ZoneId"
          ],
          "Label": {
            "default": {
              "zh-cn": "可用区配置"
            }
          }
        },
        {
          "Parameters": [
            "VpcId",
            "VSwitchId"
          ],
          "Label": {
            "default": {
              "zh-cn": "选择已有基础资源配置"
            }
          }
        },
        {
          "Parameters": [
            "PayType",
            "PayPeriodUnit",
            "PayPeriod"
          ],
          "Label": {
            "default": {
              "zh-cn": "付费类型配置"
            }
          }
        },
        {
          "Parameters": [
            "EcsInstanceType",
            "InstancePassword"
          ],
          "Label": {
            "default": {
              "zh-cn": "ECS实例配置"
            }
          }
        }
      ],
      "TemplateTags": [
        "Create by ROS CDK"
      ]
    }
  },
  "ROSTemplateFormatVersion": "2015-09-01",
  "Parameters": {
    "PayType": {
      "Type": "String",
      "Default": "PostPaid",
      "AllowedValues": [
        "PostPaid",
        "PrePaid"
      ],
      "Label": "付费类型",
      "AssociationProperty": "ChargeType",
      "AssociationPropertyMetadata": {
        "LocaleKey": "InstanceChargeType"
      }
    },
    "PayPeriodUnit": {
      "Type": "String",
      "Default": "Month",
      "AllowedValues": [
        "Month",
        "Year"
      ],
      "Label": "购买资源时长周期",
      "AssociationProperty": "PayPeriodUnit",
      "AssociationPropertyMetadata": {
        "Visible": {
          "Condition": {
            "Fn::Not": {
              "Fn::Equals": [
                "${PayType}",
                "PostPaid"
              ]
            }
          }
        }
      }
    },
    "PayPeriod": {
      "Type": "Number",
      "Default": 1,
      "AllowedValues": [
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9
      ],
      "Label": "购买资源时长",
      "AssociationProperty": "PayPeriod",
      "AssociationPropertyMetadata": {
        "Visible": {
          "Condition": {
            "Fn::Not": {
              "Fn::Equals": [
                "${PayType}",
                "PostPaid"
              ]
            }
          }
        }
      }
    },
    "ZoneId": {
      "Type": "String",
      "Label": "可用区",
      "AssociationProperty": "ALIYUN::ECS::ZoneId"
    },
    "VpcId": {
      "Type": "String",
      "Label": "VPC实例ID",
      "AssociationProperty": "ALIYUN::ECS::VPC::VPCId"
    },
    "VSwitchId": {
      "Type": "String",
      "Label": "VSwitch实例ID",
      "AssociationProperty": "ALIYUN::ECS::VSwitch::VSwitchId",
      "AssociationPropertyMetadata": {
        "VpcId": "${VpcId}",
        "ZoneId": "${ZoneId}"
      }
    },
    "EcsInstanceType": {
      "Type": "String",
      "Label": "实例类型",
      "AssociationProperty": "ALIYUN::ECS::Instance::InstanceType"
    },
    "InstancePassword": {
      "Type": "String",
      "AllowedPattern": "^[a-zA-Z0-9-\\(\\)\\`\\~\\!\\@\\#\\$\\%\\^\\&\\*\\_\\-\\+\\=\\|\\{\\}\\[\\]\\:\\;\\<\\>\\,\\.\\?\\/]*$",
      "MaxLength": 30,
      "MinLength": 8,
      "NoEcho": true,
      "Label": "实例密码",
      "AssociationProperty": "ALIYUN::ECS::Instance::Password"
    }
  },
  "Resources": {
    "EcsSecurityGroup": {
      "Type": "ALIYUN::ECS::SecurityGroup",
      "Properties": {
        "SecurityGroupEgress": [
          {
            "PortRange": "-1/-1",
            "Priority": 1,
            "IpProtocol": "all",
            "DestCidrIp": "0.0.0.0/0",
            "NicType": "intranet"
          }
        ],
        "SecurityGroupIngress": [
          {
            "Priority": 1,
            "NicType": "intranet",
            "PortRange": "80/80",
            "SourceCidrIp": "0.0.0.0/0",
            "IpProtocol": "tcp"
          }
        ],
        "SecurityGroupName": {
          "Fn::Join": [
            "-",
            [
              {
                "Ref": "ALIYUN::StackName"
              },
              "[1,4]"
            ]
          ]
        }
      }
    },
    "EcsInstanceGroup": {
      "Type": "ALIYUN::ECS::InstanceGroup",
      "Properties": {
        "ImageId": "centos_7",
        "InstanceType": {
          "Ref": "EcsInstanceType"
        },
        "MaxAmount": 2,
        "AllocatePublicIP": false,
        "AutoRenew": "False",
        "AutoRenewPeriod": 1,
        "InstanceChargeType": {
          "Ref": "PayType"
        },
        "InstanceName": {
          "Fn::Join": [
            "-",
            [
              {
                "Ref": "ALIYUN::StackName"
              },
              "[1,4]"
            ]
          ]
        },
        "InternetChargeType": "PayByTraffic",
        "InternetMaxBandwidthOut": 0,
        "IoOptimized": "optimized",
        "Password": {
          "Ref": "InstancePassword"
        },
        "Period": {
          "Ref": "PayPeriod"
        },
        "PeriodUnit": {
          "Ref": "PayPeriodUnit"
        },
        "SecurityGroupId": {
          "Fn::GetAtt": [
            "EcsSecurityGroup",
            "SecurityGroupId"
          ]
        },
        "SystemDiskCategory": "cloud_essd",
        "SystemDiskSize": 200,
        "UpdatePolicy": "ForNewInstances",
        "VpcId": {
          "Ref": "VpcId"
        },
        "VSwitchId": {
          "Ref": "VSwitchId"
        },
        "ZoneId": {
          "Ref": "ZoneId"
        }
      }
    }
  },
  "Outputs": {
    "InstanceId": {
      "Value": {
        "Fn::GetAtt": [
          "EcsInstanceGroup",
          "InstanceIds"
        ]
      }
    }
  }
}

验证生成的模板是否正确

  1. 将生成的模板复制粘贴到/test/test_cdk.py中对应的位置。

    import unittest
    import ros_cdk_core as core
    from demo.demo_stack import DemoStack
    
    class TestStack(unittest.TestCase):
        def setUp(self):
            pass
    
        def test_stack(self):
            app = core.App()
            stack = DemoStack(app, "testdemo")
            artifact = app.synth().get_stack_artifact(stack.artifact_id).template
            expect = {
                # 此处放生成的模板
            }
            self.assertDictEqual(artifact, expect)
    
        def tearDown(self):
            pass
    
    
    if __name__ == '__main__':
        unittest.main()
  2. 运行命令。

    python -m unittest  -v
  3. 查看模板测试结果。

    • 若模板测试通过,会返回如下提示。

      test_stack (test.test_demo.TestStack) ... ok
      
      ----------------------------------------------------------------------
      Ran 1 test in 0.052s
      
      OK

    • 若模板测试不通过,这会返回具体报错信息,请根据报错信息定位并修改报错,然后再进行测试。

  • 本页导读 (0)
文档反馈