本文为您介绍使用云效产品功能遇到的问题与解决方案汇总。
将其他阿里云账号添加到组织并实现账号间的资源共享
当您的组织成员有多个用户(主账号或RAM用户)时,且每个用户归属的阿里云账号都有自己的云服务资源,云效支持将多个用户的阿里云资源(例如云服务器ECS)添加至组织,实现云效流水线构建、部署发布任务。
-
将用户添加进您的组织。通过云效工作台的企业管理后台,选择 ,单击,
-
属于当前主账号下的RAM用户:选择添加RAM用户,使用自动同步可以将当前主账号下的RAM用户自动加入到组织,如果自动同步失败,可以选择手动同步将RAM用户加入组织。
-
其他主账号用户或RAM用户:选择通过链接邀请,复制邀请链接发送给相应成员,被邀请者只需打开链接并接受邀请,即可加入到对应云效组织。
-
-
创建服务连接并添加资源。以云服务器ECS添加到主机组为例:
-
不同的成员分别登录云效,在服务连接管理中添加各自的ECS服务连接,并进行服务授权,使用范围具体结合自己的需求选择。服务连接会以当前账号所属主账号进行授权,授权成功后可以通过该服务连接获取主账号下的所有ECS机器。
重要如果成员是RAM用户(子账号),可能会出现授权失败的情况,请联系阿里云账号管理RAM用户的权限AliyunRAMFullAccess权限。
新建服务连接时,使用范围可选择私密(仅自己可见)、公开(所有人可见)或自定义(指定人员可见)。
-
服务连接创建完成后,在主机组管理中选择已有主机组或新建主机组,在主机组中添加新主机,选择用户各自创建的服务连接即可添加相应账号下的ECS。
在接入新主机对话框中,添加方式选择阿里云ECS,选择地域后,在可添加的主机列表中勾选目标ECS实例,单击保存。
-
通过钉钉进行人工卡点审批
目前流水线Flow支持添加任务插件,您可以在Flow流水线中,添加人工卡点任务,实现钉钉接收发布前的审批确认,直接在钉钉客户端上操作流水线Flow审批任务。
步骤一:绑定钉钉组织和个人信息
-
组织绑定:组织。
-
个人绑定:组织。
步骤二:流水线添加人工卡点并执行流水线
在流水线编排中,添加人工卡点组件。
在阶段中添加任务时,打开选择任务组面板,在左侧分类中选择工具,然后在右侧选择人工卡点任务卡片。
选择具体的验证方式、验证者类型和验证人,保存触发流水线运行。
其中,验证者方式选择或签(一名审批人同意或拒绝即可),验证者类型选择成员(选择企业成员作为审核人员)。流水线运行至人工卡点阶段后,状态变为等待中,显示等待指定验证人验证的提示,页面提供验证不通过和验证通过两个操作按钮。
收到钉钉审批消息,进行验证操作。

解析红线异常问题
解析红线异常的问题排查与解决方案。
-
复制错误信息
单击错误信息,弹框出来的内容复制出来 .
在Maven 单元测试步骤卡片中,步骤状态显示运行失败,错误信息显示为解析红线异常,请参考文档。
双击复制红线信息。
界面左侧显示红色错误提示解析红线异常,请参考文档(https://aliyuque.antfin.com/aone/yunxiao/dpntcoqk1svpv6wi)进行排查,其下方为红线信息的JSON数据,包含文件数、Blocker/Critical/Major等统计字段及红线阈值检查结果。单击该错误信息区域后,在右侧弹出复制红线信息按钮。
-
运行排查用例
将红线信息数据放入下面用例里进行调试,排查问题.
排查所用到的示例代码如下:
import com.alibaba.fastjson.JSON; import java.io.Serializable; import java.lang.reflect.Field; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class StatInfoTest { private static Pattern STAT_INFO_TITLE_PATTERN = Pattern.compile("^STAT_INFO_TITLE:\\s*(.+)\\s*"); private static Pattern REDLINE_INFO_PATTERN = Pattern.compile("^REDLINE_ITEM_LINE:\\s*(.+)\\s*"); private static Pattern STAT_PATTERN = Pattern.compile("^STAT_(VALUE|NAME|TITLE|URL|STYLE)_([^:]+):\\s*(.*)\\s*"); public static void main(String[] args) { // 下面为一个失败的示例数据 //String statOutMapStr = "{\"10_1682051393916__10_1682051404781\":[\"STAT_INFO_TITLE: \\\"\\\"\",\"STAT_NAME_Total: 缺陷\",\"STAT_VALUE_Total: ww\",\"STAT_STYLE_Total: Error\",\"STAT_NAME_Blocker: 漏洞\",\"STAT_VALUE_Blocker: ww\",\"STAT_STYLE_Blocker: Warning\",\"REDLINE_ITEM_LINE: {\\\"key\\\":\\\"Blocker\\\",\\\"threshold\\\":43,\\\"checkVal\\\":ww,\\\"type\\\":\\\"LE\\\",\\\"checked\\\":true,\\\"checkResult\\\":false}\",\"STAT_NAME_Critical: 坏味道\",\"STAT_VALUE_Critical: ww\",\"STAT_STYLE_Critical: Warning\",\"REDLINE_ITEM_LINE: {\\\"key\\\":\\\"Critical\\\",\\\"threshold\\\":34,\\\"checkVal\\\":ww,\\\"type\\\":\\\"LE\\\",\\\"checked\\\":true,\\\"checkResult\\\":false}\",\"STAT_NAME_Major: 覆盖率\",\"STAT_VALUE_Major: ww0\",\"STAT_STYLE_Major: Default\",\"REDLINE_ITEM_LINE: {\\\"key\\\":\\\"Major\\\",\\\"threshold\\\":4,\\\"checkVal\\\":ww0,\\\"type\\\":\\\"LE\\\",\\\"checked\\\":true,\\\"checkResult\\\":false}\",\"STAT_URL__REPORT: 127.0.0.1/dashboard?id=13212323%3A31313213213\"]}"; String statOutMapStr="请将卡片上的数据信息复制到此处进行调试"; parseStatInfo(statOutMapStr); } public static void parseStatInfo(String statOutMapStr) { Map<String, List<String>> statOutMap = JSON.parseObject(statOutMapStr, new HashMap<String, List<String>>().getClass()); List<StatGroup> statGroups = new ArrayList<>(); statOutMap.forEach((k, params) -> { StatGroup statGroup = new StatGroup<StatInfo>(); Map<String, StatInfo> statInfoMap = new LinkedHashMap<>(64); List<RedlineResultItem> redlines = new ArrayList<>(64); Optional.ofNullable(params).orElse(new ArrayList<>()) .forEach(str -> { Matcher matcher = STAT_PATTERN.matcher(str); if (matcher.find()) { String fieldName = matcher.group(1).toLowerCase(); String key = matcher.group(2); String value = matcher.group(3); StatInfo statInfo = statInfoMap.get(key); if (statInfo == null) { statInfo = new StatInfo(); statInfo.setKey(key); } statInfo.setField(fieldName, value); statInfoMap.put(key, statInfo); } // check redlines Matcher redlineMatcher = REDLINE_INFO_PATTERN.matcher(str); if (redlineMatcher.find()) { String redlineResult = redlineMatcher.group(1); RedlineResultItem redlineItem = JSON.parseObject(redlineResult, RedlineResultItem.class); // check if valid redline item if (redlineItem.getKey() != "" && redlineItem.getThreshold() != null) { redlines.add(redlineItem); } } }); statGroup.setTitle(getStatInfoTitle(params)); statGroup.setStatInfoMap(statInfoMap); statGroup.setRedlineResult(redlines); statGroups.add(statGroup); }); System.out.println(JSON.toJSONString(statGroups)); } private static String getStatInfoTitle(List<String> params) { String title = null; if (params != null) { for (String param : params) { Matcher matcher = STAT_INFO_TITLE_PATTERN.matcher(param); if (matcher.find()) { title = matcher.group(1); break; } } } return title; } static class StatGroup<T> { private String title; private Map<String, T> statInfoMap; private List<RedlineResultItem> redlineResult; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Map<String, T> getStatInfoMap() { return statInfoMap; } public void setStatInfoMap(Map<String, T> statInfoMap) { this.statInfoMap = statInfoMap; } public List<RedlineResultItem> getRedlineResult() { return redlineResult; } public void setRedlineResult( List<RedlineResultItem> redlineResult) { this.redlineResult = redlineResult; } } public static class RedlineResultItem { private String key; private Integer threshold; private Integer checkVal; private String type; private Boolean checked; private Boolean checkResult; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Integer getThreshold() { return threshold; } public void setThreshold(Integer threshold) { this.threshold = threshold; } public Integer getCheckVal() { return checkVal; } public void setCheckVal(Integer checkVal) { this.checkVal = checkVal; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Boolean getChecked() { return checked; } public void setChecked(Boolean checked) { this.checked = checked; } public Boolean getCheckResult() { return checkResult; } public void setCheckResult(Boolean checkResult) { this.checkResult = checkResult; } } static class StatInfo implements Serializable { private static final long serialVersionUID = -7424772230982171781L; private String key; private String value; private String name; private String title; private String style; private String url; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getStyle() { return style; } public void setStyle(String style) { this.style = style; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public void setField(String fieldName, String value) { try { Field field = getClass().getDeclaredField(fieldName); field.set(this, value); } catch (Exception e) { } } } }
jenkins任务步骤出现403错误
jenkins任务步骤出现403错误的原因和解决方案。
-
问题现象
[19:09:28] [INFO] EMPLOYEE_ID=5f081b9b7cd41782838cb9c9 [19:09:28] [INFO] WORK_SPACE=/root/workspace [19:09:28] [INFO] PROJECT_DIR=/root/workspace/shell_A2OF [19:09:28] [INFO] PLUGIN_DIR=/root/plugins [19:09:28] [INFO] BUILD_JOB_ID=76892432 [19:09:28] [INFO] JENKINS_MASTER=http://121.40.77.247:8888 [19:09:28] [INFO] JENKINS_JOB=shell2 [19:09:28] 打印当前工作区 [19:09:28] total 16 [19:09:28] drwxr-xr-x 2 root root 4096 8月 18 19:09 . [19:09:28] drwxrwxrwx 4 root root 4096 8月 18 19:09 .. [19:09:28] -rw-r--r-- 1 root root 55 8月 18 19:09 artifacts.json [19:09:28] -rw-r--r-- 1 root root 699 8月 18 19:09 build.json [19:09:28] [19:09:28] 触发参数列表 [19:09:28] [19:09:28] [19:09:28] 开始触发Jenkins任务shell2的构建 [19:09:28] error: 错误码:403,错误信息:<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/> <title>Error 403 No valid crumb was included in the request</title> </head> <body> <h2>HTTP ERROR 403 No valid crumb was included in the request</h2> <table> <tr><th>URI:</th> <td>/job/shell2/build</td></tr> <tr><th>STATUS:</th><td>403</td></tr> <tr><th>MESSAGE:</th> <td>No valid crumb was included in the request</td></tr> <tr><th>SERVLET:</th><td>Stapler</td></tr> </table> <hr/> <a href="https://eclipse.org/jetty">Powered by Jetty:// 9.4.45.v20220203</a><hr/> </body> </html> [19:09:28] [ERROR] BUILD ERROR [19:09:28] [ERROR] 76892432 [19:09:28] [ERROR] 1 -
问题原因
调用Jenkins API 支持使用密码和Token两种方式。由于Jenkins在新版本进行了调整,不允许直接使用密码的方式调用API,会返回403报错,所以Jeknis 任务推荐使用API Token。
-
解决方案
-
创建API Token打开http://Jenkins_IP:8080/user/admin/configure,注意换成自己真实的Jenkins地址,如果没用admin用户,user后也要替换成存在的用户名,比如:http://localhost:8080/user/wangli/configure。在 API Token 管理页面,在输入框中输入 Token 名称(例如
myToken),然后单击生成按钮创建新的 API Token。记得要将Token复制出来备用,最后点保存按钮。 -
在流水线中使用API Token选择凭据类型时选择服务连接,然后在新建服务连接,新建服务授权/证书中,依次输入服务地址,用户名和刚生成的Token即可。
-
调用阿里云产品 API 异常
API异常的常见问题。
排查方案
-
主账号确认排查AliyunRDCDefaultRole角色是否被删除。
如果AliyunRDCDefaultRole 被删除了,就让主账号参考以下步骤进行重新授权。
在左侧导航栏选择服务连接管理,打开或新建服务连接,在服务授权/证书下拉框中找到已有授权项,单击其右侧的重新授权完成授权操作。
-
如果AliyunRDCDefaultRole 仍存在,请检查AliyunRDCDefaultRole对应的权限策略AliyunRDCRolePolicy是否正确。
在 RAM 控制台左侧导航栏选择身份管理 > 角色,单击 AliyunRDCDefaultRole 进入角色详情页,选择权限管理页签即可查看已授权的权限策略。
-
如果 AliyunRDCDefaultRole 中有多个权限策略,请删除AliyunRDCRolePolicy以外的其他权限策略。
-
如果AliyunRDCRolePolicy 不存在,就先删除AliyunRDCDefaultRole,然后再参考方案1重新授权。
-
如果AliyunRDCRolePolicy 存在,但调用仍报错,就先解除AliyunRDCRolePolicy 授权,然后删除AliyunRDCDefaultRole,最后再参考方案1重新授权。
-
GetPipelineRun Action说明
|
Action |
对应API |
功能 |
示例 |
说明 |
|
PassPipelineValidate |
通过人工卡点 |
|
disable: 是否有权限调用 validators:有权限通过人工卡点的用户的aliyunPk |
|
|
RefusePipelineValidate |
RefusePipelineValidator |
拒绝人工卡点 |
|
disable: 是否有权限调用 validators: 有权限通过人工卡点的用户的aliyunPk |
|
RetryPipelineJobRun |
RetryPipelineJobRun |
重试任务运行 |
|
disable: 是否有权限调用 |
|
StopPipelineJobRun |
StopPipelineJobRun |
取消任务运行 |
|
disable: 是否有权限调用 |
|
SkipPipelineJobRun |
SkipPipelineJobRun |
跳过失败任务 |
|
disable: 是否有权限调用 |
|
LogPipelineJobRun |
LogPipelineJobRun |
查看任务执行日志 |
|
disable: 是否有权限调用 |
|
GetVMDeployOrder |
GetVMDeployOrder |
获取ecs部署单详情 |
|
disable: 是否有权限调用 deployOrderId: 部署单id |
|
GetPipelineEmasArtifactUrl |
GetPipelineEmasArtifactUrl |
获取EMAS构建产物临时下载地址 |
|
serviceConnectionId: 服务连接id files: 构建产物,支持多个构建产物 emasJobInstanceId: emas 任务id md5: 构建产物md5 disable: 是否有权限调用 |
|
GetPipelineArtifactUrl |
GetPipelineArtifactUrl |
获取流水线构建产物临时下载地址 |
|
disable: 是否有权限调用 files: 构建产物,支持多个构建产物 fileName: 构建产物名称 filePath: 构建产物路径 |
|
GetPipelineScanReportUrl |
GetPipelineScanReportUrl |
获取扫描报告临时下载地址 |
{"disable":false,"type":"GetPipelineScanReportUrl","params":{"reportPath":"assets/1xxx/1xxx/05e0edb9xxxxxxxx/index.html","reportInfo":{"Major":{"name":"issue","style":"text-danger","title":"Major","value":"6","key":"Major",}"Total":{"name":"issue","style":"text-danger","title":"Total","value":"6","key":"Total"}"Critical":{"name":"issue","style":"text-danger","title":"Critical","value":"0","key":"Critical"}"Blocker":{"name":"issue","style":"text-danger","title":"Blocker","value":"0","key":"Blocker"}"_REPORT":{"title":"report","value":"link","key":"_REPORT","url":"https://flow.aliyun.com/assets/2xxxxx/1xxxx/21212xxxxxxxxxxx/index.html"}"File":{"name":"issue","style":"text-danger","title":"File","value":"4","key":"File"}}}} |
disable: 有没有权限调用 reportPath: 报告文件路径 reportInfo: 报告信息 |
调用API提示无权限
-
问题:
调用API接口提示401无权限: com.aliyun.tea.TeaException: code: 401, InvalidParam.NoPermission request 。
-
解决方案:
出现报错是由于当前账号没有权限调用云效API,如果通过ak,sk 鉴权方式调用,且调用账号为RAM子账号,需要子账号拥有AliyunRDCFullAccess 权限。
如何查询云效流水线创建、运行、删除、更新审计事件
可以在操作审计的「事件查询」根据服务名称来搜索产品所有的事件,查询地域需要为杭州。流水线事件对应的操作审计事件名称如下:
运行流水线:yunxiao.flow.pipeline.run
更新流水线:yunxiao.flow.pipeline.update
创建流水线:yunxiao.flow.pipeline.create
删除流水线:yunxiao.flow.pipeline.delete
也可以在「高级查询」通过sls查询语句来查询某一流水线的相关事件:
* AND (event.serviceName: "RDC") and 组织id and 流水线id操作审计日志中的additionalEventData.bind_id 是操作者的阿里云账号,additionalEventData.user_id是云效组织成员id,可以通过GetMember接口获取成员信息。
