mirror of
https://github.com/alibaba/higress.git
synced 2026-06-07 11:47:30 +08:00
feat: 🎸 支持多版本能力:根据不同路由映射不同的Version版本。 (#1429)
This commit is contained in:
@@ -57,15 +57,17 @@ description: 前端灰度插件配置参考
|
|||||||
|
|
||||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|----------------|--------------|------|-----|-----------------------------------------------------------------------------------|
|
|----------------|--------------|------|-----|-----------------------------------------------------------------------------------|
|
||||||
| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 |
|
| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 |
|
||||||
|
| `versionPredicates` | string | 必填 | - | 和`version`含义相同,但是满足多版本的需求:根据不同路由映射不同的`Version`版本。一般用于微前端的场景:一个主应用需要管理多个微应用 |
|
||||||
|
|
||||||
`grayDeployments`字段配置说明:
|
`grayDeployments`字段配置说明:
|
||||||
|
|
||||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||||
|--------|--------|------|-----|-------------------------------------------------|
|
|--------|--------|------|-----|-------------------------------------------------|
|
||||||
| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` |
|
| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` |
|
||||||
|
| `versionPredicates` | string | 必填 | - | 和`version`含义相同,但是满足多版本的需求:根据不同路由映射不同的`Version`版本。一般用于微前端的场景:一个主应用需要管理多个微应用 |
|
||||||
| `backendVersion` | string | 必填 | - | 后端灰度版本,配合`key`为`${backendGrayTag}`,写入cookie中 |
|
| `backendVersion` | string | 必填 | - | 后端灰度版本,配合`key`为`${backendGrayTag}`,写入cookie中 |
|
||||||
| `name` | string | 必填 | - | 规则名称和`rules[].name`关联, |
|
| `name` | string | 必填 | - | 规则名称和`rules[].name`关联 |
|
||||||
| `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 |
|
| `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 |
|
||||||
| `weight` | int | 非必填 | - | 按照比例灰度,比如`50`。注意:灰度规则权重总和不能超过100,如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
|
| `weight` | int | 非必填 | - | 按照比例灰度,比如`50`。注意:灰度规则权重总和不能超过100,如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 |
|
||||||
> 为了实现按比例(weight) 进行灰度发布,并确保用户粘滞,我们需要确认客户端的唯一性。如果配置了 grayKey,则将其用作唯一标识;如果未配置 grayKey,则使用客户端的访问 IP 地址作为唯一标识。
|
> 为了实现按比例(weight) 进行灰度发布,并确保用户粘滞,我们需要确认客户端的唯一性。如果配置了 grayKey,则将其用作唯一标识;如果未配置 grayKey,则使用客户端的访问 IP 地址作为唯一标识。
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ type GrayRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Deployment struct {
|
type Deployment struct {
|
||||||
Name string
|
Name string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Version string
|
Version string
|
||||||
BackendVersion string
|
BackendVersion string
|
||||||
Weight int
|
Weight int
|
||||||
|
VersionPredicates map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rewrite struct {
|
type Rewrite struct {
|
||||||
@@ -129,8 +130,9 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
|
|||||||
grayDeployments := json.Get("grayDeployments").Array()
|
grayDeployments := json.Get("grayDeployments").Array()
|
||||||
|
|
||||||
grayConfig.BaseDeployment = &Deployment{
|
grayConfig.BaseDeployment = &Deployment{
|
||||||
Name: baseDeployment.Get("name").String(),
|
Name: baseDeployment.Get("name").String(),
|
||||||
Version: strings.Trim(baseDeployment.Get("version").String(), " "),
|
Version: strings.Trim(baseDeployment.Get("version").String(), " "),
|
||||||
|
VersionPredicates: convertToStringMap(baseDeployment.Get("versionPredicates")),
|
||||||
}
|
}
|
||||||
for _, item := range grayDeployments {
|
for _, item := range grayDeployments {
|
||||||
if !item.Get("enabled").Bool() {
|
if !item.Get("enabled").Bool() {
|
||||||
@@ -138,11 +140,12 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
|
|||||||
}
|
}
|
||||||
grayWeight := int(item.Get("weight").Int())
|
grayWeight := int(item.Get("weight").Int())
|
||||||
grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &Deployment{
|
grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &Deployment{
|
||||||
Name: item.Get("name").String(),
|
Name: item.Get("name").String(),
|
||||||
Enabled: item.Get("enabled").Bool(),
|
Enabled: item.Get("enabled").Bool(),
|
||||||
Version: strings.Trim(item.Get("version").String(), " "),
|
Version: strings.Trim(item.Get("version").String(), " "),
|
||||||
BackendVersion: item.Get("backendVersion").String(),
|
BackendVersion: item.Get("backendVersion").String(),
|
||||||
Weight: grayWeight,
|
Weight: grayWeight,
|
||||||
|
VersionPredicates: convertToStringMap(item.Get("versionPredicates")),
|
||||||
})
|
})
|
||||||
grayConfig.TotalGrayWeight += grayWeight
|
grayConfig.TotalGrayWeight += grayWeight
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,22 +70,28 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有配置比例,则进行灰度规则匹配
|
// 如果没有配置比例,则进行灰度规则匹配
|
||||||
if isPageRequest {
|
if util.IsSupportMultiVersion(grayConfig) {
|
||||||
if grayConfig.TotalGrayWeight > 0 {
|
deployment = util.FilterMultiVersionGrayRule(&grayConfig, grayKeyValue, requestPath)
|
||||||
log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight)
|
log.Infof("multi version %v", deployment)
|
||||||
deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId)
|
|
||||||
} else {
|
|
||||||
deployment = util.FilterGrayRule(&grayConfig, grayKeyValue)
|
|
||||||
}
|
|
||||||
log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId)
|
|
||||||
} else {
|
} else {
|
||||||
grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue)
|
if isPageRequest {
|
||||||
deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest)
|
if grayConfig.TotalGrayWeight > 0 {
|
||||||
|
log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight)
|
||||||
|
deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId)
|
||||||
|
} else {
|
||||||
|
deployment = util.FilterGrayRule(&grayConfig, grayKeyValue)
|
||||||
|
}
|
||||||
|
log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId)
|
||||||
|
} else {
|
||||||
|
grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue)
|
||||||
|
deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest)
|
||||||
|
}
|
||||||
|
ctx.SetContext(config.XPreHigressTag, deployment.Version)
|
||||||
|
ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
proxywasm.AddHttpRequestHeader(config.XHigressTag, deployment.Version)
|
proxywasm.AddHttpRequestHeader(config.XHigressTag, deployment.Version)
|
||||||
|
|
||||||
ctx.SetContext(config.XPreHigressTag, deployment.Version)
|
|
||||||
ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion)
|
|
||||||
ctx.SetContext(config.IsPageRequest, isPageRequest)
|
ctx.SetContext(config.IsPageRequest, isPageRequest)
|
||||||
ctx.SetContext(config.XUniqueClientId, uniqueClientId)
|
ctx.SetContext(config.XUniqueClientId, uniqueClientId)
|
||||||
|
|
||||||
@@ -167,18 +173,24 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig,
|
|||||||
log.Errorf("error status: %s, error message: %v", status, err)
|
log.Errorf("error status: %s, error message: %v", status, err)
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
|
cacheControl, _ := proxywasm.GetHttpResponseHeader("cache-control")
|
||||||
|
if !strings.Contains(cacheControl, "no-cache") {
|
||||||
|
proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate")
|
||||||
|
}
|
||||||
|
|
||||||
proxywasm.ReplaceHttpResponseHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate")
|
frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string)
|
||||||
|
xUniqueClient, isUniqClientOk := ctx.GetContext(config.XUniqueClientId).(string)
|
||||||
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
|
||||||
xUniqueClient := ctx.GetContext(config.XUniqueClientId).(string)
|
|
||||||
|
|
||||||
// 设置前端的版本
|
// 设置前端的版本
|
||||||
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge))
|
if isFeVersionOk && isUniqClientOk && frontendVersion != "" {
|
||||||
|
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge))
|
||||||
|
}
|
||||||
// 设置后端的版本
|
// 设置后端的版本
|
||||||
if util.IsBackendGrayEnabled(grayConfig) {
|
if util.IsBackendGrayEnabled(grayConfig) {
|
||||||
backendVersion := ctx.GetContext(grayConfig.BackendGrayTag).(string)
|
backendVersion, isBackVersionOk := ctx.GetContext(grayConfig.BackendGrayTag).(string)
|
||||||
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge))
|
if isBackVersionOk && backendVersion != "" {
|
||||||
|
proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
@@ -188,16 +200,13 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b
|
|||||||
if !enabledGray {
|
if !enabledGray {
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool)
|
isPageRequest, isPageRequestOk := ctx.GetContext(config.IsPageRequest).(bool)
|
||||||
if !ok {
|
frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string)
|
||||||
isPageRequest = false // 默认值
|
|
||||||
}
|
|
||||||
// 只处理首页相关请求
|
// 只处理首页相关请求
|
||||||
if !isPageRequest {
|
if !isFeVersionOk || !isPageRequestOk || !isPageRequest {
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
frontendVersion := ctx.GetContext(config.XPreHigressTag).(string)
|
|
||||||
isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool)
|
isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool)
|
||||||
if !ok {
|
if !ok {
|
||||||
isNotFound = false // 默认值
|
isNotFound = false // 默认值
|
||||||
|
|||||||
@@ -276,6 +276,43 @@ func GetGrayKey(grayKeyValueByCookie string, grayKeyValueByHeader string, graySu
|
|||||||
return grayKeyValue
|
return grayKeyValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果基础部署或任何灰度部署中包含VersionPredicates,则认为是多版本配置
|
||||||
|
func IsSupportMultiVersion(grayConfig config.GrayConfig) bool {
|
||||||
|
if len(grayConfig.BaseDeployment.VersionPredicates) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, deployment := range grayConfig.GrayDeployments {
|
||||||
|
if len(deployment.VersionPredicates) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterMultiVersionGrayRule 过滤多版本灰度规则
|
||||||
|
func FilterMultiVersionGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, requestPath string) *config.Deployment {
|
||||||
|
// 首先根据灰度键值获取当前部署
|
||||||
|
currentDeployment := FilterGrayRule(grayConfig, grayKeyValue)
|
||||||
|
|
||||||
|
// 创建一个新的部署对象,初始化版本为当前部署的版本
|
||||||
|
deployment := &config.Deployment{
|
||||||
|
Version: currentDeployment.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对版本谓词的键进行排序
|
||||||
|
keys := SortKeysByLengthAndLexicographically(currentDeployment.VersionPredicates)
|
||||||
|
|
||||||
|
// 遍历排序后的键
|
||||||
|
for _, prefix := range keys {
|
||||||
|
// 如果请求路径以当前前缀开头
|
||||||
|
if strings.HasPrefix(requestPath, prefix) {
|
||||||
|
deployment.Version = currentDeployment.VersionPredicates[prefix]
|
||||||
|
return deployment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deployment
|
||||||
|
}
|
||||||
|
|
||||||
// FilterGrayRule 过滤灰度规则
|
// FilterGrayRule 过滤灰度规则
|
||||||
func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment {
|
func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment {
|
||||||
for _, deployment := range grayConfig.GrayDeployments {
|
for _, deployment := range grayConfig.GrayDeployments {
|
||||||
|
|||||||
Reference in New Issue
Block a user