实现基于k8s的sidecar设计模式的微服务系统

587 阅读5分钟

背景

CC的通用容灾方案是基于公司Janus Mini来做的, Janus Mini是一种去中心化的网关, 本质上是基于k8s sidecar设计模式的实现; 为了探究其底层原理, 下面便写一个demo来自己实现一个基于k8s sidecar设计模式的“微服务系统”

整体架构

整体包含三个应用(Container):

  1. 对外(k8s集群外)暴露HTTP端口的WEB服务A
  2. 不对外暴露端口的WEB服务B
  3. 不对外暴露端口的RPC服务C

其中:

  1. WEB服务A用于接受外部HTTP请求并转发到WEB服务B
  2. WEB服务B接受被转发的HTTP请求, 并调用RPC服务C(自实现的简易RPC协议)
  3. RPC服务C接受RPC请求处理逻辑并返回(做最简单的加减法)
  4. WEB服务A和WEB服务B是同一个POD里的不同container (sidecar模式)

具体实现

RPC服务C

github: github.com/SummitXY/go…

主要逻辑便是TCP监听 和 “协议”解析

所谓“协议”采用的是最简单的TLV格式, 前面4字节表示后面body的长度, body格式: "ADD 4 5"或"SUB 10 8"

这里贴一下监听的代码, 后面会用到端口号:

const (
	HOST = "0.0.0.0"
	PORT = "9001"
	TYPE = "tcp"
)

func main () {
	fmt.Println("start listening")

	listen, err := net.Listen(TYPE, HOST+":"+PORT)
        
        //具体代码在上面github里...

先用k8s部署RPC服务(go-rpc-simple-inter.yaml文件):

apiVersion: apps/v1  
kind: Deployment  
metadata:
  name: go-rpc-simple-inter
  labels:
    app: go-rpc-simple-inter
spec:
  selector:
    matchLabels:
      app: go-rpc-simple-inter
  replicas: 3  # Pod 副本数量
  template:  
    metadata:
      labels:
        app: go-rpc-simple-inter
    spec:
      containers:
        - name: go-rpc-simple-inter
          image: qxybest/go-rpc-simple:latest   # 上面代码打成的docker镜像
          imagePullPolicy: Always 
          ports:
            - containerPort: 9001  # container打开的端口, 需要和代码里监听的端口一致
---
apiVersion: v1
kind: Service  # 下面是service的配置
metadata:
  name: go-rpc-simple-inter-service
  labels:
    app: go-rpc-simple-inter
spec:
  selector:
    app: go-rpc-simple-inter
  ports:
    - protocol: TCP
      port: 9009       # service在k8s集群内部暴露的service端口,k8s集群外无法通过这个port访问这个service
      targetPort: 9001 # service接收到请求后要打到的container的端口,就是上面Deployment的containerPort

然后部署: kubectl apply -f go-rpc-simple-inter.yaml

然后查看部署情况: kubectl get service 查看service:

k8s ❯ kubectl get service        
NAME                               TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
go-rpc-simple-inter-service        ClusterIP      10.98.33.113   <none>        9009/TCP         2d

TYPE是ClusterIP表示对内的service, 同时集群内的IP和PORT也有了:10.98.33.113:9009 (这里9009就是service配置里的port

现在便可以通过上面的IP:PORT在k8s集群内访问RPC服务C

WEB服务B

github: github.com/SummitXY/go…

WEB服务B的主要功能是接收HTTP请求(WEB服务A转发来的), 然后封装“RPC协议”再请求RPC服务C, 下面的代码便是“RPC协议”:

func RPC(op string, a, b int64) (int32, error) {
	conn, err := net.Dial("tcp", "10.98.33.113:9009")   // 这里访问RPC服务C的IP:PORT便是上面得到的IP:PORT
	if err != nil {
		return 0, err
	}
	defer conn.Close()

	//msg := "ADD 33 2"
	msg := fmt.Sprintf("%s %d %d",op, a, b)
       // 简易的RPC协议, 头部固定4字节长度, 头部的内容存的是body的长度
	header := make([]byte, 4)
	body := []byte(msg)
	binary.BigEndian.PutUint32(header, uint32(len(body)))

	_, err = conn.Write(header)
	if err != nil {
		return 0, err
	}
	_, err = conn.Write(body)
	if err != nil {
		return 0, err
	}

	buffer := make([]byte, 4)
	_, err = conn.Read(buffer)
	if err != nil {
		return 0, err
	}

	res := binary.BigEndian.Uint32(buffer)
	log.Printf("ans :%d\n",res)

	return int32(res), nil
}

WEB服务B使用gin监听端口8888

kubectl apply -f go-server-simple.yaml

apiVersion: apps/v1  # API版本
kind: Deployment  # API对象类型
metadata:
  name: go-server-simple
  labels:
    app: go-server-simple
spec:
  selector:
    matchLabels:
      app: go-server-simple
  replicas: 3  # Pod 副本数量
  template:  # Pod 模板
    metadata:
      labels:
        app: go-server-simple
    spec:
      containers:
        - name: go-server-simple
          image: qxybest/go-server-simple:latest
          imagePullPolicy:  Always # IfNotPresent
          ports:
            - containerPort: 8888 # web服务B代码监听的端口是8888
---
apiVersion: v1
kind: Service
metadata:
  name: go-server-simple-service
  labels:
    app: go-server-simple
spec:
  selector:
    app: go-server-simple
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 8889 #web服务B集群内部暴露的端口是8889
      targetPort: 8888 #打到8888, 对应上面的8888
      nodePort: 30001 # 集群外暴露的端口30001

WEB服务A

github: github.com/SummitXY/go…

WEB服务A只是起到转发HTTP请求到WEB服务B的作用, 代码比较简单:

func main() {
	fmt.Println("sidecar start...")

	r := gin.Default()

	api := r.Group("/api")

	// 转发
	api.GET("/add/:a/:b", func(c *gin.Context) {
                // 因为进程A和B在一个POD内, 所以他们共享IP
		target := "http://localhost:8888"  
		remote, err := url.Parse(target)
		if err != nil {
			c.String(http.StatusOK, "Error is :%s",err.Error())
		} else {
			proxy := httputil.NewSingleHostReverseProxy(remote)
			proxy.ServeHTTP(c.Writer, c.Request)
		}
	})

	r.Run(port) // WEB服务A监听的端口是9999
}

这里直接访问localhost的原因是WEB服务B和A部署在同一个POD中, 共享网络基础设施, 二者相当于两个进程, 故可以用localhost+PORT的方式访问

minikube部署服务B和A在同一个POD中(go-sidecar-server-simple.yaml):

apiVersion: apps/v1  
kind: Deployment  
metadata:
  name: go-sidecar-server-simple
  labels:
    app: go-sidecar-server-simple
spec:
  selector:
    matchLabels:
      app: go-sidecar-server-simple
  replicas: 3  # Pod 副本数量
  template:  
    metadata:
      labels:
        app: go-sidecar-server-simple
    spec:
      containers:
        - name: go-server-simple                           # WEB服务B的镜像
          image: qxybest/go-server-simple:latest
          imagePullPolicy:  Always
          ports:
            - containerPort: 8888                              #WEB服务B监听的端口

        - name: go-sidecar-simple                           # WEB服务A的镜像
          image: qxybest/go-sidecar-simple:latest
          imagePullPolicy:  Always 
          ports:
            - containerPort: 9999                              #WEB服务A监听的端口
---
apiVersion: v1
kind: Service
metadata:
  name: go-sidecar-server-simple-service
  labels:
    app: go-sidecar-server-simple
spec:
  selector:
    app: go-sidecar-server-simple
  type: LoadBalancer                          # 对集群外暴露端口使用LoadBalancer
  ports:
    - protocol: TCP
      port: 8889                                      # service在k8s集群内暴露的端口, 这次用不到
      targetPort: 9999                             # service收到请求后打到POD端口, 这里打到服务A, 所以是9999
      nodePort: 30002                            # service对集群外暴露的端口

部署kubectl apply -f go-sidecar-server-simple.yaml 查看kubectl get service

NAME                               TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
go-rpc-simple-inter-service        ClusterIP      10.98.33.113   <none>        9009/TCP         2d
go-sidecar-server-simple-service   LoadBalancer   10.97.90.133   <pending>     8889:30002/TCP   40h

浏览器发起HTTP请求

需要使用命令minikube service go-sidecar-server-simple-service --url来得到go-sidecar-server-simple-service对外暴露的IP, 而不能直接使用CLUSTER-IP

拿到IP:PORT后便可以通过我们代码里监听的PATH来依次请求WEB服务A、B、RPC服务C来得到我们的计算结果:

最后放一个关于k8s全链路IP的图: