容器技术回顾 - 使用 UDS 实现 Pod 间通信



简介
传统 Socket 包含 Stream Socket 和 Datagram Socket,这两种 Socket 通常分别是基于 tcp 和 udp 协议来进行数据的传输。这两种 Socket 都有一个共同的特点,那就是需要一个 IP 地址和端口来建立客户端和服务器端的连接。
Unix domain Socket(UDS)不需要使用传统的IP地址和端口,而使用文件系统来进行程序之间的通信(顾名思义,只有 Unix/Linux 才有)。
关于 UDS 的介绍可以参考:https://en.wikipedia.org/wiki/Unix_domain_socke
使用 UDS 加速 K8s Sidecar
TCP lookback vs UDS

1. Redis 测试:https://redis.io/docs/management/optimization/benchmarks/
When the server and client benchmark programs run on the same box, both the TCP/IP loopback and unix domain sockets can be used. Depending on the platform, unix domain sockets can achieve around 50% more throughput than the TCP/IP loopback (on Linux for instance). The default behavior of redis-benchmark is to use the TCP/IP loopback.
The performance benefit of unix domain sockets compared to TCP/IP loopback tends to decrease when pipelining is heavily used (i.e. long pipelines).
2. Benchmark:https://github.com/rigtorp/ipc-bench
Here you have the results on a single CPU 3.3GHz Linux machine :TCP average latency: 6 usUDS average latency: 2 usPIPE average latency: 2 usTCP average throughput: 0.253702 million msg/sUDS average throughput: 1.733874 million msg/sPIPE average throughput: 1.682796 million msg/s
66% latency reduction and almost 7X more throughput explain why most performance-critical software has their own IPC custom protocol.
3.原理:https://lists.freebsd.org/pipermail/freebsd-performance/2005-February/001143.html
背景
UDS 在传输速度上比 TCP loopback 要快得多(不用走网络栈),我们可以将它用于 Kubernetes sidecar 场景。例如当我们在 pod 中添加一项服务( Nginx 或我们自己编写的微服务),我们可以使用 UDS 而不是 TCP loopback 来加快服务间的网络通信速度。 
我们使用 go 语言作为示例服务器和客户端示例语言。
TCP 服务端
package mainimport ( “log” “net”)func main() { l, err := net.Listen(“tcp4”, “127.0.0.1:8080”) if err != nil { log.Fatal(err) return } defer l.Close() for { c, err := l.Accept() if err != nil { log.Fatal(err) return } go handleConnection(c) }}func handleConnection(c net.Conn) { c.Write([]byte(string(“test“))) c.Close()}
如果我们要使用 Unix 套接字,以上代码需要做一些调整:
l, err := net.Listen(“tcp4”, “127.0.0.1:8080”)#改为如下l, err := net.Listen(“unix”, “/tmp/test.sock”)
Socket 清理代码:
// Cleanup the sockfile on ctrl+c c := make(chan os.Signal)signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c os.Remove(“/tmp/test.sock”) os.Exit(1) }()
TCP 客户端
package mainimport ( “bufio” “log” “net” “os” “runtime/pprof” “time”)func main() { start := time.Now() for i := 0; i < 10000; i++ { conn, err := net.Dial(“tcp”, “127.0.0.1:8080”) if err != nil { log.Fatal(err) return } _, err = bufio.NewReader(conn).ReadString(‘’) if err != nil { log.Fatal(err) return } conn.Close() } t := time.Now() elapsed := t.Sub(start) log.Printf(“Time taken %s“, elapsed)}
如果要使用 Unix 套接字,代码需做如下调整:
conn, err := net.Dial(“tcp”, “127.0.0.1:8080”)#改为l, err := net.Listen(“unix”, “/tmp/test.sock”)
YAML 文件
apiVersion: apps/v1kind: Deploymentmetadata:  name: uds-deployspec: replicas: 1 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: business        image: business_service:latest volumeMounts: - mountPath: /tmp/ name: socket-volume - name: sidecar image: sidecar:latest volumeMounts: - mountPath: /tmp/ name: socket-volume volumes: - name: socket-volume emptyDir: {}
总结
上述我们是在一个 Pod 内的不同容器间通过 UDS 进行通信,我们也可以使用 hostpath 的方式在不同的 Pod 之间使用相同的 uds 进行通信。
因此我们可以使用 System V、tmpfs 和 UDS 在运行在同一节点的不同 Pod 之间进行高效通信。
容器技术回顾 - 多 Pod 间共享内存通信(System V)
容器技术回顾 - Linux 内存文件系统(tmpfs)
到顶部