乐趣区

关于网络安全:MOSN-反向通道详解

文|郑泽超(GitHub ID:CodingSinger )

字节跳动高级工程师

热衷于微服务和 ServiceMesh 开源社区

本文 6802 字,浏览 15 分钟

Part.1– 贡献者前言

说起来十分的抓马,过后和 MOSN 的相遇是在给于雨负责的开源我的项目 Dubbo-go 奉献代码那阵。在本人顺利当上了 Dubbo 开源社区的 Committer 之后,心想着能更深刻的学习 Golang 语言,机缘巧合之下碰到了 MOSN 的老大哥烈元 (也是元总领我进了 MOSN 社区的大门)

作为一款指标对齐 Envoy 的高性能可扩大平安网络代理,MOSN 反对的生态能力更贴近国内互联网公司的技术栈,并且对新性能的响应也很迅速。其次 MOSN 有着很多值得借鉴的奇妙设计和进阶的应用技巧,能充沛满足本人在工作之外深刻学习 Golang 语言的诉求。

目前,我在社区里陆续参加了 EDF Scheduler、LAR、WRR 负载平衡、DSL 路由能力、UDS Listener、Plugin 模式的 Filter 扩大以及反向通道等一些比拟大的 feature 能力建设。再次感激雨哥、元总、鹏总、毅松等社区内一众大佬们帮我讲究计划并且帮我 Review 代码。

本文次要介绍之前新合入 master 分支的「反向通道」的应用场景和设计原理,欢送大家留言探讨。

MOSN 我的项目概述

MOSN(Modular Open Smart Network)是一款次要应用 Go 语言开发的云原生网络代理平台,由蚂蚁团体开源并通过双 11 大促几十万容器的生产级验证,具备 高性能 易扩大 的特点。MOSN 能够和 Istio 集成构建 Service Mesh,也能够作为独立的四、七层负载平衡、API Gateway、云原生 Ingress 等应用。

Part.2–MOSN 的反向通道实现

在云边协同的网络场景,通常都是单向网络,云侧节点无奈被动发动连贯与边缘节点通信。这种限度尽管在极大水平上保障了边缘节点的平安,但毛病也很显著,即只容许边缘节点被动发动拜访云端节点。

云边隧道旨在解决云端无奈被动拜访边缘节点的问题,其本质是一个反向通道 (后文统称为反向通道)。通过在边缘侧被动发动建连的形式与云端节点之间构建一条专用的全双工连贯,用来传输云端节点的申请数据和回传最终的响应后果。

目前例如 SuperEdge、Yurttunnel 等业界出名云边协同开源框架,对于云边通信的实现计划都是基于反向通道。

本文将着重介绍 MOSN 之上的反向通道运作流程和原理。总体架构如下所示 (图中箭头示意 TCP 建连反向)

整个运作流程能够简略概括为:

1. 边缘侧的 MOSN 实例 (后文统称为 Tunnel Agent) 在启动时 Tunnel Agent 相干服务协程。

2. 通过指定的动态配置或者动静服务发现形式拿到须要反向建连的私有云侧的 MOSN Server 地址列表 (后文统称 Tunnel Server),并且建设反向连贯。

3. 云侧的 Frontend 与 Tunnel Server 侧的转发端口进行数据交互,这部分数据会被托管到之前建设的反向连贯进行发送。

4. 边缘节点承受到申请之后,再将申请转发给理论的后端指标节点,回包过程则远路返回。

Part.3– 反向通道启动过程

MOSN Agent 通过 ExtendConfig 个性,在 MOSN 启动时加载和实现初始化 Tunnel Agent 的工作。

ExtendConfig 中定义 AgentBootstrapConfig 构造如下:

type AgentBootstrapConfig struct {
    Enable bool `json:"enable"`
    // The number of connections established between the agent and each server
    ConnectionNum int `json:"connection_num"`
    // The cluster of remote server
    Cluster string `json:"cluster"`
    // After the connection is established, the data transmission is processed by this listener
    HostingListener string `json:"hosting_listener"`
    // Static remote server list
    StaticServerList []string `json:"server_list"`

    // DynamicServerListConfig is used to specify dynamic server configuration
    DynamicServerListConfig struct {DynamicServerLister string `json:"dynamic_server_lister"`}

    // ConnectRetryTimes
    ConnectRetryTimes int `json:"connect_retry_times"`
    // ReconnectBaseDuration
    ReconnectBaseDurationMs int `json:"reconnect_base_duration_ms"`

    // ConnectTimeoutDurationMs specifies the timeout for establishing a connection and initializing the agent
    ConnectTimeoutDurationMs int    `json:"connect_timeout_duration_ms"`
    CredentialPolicy         string `json:"credential_policy"`
    // GracefulCloseMaxWaitDurationMs specifies the maximum waiting time to close conn gracefully
    GracefulCloseMaxWaitDurationMs int `json:"graceful_close_max_wait_duration_ms"`

    TLSContext *v2.TLSConfig `json:"tls_context"`
}

ConnectionNum:Tunnel Agent 和每个 Tunnel Server 建设的物理连贯数量。

HostingListener:指定 Agent 建设连贯之后托管的 MOSN Listener,即 Tunnel Server 发来的申请会由该 Listener 托管解决。

DynamicServerListConfig:动静 Tunnel Server 的服务发现相干配置,可通过自定义的服务发现组件提供动静的地址服务。

CredentialPolicy:自定义的连贯级别的鉴权策略配置。

TLSContext:MOSN TLS 配置,提供 TCP 之上通信的保密性和可靠性。

针对每个远端的 Tunnel Server 实例,Agent 对应一个 AgentPeer 对象,启动时除了被动建设 ConnectionNum 个反向通信连贯,还会额定建设一条旁路连贯,这条旁路连贯次要是用来发送一些管控参数,例如平滑敞开连贯、调整连贯比重。

func (a *AgentPeer) Start() {connList := make([]*AgentClientConnection, 0, a.conf.ConnectionNumPerAddress)
    for i := 0; i < a.conf.ConnectionNumPerAddress; i++ {
      // 初始化和建设反向连贯
        conn := NewAgentCoreConnection(*a.conf, a.listener)
        err := conn.initConnection()
        if err == nil {connList = append(connList, conn)
        }
    }
    a.connections = connList
    // 建设一个旁路管制连贯
    a.initAside()}

initConnection 办法进行具体的初始化残缺的反向连贯,采取指数退却的形式保障在最大重试次数之内建连胜利。

func (a *connection) initConnection() error {
    var err error
    backoffConnectDuration := a.reconnectBaseDuration

    for i := 0; i < a.connectRetryTimes || a.connectRetryTimes == -1; i++ {if a.close.Load() {return fmt.Errorf("connection closed, don't attempt to connect, address: %v", a.address)
        }
        // 1. 初始化物理连贯和传输反向连贯元数据
        err = a.init()
        if err == nil {break}
        log.DefaultLogger.Errorf("[agent] failed to connect remote server, try again after %v seconds, address: %v, err: %+v", backoffConnectDuration, a.address, err)
        time.Sleep(backoffConnectDuration)
        backoffConnectDuration *= 2
    }
    if err != nil {return err}
    // 2. 托管 listener
    utils.GoWithRecover(func() {ch := make(chan api.Connection, 1)
        a.listener.GetListenerCallbacks().OnAccept(a.rawc, a.listener.UseOriginalDst(), nil, ch, a.readBuffer.Bytes(), []api.ConnectionEventListener{a})
    }, nil)
    return nil
}

该办法次要步骤:

1. a.init() 办法会调用 initAgentCoreConnection 办法初始化物理连贯并实现建连交互过程。Tunnel Server 通过 Agent 传输的元数据信息,进行治理反向连贯。具体的交互过程和协定后文会细讲。

2. 建连胜利之后,Tunnel Agent 托管 raw conn 给指定的 Listener。之后该 raw conn 的生命周期由该 Listener 全权治理,并且齐全复用该 Listener 的能力。

其定义了初始化反向连贯的交互流程,具体代码细节能够看:

pkg/filter/network/tunnel/connection.go:250,本文不开展技术细节。

Part.4– 交互过程

目前 MOSN 的反向通道只反对了 raw conn 的实现,因而定义了一套简单明了的网络通信协定。

次要包含:

协定魔数:2 byte;

协定版本:1 byte;

主体构造类型:1 byte,包含初始化、平滑敞开等;

主体数据长度:2 byte;

JSON 序列化的主体数据。

MOSN 反向通道残缺的生命周期交互过程:

建连过程中由 Tunnel Agent 被动发动,并且在 TCP 连贯建设胜利 (TLS 握手胜利) 之后,将反向建连的要害信息 ConnectionInitInfo 序列化并传输给对端 Tunnel Server,该构造体定义了反向通道的元数据信息。

// ConnectionInitInfo is the basic information of agent host,
// it is sent immediately after the physical connection is established
type ConnectionInitInfo struct {
    ClusterName      string                 `json:"cluster_name"`
    Weight           int64                  `json:"weight"`
    HostName         string                 `json:"host_name"`
    CredentialPolicy string                 `json:"credential_policy"`
    Credential       string                 `json:"credential"`
    Extra            map[string]interface{} `json:"extra"`}

Tunnel Server 承受该元数据信息之后,次要工作包含:

1. 如果有设置自定义鉴权形式,则进行连贯鉴权;

2. clusterManager 将该连贯退出到指定的 ClusterSnapshot 并回写建连后果。

此时建连过程才算实现。

func (t *tunnelFilter) handleConnectionInit(info *ConnectionInitInfo) api.FilterStatus {
    // Auth the connection
    conn := t.readCallbacks.Connection()
    if info.CredentialPolicy != "" {// 1. 自定义鉴权操作,篇幅起因省略}
    if !t.clusterManager.ClusterExist(info.ClusterName) {writeConnectResponse(ConnectClusterNotExist, conn)
        return api.Stop
    }
    // Set the flag that has been initialized, subsequent data processing skips this filter
    err := writeConnectResponse(ConnectSuccess, conn)
    if err != nil {return api.Stop}
    conn.AddConnectionEventListener(NewHostRemover(conn.RemoteAddr().String(), info.ClusterName))
    tunnelHostMutex.Lock()
    defer tunnelHostMutex.Unlock()
    snapshot := t.clusterManager.GetClusterSnapshot(context.Background(), info.ClusterName)
    // 2. host 退出到指定的 cluster
    _ = t.clusterManager.AppendClusterTypesHosts(info.ClusterName, []types.Host{NewHost(v2.Host{
        HostConfig: v2.HostConfig{Address:    conn.RemoteAddr().String(),
            Hostname:   info.HostName,
            Weight:     uint32(info.Weight),
            TLSDisable: false,
        }}, snapshot.ClusterInfo(), CreateAgentBackendConnection(conn))})
    t.connInitialized = true
    return api.Stop
}

而后是通信过程,为了便于了解,以下图申请单向流转示意图举例:

在传统的 MOSN Sidecar 利用场景中,Frontend 发送的申请首先通过 Client-MOSN,而后通过路由模块,被动创立连贯 (虚线局部) 并流转到对端,经由 Server-MOSN biz-listener 解决转交给 Backend。

而在云边场景的反向通道实现中,Client MOSN (Tunnel Server) 在承受到对端 Tunnel Agent 发动创立反向通道的申请后,行将该物理连贯退出路由到对端 MOSN 的 cluster snapshot 中。从而 Frontend 的申请流量能由该反向通道流转到对端 MOSN,而因为 Tunnel Agent 侧把该连贯托管给了 biz-listener,则读写解决都由 biz-listener 进行解决,biz-listener 将解决完的申请再转发给真正的 Backend 服务。

Part.5– 总结和布局

本文次要介绍了 MOSN 反向通道的实现原理和设计思路。MOSN 作为高性能的云原生网络代理,心愿反向通道的能力能更加无效地反对其作为云边协同场景中承接东西向流量的职责。

当然,后续咱们也会持续做一系列的拓展反对,包含但不限于:

1. 反向通道反对 gRPC 实现,gRPC 作为云原生时代最通用的服务通信框架,自身内置了各种弱小的治理能力;

2. 联合更多云原生场景,内置更加通用的 Tunnel Server 动静服务发现能力组件;

3. 更多的配套自动化运维和部署工具。

理解更多…

MOSN Star 一下✨:
https://github.com/mosn/mosn

快来和咱们一起共建吧🧸

本周举荐浏览

Go 原生插件应用问题全解析

MOSN 构建 Subset 优化思路分享

MOSN 文档使用指南

MOSN 1.0 公布,开启新架构演进

欢送扫码关注咱们的公众号:

退出移动版