微服务边车模式深度解析:赋能云原生应用的终极指南(自己搞一个简单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?) -- 小厂程序员

相关推荐
@东辰28 分钟前
【golang-技巧】- 定时任务 - cron
开发语言·golang·cron
jerry6097 小时前
7天用Go从零实现分布式缓存GeeCache(改进)(未完待续)
分布式·缓存·golang
杜杜的man7 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
白总Server11 小时前
JVM解说
网络·jvm·物联网·安全·web安全·架构·数据库架构
CodingBrother12 小时前
软考之面向服务架构SOA
微服务·架构
甘橘籽12 小时前
【RPC】 gRPC、pb基本使用--经验与总结
golang
杜杜的man13 小时前
【go从零单排】HTTP客户端和服务端
开发语言·http·golang
材料苦逼不会梦到计算机白富美13 小时前
golang分布式缓存项目 Day6 防止缓存击穿
分布式·缓存·golang
杜杜的man16 小时前
【go从零单排】Environment Variables环境变量
golang
Diamond技术流16 小时前
从0开始学习Linux——进程管理
linux·运维·学习·系统架构·centos