全部产品
云市场
云游戏

应用:实例分发型-边缘托管应用

更新时间:2020-01-09 09:36:56

本章将为您介绍如何管理【实例分发型-边缘托管应用】。

整体流程

一个“实例分发的边缘托管应用”的上线整体流程,包括:应用对接开发、创建应用、应用配置、部署测试、集群管理。

2.对接介绍

1. 拆分应用节点

大部分应用是有不同独立可运行的组件或者模块构成的,这些独立可运行的组件或者模块,我们称为“节点”。比如一个“停车场管理系统”,典型包括:设备控制服务节点、业务管理节点、数据库节点。那么,其第一个是一个运行在jre的jar包、第二个是一个运行在tomcat上的war包、第三个是一个开源的MySQL数据库。
此外,拆分节点时,需要注意以下两点:1. 功能相对独立;2. 存储尽量放在数据库节点上。

2. 对业务代码的改造

容器化部署对应用本身的业务逻辑有两个地方的影响,在应用打包成镜像之前(无论后续是否还有其他对接开发导致的调整),请先做好这两方面的改动:

  1. 节点之间如何访问:在非容器化前,由于每个节点单独运行在主机上,因此一般情况下,节点之间的访问是通过IP地址来访问的。但是容器化改造之后,由于IP并不确定,因此请使用节点的“服务名称”(服务名称是在可视化编排页面设定的)。
  2. 应用初始化信息的获取:应用初始化信息包含两种:应用自身在节点配置中所配置的自定义环境变量、系统分配的一些初始化信息(如AppKey)。这两类信息都位于环境变量中,应用涉及到这两类初始化信息,则需要作此改造。

3. 打包Docker镜像

目前仅支持Linux镜像,打包流程参考线上文档:https://help.aliyun.com/document_detail/114832.html。建议在Linux操作系统上执行打包操作。

4.OAuth对接示例

1.集群内部发起API请求,获取应用App的登录地址。代码示例:

  1. /**
  2. * 获取环境变量和回调的url
  3. * @return
  4. */
  5. //iot.hosting.api.schema----是请求协议格式http
  6. private static final String API_GATEWAY_SCHEMA = System.getenv("iot.hosting.api.schema");
  7. //iot.hosting.api.domain--是跳转路径的回调ip地址
  8. private static final String API_GATEWAY_DOMAIN = System.getenv("iot.hosting.api.domain");
  9. //iot.hosting.api.port----是请求端口
  10. private static final String API_GATEWAY_SCHEMA = System.getenv("iot.hosting.api.port");
  11. // iot.hosting.appKey----是请求的appkey
  12. private static final String API_GATEWAY_SCHEMA = System.getenv(" iot.hosting.appKey");
  13. //PATH_APP_GET是请求应用app的响应路径
  14. private static final String PATH_APP_GET = "/api/console/app/get";
  15. @Override
  16. public String getAppIndex() {
  17. RequestBuilder builder = RequestBuilder.create(METHOD_GET);
  18. Map<String, String> queryParams = Maps.newHashMap();
  19. queryParams.put(PARAM_APP_KEY, appKey);
  20. builder.setUri(HttpUtils.buildUrl(schema, apiGatewayDomain, apiGatewayPort, PATH_APP_GET, queryParams));
  21. IoTxResult<AppDTO> result = httpProxy.invoke(
  22. (HttpRequestBase) builder.build(),
  23. new TypeReference<IoTxResult<AppDTO>>() {
  24. }
  25. );
  26. logger.info("path={}; params={}; result={}", PATH_APP_GET, JSON.toJSONString(queryParams), JSON.toJSONString(result));
  27. Assert.assertSuccess(result);
  28. AppDTO app = result.getData();
  29. return app.getLoginUrl();
  30. }

请求拼接示例:

  1. http://30.42.82.42:32187/api/console/app/get?appKey=28135051

返回结果示例:

  1. {
  2. {
  3. "code": 200,
  4. "data": {
  5. "aliyunPk": "101248722030****",
  6. "appKey": "28135***",
  7. "appMeta": {
  8. "logoUrl": "http://192.168.11.130:32628/index/28135***",
  9. "name": "oauth2边缘托管",
  10. "subVersionId": "1.0",
  11. "uuid": "937aaa0284864396b54ecadb0d1629c6",
  12. "versionUuid": "9ff097f16d8347e6abfcaec0cd14ce56"
  13. },
  14. "appSecret": "NGNiOWQyYzI4MGVhOWNlYmNhOTdmMDIyNTg2MzlhY2Q=",
  15. "clusterId": "bbeba04d880d49dc80bf83632619341c",
  16. "configName": "oauth2边缘托管",
  17. "configUuid": "a7b996cd6ab04b7fb3a54abbc81b88ea",
  18. "configVersionUuid": "8e43f4d96bef4a289ad9f9af079df8bb",
  19. "loginUrl": "http://192.168.11.130:30313/index",//应用登录url
  20. "name": "Oauth2演示",
  21. "oauth": {
  22. "path": "/index",
  23. "port": 8080,
  24. "protocol": "",
  25. "serviceName": "edge",
  26. "serviceUuid": ""
  27. },
  28. "type": "GENERAL_APP",
  29. "uuid": "731082ac0c444053bad624ec915b8a6f"
  30. },
  31. "message": "success"
  32. }
  33. }

2.应用请求控制台地址获取,用于免登跳转地址的获取。

  1. /**
  2. *
  3. * /api/console/host/account
  4. * @param path
  5. * @return
  6. */
  7. @Override
  8. public URI getURI(String path) {
  9. RequestBuilder builder = RequestBuilder.create(METHOD_GET);
  10. Map<String, String> queryParams = Maps.newHashMap();
  11. builder.setUri(HttpUtils.buildUrl(schema, apiGatewayDomain, apiGatewayPort, path, queryParams));
  12. logger.info("RequestBuilder请求url");
  13. IoTxResult<String> result = httpProxy.invoke(
  14. (HttpRequestBase) builder.build(),
  15. new TypeReference<IoTxResult<String>>() {
  16. }
  17. );
  18. logger.info("path={}; params={}; result={}", path, JSON.toJSONString(queryParams), JSON.toJSONString(result));
  19. Assert.assertSuccess(result);
  20. try {
  21. return new URI(result.getData());
  22. } catch (URISyntaxException e) {
  23. throw new RuntimeException(e);
  24. }
  25. }

请求拼接示例:

  1. http://30.42.82.42:32187/api/console/host/account

3.应用请求发起。根据当前登陆态,获取authcode,oauth授权的页面,可直接跳过授权页面,请求免登的地址:http://30.42.82.42:32187:80/oauth2/auth? 请求入参:

参数名 类型 必填 描述
client_id String 应用的appkey
redirect_uri String OAuth认证通过后的重定向应用的URI,包含完整的域名
response_type String 返回类型。根据OAuth 2.0标准,目前支持设置此参数的取值为code
state String 应用的appkey携带项
scope String 空格分隔的OAuth范围列表。如不指定此参数取值,则默认为应用注册的全部OAuth范围,加上openid

请求示例:

  1. http://30.42.82.42:32187/oauth2/auth?
  2. redirect_uri=http://30.42.82.42:32187/index&
  3. client_id=28135051&state=28135051&
  4. response_type=code

代码示例:

  1. /**
  2. *获取免登url和code
  3. * @param appKey
  4. * @param redirectUri
  5. * @return
  6. */
  7. public String getLoginRedirectUrl(String appKey, String redirectUri) {
  8. Map<String, String> queryParams = Maps.newHashMap();
  9. queryParams.put(PARAM_CLIENT_ID, appKey);
  10. queryParams.put(PARAM_REDIRECT_URI, redirectUri);
  11. queryParams.put(PARAM_RESPONSE_TYPE, "code");
  12. URI accountUri = getAccountUri();
  13. return HttpUtils.buildUrl(
  14. schema,
  15. accountUri.getHost(),
  16. accountUri.getPort(),
  17. PATH_LOGIN,
  18. queryParams).toASCIIString();
  19. }

4.跳转redirect_uri获取oauthcode,IoT在验证当前用户合法后,将生成当前用户授权码oauthcode,在回跳redirect_uri地址时通过GET方式传递oauthcode,并同时返回state。返回示例:

  1. http://30.42.82.42:32187/index?code=64a67ee15534defea7ad0d0535189b24&state=28135051

5.通过oauthcode换取accessToken,获取OAuth授权code后可通过该接口获取accessToken身份信息,详情请参见链接OAuth对接API。代码示例:

  1. /**
  2. * 根据code获取到token
  3. * @param appKey
  4. * @param oauthCode
  5. * @return
  6. */
  7. public String getAccessTokenByOauthCode(String appKey, String oauthCode) {
  8. RequestBuilder builder = RequestBuilder.create(METHOD_GET);
  9. Map<String, String> queryParams = Maps.newHashMap();
  10. queryParams.put(PARAM_CODE, oauthCode);
  11. queryParams.put(PARAM_GRANT_TYPE, "authorization_code");
  12. queryParams.put(PARAM_CLIENT_ID, appKey);
  13. URI accountUri = getAccountUri();
  14. builder.setUri(HttpUtils.buildUrl(accountUri.getScheme(), accountUri.getHost(), accountUri.getPort(), PATH_GET_ACCESS_TOKEN_BY_OAUTH_CODE, queryParams));
  15. IoTxResult<AccessTokenDTO> result = httpProxy.invoke(
  16. (HttpRequestBase) builder.build(),
  17. new TypeReference<IoTxResult<AccessTokenDTO>>() {
  18. }
  19. );
  20. PROXY_LOGGER.info("path={}; params={}; result={}", PATH_GET_ACCESS_TOKEN_BY_OAUTH_CODE, JSON.toJSONString(queryParams), JSON.toJSONString(result));
  21. Assert.assertSuccess(result);
  22. AccessTokenDTO accessTokenDTO = result.getData();
  23. Assert.assertNotNull(accessTokenDTO, "get accessToken failed");
  24. return accessTokenDTO.getAccessToken();
  25. }

6.通过accessToken换取用户信息,获取accessToken信息后,可通过accessToken来换取登录用户的用户信息,详情请参见OAuth对接API。代码示例:

  1. /**
  2. * 根据token获取用户信息
  3. * @param accessToken
  4. * @return
  5. */
  6. public UserInfoDTO getUserInfoByAccessToken(String accessToken) {
  7. RequestBuilder builder = RequestBuilder.create(METHOD_GET);
  8. Map<String, String> queryParams = Maps.newHashMap();
  9. queryParams.put(PARAM_ACCESS_TOKEN, accessToken);
  10. URI accountUri = getAccountUri();
  11. builder.setUri(HttpUtils.buildUrl(accountUri.getScheme(), accountUri.getHost(), accountUri.getPort(), PATH_GET_USER_INFO_BY_ACCESS_TOKEN, queryParams));
  12. IoTxResult<UserInfoDTO> result = httpProxy.invoke(
  13. (HttpRequestBase) builder.build(),
  14. new TypeReference<IoTxResult<UserInfoDTO>>() {
  15. }
  16. );
  17. PROXY_LOGGER.info("path={}; params={}; result={}", PATH_GET_USER_INFO_BY_ACCESS_TOKEN, JSON.toJSONString(queryParams), JSON.toJSONString(result));
  18. Assert.assertSuccess(result);
  19. UserInfoDTO userInfoDTO = result.getData();
  20. Assert.assertNotNull(userInfoDTO, "get userInfo failed");
  21. return userInfoDTO;
  22. }

7.accessToken有效性判断,检查当前url登录的token是否有效, /user/oauth2/accesstoken/check?access_token=xxx。代码示例:

  1. /**
  2. * token登录有效期检查
  3. * @param token
  4. * @return
  5. */
  6. public boolean checkLogin(String token) {
  7. RequestBuilder builder = RequestBuilder.create(METHOD_GET);
  8. Map<String, String> queryParams = Maps.newHashMap();
  9. queryParams.put(PARAM_ACCESS_TOKEN, token);
  10. URI accountUri = getAccountUri();
  11. builder.setUri(HttpUtils.buildUrl(accountUri.getScheme(), accountUri.getHost(), accountUri.getPort(), PATH_LOGINCHECK, queryParams));
  12. IoTxResult<Boolean> result = httpProxy.invoke(
  13. (HttpRequestBase) builder.build(),
  14. new TypeReference<IoTxResult<Boolean>>() {
  15. }
  16. );
  17. PROXY_LOGGER.info("path={}; params={}; result={}", PATH_LOGINCHECK, JSON.toJSONString(queryParams), JSON.toJSONString(result));
  18. Assert.assertSuccess(result);
  19. return result.getData();
  20. }

具体oauth-demo参考附件:

5.多副本应用部署

5.1注意事件

1.多副本部署目前仅支持RedisHA和MysqlHA的三方节点进行数据存储,在应用配置可选择副本数。如图所示:多副本
2.系统应用如果接入了应用设备,每个应用实例需要有一个独立的三元组身份,那可能就不能同时运行2个副本的应用。此时cmp的连接会有互踢机制,导致部署失败。
3.应用自身存在逻辑问题,举例说明,如下图所示:案例

3.应用管理

3.1 创建应用

应用接入 > 创建应用 页面填写应用基本信息,如图所示:创建应用

选择应用类型为实例分发,如图所示:实例分发

根据应用实际情况选择部署方式与系统类型,如图所示:类型

  • 应用类型:
  • 账号分发:用户付款后,只需要交付账号给其使用。
  • 实例分发:用户付款后,单独为客户部署一套应用。
  • 一次性交付:定制项目或交付型应用。
  • 部署方式:
  • 云端托管部署:平台根据应用提供者的配置,自动分配资源,并部署应用。
  • 边缘托管部署:平台根据应用提供者的配置,自动分配资源,并部署应用到边缘。

3.2 应用配置

创建应用完成后点击初始化应用,填写版本说明,创建应用完成后可以看到版本管理、实例管理、授权实例功能,如图所示:初始化

3.2.1 可视化编排

点击 版本管理 > 可视化编排 ,可视化编排是对一个应用的定义。包括一份应用中的节点构成、节点的配置、启动顺序等,如图所示:配置

注意⚠️,如果需要对接硬件设备,自研节点的主机网络请选择true,表示部署的服务Pod将使用宿主机网段而不是分配给该主机的Overlay网络。服务类型

ClusterIP:表示节点提供的服务主要对该配置其他节点提供服务,系统会分配一个
ClusterIP与节点Service绑定,在overlay网络外不能访问。
LoadBalancer:会从创建集群时提供的服务地址段中分配一个IP绑定到该服务,可以在集群外可通局域网络里访问。

应用配置可视化编辑页面分为三部分内容:

  • 左侧部分为节点:显示已支持的各类节点,所有节点配置请查看节点说明。
  • 中间部分为画布:显示应用需要的节点及部署顺序关系。注意⚠️,节点间的部署顺序关系,是通过连线来表示的,连线箭头所指节点先部署,连线起点后部署。
  • 右侧部分为节点属性:显示某个节点可以配置的参数。

左侧的节点列表,分成了四类:

  • 自研节点:用户自己上传的镜像的载体。一份配置,可以又多个自研节点。
  • 阿里云节点:一个节点,在应用被部署之后,对应阿里云RDS产品的一个实例(如一个RDS for MySQL数据库实例)。
  • 三方节点:各类主流开源中间件Docker镜像。这一类镜像完全来自于第三方,功能、性能、质量、安全等,均保持不变。
  • 初始化节点:这类节点属于辅助节点,在整个部署的生命周期,只会执行一次(其他节点通常不止一次,比如节点健康检查失败就会重启)。

3.2.2模型与权限

权限声明

点击版本管理>模型与权限>权限声明,在应用的分发模式,每一次分发,都有可能归属到不同的买家。因此,每一个应用实例,都需要一个唯一的值来代表应用实例的身份。这里,我们采用了AppKey + AppSecret的模式。随之而来的是,我们如何给AppKey授权。应用在调用IoT的各种API的时候,拿的这个AppKey需要被事先授权。因此,应用上架时,有必要清晰的指出该应用会调用哪些API,这样才能在AppKey产生的那一刻,给他授相应的权限,在此页面可对实例进行授权等操作,如图所示:权限注意⚠️ 若需要对接数据模型服务需要添加数据模型权限,对接单租户免登,需要开通oauth协议服务权限

数据模型

点击数据模型>添加数据模型,在列表中,除了选择要声明的模型之外,还要选择版本、数据权限、订阅。其中,数据权限有三种:查、增 | 查、增 | 删 | 改 | 查,分别对应不同的操作类型。如图所示:数据

服务依赖

点击服务依赖 > 添加服务模型(应用为服务依赖方添加,服务提供方无需添加),选择对应的服务模型,并配置服务模型的API,如图所示:
依赖

服务提供

点击服务提供>添加服务模型(应用为服务提供方添加,服务依赖方无需添加),选择对应的服务模型,并选择相应的端口,如图所示:
提供

3.2.3免登路径

如果需要在边缘控制台中查看已部署的应用信息,则需要配置免登路径,否则边缘控制台的免登跳转会无法识别跳转路径,如图所示:免登

3.3 实例管理

点击实例管理>部署实例,按照界面提示填写参数,如图所示:实例
参数设置如下:

参数 描述
应用名称 部署后应用实例的名称。该名称需账号内唯一。
应用配置 选择应用配置界面中创建的配置。
部署区域 当应用配置为边缘配置时,显示您在集群管理中创建的边缘集群。
资源信息状态 展示您选择的配置所有的节点及占有的资源情况
  • 单击部署,执行部署任务。
  • 部署应用时,需要根据应用配置占用相应的云资源。
  • 部署过程可能会花费较长时间,请耐心等待。
  • 应用部署成功后,您可以在部署记录页进行查看、运维和删除等操作,详情请参考运维工具文档

3.4 发布版本

应用部署成功后,可点击发布版本,版本发布后将不能在进行调试、修改、删除等操作,如图所示:发布

4.云端集群管理

4.1 创建集群

应用接入 > 集群管理 页面填写集群基本信息,如图所示:集群

  • 边缘集群:支持脱离公网,实现集群与应用边对边通讯。
  • 智能边缘一体机集群:支持脱离公网,实现集群与应用边对边通讯,同时提供智能边缘一体机集群的集群组件。
  • 存储地址:NFS服务器地址。
  • 存储路径:NFS Mount路径。
  • 服务地址段:边缘可用来分配的边缘服务网段。

建议使用与当前边缘局域网段不同的子网,否则有发生IP冲突的可能性导致服务异常。例如当前主机所在网络为192.168.1.0/24;可在路由器中再添加另一个子网192.168.2.0/24专门用来分配服务VIP。

4.2 创建节点

管理 > 添加通用节点 页面填写节点基本信息,如图所示:节点

节点名称:与集群名称不同,节点名称会被使用在K8S中,所以对命名有一定要求:只支持数字、小写英文、短划线,不能以短划线开头和结尾,长度限制4-30。
节点IP段:节点IP段为当前主机所在网段,需要用户提供以分配对应的Flannel Overlay服务网段。

4.3 加入节点

节点创建完成后点击 启动脚本 > 复制脚本 到主机命令行执行,加入完成,控制台状态将变成运行中,如图所示:启动

4.4 组件管理(智能边缘一体机集群)

组件管理 > 初始化集群底座 按钮对智能边缘一体机集群提供的集群组件进行初始化,如图所示:初始化

5.边缘集群管理

点击集群管理>应用管理 可查看部署在此集群的所有部署的应用列表与提供的集群组件信息,同时可以对已部署的应用进行测试,如图所示:测试

点击边缘控制台>管理 复制IP+端口号,打开新的浏览器页面进行访问,使用“超级账户iotedgeadmin”登录“集群控制台”,用户名与密码一致,首次登录强制修改密码。管理

登录成功后,可点击部署应用的应用卡片,进入应用详情,如图所示:详情
点击账号管理>新建账号,添加“集群用户”(用户名、密码、手机号),其中,手机号必填且唯一,并提示用户,手机号是系统之间免登的凭据,如图所示:新增