Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

配置下发通道与配置热加载 #500

Closed
unionhu opened this issue Apr 27, 2022 · 60 comments · Fixed by #762
Closed

配置下发通道与配置热加载 #500

unionhu opened this issue Apr 27, 2022 · 60 comments · Fixed by #762
Assignees
Labels

Comments

@unionhu
Copy link

unionhu commented Apr 27, 2022

希望提供一个通用的配置下发的grpc协议通道,用于layotto与控制面的交互(可以参考go-control-plane),
可以随意定义用户可以,随意的定义下发配置的策略内容

用途:
1.redis、kafka客户端流量管理控制,可能需要容灾切换、灰度发布策略切换
2.redis连接参数调整、安全弱密码自动切换

@seeflood
Copy link
Member

seeflood commented Apr 28, 2022

Cool. 个人一直期望在layotto runtime 层支持 流量治理规则、支持规则动态下发(像 envoy 那样),这样能帮助所有组件都获得强大的流量治理能力。

不过您的描述比较简单,我有一些疑问,比如:

  • 是希望下发 key-value 结构的“配置”,还是类似于 xds 那样、有一定结构的“规则”呢?
  • redis 的组件现在有一些failover之类的配置,这些配置项能否满足您的需求,见 https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-redis/
  • 启动配置文件里,现在有一些key/value配置项(见下图),layotto 会用这些配置项去初始化组件。如果加入动态规则下发后,您希望layotto 启动后,用哪里的配置做初始化,是启动后立刻读控制面的配置、用控制面的配置覆盖掉配置文件里的配置、然后再初始化么?
    image

所以,您能否写个设计proposal,写下“您希望这个功能被设计成什么样”,比如写下您希望 layotto 对控制面开什么样的 api 出来、api 字段有哪些。因为由别人来设计的话,很可能设计出来的字段/功能满足不了您的需求~

@unionhu
Copy link
Author

unionhu commented Apr 28, 2022

Cool. 个人一直期望在layotto runtime 层支持 流量治理规则、支持规则动态下发(像 envoy 那样),这样能帮助所有组件都获得强大的流量治理能力。

不过您的描述比较简单,我有一些疑问,比如:

  • 是希望下发 key-value 结构的“配置”,还是类似于 xds 那样、有一定结构的“规则”呢?
  • redis 的组件现在有一些failover之类的配置,这些配置项能否满足您的需求,见 https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-redis/
  • 启动配置文件里,现在有一些key/value配置项(见下图),layotto 会用这些配置项去初始化组件。如果加入动态规则下发后,您希望layotto 启动后,用哪里的配置做初始化,是启动后立刻读控制面的配置、用控制面的配置覆盖掉配置文件里的配置、然后再初始化么?
    image

所以,您能否写个设计proposal,写下“您希望这个功能被设计成什么样”,比如写下您希望 layotto 对控制面开什么样的 api 出来、api 字段有哪些。因为由别人来设计的话,很可能设计出来的字段/功能满足不了您的需求~

如果要抽象到xds级别的结构的规则,可能相对困难
比如不同的mq,协议结构都不太一样,需要设计人员对每种mq的配置属性十分熟悉,才能抽象好
所以能否先以key-value形式发布,主要是针对企业自定义的组件api功能的规则

redis这种简单的容灾切换应该能满足,但是可能会考虑的更多

通常来说,layotto本地只会有控制面地址配置,其他配置均可以替换,当然也可以从本地配置获取
优先级为:控制面>本地配置

@seeflood
Copy link
Member

seeflood commented May 6, 2022

方案 v0.1:

需求

组件能查询、订阅配置中心的 kv 配置(比如 apollo 中的配置)。

产品设计

User story

  1. 用户在 apollo 页面改一下 Redis 的容灾切换配置,Redis 组件就能接收到新配置,把流量切到灾备集群

编程界面

允许组件依赖 config_store 组件,通过 config_store 组件查询、订阅配置数据。
组件之间的依赖关系如图:

image

如果组件有这种需求,需要做以下改动:

  1. 组件的配置文件里,添加import配置项,声明希望引用的 config_store 组件名:
  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "import": {
        "config_store": {
          "name": "xxx"
        }
      }
    }
  },
  1. 组件要实现 configstores.Setter interface, 以便 runtime 把config_store 注入进去
type Setter interface {
	SetConfigStore(store Store)
}

方案设计

  1. 启动时,优先初始化所有 config_store 组件、再创建其他组件
  2. runtime 创建组件时,如果组件配置了import,则检查组件有没有实现 Setter 接口,实现了的话就注入对应的 config_store 组件
  3. 调组件的 Init 接口,初始化组件

生命周期为:

image

其他

  1. 有没有密钥动态下发的需求?

@seeflood
Copy link
Member

seeflood commented May 6, 2022

@unionhu 看下这样能否满足需求~以及是否有密钥动态下发的需求?

@unionhu
Copy link
Author

unionhu commented May 6, 2022

@unionhu 看下这样能否满足需求~以及是否有密钥动态下发的需求?

好巧,今天团队内部讨论到,真好我们也有apollo,也希望config组件能在启动过程中最先加载拉取配置,然后提供给其他组件用
这个特性对我们十分重要,你们优先安排吗?

有密钥动态下发的需求,而且我们可能希望密钥能动态更换,因为存在弱密码场景,安全要求整改

@seeflood seeflood self-assigned this May 7, 2022
@seeflood seeflood added the kind/enhancement New feature or request label May 7, 2022
@ZLBer
Copy link
Member

ZLBer commented May 8, 2022

建议config和secret都放在 import标签里做,大一统

@seeflood
Copy link
Member

seeflood commented May 10, 2022

建议config和secret都放在 import标签里做,大一统

ok
关于 json 配置文件的结构,这么设计如何:

  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "ref": [
        {
          "type": "component",
          "subType": "config_store",
          "name": "apollo"
        },
        {
          "type": "secret",
          "name": "xxx",
          "key": "yyy"
        }
      ]
    }
  },

@unionhu @ZLBer @zhenjunMa @JervyShi 帮忙把把关

@seeflood
Copy link
Member

seeflood commented May 10, 2022

或者用 kind 标识种类, type 标识子类

  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "ref": [
        {
          "kind": "component",
          "type": "config_store",
          "name": "apollo"
        },
        {
          "kind": "secret",
          "name": "xxx",
          "key": "yyy"
        }
      ]
    }
  },

不过我个人觉得配置结构怎么定都行,上面这种结构可能反序列化代码写起来麻烦点。
或者这样,反序列化代码写起来简单一点:

  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "componentRef": [
        {
          "type": "config_store",
          "name": "apollo"
        }
      ],
      "secretRef": [
        {
          "name": "xxx",
          "key": "yyy"
        }
      ]
    }
  }

@seeflood
Copy link
Member

seeflood commented May 10, 2022

@unionhu 这个 feature 实现起来应该很简单,你们有开发同学感兴趣认领么~

@unionhu
Copy link
Author

unionhu commented May 10, 2022

@unionhu 这个 feature 实现起来应该很简单,你们有开发同学感兴趣认领么~

感兴趣,只是我们最近忙着上线mosn以及配套设施,估计要下下周,我周末有空的时候看看应该怎么搞

@ZLBer
Copy link
Member

ZLBer commented May 10, 2022

默认secret是只获取一次? config是获取一次+订阅的吗?

@seeflood seeflood removed their assignment May 10, 2022
@seeflood
Copy link
Member

seeflood commented May 10, 2022

标记一下,就叫这版是 proposal v0.2:

@ZLBer 以这个配置结构为例:

  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "componentRef": [
        {
          "type": "config_store",
          "name": "apollo"
        }
      ],
      "secretRef": [
        {
          "name": "xxx",
          "key": "yyy"
        }
      ]
    }
  },
  1. secretRef 代表“启动时只获取一次这个secret”
  2. componentRef 代表 “需要在启动时注入组件”,组件type 是 "config_store",name 是"apollo"
    而且 redis 组件要实现 configstores.Setter interface, 以便 runtime 把config_store 注入进去
type Setter interface {
	SetConfigStore(store Store)
}

至于是“获取一次+订阅”,还是“获取一百次、不订阅”,全看 redis 组件的实现,看写redis 组件的人怎么写了~ redis 组件的开发者可以自由使用该 config_store 组件

  1. 假如我们以后有新需求"redis 组件想要动态下发的密钥,密钥托管在 AWS key vault 之类的密钥存储中"(只是举个例子,aws这个服务好像不支持监听密钥变更),实现方案是支持“在 redis 组件启动时,注入 secret_store 组件”,配置结构像这样:
  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "componentRef": [
        {
          "type": "config_store",
          "name": "apollo"
        },
        {
          "type": "secret_store",
          "name": "foo"
        }
      ],
      "secretRef": [
        {
          "name": "xxx",
          "key": "yyy"
        }
      ],
      "configurationRef": [
        {
          "name": "apollo",
          "key": "xxxxx"
        }
      ]
    }
  },

@seeflood
Copy link
Member

@unionhu 这个 feature 实现起来应该很简单,你们有开发同学感兴趣认领么~

感兴趣,只是我们最近忙着上线mosn以及配套设施,估计要下下周,我周末有空的时候看看应该怎么搞

@unionhu 欢迎~ 可以先看下,如果不太明白的话我可以投屏和你说下改哪

@ZLBer
Copy link
Member

ZLBer commented May 13, 2022

redis 组件让我理解了半天... 就相当于 state.redis 持有了componentRef的引用,框架侧只需要启动的时候注入进去?(有spring那味了) 听起来还是不自动啊,那我只想注入点配置,你给我直接注入到meta里我直接用不行吗?

还有组件启动权重? 是我们手动调整下代码顺序?

@seeflood
Copy link
Member

seeflood commented May 13, 2022

@ZLBer

redis 组件让我理解了半天... 就相当于 state.redis 持有了componentRef的引用,框架侧只需要启动的时候注入进去?(有spring那味了)

是的,就是 spring 那味 👃

听起来还是不自动啊,那我只想注入点配置,你给我直接注入到meta里我直接用不行吗?

澄清一下,我们可以把下发的配置分为两类:

  • 静态配置:启动时候用,启动完了就不再改动了
  • 动态配置:比如 state.redis 组件监听某个配置项 openFeatureA(用于开关某个功能),一但有变更,立刻能够获取到,然后打开/关闭相关功能

所以这里“注入一个组件”是为了满足“动态配置”的需求;
而“注入静态配置”可以这样:

  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "configurationRef": [
        {
          "name": "apollo",
          "key": "xxxxx"
        }
      ]
    }
  },

@seeflood
Copy link
Member

seeflood commented May 13, 2022

还有组件启动权重? 是我们手动调整下代码顺序?

是的,调整启动顺序,先启动、初始化 config_store 组件, 再启动别的类型的组件

@ZLBer
Copy link
Member

ZLBer commented May 13, 2022

@seeflood 动态配置这个,那用户还是需要修改layotto的代码吗 ?

@seeflood
Copy link
Member

seeflood commented May 13, 2022

我们把框架开发好(把依赖注入的逻辑开发好),用户不用修改layotto 代码,但是用户要写自己的组件、自己订阅配置变更、做打开开关之类的功能


这只是我目前想到的方案, 不代表就一定要这样哈,有更好的方案的话都可以讨论

@ZLBer
Copy link
Member

ZLBer commented May 13, 2022

忽然又意识到了sdk的好处,直接写回调函数
很cool的功能,没人写可以@我

@seeflood
Copy link
Member

@ZLBer 感觉你说的是个问题哦,想用这个功能必须写自己的组件,有点不通用?

@seeflood
Copy link
Member

seeflood commented May 13, 2022

5.13 社区会议讨论action:

  • 需要“默认订阅”功能(某个租户/组 下面会默认订阅一批 kv), @unionhu 可以详细描述下场景、建议的设计; 这个功能应该有现成的,apollo 组件可以配 namespace 等参数,按参数取默认订阅的配置
  • 先按当前方案做个 alpha版,后续 @unionhu 可以基于生产需求来改 alpha 版

@ZLBer
Copy link
Member

ZLBer commented May 13, 2022

干脆就静态配置我们就加载一次,动态配置搞成订阅塞到meta里,但这样缺点就是没法加用户自己的逻辑。

@seeflood
Copy link
Member

动态配置搞成订阅塞到meta里

这个是啥意思,是说组件配置的metadata 里,配想要订阅的 key,是这样么:

  "state": {
    "redis": {
      "metadata": {
        "redisHost": "localhost:6380",
        "redisPassword": ""
      },
      "subscriptionRef": [
        {
          "name": "apollo",
          "key": ["switchA","switchB"]
        }
      ]
    }
  },

@seeflood
Copy link
Member

会议结论

  1. 可能的业务场景:
  • 发短信
  • 长链转短链
  • mq
  1. 不需要配置中心下发配置了,希望xds下发(所以不需要 component 注入这种方案
  2. 时间节奏:
    争取8月中旬定下来配置下发的方案

@unionhu
Copy link
Author

unionhu commented Jul 27, 2022

如果type_config 的type是StringValue或ANY是可以下发一段Json或一个Object,如下例:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: flowcontrol
  namespace: brpc-rank 
spec:
  configPatches:
    - applyTo: NETWORK_FILTER
      match:
        context: SIDECAR_INBOUND 
      patch:
        operation: INSERT_BEFORE
        value:
          name: testfilter
          typed_config:
            "@type": type.googleapis.com/google.protobuf.StringValue
            value: |
              {
              	"app_name": "ratelimiter",
              	"log_path": "stdout",
              	"global_switch": true,
              	"monitor": true,
              	"limit_key_type": "PATH",
              	"action": {
              		"status": 2004,
              		"body": ""
              	},
              	"rules": [{
              		"resource": "searchEngine.rankService",
              		"limitApp": "searchEngine",
              		"threshold": 1000,
              		"grade": 1,
              		"strategy": 0,
              		"StatIntervalInMs": 500
              	}],
              	"system_rules": [{
              		"metricType": 4,
              		"strategy": 0,
              		"triggerCount": 0.85
              	}]
              }

看了一下mosn加载xDS的逻辑,也是在LDS请求后,拿到了listener进行处理,目前看只支持http的listener,能不能新增一个特别的listener,接受multi runtime的所有配置,专门传递给layotto使用

func HandleListenerResponse(resp *envoy_service_discovery_v3.DiscoveryResponse) []*envoy_config_listener_v3.Listener {
	listeners := make([]*envoy_config_listener_v3.Listener, 0, len(resp.Resources))
	for _, res := range resp.Resources {
		listener := &envoy_config_listener_v3.Listener{}
		if err := ptypes.UnmarshalAny(res, listener); err != nil {
			log.DefaultLogger.Errorf("ADSClient unmarshal listener fail: %v", err)
			continue
		}
		listeners = append(listeners, listener)
	}
	return listeners
}

@seeflood
Copy link
Member

听起来可行? @ZLBer @wenxuwan @rayowang @zhenjunMa @nejisama 帮忙一起看看哈

@nejisama
Copy link
Contributor

没看懂 不知道说的什么

@seeflood
Copy link
Member

@nejisama 我理解核心思想是:

  1. 通过xDS 里面的 envoyFilter 下发一个字符串(这个字符串就是layotto配置),示例是上面的 配置下发通道与配置热加载 #500 (comment)
  2. 这个字符串下发到 mosn 后,怎么传递给layotto ?现在卡在这个问题上了

@nejisama
Copy link
Contributor

xds 下发字符串的通道是什么?还是新的一种新的,和listener(lds)、cluster(cds)并列的资源类型?还是用某个资源的字段做特殊的解析?

@nejisama
Copy link
Contributor

上面例子里 就是一个filter配置,那就是转换成一个标准的filter实现,比如是一个flowcontrol 的filter 那就只能转换成一个 对应实现的flowcontrol 的filter 。 如果是这样 那对应实现相关的filter就可以了

@seeflood
Copy link
Member

seeflood commented Jul 28, 2022

@unionhu 最新进展,不用改控制面的方案:

  1. 可以通过 udpa 里的 typedstruct 类型下发配置; mosn 处理下发配置的代码在 convertUdpaTypedStructConfig 函数里,可以搜下
    istio 文档里也有个示例,见 https://istio.io/latest/docs/reference/config/networking/envoy-filter/
    image

  2. mosn 提供了扩展机制,去解析自己下发的配置。参考 PR: support xds for stream filter mosn#2081
    用这种方式,下发的配置数据可以自己用 proto 描述

各位帮忙一起看下哈

@unionhu
Copy link
Author

unionhu commented Jul 29, 2022

@unionhu 最新进展,不用改控制面的方案:

  1. 可以通过 udpa 里的 typedstruct 类型下发配置; mosn 处理下发配置的代码在 convertUdpaTypedStructConfig 函数里,可以搜下
    istio 文档里也有个示例,见 https://istio.io/latest/docs/reference/config/networking/envoy-filter/
    image
  2. mosn 提供了扩展机制,去解析自己下发的配置。参考 PR: support xds for stream filter mosn#2081
    用这种方式,下发的配置数据可以自己用 proto 描述

各位帮忙一起看下哈

但是这个并没有告知怎么传递给layotto的grpc filter

@seeflood
Copy link
Member

@unionhu 我们有两个问题要解决:

  1. 怎么通过 XDS 把配置项传下来,并且不用改控制面代码
    上面的PR 演示了如何解决这个问题

  2. Layotto 如果做配置热加载
    这个我理解得让 Layotto 单独开一个接口

@unionhu
Copy link
Author

unionhu commented Jul 29, 2022

是的,第一个问题好处理
第二个问题mosn接收到配之后能不能触发一个event,layotto订阅了event 拿到配置进行热加载

@seeflood
Copy link
Member

seeflood commented Jul 29, 2022

关于 "2. Layotto 如果做配置热加载",有俩方案:

方案 A. 通过 XDS 做配置变更后,mosn 下次接受新连接、创建新的 networkfliter 时,会自动用新配置创建
image
好处:啥代码都不用改,mosn 自动帮忙搞定
缺点: 旧连接不受影响

@unionhu 如果先用这种方案呢?

方案 B. 下发的 XDS 配置里,把 Layotto 相关的配置存到某个全局 map去,然后 Layotto 做个配置监听功能,监听这个全局 map,有变更则热加载

好处:轻量;旧连接也能更新

@unionhu
Copy link
Author

unionhu commented Jul 29, 2022

关于 "2. Layotto 如果做配置热加载",有俩方案:

方案 A. 通过 XDS 做配置变更后,mosn 下次接受新连接、创建新的 networkfliter 时,会自动用新配置创建 image 好处:啥代码都不用改,mosn 自动帮忙搞定 缺点: 旧连接不受影响

@unionhu 如果先用这种方案呢?

方案 B. 下发的 XDS 配置里,把 Layotto 相关的配置存到某个全局 map去,然后 Layotto 做个配置监听功能,监听这个全局 map,有变更则热加载

好处:轻量;旧连接也能更新

偏向方案B,能否你先设计,如果代码量不多,我看看能不能搞定

@seeflood
Copy link
Member

seeflood commented Jul 29, 2022

@unionhu 恩,按方案 B 去推演(把 Layotto 相关的配置存到某个全局 map去,然后 Layotto 做个配置监听功能,监听这个全局 map,有变更则热加载),其实就说回到了咱们一开始讨论的两种方案:

B1. 组件注入

比如支持把 config store 组件注入进 pubsub 组件
然后你就可以实现一个 XDS config store 组件,注入进 pubsub 组件

B2. Layotto 开一个抽象的 update 接口

见上面的讨论 #500 (comment)
image

两种方案都能满足你的需求,而且目前看来,两种方案将来都要做(现在就有用户想用 B1 方案)

@seeflood
Copy link
Member

seeflood commented Jul 29, 2022

@ZLBer Hi, 要不,继续搞 component-ref 这种组件注入的方案?
因为最近聊下来发现,其实这两种方案都需要……

@ZLBer
Copy link
Member

ZLBer commented Jul 30, 2022

@seeflood 可以的,我试图理解下

@nejisama
Copy link
Contributor

nejisama commented Aug 4, 2022

LDS更新旧连接的问题,由于layotto是grpc 应该可以考虑用goaway的方式通知旧连接断开,重新建新连接就可以了

@seeflood
Copy link
Member

seeflood commented Aug 6, 2022

LDS更新旧连接的问题,由于layotto是grpc 应该可以考虑用goaway的方式通知旧连接断开,重新建新连接就可以了

恩感觉这也是个思路。上次看mosn grpcFilter 的代码,发现其实注册进去的 handler 只会初始化、启动一次(如下图),也就是说每次下发新的 LDS 配置后,即使有新连接,grpcFilter 也不会按新配置重新加载 Layotto;所以按这个思路要新写个 Layotto 自己的 grpcFilter
image

如果发 goaway、让app 重连,会影响业务么?

@unionhu
Copy link
Author

unionhu commented Aug 9, 2022

component-ref

配置组件注入的方案预计几时能搞定?

@seeflood
Copy link
Member

seeflood commented Aug 9, 2022

component-ref

配置组件注入的方案预计几时能搞定?

cc @ZLBer
🐶

@seeflood
Copy link
Member

@unionhu 我在改启动生命周期的时候也遇到了配置下发的问题,见 #744 (comment)
我这两周搞下 "Layotto 开一个抽象的 update 接口" 这个方案 #500 (comment)
component-ref 方案看立斌时间

@ZLBer
Copy link
Member

ZLBer commented Aug 10, 2022

component-ref预计该周末提交pr

@seeflood
Copy link
Member

action:

  • 催下mosn 发版,方便发 layotto v0.5 @seeflood

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants