从零开始使用 ROS CDK 搭建云上解决方案

作者: 金湛

前言

资源编排服务ROS(Resource Orchestration Service)是阿里云提供的一项简化云计算资源管理的服务。开发者和管理员可以编写模板,在模板中定义所需的阿里云资源(例如:ECS实例、RDS数据库实例)、资源间的依赖关系等。ROS的编排引擎将根据模板自动完成所有资源的创建和配置,实现自动化部署及运维。

🪶为什么选择ROS资源编排

企业云上大量的资源管理需求带来了效率、合规和成本方面的挑战,ROS可以帮助您轻松管理多个资源:

  • 采用基础设施即代码(Infrastructure as Code, IaC)的设计理念,模板的管理融入CI/CD流程,确保合规性。
  • 无需手动创建多个资源,使用模板一键部署多个资源。
  • 提供大量模板示例和解决方案示例,帮助企业客户部署复杂解决方案。
  • 利用云上弹性,按需批量部署和释放资源,节省成本。

更多选择理由,请参见产品优势应用场景

本文将以一个小白视角,探索如何根据阿里云的文档从0开始写一篇ROS资源编排的代码。

1️⃣Part1: 工具准备

注:本次教程所有的操作均基于macos,其他平台的操作请自行稍作修改

1. ROS CDK安装

阿里云ROS CDK(Cloud Development Toolkit)是资源编排(ROS)提供的一种命令行工具,帮助您使用多种编程语言定义云资源。您无需使用繁琐的JSON或YAML模板语法,即可使用ROS CDK完成资源的创建和配置,实现自动化部署及运维。写一篇ROS自动化测试脚本的第一步是安装ROS CDK工具。如何安装ROS在帮助文档中已经写得非常详细。

https://help.aliyun.com/zh/ros/developer-reference/install-ros-cdk?spm=a2c4g.11186623.0.i1

2. python环境安装

a. 使用Anaconda(推荐)

ⅰ. 开源、免费,强大的python环境管理软件,社区版包含很多常用软件

ⅱ. 简单、方便的环境隔离与虚拟环境创建

ⅲ. Anaconda 还附带了一个叫做 Navigator(导航)的桌面 GUI 工具,可以直观地创建和管理环境,安装、删除扩展包

ⅳ. 下载链接:https://repo.anaconda.com/archive/Anaconda3-2023.09-0-MacOSX-arm64.pkg

b. 直接安装python

ⅰ. 下载链接:https://www.python.org/downloads/release/python-3120/

3. IDE、代码编辑器推荐

a. VScode(推荐)

ⅰ. 开源免费、轻量的代码编辑器

ⅱ. 社区生态好、更新快

ⅲ. 插件生态众多,推荐部分插件

  • Python
  • Atom Material Icons
  • GitLens --- Git supercharged
  • IntelliCode

ⅳ. 下载链接:https://az764295.vo.msecnd.net/stable/1a5daa3a0231a0fbba4f14db7ec463cf99d7768e/VSCode-darwin-universal.zip

b. pycharm

ⅰ. 开箱即用、功能强大的IDE

ⅱ. 补全、代码检查等功能做得较好

ⅲ. 下载链接:https://download.jetbrains.com.cn/python/pycharm-community-2023.2.4-aarch64.dmg

4. AI编码插件

使用AI编码插件可以大幅提升代码编写效率,最经典的插件是Github Copilot,功能包括行/函数级自动续写、自然语言生成代码、生成单元测试、生成代码注释等等,然而这是一个收费插件(10美元/月),有很多免费版的插件可以平替,这里推荐几个

a. 通义灵码

ⅰ. 通义千问系列产品,目前已经开放公测,阿里云账号可以直接使用

ⅱ. 功能简介与下载链接:https://tongyi.aliyun.com/lingma

ⅲ. 代码解释,询问一段代码是什么意思,帮忙理解代码逻辑,这里给出一段阿里云ROS的代码,但是插件给出的解释是AWS的CDK,但是除了这一点错误,其余的代码解释都是正确的

ⅳ. AI代码生成(根据注释生成代码),这里我只写了注释「#创建安全组」,插件自动帮我生成了一段代码来放开80端口

b. Amazon CodeWhisperer

ⅰ. AWS出品的AI编码工具,免费

ⅱ. 功能简介与下载链接:https://aws.amazon.com/cn/codewhisperer/

c. CodeGeeX

ⅰ. 模型开源:https://github.com/THUDM/CodeGeeX2

ⅱ. 功能简介与下载链接:https://codegeex.cn/

2️⃣Part2: 环境准备

1. 创建工程目录并初始化工程

进入终端,执行以下命令,创建工程目录并初始化工程。每个ROS CDK应用都要求创建在一个独立的工程目录下,且该应用需要使用独立工程目录中模块的依赖项。所以在创建应用之前,需要先创建一个工程目录并进行初始化。

#创建iac-ros-cdk文件夹
mkdir iac-ros-cdk
#进入iac-ros-cdk文件夹
cd iac-ros-cdk
#使用ros cdk初始化项目
ros-cdk init --language=python --generate-only=true

2. 创建虚拟环境

Python工程的运行依赖于虚拟环境(virtualenv),所以在初始化Python工程之后需要创建一个属于当前工程的虚拟环境。

python3 -m venv .venv

3. 进入虚拟环境

source .venv/bin/activate

4. 测试环境是否创建成功

在终端执行以下命令,测试环境是否创建成功

python

若出现此界面则说明你的虚拟环境已经创建成功,此时的项目应该是这样的

5. 在编译器中配置环境

打开项目中的一个示例python文件「iac_ros_cdk_stack.py」,在VScode中选择python解释器,开始编写python代码

6. 安装需要的包

此时无法解析编译器无法解析「ros_cdk_core」,表明当前python环境「.venv」里没有这个包

在VScode中新建终端,终端会自动执行命令切换到当前目录并激活「.venv」环境

将pip源换成清华大学的源,加速pip安装包的速度(可选),然后执行pip install xx,在「.venv」环境中安装包

示例,换源并安装「ros_cdk_core」这个包

以上安装方式对应的是单次安装一个包的情况,如果你需要安装很多包,可以在「requirements.txt」中添加你需要的包

然后在终端中执行如下命令来安装「requirements.txt」中写的所有包

#安装requirements.txt中写的包
pip install -r requirements.txt

7. 配置阿里云凭证信息

# 将pip的源换为国内的清华源,加速下载,这一步为可选项
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 安装ros_cdk_core
pip install ros_cdk_core

登录阿里云官网,获取当前账号的AK信息

执行以下命令,配置阿里云凭证信息。

ros-cdk config

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

endpoint(optional, default:https://ros.aliyuncs.com):
defaultRegionId(optional, default:cn-hangzhou):cn-hangzhou
[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!

配置内容说明:

  • endpoint:ROS服务地址。默认值为https://ros.aliyuncs.com
  • defaultRegionId:ROS资源栈部署的地域。默认值为cn-hangzhou。
  • Authenticate mode:鉴权方式。本示例的鉴权方式为AccessKey,您需要输入AccessKey ID和AccessKey Secret。关于如何获取AccessKey,请参见交互式配置(快速配置)

3️⃣Part3: 代码编写演示

1. 代码文件建立与命名规范

找到需要转成 CDK 的技术解决方案文档,这里有一个示例

https://help.aliyun.com/zh/ecs/use-cases/build-an-ftp-site-on-an-instance-that-runs-alibaba-cloud-linux

完整代码如下

https://github.com/XiaoTianJianJun/ROS_JINZ_DEV/blob/master/examples/documents/ros/usage-examples/python/python_cdk/ECS/deploy_FTP_on_ECS_460772.py

新建文件,可注意以下规范

  • 文件正确归类---计算的ROS代码应该放在ECS目录下

  • 文件正确命名---deploy_FTP_on_ECS_460772.py

    • 「deploy_FTP_on_ECS」描述文档测试内容,在ECS上部署FTP服务
    • 「460772」该文档的id
  • 类的正确命名

2. 找寻文档描述的前提条件

一般文档会描述实验前需要部署的资源,比如本篇文档规定了以下内容

  • 实例规格:ecs.g6.large
  • 操作系统:Alibaba Cloud Linux 3.2104 LTS 64位

现在已经知道需要首先建立一个ECS,建立ECS需要以下依赖资源

  • VPC
  • Vswitch
  • 安全组

那么已经可以明确你的ROS代码需要编排的资源清单

3. 分步骤编写代码

如果是没用过的资源怎么找必填参数、怎么填写参数(通过文档,比如https://help.aliyun.com/zh/ros/developer-reference/datasource-cs-clusternodepools

a. 建立VPC

python 复制代码
# 创建VPC
vpc = ecs.Vpc(
    self,
    "VPC",
    ecs.VPCProps(
        cidr_block="10.0.0.0/8",
        vpc_name="ftp-ros-cdk-vpc",
        description="This is ros python cdk from jinzhan",
    ),
)

b. 建立VSwitch

python 复制代码
# 创建交换机
vswitch = ecs.VSwitch(
    self,
    "VSwitch",
    ecs.VSwitchProps(
        zone_id=zone_id_param,
        vpc_id=vpc.resource.ref,
        cidr_block="10.0.0.0/20",
    ),
)

c. 创建ECS

ⅰ. 首先定义ECS的密码

python 复制代码
# 设置ECS实例密码
ecs_password_param = core.RosParameter(
    self,
    "ECS_password",
    type=core.RosParameterType.STRING,
    association_property="ALIYUN::ECS::Instance::Password",
    default_value="Jinz_Aliyun1",
)

ⅱ. 定义ECS使用的镜像

python 复制代码
# 设置ECS实例镜像
image_id_param = core.RosParameter(
    self,
    "ImageId",
    default_value="aliyun_3_x64_20G_alibase_20230629.vhd",
    type=core.RosParameterType.STRING,
    description={"zh-cn": "镜像ID", "en": "Image ID"},
    label="公共镜像Alibaba Cloud Linux 3.2104 LTS 64位",
)

ⅲ. 设置ECS的实例类型

python 复制代码
# 设置ECS实例类型
instance_type_param = core.RosParameter(
    self,
    "InstanceType",
    association_property="ALIYUN::ECS::Instance::InstanceType",
    association_property_metadata={"ZoneId": "ZoneId"},
    default_value="ecs.g6.large",
    label="Instance Type",
    type=core.RosParameterType.STRING,
    description={},
)

ⅳ. 设置ECS的区域

python 复制代码
# 地区id类型
zone_id_param = core.RosParameter(
    self,
    "ZoneId",
    type=core.RosParameterType.STRING,
    association_property="ALIYUN::ECS::Instance::ZoneId",
    default_value="cn-hangzhou-k",
)

ⅴ. 创建ECS实例,按照文档的要求在ECS上执行命令

python 复制代码
# 创建ECS实例
ecs_instance = ecs.Instance(
    self,
    "ECS-demo",
    ecs.InstanceProps(
        instance_name="ecs-jinzhan-agent-ros-python-cdk",
        vpc_id=vpc.resource.ref,
        v_switch_id=vswitch.resource.ref,
        security_group_id=sg.resource.ref,
        image_id=image_id_param,
        instance_type=instance_type_param,
        instance_charge_type="PayAsYouGo",
        password=ecs_password_param,
        allocate_public_ip=True,
        user_data=core.FnReplace(
            value=[
                {"ros-notify": wait_condition_handle_param.attr_curl_cli},
                {
                    "Fn::Join": [
                        "",
                        [
                            "#!/bin/bash\n",
                            "dnf install -y vsftpd\n",
                            "systemctl enable vsftpd.service\n",
                            "systemctl start vsftpd.service\n",
                            "netstat -antup | grep ftp\n",
                            "adduser ftptest\n",
                            # 将ftp用户密码设置为abc123
                            "sudo chpasswd <<< "ftptest:abc123"\n",
                            "mkdir /var/ftp/test\n",
                            "touch /var/ftp/test/testfile.txt\n",
                            "chown -R ftptest:ftptest /var/ftp/test\n",
                            "sudo echo '#!/bin/bash' | sudo tee update_vsftpd_conf.sh\n",
                            '''sudo echo 'if [ -f "/etc/vsftpd/vsftpd.conf" ]; then \n    sed -i "s/^anonymous_enable=.*/anonymous_enable=NO/" /etc/vsftpd/vsftpd.conf \n    sed -i "s/^local_enable=.*/local_enable=YES/" /etc/vsftpd/vsftpd.conf\n    sed -i "s/^listen=.*/listen=YES/" /etc/vsftpd/vsftpd.conf\n    echo "修改成功!"\nelse\n    echo "vsftpd.conf文件不存在。"\nfi' | sudo tee  -a update_vsftpd_conf.sh'''
                            "\n",
                            "sudo chmod +x update_vsftpd_conf.sh\n",
                            "sudo ./update_vsftpd_conf.sh\n",
                            # 在行首添加#注释掉listen_ipv6=YES,关闭监听IPv6 sockets
                            "sudo sed -i 's/^listen_ipv6=YES/#&/' /etc/vsftpd/vsftpd.conf\n",
                            "sudo tee -a /etc/vsftpd/vsftpd.conf << EOF\n",
                            "#设置本地用户登录后所在目录。\n"
                            "local_root=/var/ftp/test\n",
                            "#全部用户被限制在主目录。\n",
                            "chroot_local_user=YES\n",
                            "#启用例外用户名单。\n",
                            "chroot_list_enable=YES\n",
                            "#指定例外用户列表文件,列表中用户不被锁定在主目录。\n",
                            "chroot_list_file=/etc/vsftpd/chroot_list\n",
                            "#开启被动模式。\n",
                            "pasv_enable=YES\n",
                            "allow_writeable_chroot=YES\n",
                            "#本教程中为Linux实例的公网IP。\n",
                            "EOF\n",
                            # 创建update_vsftpd_conf_ip.sh脚本,将获取本机ip并替换pasv_address=<本机ftp地址>
                            "echo '#!/bin/bash' > update_vsftpd_conf_ip.sh\n",
                            "echo '' >> update_vsftpd_conf_ip.sh\n",
                            "echo 'public_ip=$(curl -s ifconfig.me)' >> update_vsftpd_conf_ip.sh\n"
                            "echo '' >> update_vsftpd_conf_ip.sh\n",
                            '''echo 'sudo sh -c "echo "pasv_address=$public_ip" >> /etc/vsftpd/vsftpd.conf"' >> update_vsftpd_conf_ip.sh\n''',
                            "echo '' >> update_vsftpd_conf_ip.sh\n",
                            "chmod +x update_vsftpd_conf_ip.sh\n",
                            "./update_vsftpd_conf_ip.sh\n",
                            "sudo tee -a /etc/vsftpd/vsftpd.conf << EOF\n",
                            "#设置被动模式下,建立数据传输可使用的端口范围的最小值。\n",
                            "#建议您把端口范围设置在一段比较高的范围内,例如50000~50010,有助于提高访问FTP服务器的安全性。\n",
                            "pasv_min_port=50000\n",
                            "#设置被动模式下,建立数据传输可使用的端口范围的最大值。\n",
                            "pasv_max_port=50010\n",
                            "EOF\n",
                            
                            "touch /etc/vsftpd/chroot_list\n",
                            "systemctl restart vsftpd.service\n"
                        ],
                    ]
                },
            ]
        ),
    ),
)

d. 创建安全组

python 复制代码
# 创建安全组
sg = ecs.SecurityGroup(
    self,
    "SecurityGroup",
    ecs.SecurityGroupProps(
        vpc_id=vpc.resource.ref,
    ),
)

e. 添加安全组入方向端口

python 复制代码
# 添加安全组入方向端口
sg_ingress_50000_50010 = ecs.SecurityGroupIngress(
    self,
    "SecurityGroupIngress_80",
    ecs.SecurityGroupIngressProps(
        security_group_id=sg.resource.ref,
        source_cidr_ip="0.0.0.0/0",
        ip_protocol="tcp",
        nic_type="intranet",
        port_range="50000/50010",
    ),
)
sg_ingress_21 = ecs.SecurityGroupIngress(
    self,
    "SecurityGroupIngress_8080",
    ecs.SecurityGroupIngressProps(
        security_group_id=sg.resource.ref,
        source_cidr_ip="0.0.0.0/0",
        ip_protocol="tcp",
        nic_type="intranet",
        port_range="21/21",
    ),
)
sg_ingress_22 = ecs.SecurityGroupIngress(
    self,
    "SecurityGroupIngress_22",
    ecs.SecurityGroupIngressProps(
        security_group_id=sg.resource.ref,
        source_cidr_ip="0.0.0.0/0",
        ip_protocol="tcp",
        nic_type="intranet",
        port_range="22/22",
    ),
)

f. 使用云助手运行命令

python 复制代码
# 安全组:入方向添加安全组规则并放行50000/50010、22、21端口
install_ftp = ecs.RunCommand(
    self,
    "InstallFTP",
    ecs.RunCommandProps(
        instance_ids=[ecs_instance.attr_instance_id],
        type="RunShellScript",
        sync=True,
        timeout=600,
        command_content=""
    ),
)
# 如果开了安全组,才运行installftp
install_ftp.add_dependency(sg_ingress_50000_50010)
install_ftp.add_dependency(sg_ingress_22)
install_ftp.add_dependency(sg_ingress_21)

g. 添加输出

python 复制代码
# Output
core.RosOutput(self, "FTP URL",
               value=core.FnJoin("", ["ftp://", ecs_instance.attr_public_ip, ":21"]), description="FTP User:ftptest, FTP Password:abc123, port:21, 50000/50010")

h. 更改app.py文件

修改app.py文件,导入你刚刚写好的文件,起个名字,这里起名叫「FTP-stack」

python 复制代码
#!/usr/bin/env python3 ***
import ros_cdk_core as core
from python_cdk.ECS.deploy_FTP_on_ECS_460772 import DeployFTP
app = core.App()
DeployFTP(app, "FTP-stack")
app.synth()

4. 如何创建shell脚本来执行复杂命令

此文档中有一些复杂操作,无法使用简单的一两句命令来实现,这时候需要使用shell脚本来完成操作,例如这里需要使用vim来更改文件,在文件末尾添加参数并且注释某些行,显然这里已经无法使用简单的命令行来完成了。

这时候比较考验你的代码能力,如果你实在不清楚shell脚本怎么编写,这时候应该拿出我们的最强AI编码工具Chatgpt。使用Chatgpt来辅助我们编写代码。

此时GPT给出了一个实现此功能的脚本,但是此脚本需要你手动创建文件,在ROS中我们需要纯命令完成,那要如何解决?

现在gpt给出了纯命令行版本,你只需要将这段命令添加上引号,按照python中的代码规范填入user_data中,这段任务就完成了。其他比较复杂的操作类似

4️⃣Part4: 使用代码部署资源

1. 使用ROS CDK部署资源

打开终端,执行如下命令,其中「FTP-stack」是你在app.py中起的名字

ros-cdk synth FTP-stack -j
ros-cdk deploy FTP-stack --sync=true

看到如下输出说明第一行执行成功

看到如下输出说明第二行执行成功

2. 检查资源部署情况

从控制台打开资源编排,查看事件。如果你的资源状态没有任何回滚,说明至少此次的资源部署是成功的

5️⃣Part5: 验证任务是否完成

1. 进入ECS查询日志文件

连接你创建的ECS,执行如下命令,查看每一步命令是否都执行成功

less /var/log/cloud-init-output.log

比方说我此时看到的结果如下,认真查看发现没有报错,那可以暂时确定ECS上所有的命令都成功执行

2. 手动验证任务是否完成

此文档的最终目的是建立一个FTP服务器,那么需要我们手动验证服务器是否建立成功,文档原文如下

现在已经完成资源部署,ECS上的命令也已经按照文档全部执行完成。最后还需要手动验证一下FTP服务器是否建立成功,可以在自己的电脑上连接创立的FTP服务器,检查FTP服务是否正常运行

可以看到成功连接服务器,并且服务器上有文件,那么此次ROS文档测试完成。

3. 当你确定你此次任务完成,请及时销毁资源

ros-cdk destroy FTP-stack --sync=true

4. 将完成的代码更新推送到Github上

初次配置git

https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%88%9D%E6%AC%A1%E8%BF%90%E8%A1%8C-Git-%E5%89%8D%E7%9A%84%E9%85%8D%E7%BD%AE

git常用命令

https://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html

# 初始化git
git init
# 将本地仓库与远程仓库关联
git remote add origin https://github.com/XiaoTianJianJun/ROS_JINZ_DEV/tree/master
# 创建名为jinz_dev的分支
git checkout -b jinz_dev
# 添加本次修改的内容
git add .
# 简要介绍一下本次修改的内容
git commit -m "Initial commit"
# 将更改推送到代码仓库
git push origin jinz_dev

6️⃣Part6: 弹药库

  1. ROS CDK官方文档是一个很好的学习途径
  2. OpenAPI CDK:cdk的代码示例可以查看openapi对应产品的cdk页面,可以动态根据输入参数输出运行示例
  3. 模版示例
  4. Chatgpt

总结

🪶核心内容:未接触过ROS资源部署的新人如何快速上手

  1. 全流程演示ROS资源编排:本文详细描述了从零开始完成一次ROS资源编排的代码编写的过程。它包含了可能遇到的大部分问题,并致力于让初学者也能够快速上手。

  2. ROS的优势:ROS资源编排能够提高云资源的管理效率和稳定性,减少人工操作和错误,同时提供了可视化的监控和安全可靠的保障。

  3. 写代码遇到困难如何解决:

    1. 善用搜索引擎,自行解决问题
    2. 使用AI工具,提升编码效率
    3. 遇到不懂的问题时,积极向他人请教