微服务边车模式深度解析:赋能云原生应用的终极指南(自己搞一个简单SideCar?)

什么是SideCar?

Sidecar模式定义:

Sidecar 模式是一种常用于微服务架构中的设计模式,该模式允许将应用程序的核心功能与辅助功能(如日志记录、监控、配置管理、网络通信等)分离开来。在这种设计模式中,每个微服务主容器旁边都有一个"边车"(Sidecar)容器运行辅助功能,就像摩托车旁的边车一样,Sidecar 容器和主容器共享同一个生命周期和网络空间。

你专心开车,其他交给我。

整体Sidecar就是这样:

总结:把非核心业务杂七杂八的东西分离出来,微服务的关注点尽可能只在业务上面,减少业务开发的复杂度。

Sidecar主要干啥?

Sidecar模式通常会处理以下范围的功能:

  1. 网络通信

    • 代理和管理微服务之间的通信,例如用来处理进出的网络流量、服务发现、负载均衡、断路、重试和超时。
  2. 安全性

    • 实现安全协议,如TLS加密、认证、授权和密钥管理。
  3. 监控与日志记录

    • 收集和导出日志、性能度量指标和追踪信息,以供监控系统使用。
  4. 配置管理

    • 动态加载和管理配置数据,而无需重启应用程序。
  5. 故障处理和调试

    • 提供调试支持,记录崩溃报告和性能瓶颈。
  6. 健康检查

    • 周期性地检查主服务的健康,并可实施自我修复措施。
  7. API 网关或接口转换

    • 处理服务的REST或gRPC API,提供API网关功能,或者对老旧系统进行接口转换。
  8. 服务依赖抽象

    • 管理服务间的数据库连接、队列等系统资源的访问。
  9. 请求/响应转换

    • 对进出的请求或响应数据进行转换或扩展。

Sidecar有哪些实现?

Sidecar在容器化和微服务领域有很多实现:

1. Envoy

Envoy是一个开源的、高性能的边缘和服务代理,专门设计用于云原生应用环境。由Lyft公司首次开发发布,后来成为了Cloud Native Computing Foundation(CNCF)的一部分。它的设计允许它同时在应用程序的边缘和内部使用,作为边缘网关、负载均衡器或服务网格的一部分,以提高微服务之间的通信质量和效率。

  1. Istio

    Istio是一个开源的服务网格,用于连接、保护、控制和观察在Kubernetes集群中运行的服务。它为微服务提供一种统一的方式来管理网络通信,安全性,策略和观察性,无需改变应用本身的代码。Istio利用了Envoy代理,它作为Sidecar部署在每个服务的Pod中,拦截所有进出Pod的网络通信。

  2. Linkerd

    Linkerd是一个开源的服务网格,专门为云原生应用设计,用以在微服务架构中提供可靠的服务间通信。它提供关键功能,如服务发现、负载均衡、故障处理、请求路由和安全性,在不改动服务代码的情况下,使得服务更容易监控、管理和控制。Linkerd是由Buoyant公司开源的,并且是Cloud Native Computing Foundation(CNCF)的一部分。

  3. ...

自己搞一个简单的?

我们个人势单力薄,搞一个像他们这么有高级功能的组件还是挺费时间的,况且我们还在温饱线上挣扎。

但是又想在这块搞点小事情,那么整个简单的。

先来个简单的需求

一个业务系统部署在k8s集群中,每个服务采用一个Pod一个服务的方式部署,每个Pod都有一个Service。

就这么简单来一下就可以了,Service关联Pod提供统一访问入口。

那根据SideCar的拓扑,我们可以在Service和Pod中容器之间加一个Sidecar来管理流量,像这样:

在这个拓扑中,所有流量先进SVC(逻辑上哈),然后转发给车最后转发给Pod。

车的具体需求?

  1. 管理HTTP流量(TCP的暂时不支持):只开发一个IP白名单规则。如果请求这个服务的IP不在这个白名单中,则返回404,否则转发该请求。
  2. 白名单规则配置:支持单个IP地址,支持CIDR地址。

需求就是这么简单,我们让SVC把流量先转给车,车处理了一下以后再转发给业务服务。

准备开干

首先开发一个HTTP服务器

使用Go built-in API就可以很简单开发实现一个HTTP服务器,就是使用net包:

复制代码
    http.HandleFunc("/", handler)
    err = http.ListenAndServe(conf.Server.Addr, nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }

COPY

几行代码搞定,额,这还是有点快,让人猝不及防。那这样我们的HTTP服务器就搞定了

其次 读取配置文件

我们用yaml来定义白名单配置,白名单中有IP和CIDR地址。

复制代码
whitelist:
  - 192.168.1.1
  - 192.168.1.2
  - 10.22.81.0/24
  - ::1

COPY

然后我们使用yaml解析库来读取它。

先来下载一下依赖库:

复制代码
go get gopkg.in/yaml.v2

COPY

然后使用库提供的api来读取yaml文件:

复制代码
type Conf struct {
    Server     Server     `yaml:"server"`
    Downstream Downstream `yaml:"downstream"`
    Whitelist  []string   `yaml:"whitelist"`
}

err = yaml.Unmarshal(source, &conf)
    if err != nil {
        log.Panic(err)
    }

COPY

设置HTTP的请求处理函数

我们请求处理函数从X-FORWARDED-FOR协议头来获取请求的Ip列表字符串,字符串以ip1,ip2,ip3,...来组成的。

我们只取第一个,就是上面示例的ip1,因为只有这个IP才表示请求方的IP,其他都是代理方的。

复制代码
func handler(w http.ResponseWriter, r *http.Request) {
    ipStr := r.Header.Get("X-FORWARDED-FOR")
    if ipStr == "" {
        ipStr = strings.Split(r.RemoteAddr, ":")[0]
    } else {
        ipStr = strings.TrimSpace(strings.Split(ipStr, ",")[0])
    }

    // 如果IP地址在白名单中,那么就转发请求
    if ipInWhitelist(ipStr, conf.Whitelist) {
        serveReverseProxy(conf.Downstream.URL, w, r)
        return
    }

    // 否则返回404
    http.NotFound(w, r)
}

COPY

转发请求

Go内建包net实现了ReverseProxy,我们直接使用httputil.NewSingleHostReverseProxy就可以直接转发HTTP请求,很快~。

复制代码
func serveReverseProxy(target string, w http.ResponseWriter, r *http.Request) {
    url, _ := url.Parse(target)

    proxy := httputil.NewSingleHostReverseProxy(url)

    r.URL.Host = url.Host
    r.URL.Scheme = url.Scheme
    r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
    r.Host = url.Host

    proxy.ServeHTTP(w, r)
}

COPY

这样我们的核心功能就实现了。

一点小细节

实现了核心流程以后,我们还得关注配置中IP的解析。

Go的net 包也提供了IP地址和CIDR地址的解析,我们只需要调用一下

  1. net.ParseIP
  2. net.ParseCIDR
    就可以实现IP解析了。

transformToCIDR 这个函数就给IP地址增加/32转换成CIDR形式,大家可以不用太关注。

复制代码
func ipInWhitelist(ipStr string, whitelist []string) bool {

    ip := net.ParseIP(ipStr)
    for _, cidr := range whitelist {
        _, ipNet, err := net.ParseCIDR(transformToCIDR(cidr))
        if err != nil {
            return false
        }
        if ipNet.Contains(ip) {
            return true
        }
    }
    return false
}

COPY

部署

开发这个简单的需求一百多行代码就可以搞定了,但是部署怎么办?

我们在k8s中不是有Service嘛,我们Service在配置流量转发规则的时候先转到我们的HTTP Server,然后我们HTTP再转发给业务服务。

Pod改造

原来我们的拓扑是这样的:

加上我们自己的sidecar就变成这样了:

随之而来的改造

k8s Pod支持一个Pod多个Container,所以我们可以直接在Pod中增加一个Container:

比如:原来我们的Pod定义是这样的:

复制代码
        - name: my-order-app
          image: my-registry-domain-name/ my-order-app:${my-order-app-version}
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080

COPY

改造后:

复制代码
      containers:
        - name: sidecar
          image: my-registry-domain-name/my-custom-sidecar:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8081
        - name: my-order-app
          image: my-registry-domain-name/ my-order-app:${my-order-app-version}
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080

COPY

Pod的改造就完成了,接下来就是Service改造了。

Service改造

我们要把Service的目标端口改成我们自己的Sidecar应用才可以。

比如:原来我们的Service是这样的:

Service端口为8080,转发到app的8080端口中。

复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-order-app-service
  namespace: my-app
  labels:
    app: my-order-app
spec:
  ports:
    - name: my-order-http
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: my-order-app
  type: ClusterIP

COPY

现在我们要把Target改成8081,也就是我们的Sidecar HTTP服务器端口。

复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-order-app-service
  namespace: my-app
  labels:
    app: my-order-app
spec:
  ports:
    - name: my-order-http
      port: 8080
      protocol: TCP
      targetPort: 8081
  selector:
    app: my-order-app
  type: ClusterIP

COPY

这样我们的Sidecar就搞定了。

总结

通过使用Sidecar模式,我们能够实现对微服务架构的强化,也就是关注点分离,让你专注于开车,开好车,开快车。

它提供了一种有效的方式去分摊微服务的辅助功能(如日志记录、监控、配置管理、网络通信等),而无需改动微服务的核心业务逻辑。这带来了显著的优点,包括简化开发复杂度、增强服务的可维护性和可伸缩性,以及提供了横切关注点(cross-cutting concerns)的统一管理。

优点

关注点分离 :通过将非核心功能从应用程序逻辑中分离出来,开发者可以更专注于业务逻辑。
可插拔性 :Sidecar可以独立于应用程序部署和更新,使得维护工作更加灵活。
一致性 :在多个服务中应用相同的配置和策略,不需要在每个服务上单独配置。
扩展性 :新增功能时,可以在不影响现有服务的情况下,在Sidecar中添加新的组件。
增强的能力:例如,集中日志处理、安全性控制、以及动态路由等。

缺点(Sidecar的通病):

资源使用 :每个Sidecar都会消耗额外的计算和内存资源,这可能导致资源的浪费。
复杂性 :虽然单个服务的复杂性降低了,但整体架构可能会因为增加的组件而变得复杂。
调试困难 :由于Sidecar模式涉及多个容器协同工作,调试和追踪问题可能比传统单体应用更加困难。
网络延迟:每个请求都要通过Sidecar代理,可能会增加网络延迟。

参考

微服务侧车模式深度解析:赋能云原生应用的终极指南(自己搞一个简单SideCar?) -- 小厂程序员

相关推荐
Lee川1 小时前
深度拆解:基于面向对象思维的“就地编辑”组件全模块解析
javascript·架构
勤劳打代码1 小时前
Flutter 架构日记 — 状态管理
flutter·架构·前端框架
子兮曰7 小时前
后端字段又改了?我撸了一个 BFF 数据适配器,从此再也不怕接口“屎山”!
前端·javascript·架构
卓卓不是桌桌9 小时前
如何优雅地处理 iframe 跨域通信?这是我的开源方案
javascript·架构
Qlly9 小时前
DDD 架构为什么适合 MCP Server 开发?
人工智能·后端·架构
用户881586910911 天前
AI Agent 协作系统架构设计与实践
架构
鹏北海1 天前
Qiankun 微前端实战踩坑历程
前端·架构
货拉拉技术1 天前
货拉拉海豚平台-大模型推理加速工程化实践
人工智能·后端·架构
RoyLin1 天前
libkrun 深度解析:架构设计、模块实现与 Windows WHPX 后端
架构
CoovallyAIHub2 天前
实时视觉AI智能体框架来了!Vision Agents 狂揽7K Star,延迟低至30ms,YOLO+Gemini实时联动!
算法·架构·github