第 02 期 / 共 10 期

控制面启动与初始化

cmd/higress/main.go 一路追到 pkg/bootstrap/server.go 的最后一个 init。读完这一期你能在白板上完整画出 Higress 启动的 6 步主线。

L01

main.go 全文 32 行

package main
import (
    "fmt"; "os"
    "istio.io/pkg/log"
    "github.com/alibaba/higress/v2/pkg/cmd"
)
func main() {
    log.EnableKlogWithCobra()
    if err := cmd.GetRootCommand().Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err); os.Exit(1)
    }
}

它只是一个 thin shell:日志接管 + 启动 Cobra root 命令。所有逻辑都被推到 pkg/cmd,让 main 可被替换、可被测试。

思考:为什么主函数代码越少越好?提示:可测试、可复用为库。
L02

EnableKlogWithCobra

该函数把 K8s 的 klog 输出桥接到 Istio 的 istio.io/pkg/log,并把 klog 的 --v-vmodule 等 flag 注册到 cobra root。这是 Higress 与 client-go 共用日志栈的关键。

它的意义在于:你可以用 Istio 的 --log_output_level 控制所有依赖库日志。

思考:调试时想看 client-go 详细日志,你应该传什么 flag?答:--log_output_level=klog:debug 这类。
L03

GetRootCommand

// pkg/cmd/root.go
func GetRootCommand() *cobra.Command {
    cmd := &cobra.Command{Use: "higress", Short: "Higress",
        Long: "Next-generation Cloud Native Gateway"}
    cmd.AddCommand(getServerCommand())
    cmd.AddCommand(getVersionCommand())
    return cmd
}

整棵命令树只有两枝:serveversion。结构简单到极致——所有复杂度都在 getServerCommand 的 RunE 里。

思考:如果要加一个 higress debug 子命令,从哪个文件开始?
L04

server 子命令

pkg/cmd/server.go 是控制面真正的入口。三件事:

  1. 把 cobra flag 绑到 serverArgs *bootstrap.ServerArgs
  2. RunE 里调 serverProvider(serverArgs)bootstrap.NewServer 得到 Server。
  3. server.Start(stop),再 waitForMonitorSignal 阻塞直到收到 SIGTERM。
serverProvider = func(args *bootstrap.ServerArgs) (bootstrap.ServerInterface, error) {
    return bootstrap.NewServer(args)
}

这层间接抽出 serverProvider 是为了测试可替换。

思考:为什么要把 serverProvider 弄成包级变量?提示:pkg/cmd/server_test.go
L05

version 子命令

用法:higress version -o json。实现在 pkg/cmd/version/,输出二进制版本、git commit、build date,并尝试连接 xDS 拉取数据面版本。这是排查"我装的是哪个版本"问题的官方途径。

思考:构建时如何把 git commit 注入二进制?提示:-ldflags "-X version.gitRevision=..."
L06

ServerArgs 参数对象

定义于 pkg/bootstrap/server.go。它聚合了所有命令行参数:

type ServerArgs struct {
    XdsOptions       XdsOptions       // 推送 debounce/EDS 控制
    RegistryOptions  RegistryOptions  // K8s / FileDir 等
    KeepaliveOptions *keepalive.Options
    HTTPAddr, GRPCAddr, MonitoringAddr string
    Namespace        string
    GatewaySelectorKey, GatewaySelectorValue string
    EnableAutomaticHttps bool
    // ...
}

这是经典的"Parameter Object"模式,让 server 包能被库式调用。

思考:如果新增一个 flag,应该改哪几个文件?答:pkg/cmd/server.go 绑定 + ServerArgs 加字段 + 真正消费它的地方。
L07

ServerInterface 抽象

type ServerInterface interface {
    Start(stop <-chan struct{}) error
    WaitUntilCompletion()
}

把 NewServer 返回类型抽象为接口,让 pkg/cmd/server.go 不直接依赖 *Server,方便测试替换。这是依赖倒置的微缩样本。

思考:单元测试中如何用 fake server 替换真 server?
L08

NewServer 主线

func NewServer(args *ServerArgs) (*Server, error) {
    s := &Server{...}
    s.initKubeClient(args)
    s.initMeshConfig()
    s.initConfigController()
    s.initRegistryEventHandlers()
    s.initXdsServer()
    s.initGrpcServer()
    s.initAuthenticators()
    s.initAutomaticHttps()
    s.initHttpServer()
    return s, nil
}

这就是控制面的"启动 6+步"骨架——把每一步当黑盒,就能在 5 分钟内读完 main path。

思考:这些 init 函数之间有强先后依赖吗?答:有部分,例如 KubeClient 必须先于 ConfigController。
L09

initKubeClient

位于 server.go 第 411 行附近。基于 in-cluster 或 kubeconfig 创建 istiokube.Client,并包一层 Higress 自己的 higresskube.Client(pkg/kube/client.go)。

关键点:Higress 用的是 Istio 的 multi-client 封装,一份 Client 持有 typed kube + dynamic + istio + 自定义 CRD 多个 clientset。

思考:本地开发想跑 controller 怎么传 kubeconfig?答:--kubeconfig ~/.kube/config + --master ...
L10

MeshConfig Watcher

Higress 复用 Istio 的 meshwatcher:监听一个 ConfigMap(默认 higress-config)里的 mesh config YAML,变更时把新配置 hot reload 到 environment.Mesh

这是 Pilot 推送语义的关键依赖:Mesh 决定了默认 sidecar 行为、扩展协议等。

思考:把 mesh config 放 ConfigMap 而不是 CRD 的原因?提示:兼容历史、便于 Helm 模板化。
L11

initConfigController

核心:构造一个聚合 ConfigStore,把 K8s 原生 CRD store 与 Higress 自己的 IngressTranslation 合并:

stores := []model.ConfigStoreController{}
stores = append(stores, kubeCRDStore)
stores = append(stores, ingressTranslation)
configController = configaggregate.MakeWriteableCache(stores, nil)

对 Pilot 来说看到的是一份 ConfigStore;对 Higress 来说,IngressConfig 可以注入任意自定义合成的 VirtualService / DR / EnvoyFilter。这是 Higress 改造 Istio 最巧妙的接缝。

思考:configaggregate 聚合多个 store 时,重名资源怎么去重?
L12

initRegistryEventHandlers

把 ServiceRegistry 的事件(Service/Endpoint 增删改)转发给 xDS:变更触发 push。具体做法是注册 ServiceHandlerWorkloadHandler 到聚合 ServiceRegistry,回调里调用 XDSServer.ConfigUpdate

思考:注册中心实例频繁抖动会引发推送风暴吗?防护手段?答:debounce + 增量 EDS。
L13

initXdsServer

构造 xds.NewDiscoveryServer(environment, generators, ...)environment 包了 ConfigStore + ServiceRegistry + Mesh + PushContext。generators 是按资源类型分发的 xDS 生成器(LDS/RDS/CDS/EDS/SDS 等)。

Higress 在此阶段可以注入自定义 generator(如对 WasmPlugin 特殊处理),但大多数情况复用 Istio 的实现。

思考:Higress 如果想把 EnvoyFilter 渲染成 LDS 的 typed_config,需要 hack 哪个 generator?
L14

initGrpcServer

初始化一个 gRPC server(默认 :15010 明文 / :15012 双向 TLS),把 xds.DiscoveryServer 注册进去,再加上 grpc_prometheusreflection

istiogrpc.NewGrpcServer(...) // 复用 Istio 的辅助函数
prometheus.Register(grpcSrv)
reflection.Register(grpcSrv)
思考:为什么把 reflection 注册到 gRPC server?答:方便 grpcurl 调试。
L15

initAuthenticators

为 xDS 连接配置认证器列表(kube SAToken / JWT / clientCert)。Higress 默认开启 kubeauth.NewKubeJWTAuthenticator,让 Envoy sidecar 用 ServiceAccount Token 鉴权。

思考:Higress Gateway 与 Higress Controller 共部署时这一步是否还有意义?
L16

initAutomaticHttps

对接 pkg/cert 的 CertManager:开启自动证书签发(ACME 或自签)。具体实现见第 10 期。这里只看到一个轻量入口:根据 ServerArgs.EnableAutomaticHttps 是否启动。

思考:自动证书与手动 Secret 共存时谁优先?
L17

initHttpServer 调试入口

默认 :8080 暴露调试端点:

  • /ready readyHandler — 等所有 cache synced
  • /registry/status registryWatcherStatusHandler — 各注册中心健康度
  • Pilot 自带的 /debug/... 路径(如 /debug/configz/debug/syncz
思考:线上排查"Envoy 配置是不是最新",你访问哪个 URL?答:/debug/syncz
L18

Start 与 waitForCacheSync

func (s *Server) Start(stop <-chan struct{}) error {
    s.server.RunComponent(...)            // 一组 lifecycle 组件
    if !s.waitForCacheSync(stop) { ... }   // 所有 informer cache 完成
    // 启动 HTTP/gRPC listener,开始接受 ADS 连接
    return nil
}

waitForCacheSync 阻塞到所有 Lister 列表完成首次 List & Watch;否则 xDS 推送的内容是空的会引发 Envoy 误判。

思考:cache 永远 sync 不上的常见原因?答:RBAC 不够、Watch 被 kube-apiserver 限流。
L19

优雅退出 waitForShutDown

收到 SIGTERM 后:

  1. 停接受新的 ADS 连接(关闭 listener);
  2. 给 Envoy 留一段时间消费已发送的资源;
  3. 退出 informer / workqueue;
  4. 退出进程。

这是控制面"不重启 Envoy"零中断升级的基础。

思考:滚动升级 Higress 控制面 Pod,数据面会断流吗?答:通常不会,Envoy 缓存最后一份配置。
L20

环境变量与 Feature Flag

Higress 复用 Istio 的 env.Register 模式,把 feature flag 集中在 pkg/cmd/server.go 顶部:

keepConfigLabels = env.Register(
    "CONTROLLER_KEEP_XDS_CONFIG_LABELS", true,
    "If enabled, Higress Controller will keep all the labels ...").Get()

所有环境变量在 /debug/info 类页面可见,便于运维核对。

本期收尾:你现在能在白板上画出 Higress NewServer 的 6+步。下一期我们走进 Istio Pilot 看 xDS 怎么把 ConfigStore 推到 Envoy。