背景
CC的通用容灾方案是基于公司Janus Mini来做的, Janus Mini是一种去中心化的网关, 本质上是基于k8s sidecar设计模式的实现; 为了探究其底层原理, 下面便写一个demo来自己实现一个基于k8s sidecar设计模式的“微服务系统”
整体架构
整体包含三个应用(Container):
- 对外(k8s集群外)暴露HTTP端口的WEB服务A
- 不对外暴露端口的WEB服务B
- 不对外暴露端口的RPC服务C
其中:
- WEB服务A用于接受外部HTTP请求并转发到WEB服务B
- WEB服务B接受被转发的HTTP请求, 并调用RPC服务C(自实现的简易RPC协议)
- RPC服务C接受RPC请求处理逻辑并返回(做最简单的加减法)
- 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的图: