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. 使用rem配置PC端自适应大屏

    效果如下 使得大屏不论在什么宽高比例依然能展示全部数据 安装 npm install -S postcss-pxtorem rem配置思路 原先的rem函数是能解决大部分的问题的,如果展示不全,也可以 ...

  2. mysql 使用技巧 分页limit

    mysql 分页使用 limit关键字,limit x,y (x代表从哪条数据开始,y代表页面大小.mysql第一条数据在limit计算时索引为0) 前10条 limit , 从第1条开始的10条 l ...

  3. AJ学IOS(33)UI之Quartz2D雪花飘落效果刷帧

    AJ分享,必须精品 效果: 可以加入随机数实现真的飘落效果哦. 代码: -(id)initWithCoder:(NSCoder *)aDecoder { //请注意这里一定要先初始化父类的构造方法 i ...

  4. Gallery实现图片拖动切换

    Gallery中文意思为画廊,通过Gallery能够实现用手指在屏幕上滑动实现图片的拖动.效果如下: 上面,为了学习了解,只用了android默认的Icon图片. 主程序中创建了一个继承自BaseAd ...

  5. python成功安装torch模块

    最近项目要使用到torch模块,但是在安装的过程中发现torch直接使用pip install安装是安装不成功的.然后就百度,发现并没有什么卵用,所以就google一番,不禁感叹,这种新的东西,还是外 ...

  6. 如何用python批量生成真实的手机号码

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:Python测试社区 1目 标 场 景 平时在工作过程中,偶尔会需要大 ...

  7. Linux protobuf

    生成C# protobuf 最终文件Net.cs .protoc --descriptor_set_out=a.protobin a.proto .mono protogen.exe -i:Net.p ...

  8. Laravel 上手增删改查

    拿到一个框架,除了解框架,还要能实现基本的CURD操作. 添加 1.配置路由,指定添加页面: // routes/web.php 中增加如下: // 添加页面.存放路径 Laravel7/resour ...

  9. MVC-基础02

    MVC是Model(模型).View(视图)和Controller(控制). 1)最上面的一层,是直接面向最终用户的"视图层"(View).它是提供给用户的操作界面,是程序的外壳. ...

  10. python 基础篇 错误和异常处理

    语法错误 所谓语法错误,也就是你写的代码不符合编程规范,无法被识别与执行,比如下面这个例子: if name is not None print(name) If 语句漏掉了冒号,不符合 Python ...