單賬號邊緣托管應(yīng)用
本章將為您介紹如何管理【單賬號邊緣托管應(yīng)用】。
整體流程
一個“單賬號邊緣托管應(yīng)用”的上線整體流程,包括:應(yīng)用對接開發(fā)、創(chuàng)建應(yīng)用、應(yīng)用配置、部署測試、集群管理。操作路徑:登錄應(yīng)用開發(fā)工作臺,單擊 應(yīng)用管理
進入應(yīng)用列表
對接介紹
1. 拆分應(yīng)用節(jié)點
大部分應(yīng)用是有不同獨立可運行的組件或者模塊構(gòu)成的,這些獨立可運行的組件或者模塊,我們稱為“節(jié)點”。比如一個“停車場管理系統(tǒng)”,典型包括:設(shè)備控制服務(wù)節(jié)點、業(yè)務(wù)管理節(jié)點、數(shù)據(jù)庫節(jié)點。那么,其第一個是一個運行在jre的jar包、第二個是一個運行在tomcat上的war包、第三個是一個開源的MySQL數(shù)據(jù)庫。
此外,拆分節(jié)點時,需要注意以下兩點:1. 功能相對獨立;2. 存儲盡量放在數(shù)據(jù)庫節(jié)點上。
2. 對業(yè)務(wù)代碼的改造
容器化部署對應(yīng)用本身的業(yè)務(wù)邏輯有兩個地方的影響,在應(yīng)用打包成鏡像之前(無論后續(xù)是否還有其他對接開發(fā)導(dǎo)致的調(diào)整),請先做好這兩方面的改動:
節(jié)點之間如何訪問:在非容器化前,由于每個節(jié)點單獨運行在主機上,因此一般情況下,節(jié)點之間的訪問是通過IP地址來訪問的。但是容器化改造之后,由于IP并不確定,因此請使用節(jié)點的“服務(wù)名稱”(服務(wù)名稱是在可視化編排頁面設(shè)定的)。
應(yīng)用初始化信息的獲取:應(yīng)用初始化信息包含兩種:應(yīng)用自身在節(jié)點配置中所配置的自定義環(huán)境變量、系統(tǒng)分配的一些初始化信息(如AppKey)。這兩類信息都位于環(huán)境變量中,應(yīng)用涉及到這兩類初始化信息,則需要作此改造。
3. 打包Docker鏡像
目前僅支持Linux鏡像,打包流程參考線上文檔:http://m.bestwisewords.com/document_detail/114832.html。建議在Linux操作系統(tǒng)上執(zhí)行打包操作。
4.OAuth對接示例
4.1 集群內(nèi)部發(fā)起API請求,獲取應(yīng)用App的登錄地址。
代碼示例:
/**
* 獲取環(huán)境變量和回調(diào)的url
* @return
*/
//iot.hosting.api.schema----是請求協(xié)議格式http
private static final String API_GATEWAY_SCHEMA = System.getenv("iot.hosting.api.schema");
//iot.hosting.api.domain--是跳轉(zhuǎn)路徑的回調(diào)ip地址
private static final String API_GATEWAY_DOMAIN = System.getenv("iot.hosting.api.domain");
//iot.hosting.api.port----是請求端口
private static final String API_GATEWAY_SCHEMA = System.getenv("iot.hosting.api.port");
// iot.hosting.appKey----是請求的appkey
private static final String API_GATEWAY_SCHEMA = System.getenv(" iot.hosting.appKey");
//PATH_APP_GET是請求應(yīng)用app的響應(yīng)路徑
private static final String PATH_APP_GET = "/api/console/app/get";
@Override
public String getAppIndex() {
RequestBuilder builder = RequestBuilder.create(METHOD_GET);
Map<String, String> queryParams = Maps.newHashMap();
queryParams.put(PARAM_APP_KEY, appKey);
builder.setUri(HttpUtils.buildUrl(schema, apiGatewayDomain, apiGatewayPort, PATH_APP_GET, queryParams));
IoTxResult<AppDTO> result = httpProxy.invoke(
(HttpRequestBase) builder.build(),
new TypeReference<IoTxResult<AppDTO>>() {
}
);
logger.info("path={}; params={}; result={}", PATH_APP_GET, JSON.toJSONString(queryParams), JSON.toJSONString(result));
Assert.assertSuccess(result);
AppDTO app = result.getData();
return app.getLoginUrl();
}
請求拼接示例:
1. http://30.42.XX.XX:32187/api/console/app/get?appKey=2813****
返回結(jié)果示例:
{
{
"code": 200,
"data": {
"aliyunPk": "101248722030****",
"appKey": "2813****",
"appMeta": {
"logoUrl": "http://192.168.11.130:32628/index/2813****",
"name": "oauth2邊緣托管",
"subVersionId": "1.0",
"uuid": "937aaa0284864396b54ecadb0d16****",
"versionUuid": "9ff097f16d8347e6abfcaec0cd14****"
},
"appSecret": "NGNiOWQyYzI4MGVhOWNlYmNhOTdmMDIyNTg2Mzlh****",
"clusterId": "bbeba04d880d49dc80bf83632619****",
"configName": "oauth2邊緣托管",
"configUuid": "a7b996cd6ab04b7fb3a54abbc81b****",
"configVersionUuid": "8e43f4d96bef4a289ad9f9af079d****",
"loginUrl": "http://192.168.XX.XX:30313/index",//應(yīng)用登錄url
"name": "Oauth2演示",
"oauth": {
"path": "/index",
"port": 8080,
"protocol": "",
"serviceName": "edge",
"serviceUuid": ""
},
"type": "GENERAL_APP",
"uuid": "731082ac0c444053bad624ec915b****"
},
"message": "success"
}
}
4.2 應(yīng)用請求控制臺地址獲取,用于免登跳轉(zhuǎn)地址的獲取。
/**
*
* /api/console/host/account
* @param path
* @return
*/
@Override
public URI getURI(String path) {
RequestBuilder builder = RequestBuilder.create(METHOD_GET);
Map<String, String> queryParams = Maps.newHashMap();
builder.setUri(HttpUtils.buildUrl(schema, apiGatewayDomain, apiGatewayPort, path, queryParams));
logger.info("RequestBuilder請求url");
IoTxResult<String> result = httpProxy.invoke(
(HttpRequestBase) builder.build(),
new TypeReference<IoTxResult<String>>() {
}
);
logger.info("path={}; params={}; result={}", path, JSON.toJSONString(queryParams), JSON.toJSONString(result));
Assert.assertSuccess(result);
try {
return new URI(result.getData());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
請求拼接示例:
1. http://30.42.XX.XX:32187/api/console/host/account
4.3 應(yīng)用請求發(fā)起。
根據(jù)當(dāng)前登錄態(tài),獲取authcode,oauth授權(quán)的頁面,可直接跳過授權(quán)頁面,請求免登的地址:http://30.42.XX.XX:32187:80/oauth2/auth? 請求入?yún)ⅲ?/p>
參數(shù)名 | 類型 | 必填 | 描述 |
client_id | String | 是 | 應(yīng)用的appkey |
redirect_uri | String | 是 | OAuth認(rèn)證通過后的重定向應(yīng)用的URI,包含完整的域名 |
response_type | String | 是 | 返回類型。根據(jù)OAuth 2.0標(biāo)準(zhǔn),目前支持設(shè)置此參數(shù)的取值為code |
state | String | 否 | 應(yīng)用的appkey攜帶項 |
scope | String | 否 | 空格分隔的OAuth范圍列表。如不指定此參數(shù)取值,則默認(rèn)為應(yīng)用注冊的全部OAuth范圍,加上openid |
請求示例:
http://30.42.XX.XX:32187/oauth2/auth?
redirect_uri=http://30.42.XX.XX:32187/index&
client_id=2813****&state=2813****&
response_type=code
代碼示例:
/**
*獲取免登url和code
* @param appKey
* @param redirectUri
* @return
*/
public String getLoginRedirectUrl(String appKey, String redirectUri) {
Map<String, String> queryParams = Maps.newHashMap();
queryParams.put(PARAM_CLIENT_ID, appKey);
queryParams.put(PARAM_REDIRECT_URI, redirectUri);
queryParams.put(PARAM_RESPONSE_TYPE, "code");
URI accountUri = getAccountUri();
return HttpUtils.buildUrl(
schema,
accountUri.getHost(),
accountUri.getPort(),
PATH_LOGIN,
queryParams).toASCIIString();
}
4.4 跳轉(zhuǎn)redirect_uri獲取oauthcode。
IoT在驗證當(dāng)前用戶合法后,將生成當(dāng)前用戶授權(quán)碼oauthcode,在回跳redirect_uri地址時通過GET方式傳遞oauthcode,并同時返回state。返回示例:
http://30.42.XX.XX:32187/index?code=64a67ee15534defea7ad0d0535189b24&state=2813****
4.5 通過oauthcode換取accessToken。
獲取OAuth授權(quán)code后可通過該接口獲取accessToken身份信息,詳情請參見鏈接OAuth對接API。代碼示例:
/**
* 根據(jù)code獲取到token
* @param appKey
* @param oauthCode
* @return
*/
public String getAccessTokenByOauthCode(String appKey, String oauthCode) {
RequestBuilder builder = RequestBuilder.create(METHOD_GET);
Map<String, String> queryParams = Maps.newHashMap();
queryParams.put(PARAM_CODE, oauthCode);
queryParams.put(PARAM_GRANT_TYPE, "authorization_code");
queryParams.put(PARAM_CLIENT_ID, appKey);
URI accountUri = getAccountUri();
builder.setUri(HttpUtils.buildUrl(accountUri.getScheme(), accountUri.getHost(), accountUri.getPort(), PATH_GET_ACCESS_TOKEN_BY_OAUTH_CODE, queryParams));
IoTxResult<AccessTokenDTO> result = httpProxy.invoke(
(HttpRequestBase) builder.build(),
new TypeReference<IoTxResult<AccessTokenDTO>>() {
}
);
PROXY_LOGGER.info("path={}; params={}; result={}", PATH_GET_ACCESS_TOKEN_BY_OAUTH_CODE, JSON.toJSONString(queryParams), JSON.toJSONString(result));
Assert.assertSuccess(result);
AccessTokenDTO accessTokenDTO = result.getData();
Assert.assertNotNull(accessTokenDTO, "get accessToken failed");
return accessTokenDTO.getAccessToken();
}
4. 6.通過accessToken換取用戶信息。
獲取accessToken信息后,可通過accessToken來換取登錄用戶的用戶信息,詳情請參見OAuth對接API。代碼示例:
/**
* 根據(jù)token獲取用戶信息
* @param accessToken
* @return
*/
public UserInfoDTO getUserInfoByAccessToken(String accessToken) {
RequestBuilder builder = RequestBuilder.create(METHOD_GET);
Map<String, String> queryParams = Maps.newHashMap();
queryParams.put(PARAM_ACCESS_TOKEN, accessToken);
URI accountUri = getAccountUri();
builder.setUri(HttpUtils.buildUrl(accountUri.getScheme(), accountUri.getHost(), accountUri.getPort(), PATH_GET_USER_INFO_BY_ACCESS_TOKEN, queryParams));
IoTxResult<UserInfoDTO> result = httpProxy.invoke(
(HttpRequestBase) builder.build(),
new TypeReference<IoTxResult<UserInfoDTO>>() {
}
);
PROXY_LOGGER.info("path={}; params={}; result={}", PATH_GET_USER_INFO_BY_ACCESS_TOKEN, JSON.toJSONString(queryParams), JSON.toJSONString(result));
Assert.assertSuccess(result);
UserInfoDTO userInfoDTO = result.getData();
Assert.assertNotNull(userInfoDTO, "get userInfo failed");
return userInfoDTO;
}
4.7.accessToken有效性判斷。
檢查當(dāng)前URL登錄的token是否有效, /user/oauth2/accesstoken/check?access_token=xxx。代碼示例:
/**
* token登錄有效期檢查
* @param token
* @return
*/
public boolean checkLogin(String token) {
RequestBuilder builder = RequestBuilder.create(METHOD_GET);
Map<String, String> queryParams = Maps.newHashMap();
queryParams.put(PARAM_ACCESS_TOKEN, token);
URI accountUri = getAccountUri();
builder.setUri(HttpUtils.buildUrl(accountUri.getScheme(), accountUri.getHost(), accountUri.getPort(), PATH_LOGINCHECK, queryParams));
IoTxResult<Boolean> result = httpProxy.invoke(
(HttpRequestBase) builder.build(),
new TypeReference<IoTxResult<Boolean>>() {
}
);
PROXY_LOGGER.info("path={}; params={}; result={}", PATH_LOGINCHECK, JSON.toJSONString(queryParams), JSON.toJSONString(result));
Assert.assertSuccess(result);
return result.getData();
}
5.多副本應(yīng)用部署
5.1注意事件
1.多副本部署目前僅支持RedisHA和MysqlHA的三方節(jié)點進行數(shù)據(jù)存儲,在應(yīng)用配置可選擇副本數(shù)。
2. 系統(tǒng)應(yīng)用如果接入了應(yīng)用設(shè)備,每個應(yīng)用實例需要有一個獨立的設(shè)備證書(ProductKey、DeviceName、DeviceSecret)身份,那可能就不能同時運行2個副本的應(yīng)用。此時cmp的連接會有互踢機制,導(dǎo)致部署失敗。
集群管理
1 創(chuàng)建集群
在 應(yīng)用接入
> 集群管理
頁面填寫集群基本信息,如圖所示:
邊緣集群:支持脫離公網(wǎng),實現(xiàn)集群與應(yīng)用邊對邊通訊。
EdgeBox集群:支持脫離公網(wǎng),實現(xiàn)集群與應(yīng)用邊對邊通訊,同時提供EdgeBox集群的集群組件。
存儲地址:NFS服務(wù)器地址。
存儲路徑:NFS Mount路徑。
服務(wù)地址段:邊緣可用來分配的邊緣服務(wù)網(wǎng)段。
建議使用與當(dāng)前邊緣局域網(wǎng)段不同的子網(wǎng),否則有發(fā)生IP沖突的可能性導(dǎo)致服務(wù)異常。例如當(dāng)前主機所在網(wǎng)絡(luò)為192.168.1.0/24;可在路由器中再添加另一個子網(wǎng)192.168.2.0/24專門用來分配服務(wù)VIP。
2 創(chuàng)建節(jié)點
在 管理
> 添加通用節(jié)點
頁面填寫節(jié)點基本信息,如圖所示:
節(jié)點名稱:與集群名稱不同,節(jié)點名稱會被使用在K8s中,所以對命名有一定要求:只支持數(shù)字、小寫英文、短劃線,不能以短劃線開頭和結(jié)尾,長度限制4-30。
節(jié)點IP段:節(jié)點IP段為當(dāng)前主機所在網(wǎng)段,需要用戶提供以分配對應(yīng)的Flannel Overlay服務(wù)網(wǎng)段。
3 加入節(jié)點
節(jié)點創(chuàng)建完成后點擊 啟動腳本
> 復(fù)制腳本
到主機命令行執(zhí)行,加入完成,控制臺狀態(tài)將變成運行中,如圖所示:
4 組件管理(EdgeBox集群)
在 組件管理
> 初始化集群底座
按鈕對EdgeBox集群提供的集群組件進行初始化,如圖所示:
邊緣集群管理
點擊集群管理
>應(yīng)用管理
可查看部署在此集群的所有部署的應(yīng)用列表與提供的集群組件信息,同時可以對已部署的應(yīng)用進行測試
,如圖所示:
點擊邊緣控制臺
>管理
復(fù)制IP+端口號,打開新的瀏覽器頁面進行訪問,使用“超級賬戶iotedgeadmin”登錄“集群控制臺”,用戶名與密碼一致,首次登錄強制修改密碼。
登錄成功后,可點擊部署應(yīng)用的應(yīng)用卡片,進入應(yīng)用詳情
,如圖所示:
點擊賬號管理
>新建賬號
,添加“集群用戶”(用戶名、密碼、手機號),其中,手機號必填且唯一,并提示用戶,手機號是系統(tǒng)之間免登的憑據(jù),如圖所示: