feat: 🎸 支持多版本能力:根据不同路由映射不同的Version版本。 (#1429)

This commit is contained in:
mamba
2024-11-07 09:11:57 +08:00
committed by GitHub
parent 00cac813e3
commit 9b995321bb
4 changed files with 91 additions and 40 deletions

View File

@@ -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 地址作为唯一标识。

View File

@@ -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
} }

View File

@@ -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 // 默认值

View File

@@ -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 {