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 来设置共享内存的大小:

												

最新文章

  1. 洛谷 P1378 油滴扩展 Label:搜索
  2. IIS+域组策略+hosts:禁止访问指定网站
  3. 验证标题是否存在(TextBox控件失去焦点验证)
  4. js之变量和作用域
  5. 转:40多个关于人脸检测/识别的API、库和软件
  6. 何时可以开启透明数据加密(TDE)?
  7. 3天CSS总结
  8. Windows下用Composer引入官方GitHub扩展包
  9. 【Unity3D】Unity3D开发《我的世界》之二、创建一个立方体
  10. FPGA学习笔记(三)—— 数字逻辑设计基础(抽象的艺术)
  11. windows10 uwp获取设备当前地理位置(经纬度)
  12. Ubuntu系统桌面任务栏和启动器全部消失解决方案
  13. mysql:The user specified as a definer ('xxx'@'%') does not exist 解决方法
  14. celery4+django2定时任务
  15. 三十一、Linux 进程与信号——SIGCHLD 信号、kill和raise函数以及alarm函数
  16. 响应式网页:用em,rem设置网页字体大小自适应
  17. atom 为什么启动terminal是总是打开是用户目录?
  18. debian系统下安装ssh
  19. mybatis提取<where><if>共用代码
  20. python版本共存

热门文章

  1. Jmeter使用Websocket插件测试SingalR,外加还有阿里云PTS的Jmeter原生测试爬坑日志。
  2. Hadoop(二) 单节点案例grep和wordcount|4
  3. Codeup 25594 Problem H 例题5-8 Fibonacci数列
  4. CVE-2019-0193 远程命令执行-漏洞复现
  5. 自动补全、回滚!介绍一款可视化 sql 诊断利器
  6. \r\n的意思
  7. 纯js时钟特效详细代码分析实例教程
  8. 【Vue】状态管理
  9. TensorFlow命令行参数FLAGS使用
  10. h5前端animate等js特效问题汇总