阿里云ROS CDK(Cloud Development Toolkit)是资源编排(ROS)提供的一种命令行工具,帮助您使用多种编程语言定义云资源。您无需使用繁琐的JSON或YAML模板语法,即可使用ROS CDK完成资源的创建和配置,实现自动化部署及运维。本文以创建ECS实例模板为例生成CDK代码。
配置CDK开发环境
安装ROS CDK,执行以下命令创建一个工程目录并进行初始化。
mkdir demo cd demo ros-cdk init --language=python --generate-only=true #初始化环境
执行以下命令,创建一个属于当前工程的虚拟环境。
python3 -m venv .venv # 创建一个属于当前工程的虚拟环境 source .venv/bin/activate # 进入虚拟环境
执行以下命令,配置阿里云凭证信息。
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代码
分析模板可能会用到的资源,安装对应的资源依赖包并引入。
pip install ros-cdk-core==1.0.16 # 使用指定版本 pip install ros-cdk-ecs # 不加版本默认下载最新版
根据CDK语法与ROS模板属性参数的对应关系并编写代码。
ROS模板属性与CDK属性对应关系如下表所示。
ROS常用模板属性
CDK属性
Parameters
core.RosParameter
Metadata
core.RosInfo.metadata
Resources
无
Conditions
core.RosCondition
OutPuts
core.RosOutput
创建参数。
参数
AssociationProperty
字段需要对照文档将类型转化为core.RosParameter.AssociationProperty
属性。zone_id = core.RosParameter(self, 'ZoneId', type=core.RosParameterType.STRING, label='可用区', association_property=core.RosParameter.AssociationProperty.ECS_ZONE_ID )
创建资源。
在
/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"
]
}
}
}
}
验证生成的模板是否正确
将生成的模板复制粘贴到
/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()
运行命令。
python -m unittest -v
查看模板测试结果。
若模板测试通过,会返回如下提示。
test_stack (test.test_demo.TestStack) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.052s OK
若模板测试不通过,这会返回具体报错信息,请根据报错信息定位并修改报错,然后再进行测试。
文档内容是否对您有帮助?