Kubernetes容器隔离问题实例分析

66次阅读
没有评论

共计 6337 个字符,预计需要花费 16 分钟才能阅读完成。

这篇文章主要讲解了“Kubernetes 容器隔离问题实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着丸趣 TV 小编的思路慢慢深入,一起来研究和学习“Kubernetes 容器隔离问题实例分析”吧!

背景

容器中看到的 /proc 伪文件系统的信息是宿主的 /proc,没有隔离 /proc 意味着获取不到容器中进程相关的 proc 信息。另外,一些需要读取 proc 信息的应用,会获取到错误的数据。/proc/meminfo,/proc/cpuinfo, /proc/stat, /proc/uptime, /proc/loadavg 等

用户 UID/GID 的映射, 导致容器中的进程具有宿主上相同 uid/gid 用户的权限

需求分析方案调研

Docker 社区里有讨论过类似的问题,https://github.com/docker/docker/issues/8427,可以通过 kernel patch 或者 bind mount /proc  来实现。 
淘宝团队在几年前曾经发过一个 kernel patch 
https://github.com/alibaba/taobao-kernel/blob/master/patches.taobao/overlayfs-0005-vfs-introduce-clone_private_mount.patch

业界讨论主要有以下几种方案:

##  方案一、-  直接修改 proc 文件系统  
- https://lkml.org/lkml/2012/5/28/299
- mount -t proc -o meminfo-from-cgroup none /path/to/container/proc
缺点:不可能要求并入内核

缺点:不是一个广泛接受的方案,不说自己修改命令可能产生的 bug  和成本。不同的 linux 版本,可能会需要不同的补丁。

解决方案

为 LXC 准备的 FUSE 文件系统, 提供了如下的特性:

* a cgroupfs compatible view for unprivileged containers
* a set of cgroup-aware files:
 * cpuinfo
 * meminfo
 * stat
 * uptime

用户空间文件系统 (Filesystem in Userspace, FUSE)

用户空间文件系统 是操作系统中的概念,指完全在用户态实现的文件系统。

目前 Linux 通过内核模块对此进行支持。一些文件系统如 ZFS,glusterfs 使用 FUSE 实现。

FUSE 的工作原理如上图所示。假设基于 FUSE 的用户态文件系统 hello 挂载在 /tmp/fuse 目录下。当应用层程序要访问 /tmp/fuse 下的文件时,通过 glibc 中的函数进行系统调用,处理这些系统调用的 VFS 中的函数会调用 FUSE 在内核中的文件系统;内核中的 FUSE 文件系统将用户的请求,发送给用户态文件系统 hello;用户态文件系统收到请求后,进行处理,将结果返回给内核中的 FUSE 文件系统;最后,内核中的 FUSE 文件系统将数据返回给用户态程序。

Linux 内核从 2.6.14 支持通过 FUSE 模块

在用户空间实现文件系统

libfuse: 用户空间的 fuse 库, 非特权用户可访问。

LXCFS – 基于 FUSE 实现的用户空间文件系统

站在文件系统的角度: 通过调用 libfuse 库和 内核的 FUSE 模块交互实现

两个基本功能

让每个容器有自身的 cgroup 文件系统视图, 类似 Cgroup Namespace

提供容器内部虚拟的 proc 文件系统

LXCFS 视角

从 main 函数可以看出, 初始化的过程包括:

将运行时工作目录 /run/lxcfs/controllers/ 挂载到 tmpfs 文件系统

将当前系统的各个 group 子系统重新挂载到 /run/lxcfs/controllers/ 目录

调用 libfuse 库的主函数 fuse_main(), 指定用户态文件系统的目标目录 - /var/lib/lxcfs/

使用 struct fuse_operations 的 ops 方法, 与内核中的 FUSE 模块交互。lxcfs.c:701

容器视角

把虚拟 proc 文件系统挂载到 docker 容器

用户在容器中读取 /proc/meminfo ,cpuinfo 等信息

在 proc_meminfo_read 操作中实现 读取 meminfo

过程: 拿到 meminfo 进程的 pid 传给 lxcfs — 拿到 pid 的 cgroup 分组 — host 的 /cgroup 目录对应进程的 cgroup 子系统信息

存在的问题以及如何解决

LXCFS 的部署问题以及带来的影响和成本?见如下补充的 LxcFS – k8s 实践

故障恢复,如何自动 remount?如果 lxcfs 进程重启了,那么容器里的 /proc/cpuinfo 等等都会报 transport connected failed 这个是因为 /var/lib/lxcfs 会删除再重建,inode 变了。所以参考豆瓣的做法,共享 mount 事件,重新给容器挂载

https://github.com/lxc/lxcfs/issues/193

https://github.com/alibaba/pouch/issues/140

User Namespace

解决什么问题?

用户 UID/GID 的映射, 导致容器中的进程具有宿主上相同 uid/gid 用户的权限

docker 从 1.10 版本 开始支持 user namespace 隔离。使用参数:DOCKER_OPTS= –userns-remap=default

相关文档链接:

基于 LXCFS 增强 docker 容器隔离性的分析 2015.12.9

docker 容器显示问题及修复 2017-03-23

lxc-1.0.9 lxcfs-2.0.0 fuse-2.8.7 源码详细注释分析

Kubernetes 之路 2 – 利用 LXCFS 提升容器资源可见性

Kubernetes Initializers

看看大阿里 pouch 怎么解决 remount 这个问题

lxcfs 的 Kubernetes 实践

注:以下内容来自文档链接 4,内容有稍作修改

首先我们要在集群节点上安装并启动 lxcfs,我们将用 Kubernetes 的方式,用利用容器和 DaemonSet 方式来运行 lxcfs FUSE 文件系统。

本文所有示例代码可以通过以下地址从 Github 上获得

git clone https://github.com/denverdino/lxcfs-initializer
cd lxcfs-initializer

其 manifest 文件如下

apiVersion: apps/v1beta2
kind: DaemonSet
metadata:
 name: lxcfs
 labels:
 app: lxcfs
spec:
 selector:
 matchLabels:
 app: lxcfs
 template:
 metadata:
 labels:
 app: lxcfs
 spec:
 hostPID: true
 tolerations:
 - key: node-role.kubernetes.io/master
 effect: NoSchedule
 containers:
 - name: lxcfs
 image: dockerhub.nie.netease.com/whale/lxcfs:2.0.8
 imagePullPolicy: Always
 securityContext:
 privileged: true
 volumeMounts:
 - name: rootfs
 mountPath: /host
 volumes:
 - name: rootfs
 hostPath:
 path: /

注:由于 lxcfs FUSE 需要共享系统的 PID 名空间以及需要特权模式,所有我们配置了相应的容器启动参数。

可以通过如下命令在所有集群节点上自动安装、部署完成 lxcfs,是不是很简单?:-)

kubectl create -f lxcfs-daemonset.yaml

那么如何在 Kubernetes 中使用 lxcfs 呢?和上文一样,我们可以在 Pod 的定义中添加对 /proc 下面文件的 volume(文件卷)和对 volumeMounts(文件卷挂载)定义。然而这就让 K8S 的应用部署文件变得比较复杂,有没有办法让系统自动完成相应文件的挂载呢?

Kubernetes 提供了 Initializer 扩展机制,可以用于对资源创建进行拦截和注入处理,我们可以借助它优雅地完成对 lxcfs 文件的自动化挂载。

其 manifest 文件如下

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
 name: lxcfs-initializer-default
 namespace: kube-system
rules:
- apiGroups: [*]
 resources: [deployments]
 verbs: [initialize ,  patch ,  watch ,  list]
apiVersion: v1
kind: ServiceAccount
metadata:
 name: lxcfs-initializer-service-account
 namespace: kube-system
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 name: lxcfs-initializer-role-binding
subjects:
- kind: ServiceAccount
 name: lxcfs-initializer-service-account
 namespace: kube-system
roleRef:
 kind: ClusterRole
 name: lxcfs-initializer-default
 apiGroup: rbac.authorization.k8s.io
apiVersion: apps/v1beta1
kind: Deployment
metadata:
 initializers:
 pending: []
 labels:
 app: lxcfs-initializer
 name: lxcfs-initializer
spec:
 replicas: 1
 template:
 metadata:
 labels:
 app: lxcfs-initializer
 name: lxcfs-initializer
 spec:
 serviceAccountName: lxcfs-initializer-service-account
 containers:
 - name: lxcfs-initializer
 image: dockerhub.nie.netease.com/whale/lxcfs-initializer:0.0.2
 imagePullPolicy: Always
 args:
 -  -annotation=initializer.kubernetes.io/lxcfs 
 -  -require-annotation=true 
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:
 name: lxcfs.initializer
initializers:
 - name: lxcfs.initializer.kubernetes.io
 rules:
 - apiGroups:
 -  * 
 apiVersions:
 -  * 
 resources:
 - deployments

注:这是一个典型的 Initializer 部署描述,首先我们创建了 service account lxcfs-initializer-service-account,并对其授权了 deployments 资源的查找、更改等权限。然后我们部署了一个名为 lxcfs-initializer 的 Initializer,利用上述 SA 启动一个容器来处理对 deployments 资源的创建,如果 deployment 中包含 initializer.kubernetes.io/lxcfs 为 true 的注释,就会对该应用中容器进行文件挂载

我们可以执行如下命令,部署完成之后就可以愉快地玩耍了

kubectl apply -f lxcfs-initializer.yaml

下面我们部署一个简单的 Apache 应用,为其分配 256MB 内存,并且声明了如下注释 initializer.kubernetes.io/lxcfs : true

其 manifest 文件如下

apiVersion: apps/v1beta1
kind: Deployment
metadata:
 annotations:
  initializer.kubernetes.io/lxcfs :  true 
 labels:
 app: web
 name: web
spec:
 replicas: 1
 template:
 metadata:
 labels:
 app: web
 name: web
 spec:
 containers:
 - name: web
 image: httpd:2
 imagePullPolicy: Always
 resources:
 requests:
 memory:  256Mi 
 cpu:  500m 
 limits:
 memory:  256Mi 
 cpu:  500m

我们可以用如下方式进行部署和测试

$ kubectl create -f web.yaml 
deployment  web  created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
web-7f6bc6797c-rb9sk 1/1 Running 0 32s
$ kubectl exec web-7f6bc6797c-rb9sk free
 total used free shared buffers cached
Mem: 262144 2876 259268 2292 0 304
-/+ buffers/cache: 2572 259572
Swap: 0 0 0

我们可以看到 free 命令返回的 total memory 就是我们设置的容器资源容量。

我们可以检查上述 Pod 的配置,果然相关的 procfs 文件都已经挂载正确

$ kubectl describe pod web-7f6bc6797c-rb9sk
 Mounts:
 /proc/cpuinfo from lxcfs-proc-cpuinfo (rw)
 /proc/diskstats from lxcfs-proc-diskstats (rw)
 /proc/meminfo from lxcfs-proc-meminfo (rw)
 /proc/stat from lxcfs-proc-stat (rw)
...

在 Kubernetes 中,还可以通过 Preset 实现类似的功能,篇幅有限。本文不再赘述了。

感谢各位的阅读,以上就是“Kubernetes 容器隔离问题实例分析”的内容了,经过本文的学习后,相信大家对 Kubernetes 容器隔离问题实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是丸趣 TV,丸趣 TV 小编将为大家推送更多相关知识点的文章,欢迎关注!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-04发表,共计6337字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)