Envoy Proxy 在大多数情况下都是作为 Sidecar 与应用部署在同一网络环境中,每个应用只需要与 Envoy(localhost)交互,不需要知道其他服务的地址。然而这并不是 Envoy 仅有的使用场景,它本身就是一个七层代理,通过模块化结构实现了流量治理、信息监控等核心功能,比如流量治理功能就包括自动重连、熔断、全局限速、流量镜像和异常检测等多种高级功能,因此 Envoy 也常常被用于边缘代理,比如 Istio 的 Ingress Gateway、基于 Envoy 实现的 Ingress Controller(ContourAmbassadorGloo 等)。

我的博客也是部署在轻量级 Kubernetes 集群上的(其实是 k3s 啦),一开始使用 Contour 作为 Ingress Controller,暴露集群内的博客、评论等服务。但好景不长,由于我在集群内部署了各种奇奇怪怪的东西,有些个性化配置 Contour 无法满足我的需求,毕竟大家都知道,每抽象一层就会丢失很多细节。换一个 Controller 保不齐以后还会遇到这种问题,索性就直接裸用 Envoy 作为边缘代理,大不了手撸 YAML 呗。

当然也不全是手撸,虽然没有所谓的控制平面,但仪式感还是要有的,我可以基于文件来动态更新配置啊,具体的方法参考 Envoy 基础教程:基于文件系统动态更新配置

1. UDS 介绍

说了那么多废话,下面进入正题。为了提高博客的性能,我选择将博客与 Envoy 部署在同一个节点上,并且全部使用 HostNetwork 模式,Envoy 通过 localhost 与博客所在的 Pod(Nginx) 通信。为了进一步提高性能,我盯上了 Unix Domain Socket(UDS,Unix域套接字),它还有另一个名字叫 IPC(inter-process communication,进程间通信)。为了理解 UDS,我们先来建立一个简单的模型。

现实世界中两个人进行信息交流的整个过程被称作一次通信(Communication),通信的双方被称为端点(Endpoint)。工具通讯环境的不同,端点之间可以选择不同的工具进行通信,距离近可以直接对话,距离远可以选择打电话、微信聊天。这些工具就被称为 Socket

同理,在计算机中也有类似的概念:

  • Unix 中,一次通信由两个端点组成,例如 HTTP 服务端和 HTTP 客户端。
  • 端点之间想要通信,必须借助某些工具,Unix 中端点之间使用 Socket 来进行通信。

Socket 原本是为网络通信而设计的,但后来在 Socket 的框架上发展出一种 IPC 机制,就是 UDS。使用 UDS 的好处显而易见:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的

UDS 与网络 Socket 最明显的区别在于,网络 Socket 地址是 IP 地址加端口号,而 UDS 的地址是一个 Socket 类型的文件在文件系统中的路径,一般名字以 .sock 结尾。这个 Socket 文件可以被系统进程引用,两个进程可以同时打开一个 UDS 进行通信,而且这种通信方式只会发生在系统内核里,不会在网络上进行传播。下面就来看看如何让 Envoy 通过 UDS 与上游集群 Nginx 进行通信吧,它们之间的通信模型大概就是这个样子:

2. Nginx 监听 UDS

首先需要修改 Nginx 的配置,让其监听在 UDS 上,至于 Socket 描述符文件的存储位置,就随你的意了。具体需要修改 listen 参数为下面的形式:

listen      unix:/sock/hugo.sock;

当然,如果想获得更快的通信速度,可以放在 /dev/shm 目录下,这个目录是所谓的 tmpfs,它是 RAM 可以直接使用的区域,所以读写速度都会很快,下文会单独说明。

3. Envoy-->UDS-->Nginx

Envoy 默认情况下是使用 IP 地址和端口号和上游集群通信的,如果想使用 UDS 与上游集群通信,首先需要修改服务发现的类型,将 type 修改为 static

type: static

同时还需将端点定义为 UDS:

- endpoint:
address:
pipe:
path: "/sock/hugo.sock"

最终的 Cluster 配置如下:

- "@type": type.googleapis.com/envoy.api.v2.Cluster
name: hugo
connect_timeout: 15s
type: static
load_assignment:
cluster_name: hugo
endpoints:
- lb_endpoints:
- endpoint:
address:
pipe:
path: "/sock/hugo.sock"

最后要让 Envoy 能够访问 NginxSocket 文件,Kubernetes 中可以将同一个 emptyDir 挂载到两个 Container 中来达到共享的目的,当然最大的前提是 Pod 中的 Container 是共享 IPC 的。配置如下:

spec:
...
template:
...
spec:
containers:
- name: envoy
...
volumeMounts:
- mountPath: /sock
name: hugo-socket
...
- name: hugo
...
volumeMounts:
- mountPath: /sock
name: hugo-socket
...
volumes:
...
- name: hugo-socket
emptyDir: {}

现在你又可以愉快地访问我的博客了,查看 Envoy 的日志,成功将请求通过 Socket 转发给了上游集群:

[2020-04-27T02:49:47.943Z] "GET /posts/prometheus-histograms/ HTTP/1.1" 200 - 0 169949 1 0 "66.249.64.209,45.145.38.4" "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "9d490b2d-7c18-4dc7-b815-97f11bfc04d5" "fuckcloudnative.io" "/dev/shm/hugo.sock"

嘿嘿,Google 的爬虫也来凑热闹。

你可能会问我:你这里的 Socket 为什么在 /dev/shm/ 目录下啊?别急,还没结束呢,先来补充一个背景知识。

4. Linux 共享内存机制

共享内存(shared memory),是 Linux 上一种用于进程间通信(IPC)的机制。

进程间通信可以使用管道,Socket,信号,信号量,消息队列等方式,但这些方式通常需要在用户态、内核态之间拷贝,一般认为会有 4 次拷贝;相比之下,共享内存将内存直接映射到用户态空间,即多个进程访问同一块内存,理论上性能更高。嘿嘿,又可以改进上面的方案了。

共享内存有两种机制:

  • POSIX 共享内存(shm_open()、shm_unlink()
  • System V 共享内存(shmget()、shmat()、shmdt()

其中,System V 共享内存历史悠久,一般的 UNIX 系统上都有这套机制;而 POSIX 共享内存机制接口更加方便易用,一般是结合内存映射 mmap 使用。

mmapSystem V 共享内存的主要区别在于:

  • System V shm 是持久化的,除非被一个进程明确的删除,否则它始终存在于内存里,直到系统关机。
  • mmap 映射的内存不是持久化的,如果进程关闭,映射随即失效,除非事先已经映射到了一个文件上。
  • /dev/shm 是 Linux 下 sysv 共享内存的默认挂载点。

POSIX 共享内存是基于 tmpfs 来实现的。实际上,更进一步,不仅 PSM(POSIX shared memory),而且 SSM(System V shared memory) 在内核也是基于 tmpfs 实现的。

从这里可以看到 tmpfs 主要有两个作用:

  • 用于 System V 共享内存,还有匿名内存映射;这部分由内核管理,用户不可见。
  • 用于 POSIX 共享内存,由用户负责 mount,而且一般 mount 到 /dev/shm,依赖于 CONFIG_TMPFS

虽然 System V 与 POSIX 共享内存都是通过 tmpfs 实现,但是受的限制却不相同。也就是说 /proc/sys/kernel/shmmax 只会影响 System V 共享内存,/dev/shm 只会影响 POSIX 共享内存。实际上,System VPOSIX 共享内存本来就是使用的两个不同的 tmpfs 实例。

System V 共享内存能够使用的内存空间只受 /proc/sys/kernel/shmmax 限制;而用户通过挂载的 /dev/shm,默认为物理内存的 1/2

概括一下:

  • POSIX 共享内存与 System V 共享内存在内核都是通过 tmpfs 实现,但对应两个不同的 tmpfs 实例,相互独立。
  • 通过 /proc/sys/kernel/shmmax 可以限制 System V 共享内存的最大值,通过 /dev/shm 可以限制 POSIX 共享内存的最大值。

5. Kubernetes 共享内存

Kubernetes 创建的 Pod,其共享内存默认 64MB,且不可更改。

为什么是这个值呢?其实,Kubernetes 本身是没有设置共享内存的大小的,64MB 其实是 Docker 默认的共享内存的大小。

Docker run 的时候,可以通过 --shm-size 来设置共享内存的大小:

												

Envoy 基础教程:使用 Unix Domain Socket(UDS) 与上游集群通信的更多相关文章

  1. monitor a local unix domain socket like tcpdump

    Can I monitor a local unix domain socket like tcpdump? - Super User https://superuser.com/questions/ ...

  2. 网络协议之:socket协议详解之Unix domain Socket

    目录 简介 什么是Unix domain Socket 使用socat来创建Unix Domain Sockets 使用ss命令来查看Unix domain Socket 使用nc连接到Unix do ...

  3. 由一个简单需求到Linux环境下的syslog、unix domain socket

    本文记录了因为一个简单的日志需求,继而对linux环境下syslog.rsyslog.unix domain socket的学习.本文关注使用层面,并不涉及rsyslog的实现原理,感兴趣的读者可以参 ...

  4. UNIX DOMAIN SOCKET效率

    关于UNIX DOMAIN SOCKET和普通udp socket的对比 在TX1(4核A57 1.7GHz)的板卡上进行测试,每个包大小设置为1024,全速收发,UDS的速度在90Mbps左右,UD ...

  5. php, hhvm与odp & Unix domain Socket方式

    接上一篇,复习一下 启动php或hhvm: php/sbin/php-fpm start hhvm/bin/hhvm_control start 启动nginx或lighttpd: webserver ...

  6. 问题解决:psql: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

    错误提示: psql: could not connect to server: No such file or directory Is the server running locally and ...

  7. ndk学习16: unix domain socket

    一.UNIX Domain Socket 概念: UNIX Domain Socket是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC) 特点: 1. 它不需要经过网络协议栈,不需要 ...

  8. Unix Domain Socket 域套接字实现

    主要注意流程: STREAM SOCKET: Server :  socket() --->  bind() ---> listen()  ---> accept() Client: ...

  9. libpqxx接口的在linux下的使用,解决psql:connections on Unix domain socket "/tmp/.s.PGSQL.5432"错误

    在项目中使用postgresql数据库时要求在windows和linux双平台兼容.于是在windows下使用的接口在linux下爆出异常: psql:connections on Unix doma ...

随机推荐

  1. Golang 性能测试 (3) 跟踪刨析 golang trace

    简介 对于绝大部分服务,跟踪刨析是用不到的.但是如果遇到了下面问题,可以不妨一试: 怀疑哪个协程慢了 系统调用有问题 协程调度问题 (chan 交互.互斥锁.信号量等) 怀疑是 gc (Garbage ...

  2. vueThink框架搭建与填坑(new)

    自己跟着官网搭建vueThink框架,发现github上文档有很多坑.所以总结一下(仅针对WIN端下载使用) 1.安装node.js 前端部分是基于node.js上运行的,所以必须先安装node.js ...

  3. Tomcat5启动流程与配置详解

    标签:配置 tomcat 休闲 职场 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://zhangjunhd.blog.51cto. ...

  4. 【公益线上自习室】不连麦,无微信群,无qq群

    马上就要到5月了,从刚开始的放纵已经逐渐变得慌乱. 疫情还没有完全过去,居家学习.工作是最好的选择. 但是,问题是,在家太舒服了,一点也不想学习. 一开始“哈哈哈哈哈哈哈哈”朋友了,现在已经开始“唉… ...

  5. 003-scanf函数使用和表达式-C语言笔记

    003-scanf函数使用和表达式-C语言笔记 学习目标 1.[掌握]输入函数scanf的基本使用方法 2.[掌握]输入函数scanf运行原理和缓冲区理解 3.[掌握]算术运算符和算术表达式的使用 4 ...

  6. Python变量详解

    Python变量详解 目录 Python变量详解 一.变量的定义 二.变量组成的三部分 2.1 变量名 2.1.1 变量名的命名规范 2.1.2 变量名的命名风格 2.2 赋值符号 2.3 变量值 三 ...

  7. PHP函数:func_num_args

    func_num_args()  - 返回传递给函数的参数数量. 说明: func_num_args ( void ) : int 参数: 无 返回值: 返回传入当前用户定义函数的参数数量. 参考链接 ...

  8. [Yii2] 快速套模板,加载JS,CSS(HTML标签<base>)

    刚开始学,弄了好久,不知道怎么加载JS.CSS,以及怎么不加载YII2自带的模板!其实真的好简单! 补: 其实是我垃圾,YII2默认访问路径是WEB,所以把style文件放到web下就能加载! 首先把 ...

  9. SQL Server 之T-SQL基本语句 (1)

    花了一天的时间看完了一本<SQL必知必会>,举个范例,来总结一下零碎的知识点.一般关于数据库操作的项目都会涉及到数据库的基本查询语句.在这里面就主要讲解一些基本常用的sql使用方法. 注: ...

  10. Caused by: java.lang.NumberFormatException: For input string: " 60"

    原因 原原因:string转int 格式出错 解决:我的输入文件格式在根据“,”分割完之后多出了一个空格,我想要的是“60” 但是分割完之后是“ 60”所以导致格式转换不匹配.