27

Kubernetes中Pod间共享内存方案

 4 years ago
source link: https://www.tuicool.com/articles/ZzeEjqm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【编者的话】一些公共服务组件在追求性能过程中,与业务耦合太紧,造成在制作基础镜像时,都会把这些基础组件都打包进去,因此当业务镜像启动后,容器里面一大堆进程,这让Kubernetes对Pod的管理存在很大隐患。为了让业务容器瘦身,更是为了基础组件自身的管理更独立和方便,将基础组件从业务镜像中剥离并DaemonSet容器化部署。然而一些基础组件Agent与业务Pod之间通过共享内存的方式进行通信,同一Node中跨Pod的共享内存方案是首先要解决的问题。

为什么要将公共基础组件Agent进行DaemonSet部署

自研的公共基础组件,比如服务路由组件、安全组件等,通常以进程方式部署在Node上并同时为Node上所有的业务提供服务,微服务及容器化之后,服务数量成百上千的增长,如果以sidecar或者打包到业务Image中继续Per Pod Per Agent的方式部署, 那么基础组件的Server端的压力可能也会成百上千的增长,风险是很大的。因此,我们希望能以DaemonSet方式部署这些组件的Agents。 如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

先说说Kubernetes大行其道的今天,如果不将这些基础组件从业务Pod中剥离,存在哪些问题:

  • 业务容器中存在一大堆进程,我们在为Pod申请资源(cpu/mem request and limit)时,不仅要考虑业务应用本身的资源消耗,还要考虑这些基础组件的资源消耗。而且一旦某些Agent有Bug,比如内存泄漏,这将导致Pod牵连被重建,甚至Cgroup OOM在kill进程时,可能将业务进程kill了。
  • 违背了Kubernetes&微服务的部署最佳实践:Per Process Per Contaienr,并且业务进程在前台运行,使其与容器共生死,不然这将导致Kubernetes无法根据业务进程状态关联到容器状态,进而进行高可用管理。
  • 一个Node上运行10个Pod,那么就会有x10的基础组件数量在Node上。没有容器化之前,一个Node只要部署一个组件进程即可,容器化之后,集群中组件Agents数量要几十倍的增长,如果业务进行了微服务拆分,这个指数会更大,这些基础组件服务端是否能承受比以往高几十倍上百倍的通信请求,这是未知的。
  • 如果你要全网升级某个基础组件Agent,那你可能会疯掉,你需要重新打所有业务镜像,然后全网业务要进行灰度升级。因为一个Agent的升级,导致你不得不重建业务Pod。你可能会说,基础组件Agents都会有自己的热升级方案,我们通过它们的方案升级就好了呀,那你将引入很大麻烦:Agents的热升级因为无法被Kubernetes感知,将引发Kubernetes中集群中的数据不一致问题,那就真的要回到虚拟机或者物理机部署的玩法了。当然,这样的需求,我们也想过通过Operator也实现,但代价太大了,而且很不CloudNative!

将基础组件Agents从业务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共享内存的主要区别在于:

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

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

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

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

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

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

概括一下:

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

同一Node上夸Pod的共享内存方案

基础组件Agents DaemonSet部署后,Agents和业务Pod分别在同一个Node上不同的Pod,那么Kubernetes该如何支持这两种类型的共享内存机制呢?

Yf6raie.jpg!web

当然,安全性上做出了牺牲,但在非容器化之前IPC的隔离也是没有的,所以这一点是可以接受的。

灰度上线

对于集群中的存量业务,之前都是将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: true

  hostIPC: true

  nodeSelector:

    AgentsDaemonSet: "YES"

  containers:

  - name: demo-agent

    image: demo_agent:1.0

    volumeMounts:

    - 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方式共享内存。

原文链接: https://my.oschina.net/jxcdwangtao/blog/3006365


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK