作者:王涛(腾讯云)
编辑:小君君(才云)
来源:EAWorld
你是否经常会遇到这样的困难:处理不同进程的应用程序时,需求方会要求包含所有进程以实现更多隔离。在这种情况下,一个常见的问题是:如何在同一 Node 中的 Pod 间实现共享内存。王涛是腾讯云的高级工程师,在本文中,他将阐述一种在 Pod 间利用 Posix/SystemV 来实现共享内存的解决方案,一起来看看吧。
一些公共服务组件在追求性能的过程中,大多会出现与业务耦合过紧的情况。同时,工程师们在制作基础镜像时,会把这些基础组件都打包进去。因此当业务镜像启动后,容器内部就会存在大量进程,使得 Kubernetes 对 Pod 的管理产生隐患。
为了实现业务容器瘦身,更是为了基础组件自身的管理更加独立。工程师们可以将基础组件从业务镜像中剥离并进行 DaemonSet 容器化部署。但是,一些基础组件 Agent 与业务 Pod 之间是通过共享内存的方式进行通信的,所以整个部署的首要问题是:在同一 Node 中,Pod 之间如何去实现共享内存?
通过阅读本文你将了解:
为什么要将公共基础组件 Agent 进行 DaemonSet 部署;
Linux 共享内存机制;
同一 Node 上跨 Pod 的共享内存方案;
灰度上线。
为什么要将公共基础组件 Agent 进行 DaemonSet 部署
工程师们自研的公共基础组件,比如服务路由组件、安全组件等,通常以进程的方式部署在 Node 上,并为所有的业务提供服务。但是在上微服务和容器化之后,如果工程师还是惯用 sidecar 或者将组件打包到业务 Image 中,继续以 Per Pod Per Agent 的方式部署,基础组件 Server 端的压力将会成百上千的增长。
这样做的结果对于整个部署来说风险很大。因此,工程师们会更希望能以 DaemonSet 方式部署这些组件的 Agents。
先说说如果不将这些基础组件从业务的 Pod 中剥离,业务会存在哪些问题:
业务容器中存在一大堆进程,当工程师为 Pod 申请资源(cpu/mem request and limit)时,不仅要考虑业务应用本身的资源消耗,还要考虑这些基础组件的资源消耗。当某些 Agent 存在 Bug(比如内存泄漏)时,必将导致 Pod 被重建,甚至 cgroup OOM 会将业务进程 kill;
违背了 Kubernetes&微服务部署的最佳实践:业务进程在容器前台运行,与容器共生死。这将导致 Kubernetes 无法根据业务进程状态关联到容器状态,进而使得 Kubernetes 无法及时自愈;
如果一个 Node 上运行 10 个 Pod,那么就会有 ×10 的基础组件数量在 Node 上。在没有容器化之前,一个 Node 只要部署一个组件进程即可。容器化之后,集群中组件 Agent 的数量会几十倍的增长。如果业务进行了微服务拆分,这个指数会更大,这些基础组件服务端是否能承受比以往高出几十倍甚至上百倍的通信请求,人们犹未可知;
如果你要全网升级某个基础组件 Agent,那你可能会疯掉!因为你需要重新打所有业务镜像,同时全网业务也需要进行灰度升级。因为只要有一个 Agent 升级,你就需要重新构建业务的 Pod,这是一个令人厌烦的过程。你可能会说,基础组件 Agent 都会有自己的热升级方案,只要通过它们的方案升级就好了。如果你这样做,必将引入更大的麻烦:Agents 的热升级会因为无法被 Kubernetes 感知,而引发 Kubernetes 集群数据不一致的问题。此时,你就需要利用虚拟机或者物理机来部署集群了。当然,这个问题,工程师们也可以通过 Operator 来实现,但这样做的代价太大了,并且很不云原生!
如果将基础组件 Agent 从业务 Pod 中剥离,那么以上的问题就都迎刃而解,并且架构上的解耦会带来很多好处。工程师们也可以通过 Kubernetes 管理这些基础组件的 Agents,并享受其自愈、滚动升级等功能。
Linux 共享内存机制
理想很美好,现实很残酷!在整个业务中,工程师们首先要解决的问题是:有些组件 Agent 与业务 Pod 之间是通过共享内存通信的,这跟 Kubernetes&微服务的最佳实践方案背道而驰。
众所周知,Kubernetes 单个 Pod 内是共享 IPC 的,并且它们可以通过挂载 Medium,与 Memory 中的 EmptyDir Volume 共享同一块内存 Volume。
Linux 共享内存的两种机制:
POSIX 共享内存(shm_open()、shm_unlink());
System V 共享内存(shmget()、shmat()、shmdt())。
其中,System V 共享内存历史悠久,一般的 UNIX 系统上都有这套机制;而 POSIX 共享内存机制接口更加方便易用,一般是结合内存映射 mmap 使用。
mmap 和 System V 共享内存的主要区别在于:
SystemV 是持久化的。除非被一个进程明确地删除,否则它始终存在于内存里,直到系统关机;
mmap 映射的内存不是持久化的。如果进程关闭,映射随即失效,除非事先已经映射到了一个文件上;
/dev/shm 是 Linux 下 SystemV 共享内存的默认挂载点。
POSIX 共享内存是基于 tmpfs 实现的。在内核中,不仅 PSM(POSIX shared memory),SSM(System V shared memory)也是基于 tmpfs 来实现的。
tmpfs 主要有两个作用:
用于 SystemV 共享内存、匿名内存映射。这部分由内核管理,用户不可见;
用于 POSIX 共享内存,由用户负责 mount。mount 到 /dev/shm 依赖于 CONFIG_TMPFS。
虽然 System V 与 POSIX 共享内存都是通过 tmpfs 来实现的,但是它们所受的限制却不相同。/proc/sys/kernel/shmmax 只会影响 SystemV 的共享内存。/dev/shm 只会影响 POSIX 的共享内存 。也就是说,System V 与 POSIX 的共享内存使用的是两个不同的 tmpfs 实例(instance)。SystemV 共享内存能够使用的内存空间只受 /proc/sys/kernel/shmmax 的限制;而用户通过挂载的 /dev/shm,默认为物理内存的 1/2。
概括一下:
POSIX 共享内存与 SystemV 共享内存在内核上都是通过 tmpfs 来实现的,但对应两个不同的 tmpfs 实例,它们相互独立;
通过 /proc/sys/kernel/shmmax 可以限制 SystemV 共享内存的最大值。通过 /dev/shm 可以限制 POSIX 共享内存的最大值(所有之和)。
同一 Node 上跨 Pod 的共享内存方案
当基础组件 Agents 通过 DaemonSet 部署后,Agents 和业务进程就在 Node 上的不同 Pod 中。此时,Kubernetes 该如何支持跨 Pod 的共享内存场景呢?
如上图所示,在整个方案中,业务对 POSIX Type IPC 的共享支持是通过挂载 /dev/shm 来实现的;对 SystemV Type IPC 的共享支持是通过 Share HostIPC 来实现的。但是这样的做法会使存于共享内存中的信息被其他 Pod 误操作。在业务安全性上,它们没有被完全隔离。但其实在非容器化之前,各个业务共享内存也存在同样的风险,所以这一点对于用户来说是可以接受的。
如果工程师们确实发现有些业务存在共享内存的使用冲突,也可以再通过以下规则进行隔离部署:
requiredDuringSchedulingIgnoredDuringExecution PodAntiAffinity
灰度上线
对于集群中的存量业务,之前都是将 Agents 与业务打包在同一 docker image 中,因此需要有灰度上线方案,以保证存量业务不受影响。
首先创建好对应的 Kubernetes ClusterRole、SA、ClusterRoleBinding 和 PSP Object。关于 PSP 的内容,请参考官方文档(关于 pod-security-policy 的内容);
在集群中任意选择部分 Node,给 Node 打上 Label(AgentsDaemonSet:YES)和 Taint(AgentsDaemonSet=YES:NoSchedule)。
$ kubectl label node $nodeName AgentsDaemonSet=YES
$ kubectl taint node $nodeName AgentsDaemonSet=YES:NoSchedule
部署 Agent 对应的 DaemonSet(注意 DaemonSet 需要加上对应的 NodeSelector 和 Toleration, Critical Pod Annotations)、Sample as follows:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: demo-agent
namespace: kube-system
labels:
k8s-app: demo-agent
spec:
selector:
matchLabels:
name: demo-agent
template:
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""labels:
name: demo-agent
spec:
tolerations:
- key: "AgentsDaemonSet"operator: "Equal"value: "YES"effect: "NoSchedule"hostNetwork: truehostIPC: truenodeSelector:
AgentsDaemonSet: "YES"containers:
- name: demo-agent
image: demo_agent:1.0volumeMounts:
- mountPath: /dev/shm
name: shm
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: shm
hostPath:
path: /dev/shm
type: Directory
在该 Node 上部署不包含基础组件 Agent 的业务 Pod,检查所有基础组件和业务是否正常工作。
如果正常,再分批次选择剩余的 Nodes,加上 Label(AgentsDaemonSet:YES)和 Taint(AgentsDaemonSet=YES:NoSchedule);
DaemonSet Controller 会自动在这些 Nodes 创建 DaemonSet Agents Pod。这样就可以逐批次完成集群中基础组件 Agents 的灰度上线。
总结
在高并发业务下,尤其还是以 C/C++ 代码实现的基础组件,工程师们经常会使用共享内存通信机制来追求高性能的标准。本文给出了 Kubernetes Pod 间 Posix/SystemV 共享内存方式的折中方案(以牺牲一定的安全性为代价)。
当然,业务在微服务/容器化部署后,基础服务的 Server 端是不会有压力的。在此,我建议以 SideCar Container 方式将基础服务的 Agents 与业务 Container 部署在同一 Pod 中,利用 Pod 的共享 IPC 特性及 Memory Medium EmptyDir Volume 方式共享内存。
![61201225e0b0f498210fe186fb28d6c2.png](https://i-blog.csdnimg.cn/blog_migrate/6f7d8ada8c974a9fd020ed49b78dab19.jpeg)
王涛,腾讯云高级工程师,西安电子科大硕士毕业,持续深耕云计算领域七年,目前在腾讯基于 TKE(Tencent Kubernetes Engine)构建 DevOps 平台,帮助集团自研业务上云,曾在华为、唯品会、vivo 从事企业私有云平台的建设工作,2014 年开始专注于 Kubernetes/Docker 等容器技术栈在 DevOps、AI Platform 方向的应用,积累了大量生产经验。
推荐阅读:
![a8d933022c02f5034c824dfd693e169b.png](https://i-blog.csdnimg.cn/blog_migrate/3962c40a50679e6ce552276c47388586.png)
![76aa1184c03b985e29db97df75cf6985.png](https://i-blog.csdnimg.cn/blog_migrate/987d3f3e85e8d49b42ee45c0f86b7895.png)
写留言