Higress 是基于阿里巴巴内部两年多的 Envoy Gateway 实践沉淀,以开源 Istio 与 Envoy 为核心构建的下一代云原生网关。Higress 实现了安全防护网关、流量网关、微服务网关三层网关合一,可以显著降低网关的部署和运维成本。
官网地址: https://higress.cn
GitHub 地址: https://github.com/alibaba/higress
Higress 作为云原生网关的核心优势如下:
易用性
“云原生”已经不再是一个新鲜词,但企业对云原生技术的学习使用成本仍有许多顾虑,对云原生新标准的追赶又有很多焦虑;
Higress 同时提供了本地安装/生产部署的 quickstart ,可以一键部署,并通过控制台操作快速上手;基于简单易用的控制台,Higress 可以封装 Ingress/Gateway API 的标准细节,根治技术追赶焦虑。
标准化
K8s 带来了云原生的路由标准 Ingress/Gateway API ,如同 POSIX 定义 Unix 可移植操作系统标准,历时 35 年经久不衰,云原生的路由标准的生命周期一定会远超过 K8s 本身;
Higress 结合阿里内部实践以及阿里云产品沉淀,积累了基于 Ingress API 的丰富的路由策略扩展能力,同时还兼容大部分 Nginx Ingress 能力,这些能力后续也将在 Gateway API 上支持。
高集成
企业内有大量传统架构部署的服务,会成为向云原生架构演进的技术负担,要求云原生网关具备对接异构服务架构的能力;
基于 Higress 提供的多种服务发现机制,网关路由不仅可以转发到 K8s 服务,也可以直接配置 IP 转发到到物理机上的服务;基于 Nacos/ZooKeeper 等注册中心对接,还可以轻松实现 Spring Cloud 和 Dubbo 微服务的路由,无论其是否部署在 K8s 内。
易扩展
基于扩展机制进行二次开发的能力,是云原生网关在不同业务场景下都能适配落地的关键;
Higress 提供了灵活的插件扩展机制,目前插件市场已经推出多个官方插件,并支持用户通过控制台直接上传自己开发的插件,同时开源社区的插件市场生态也在不断建设中。
热更新
传统 Nginx 更新规则需要 reload 会导致链接抖动,导致流量损失,对实时通信、视频、IOT 无法容忍;
对于路由规则,Wasm 插件逻辑更新,以及证书改动等等,Higress 全部支持热更新,不会造成任何连接抖动。
Higress 全局配置
下文将介绍 Higress 全局配置控制面实现机制,Higress 数据面基于 Envoy 是 C++ 语言开发,控制面则是基于 Go 语言开发。控制面的开发上手门槛还是很低的,欢迎有更多同学参与贡献。社区也有多个学习兴趣小组,欢迎加入学习: https://github.com/alibaba/higress/issues?q=is%3Aopen+is%3Aissue+label%3A%22learning+to+share%22
Higress 有个全局配置 ConfigMap 对象 higress-config ,参考配置如下:
apiVersion: v1
data:
higress: |-
tracing:
enable: true
sampling: 100
timeout: 500
skywalking:
service: skywalking-oap-server.op-system.svc.cluster.local
port: 11800
...
...
kind: ConfigMap
metadata:
name: higress-config
namespace: higress-system
具体配置说明请参考 Higress 全局配置说明文档,
本文介绍以 Tracing 为例,详细说明 Tracing 全局配置是如何转成 EnvoyFilter 和如何同时实现实时下发到 Higress Gateway 过程。
本文涉及整体架构流程、初始化过程和启动、higress-config 变更和处理流程、通知 XDSUpdater 、构建 EnvoyFilter 和下发以及如何扩展全局配置等内容。
整体架构流程
1. 整体架构
2. 核心组件
IngressConfig 是 Higress 一个核心结构体, 负责监控 Ingress ,McpBridge, Http2Rpc, WasmPlugin 等 k8s 资源, 同时集成 ConfigStore Interface ,通过 List 接口下发 VirtualService, DestinationRule, EnvoyFilter, ServiceEntry, WasmPlugin 等 CR 资源。
ConfigmapMgr 结构体负责整个核心流程,包括通过 Informer List/Watch 机制监控 higress-config 的变更,同时遍历 ItemControllers 下发变更通知,提供构建 EnvoyFilter 列表等功能。
TracingController 结构体负责具体的 Tracing 数据校验,构建 Tracing EnvoyFilter, 以及通过 ItemEventHandler 下发变更通知等。
HigressConfig 是 higress-config Configmap 所对应数据的结构体。
3. 核心流程
初始化过程
1. 初始化入口
初始化过程入口在 NewIngressConfig , 初始化 IngressConfig 时同时构建 HigressConfigController 和 ConfigmapMgr 。
// pkg/ingress/config/ingress_config.go
func NewIngressConfig(localKubeClient kube.Client, XDSUpdater model.XDSUpdater, namespace, clusterId string) *IngressConfig {
// ...
// 构建 controller 和 configmapMgr
higressConfigController := configmap.NewController(localKubeClient, clusterId, namespace)
config.configmapMgr = configmap.NewConfigmapMgr(XDSUpdater, namespace, higressConfigController, higressConfigController.Lister())
return config
}
2. HigressConfigController 初始化
通过 Higress 提供 NewCommonController 初始化 HigressConfigController 用于监听 higress-system 命名空间下 Configmap 的变化。
// pkg/ingress/kube/configmap/controller.go
type HigressConfigController controller.Controller[listersv1.ConfigMapNamespaceLister]
func NewController(client kubeclient.Client, clusterId string, namespace string) HigressConfigController {
informer := client.KubeInformer().Core().V1().ConfigMaps().Informer()
return controller.NewCommonController("higressConfig", client.KubeInformer().Core().V1().ConfigMaps().Lister().ConfigMaps(namespace),
informer, GetConfigmap, clusterId)
}
func GetConfigmap(lister listersv1.ConfigMapNamespaceLister, namespacedName types.NamespacedName) (controllers.Object, error) {
return lister.Get(namespacedName.Name)
}
3. ConfigmapMgr 初始化
ConfigmapMgr 初始化具体步骤如下:
// pkg/ingress/kube/configmap/controller.go
func NewConfigmapMgr(XDSUpdater model.XDSUpdater, namespace string, higressConfigController HigressConfigController, higressConfigLister listersv1.ConfigMapNamespaceLister) *ConfigmapMgr {
// 构建 ConfigmapMgr
configmapMgr := &ConfigmapMgr{
XDSUpdater: XDSUpdater,
Namespace: namespace,
HigressConfigController: higressConfigController,
HigressConfigLister: higressConfigLister,
higressConfig: atomic.Value{},
}
// 设置 HigressConfigController configmap 新增或者更新回调函数 configmapMgr.AddOrUpdateHigressConfig
configmapMgr.HigressConfigController.AddEventHandler(configmapMgr.AddOrUpdateHigressConfig)
// 设置 HigressConfig 结构体默认值
configmapMgr.SetHigressConfig(NewDefaultHigressConfig())
// 初始化 TracingController
tracingController := NewTracingController(namespace)
// 把 tracingController 添加到 configmapMgr itemControllers 里
configmapMgr.AddItemControllers(tracingController)
// 初始化 itemEventHandler , 同时遍历 itemControllers ,设置 itemEventHandler
configmapMgr.initEventHandlers()
// 返回 configmapMgr
return configmapMgr
}
启动
在 IngressConfig 添加 HigressConfigController Run() 和 HasSynced() 控制流程。
// pkg/ingress/config/ingress_config.go
func (m *IngressConfig) Run(stop
higress-config 变更和处理流程
1. configmapMgr 变更处理
ConfigmapMgr 通过收到 HigressConfigController 通知来处理变更请求。
具体变更流程如下:
// pkg/ingress/kube/configmap/controller.go
func (c *ConfigmapMgr) AddOrUpdateHigressConfig(name util.ClusterNamespacedName) {
// 只监听 higress-system 命名空间下 name 为 higress-config Configmap 的变化
if name.Namespace != c.Namespace || name.Name != HigressConfigMapName {
return
}
// ...
// 获取 higress-config 内容
higressConfigmap, err := c.HigressConfigLister.Get(HigressConfigMapName)
// 通过 yaml.Unmarshal 转成 HigressConfig
newHigressConfig := NewDefaultHigressConfig()
if err = yaml.Unmarshal([]byte(higressConfigmap.Data[HigressConfigMapKey]), newHigressConfig); err != nil {
IngressLog.Errorf("data:%s, convert to higress config error, error: %+v", higressConfigmap.Data[HigressConfigMapKey], err)
return
}
// ...
// 遍历 ItemControllers, 校验配置是否合法
for _, itemController := range c.ItemControllers {
if itemErr := itemController.ValidHigressConfig(newHigressConfig); itemErr != nil {
IngressLog.Errorf("configmap %s controller valid higress config error, error: %+v", itemController.GetName(), itemErr)
return
}
}
// 和上次比对这次数据是否有变更
oldHigressConfig := c.GetHigressConfig()
result, _ := c.CompareHigressConfig(oldHigressConfig, newHigressConfig)
// ...
// 如果数据有变更,就遍历 ItemControllers 通知每个 itemController 数据有变更,同时保存这次变更到本地。
if result == ResultReplace || result == ResultDelete {
for _, itemController := range c.ItemControllers {
IngressLog.Infof("configmap %s controller AddOrUpdateHigressConfig", itemController.GetName())
if itemErr := itemController.AddOrUpdateHigressConfig(name, oldHigressConfig, newHigressConfig); itemErr != nil {
IngressLog.Errorf("configmap %s controller AddOrUpdateHigressConfig error, error: %+v", itemController.GetName(), itemErr)
}
}
// 保存这次变更
c.SetHigressConfig(newHigressConfig)
}
}
2. TracingController 变更处理
TracingController 变更处理就比较简单:
// pkg/ingress/kube/configmap/tracing.go
func (t *TracingController) AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error {
// ...
// 检查 Tracing 部分数据是否有变更
result, _ := compareTracing(old.Tracing, new.Tracing)
// 如果有变更,DeepCopy 一份 Tracing 数据保存到本地,同时通过 eventHandler 下发变更通知
switch result {
case ResultReplace:
if newTracing, err := deepCopyTracing(new.Tracing); err != nil {
IngressLog.Infof("tracing deepcopy error:%v", err)
} else {
t.SetTracing(newTracing)
IngressLog.Infof("AddOrUpdate Higress config tracing")
t.eventHandler(higressTracingEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressTracingEnvoyFilterName)
}
case ResultDelete:
t.SetTracing(NewDefaultTracing())
IngressLog.Infof("Delete Higress config tracing")
t.eventHandler(higressTracingEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressTracingEnvoyFilterName)
}
return nil
}
通知 XDSUpdater
在 ConfigmapMgr 初始化时候调用 configmapMgr.initEventHandlers(), 这个 func 会创建 ItemEventHandler, 同时遍历 ItemControllers 设置 ItemEventHandler 。
// pkg/ingress/kube/configmap/config.go
type ItemEventHandler = func(name string)
// pkg/ingress/kube/configmap/controller.go
func (c *ConfigmapMgr) initEventHandlers() error {
itemEventHandler := func(name string) {
c.XDSUpdater.ConfigUpdate(&model.PushRequest{
Full: true,
ConfigsUpdated: map[model.ConfigKey]struct{}{{
Kind: gvk.EnvoyFilter,
Name: name,
Namespace: c.Namespace,
}: {}},
Reason: []model.TriggerReason{ModelUpdatedReason},
})
}
for _, itemController := range c.ItemControllers {
itemController.RegisterItemEventHandler(itemEventHandler)
}
return nil
}
这里 XDSUpdater 是从 IngressConfig 初始化传入,XDSUpdater.ConfigUpdate() 用于更新通知下发。
进一步跟踪可以发现在 Higress controller server 启动时执行 s.initXdsServer 函数创建 s.xdsServer ,具体逻辑不在本文讨论范围, 有兴趣可以进一步阅读源码。
// pkg/bootstrap/server.go
func NewServer(args *ServerArgs) (*Server, error) {
// ...
s := &Server{
ServerArgs: args,
httpMux: http.NewServeMux(),
environment: e,
readinessProbes: make(map[string]readinessProbe),
server: server.New(),
}
s.environment.Watcher = mesh.NewFixedWatcher(&v1alpha1.MeshConfig{})
s.environment.Init()
initFuncList := []func() error{
s.initKubeClient,
s.initXdsServer,
s.initHttpServer,
s.initConfigController,
s.initRegistryEventHandlers,
s.initAuthenticators,
}
for _, f := range initFuncList {
if err := f(); err != nil {
return nil, err
}
}
// ...
return s, nil
}
// pkg/bootstrap/server.go
func (s *Server) initXdsServer() error {
log.Info("init xds server")
s.xdsServer = xds.NewDiscoveryServer(s.environment, nil, PodName, PodNamespace, s.RegistryOptions.KubeOptions.ClusterAliases)
// ...
return s.initGrpcServer()
}
构建和下发 EnvoyFilters
1. IngressConfig List 下发 EnvoyFilters 列表
IngressConfig List 用于 VirtualService, DestinationRule, EnvoyFilter, ServiceEntry, WasmPlugin 等 CR 资源下发, 这里主要关注 EnvoyFilter CR 资源下发。
// pkg/ingress/config/ingress_config.go
func (m *IngressConfig) List(typ config.GroupVersionKind, namespace string) ([]config.Config, error) {
if typ != gvk.Gateway &&
typ != gvk.VirtualService &&
typ != gvk.DestinationRule &&
typ != gvk.EnvoyFilter &&
typ != gvk.ServiceEntry &&
typ != gvk.WasmPlugin {
return nil, common.ErrUnsupportedOp
}
// ...
if typ == gvk.EnvoyFilter {
m.mutex.RLock()
defer m.mutex.RUnlock()
var envoyFilters []config.Config
// 调用 ConfigmapMgr ConstructEnvoyFilters 获取需要下发 EnvoyFilter 列表
configmapEnvoyFilters, err := m.configmapMgr.ConstructEnvoyFilters()
if err != nil {
IngressLog.Errorf("Construct configmap EnvoyFilters error %v", err)
} else {
for _, envoyFilter := range configmapEnvoyFilters {
envoyFilters = append(envoyFilters, *envoyFilter)
}
IngressLog.Infof("Append %d configmap EnvoyFilters", len(configmapEnvoyFilters))
}
if len(envoyFilters) == 0 {
IngressLog.Infof("resource type %s, configs number %d", typ, len(m.cachedEnvoyFilters))
return m.cachedEnvoyFilters, nil
}
// 需要下发 configmap EnvoyFilter 列表 和 m.cachedEnvoyFilters 列表聚合一下下发
envoyFilters = append(envoyFilters, m.cachedEnvoyFilters...)
IngressLog.Infof("resource type %s, configs number %d", typ, len(envoyFilters))
return envoyFilters, nil
}
}
调用 ConfigmapMgr ConstructEnvoyFilters 获取需要下发 EnvoyFilter 列表, 同时和 m.cachedEnvoyFilters 列表聚合一下再下发。
这里 m.cachedEnvoyFilters 是在构建 VirtualService 时生成,有兴趣可以进一步阅读 IngressConfig 源码。
2. ConfigmapMgr 构建 EnvoyFilter 列表
这里比较简单,遍历一下 ItemControllers ,聚合每个 itemController 返回的 EnvoyFilters.
// /pkg/ingress/kube/configmap/controller.go
func (c *ConfigmapMgr) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
for _, itemController := range c.ItemControllers {
IngressLog.Infof("controller %s ConstructEnvoyFilters", itemController.GetName())
if itemConfigs, err := itemController.ConstructEnvoyFilters(); err != nil {
IngressLog.Errorf("controller %s ConstructEnvoyFilters error, error: %+v", itemController.GetName(), err)
} else {
configs = append(configs, itemConfigs...)
}
}
return configs, nil
}
3. TracingController 构建 EnvoyFilters
这里就比较简单,根据保存的 Tracing 数据构建对应的 EnvoyFilter
// pkg/ingress/kube/configmap/tracing.go
func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
// ...
tracingConfig := t.constructTracingTracer(tracing, namespace)
if len(tracingConfig) == 0 {
return configs, nil
}
config := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressTracingEnvoyFilterName,
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(tracingConfig),
},
},
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(`{
"name":"envoy.filters.http.router",
"typed_config":{
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
"start_child_span": true
}
}`),
},
},
},
},
}
configs = append(configs, config)
return configs, nil
}
如何扩展全局配置
1. HigressConfig 结构体添加对应的扩展配置
type HigressConfig struct {
Tracing *Tracing `json:"tracing,omitempty"`
// 在这里添加对应的数据结构来扩展配置
}
2. 增加扩展配置默认值
// pkg/ingress/kube/configmap/config.go
func NewDefaultHigressConfig() *HigressConfig {
higressConfig := &HigressConfig{
Tracing: NewDefaultTracing(),
// 在这里增加扩展配置默认值
}
return higressConfig
}
3. 实现 ItemController interface
type ItemController interface {
GetName() string
AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error
ValidHigressConfig(higressConfig *HigressConfig) error
ConstructEnvoyFilters() ([]*config.Config, error)
RegisterItemEventHandler(eventHandler ItemEventHandler)
}
4. 初始化扩展配置,同时添加到 ItemControllers
func NewConfigmapMgr(XDSUpdater model.XDSUpdater, namespace string, higressConfigController HigressConfigController, higressConfigLister listersv1.ConfigMapNamespaceLister) *ConfigmapMgr {
// ...
tracingController := NewTracingController(namespace)
configmapMgr.AddItemControllers(tracingController)
// ...
// 在这里初始化扩展配置,同时添加到 ItemControllers
configmapMgr.initEventHandlers()
return configmapMgr
}
参与社区贡献
Higress 开源贡献小组正在火热招募贡献者。早期参与开源更容易成为项目 Committer ,并有更多机会成为 Higress PMC(Project Management Committee) 的一员,参与主导 Higress 社区的前进方向。
欢迎加入 Higress 社区群:
higress, config, configmap, tracing