K8S namespace原理

一 基本定义

Namespace在很多情况下用于实现多租户的资源隔离。通过将集群内部的资源对象“分配”到不同的Namespce中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。

如果在创建pod,rc,service不指定namespace,则会被创建到default的namespace,定义一个namespace:

apiVersion: v1
kind: Namespace
metadata:
name: com-prod

也可以使用命令kubectl create ns com-prod

一旦定义后,就可以被其它组件使用,如下部署一个POD到该namespace.

[root@k8s-m1 ~]# cat ns-pod.yml

apiVersion: v1

kind: Pod

metadata:name: busybox

namespace: com-prod

spec:

   containers:- image: busybox

   command: ["/bin/sh","-c","sleep 3600s"]

   name: busybox

在默认情况下,Rancher集群定义了两个project:

Default:该project包含了默认namespace

System:该project包含所有其他预配置的namespace,包括kube-public、kube-system,kube-node-lease和系统提供的所有namespace

Kube-public:能被所有客户端读,包括未授权的客户端,该namespace主要保留用于集群使用,以便某些资源在整个集群范围内可以公开可见和可读。该namespace的公开属性仅仅是一种约定,并非要求。

Kube-system: 保存由k8s系统所创建的对象。

二 作用

[root@k8s-m1 ~]# cat ns-pod.yml

1 将不同的应用程序隔离开来,避免命名冲突和资源竞争

namespace非常适合在集群中划分开发、staging以及生产环境。通常情况下我们会被建议将生产工作负载部署到一个完全独立的集群中,来确保最大程度的隔离。

在管理环境时,通过将网络隔离来控制和组件之间的通信能力是很有必要的。同样,namespace范围的RBAC策略允许运维人员为生产环节设置严格的权限。配额能够确保对最敏感环境的重要资源的访问 

2 为不同的团队或项目提供独立的环境,使它们可以独立地管理和部署应用程序。

  通过给团队提供专门的namespace,你可以用RBAC策略委托某些功能来实现自我管理和自动化。比如从namespace的RoleBinding对象中添加或删除成员就是对团队资源访问的一种简单方法。除此之外,给团队和项目设置资源配额也非常有用。有了这种方式,你可以根据组织的业务需求和优先级合理地访问资源。

3 控制资源配额和访问权限,以确保应用程序之间的安全隔离。

三 namespace 资源限额

可以通过Resource Quota来限制Namespace中资源的使用,资源配额是一种控制机制,可以限制Namespace中资源使用量,包括CPU、内存、存储等。

资源限制可以针对Namespace中所有Pod进行限制,当然也可以针对单个Pod进行限制:

apiVersion: v1kind: ResourceQuotametadata:

  name: mytest-quota

  namespace: mytest

spec:

    hard:

      requests.cpu: 2

      requests.memory: 2Gi

      limits.cpu: 4

      limits.memory: 4Gi

四 namespace的资源

 1 放在namespace里的资源

kubectl api-resources --namespaced=true

pgupgrades                               postgres-operator.crunchydata.com/v1beta1   true         PGUpgrade

postgresclusters                         postgres-operator.crunchydata.com/v1beta1   true         PostgresCluster

rolebindings                             rbac.authorization.k8s.io/v1                true         RoleBinding

roles                                    rbac.authorization.k8s.io/v1                true         Role

csistoragecapacities                     storage.k8s.io/v1

 2 没有放在namespace里的资源

kubectl api-resources --namespaced=false

clusterroles                                   rbac.authorization.k8s.io/v1           false        ClusterRole

priorityclasses                   pc           scheduling.k8s.io/v1                   false        PriorityClass

csidrivers                                     storage.k8s.io/v1                      false        CSIDriver

csinodes                                       storage.k8s.io/v1                      false        CSINode

storageclasses                    sc           storage.k8s.io/v1                      false        StorageClass

volumeattachments                              storage.k8s.io/v1   

删除namespace并清理

如果不需要namespace了,我们可以删除它。

删除namespace这一功能非常强大,因为它不仅删除namespace,还会清理其中部署了的所有资源。这一功能非常方便,但是同时如果你一不小心,也会非常危险。

在删除之前,最好列出和namespace相关的资源,确定想要删除的对象

六 namespace 深层解密

1 Linux namespace

在Linux系统中,Namespace是在内核级别以一种抽象的形式来封装系统资源,通过将系统资源放在不同的Namespace中,来实现资源隔离的目的。不同的Namespace程序,可以享有一份独立的系统资源。Namespace的一个作用就是来实现容器。目前支持的Linux内核namespace有八种类型:

Cgroup,

IPC - inter process communication,

Network,

Mount,

PID - Process ID number,

Time,User,

UTS - UNIX time sharing.

在每个进程初始化期间,/proc/<$pid>/ns目录中会创建一个符号链接。这个符号链接将在运行时定义进程的能力,每个进程都将包含所有八个类别的定义能力。Linux文档中有一个示例输出,显示了可以在进程的/proc/<$pid>/ns目录中找到的内容。

$ ls -l /proc/$$/ns | awk '{print $1, $9, $10, $11}'
total 0
lrwxrwxrwx ipc -> ipc:[4026531839]

lrwxrwxrwx mnt -> mnt:[4026531840]

lrwxrwxrwx net -> net:[4026531956]

lrwxrwxrwx pid -> pid:[4026531836]

lrwxrwxrwx user -> user:[4026531837]

lrwxrwxrwx uts -> uts:[4026531838]

其中 4026531839为Namespace ID,  如果两个进程的Namespace相同意味着它们处于同一个namespace中。如果没有任何其他因素, Namespace 将在其中的最后一个进程终止或者离开该 Namespace 时自动删除。

2 Linux namespace API

  1. 涉及到 Namespace 操作接口的API有 clone(2) 、 setnx(2) 、 unshare(2) 、 ioctl(2)

clone(2)

这个系统调用创建一个新的独立的 Namespace 进程,函数描述如下:

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

通过flags参数控制创建进程的特性,例如新创建进程是否与父进程共享虚拟内存等。例如传入 CLONE_NEWNS 标志使得新创建的进程拥有独立的 Mount Namespace ,同样也可以传入多个flag使得新创建的进程拥有多种特性,
例如传入这个flags创建的新进程将同时拥有独立的 Mount Namespace 、 UTS Namespace 和 IPC Namespace 。

flags = CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC ;

setns(2)

这个系统调用允许进程加入指定的 Namespace 中,它的函数描述如下:

int setns(int fd, int nstype);

  • fd参数:表示文件描述符。可以通过打开 /proc/$pid/ns/  的方式将指定的 Namespace 保留下来,也就是说可以通过文件描述符的方式索引到某个 Namespace 。
  • nstype 参数:用来检查 fd 关联 Namespace 是否与 nstype 表明的 Namespace 一致,如果填0则不检查该项。

unshare(2)

使该进程脱离 Namespace ,并加入到一个新的 Namespace 中。与 setnx() 不同的是, unshare() 不用关联之前存在的 Namespace ,只需要指定需要分离的 Namespace 即可,该调用会自动创建一个新 Namespace 。函数描述如下:

int unshare(int flags);

其中 flags 用于指明要分离的资源类别,它支持 flags 与 clone 系统调用支持的 flags 类似

ioctl(2)

可以使用 ioctl(2) 操作发现关于 namespace 的信息。函数描述如下:

int ioctl(int fd, unsigned long request, ...);

  • fd 参数:必须是一个打开的文件描述符
  • 第二个参数:是依赖于设备的请求代码
  • 第三个参数:指向内存的无类型指针
  1.  IPC namesapce

IPC(Interprocess Communication) Namespace 是对进程通信的隔离,进程间通信常用的方法包括信号量、消息队列和共享内存。然而与虚拟机不同的是,容器内部进程通信对宿主机来说,实际上是具有相同 PID Namespace 的进程间的通信,因此需要一个唯一的标识符进行区别。申请IPC资源就申请这样一个全局唯一的32位ID,所以IPC Namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个 IPC Namespace 下的进程彼此可见,而与其它的IPC Namespace下的进程则互相不可见。
目前使用 IPC Namespace 机制的系统不多,其中比较有名的有 Postgre SQL。Docker 本身是通过 socket 或 tcp 进行通信。

2. Network namespace

当在同一个Linux中启用两个相同的进程,该进程会占用同一个端口时将会报错,这时就需要启用network namespace进行网络资源隔离了。

Network namespace 主要提供了关于网络资源的隔离,包括网络设备、IPv4 和 IPv6 协议栈、IP 路由表、防火墙、/proc/net 目录、/sys/class/net 目录、端口(socket)等等。一个物理的网络设备最多存在在一个 network namespace 中,你可以通过创建 veth pair(虚拟网络设备对:有两端,类似管道,如果数据从一端传入另一端也能接收到,反之亦然)在不同的 network namespace 间创建通道,以此达到通信的目的。
一般情况下,物理网络设备都分配在最初的 root namespace(表示系统默认的 namespace,在 PID namespace 中已经提及)中。但是如果你有多块物理网卡,也可以把其中一块或多块分配给新创建的 network namespace。需要注意的是,当新创建的 network namespace 被释放时(所有内部的进程都终止并且 namespace 文件没有被挂载或打开),在这个 namespace 中的物理网卡会返回到 root namespace 而非创建该进程的父进程所在的 network namespace。

  内核创建了 network namespace 以后,真的是得到了一个被隔离的网络。但是我们实际上需要的不是这种完全的隔离,而是一个对用户来说透明独立的网络实体,我们需要与这个实体通信。所以 Docker 的网络在起步阶段给人一种非常难用的感觉,因为一切都要自己去实现、去配置。你需要一个网桥或者 NAT 连接广域网,你需要配置路由规则与宿主机中其他容器进行必要的隔离,你甚至还需要配置防火墙以保证安全等等

Flannel 就是利用了network namespace,建立一个虚拟网桥用veth pair绑定Pod的eth0和这个虚拟网桥用于同一台主机的POD通信,然后在第三层上建立一个虚拟的vtep设备用于封解包,同时Flannel 改定本地主机的路由表,arp协议表等利用本机物理网络传送到目的主机,目的主机上也有一个vtep设备用于解包封包,目的主机解包后把包传到本地的虚拟网桥,虚拟网桥再转到到POD来进行跨主机节点的网络通信。

3. Mount Namespace

Mount Namespace 用来隔离文件系统的挂载点,不同 Mount Namesace 的进程拥有不同的挂载点,同时也拥有了不同的文件系统视图

进程在创建 mount namespace 时,会把当前的文件结构复制给新的 namespace 。新的 namespace 中所有 mount 操作都只影响自身的文件系统,而对外界不会产生任何影响。这样非常严格实现了隔离,但是某些情况下可能并不适用。例如父节点 namespace 中的进程挂载了一张 CD-ROM ,这时子节点的 namespace 拷贝的目录结构就无法自动挂载这张 CD-ROM ,因为这种操作会影响父节点的文件系统。
2006年引入了挂载传播(mount propagation)解决了这个问题,挂载传播定义了挂载对象(mount object)之间的关系,系统利用这些关系决定任何挂载对象中的挂载事件传播到其它挂载对象。所谓传播事件,就是一个挂载对象状态变化导致的其它挂载对象的挂载与解除挂载动作的事件。

  • 如果两个挂载对象具有共享关系(share relationship),那么一个挂载对象的挂载事件会传播到另一个挂载对象,反之亦然。
  • 如果两个挂载对象形成从属关系(master slave),那么一个挂载对象的挂载事件会传播到另一个挂载对象,但反之不行。在这种关系中,从属对象是事件的接受者。

一个挂载状态可以为如下的其中一种:

  • 共享状态(shared)
  • 从属状态(slave)
  • 共享/从属状态(shared and slave)
  • 私有挂载(private)
  • 不可绑定挂载(unbindable)

传播事件的挂载对象称为共享挂载(shared mount);接收传播事件的挂载对象称为从属挂载(slave mount)。既不传播也不接收传播事件的挂载对象称为私有挂载(private mount)。另一种特殊的挂载对象称为不可绑定的挂载(unbindable mount),它们与私有挂载相似,但是不允许执行绑定挂载,即创建 mount namespace 时这块文件对象不可被复制。

共享挂载的应用场景非常明显,就是为了文件数据的共享所必须的一种挂载方式;从属挂载更大的意义在于一些“只读”场景;私有挂载则是纯粹的隔离,作为独立个体存在;不可绑定挂载则有助于防止没必要的文件拷贝。

默认情况下,所有挂载都是私有的。从共享挂载克隆的挂载对象也是共享的挂载,它们互相传播挂载事件。
从属挂载克隆的挂载对象也是从属的挂载,它也从属于原来的从属挂载的主挂载对象。

4. PID namespace

PID namespace 隔离非常实用,它对进程 PID 重新标号,即两个不同 namespace 下的进程可以有同一个 PID。每个 PID namespace 都有自己的计数程序。内核为所有的 PID namespace 维护了一个树状结构,最顶层的是系统初始时创建的,我们称之为 root namespace。他创建的新 PID namespace 就称之为 child namespace(树的子节点),而原先的 PID namespace 就是新创建的 PID namespace 的 parent namespace(树的父节点)。通过这种方式,不同的 PID namespaces 会形成一个等级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。反过来,子节点不能看到父节点 PID namespace 中的任何内容。

  • 每个 PID namespace 中的第一个进程“PID 1“,都会像传统 Linux 中的 init 进程一样拥有特权,起特殊作用。
  • 一个 namespace 中的进程,不可能通过 kill 或 ptrace 影响父节点或者兄弟节点中的进程,因为其他节点的 PID 在这个 namespace 中没有任何意义。
  • 如果你在新的 PID namespace 中重新挂载 /proc 文件系统,会发现其下只显示同属一个 PID namespace 中的其他进程。
  • 在 root namespace 中可以看到所有的进程,并且递归包含所有子节点中的进程。

到这里,可能你已经联想到一种在外部监控 Docker 中运行程序的方法了,就是监控 Docker Daemon 所在的 PID namespace 下的所有进程即其子进程,再进行删选即可。

5. User namespace

User namespace 主要隔离了安全相关的标识符(identifiers)和属性(attributes),包括用户 ID、用户组 ID、root 目录、 key (指密钥)以及特殊权限。说得通俗一点,一个普通用户的进程通过clone() 创建的新进程在新user namespace 中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是他创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。

User namespace 是目前的六个 namespace 中最后一个支持的,并且直到 Linux 内核 3.8 版本的时候还未完全实现(还有部分文件系统不支持)。因为 user namespace 实际上并不算完全成熟,很多发行版担心安全问题,在编译内核的时候并未开启 USER_NS。实际上目前 Docker 也还不支持 user namespace,但是预留了相应接口,相信在不久后就会支持这一特性。所以在进行接下来的代码实验时,请确保你系统的 Linux 内核版本高于 3.8 并且内核编译时开启了 USER_NS(如果你不会选择,可以使用 Ubuntu14.04)。

Linux 中,特权用户的 user ID 就是 0,演示的最终我们将看到 user ID 非 0 的进程启动 user namespace 后 user ID 可以变为 0。使用 user namespace 的方法跟别的 namespace 相同,即调用 clone() 或 unshare() 时加入 CLONE_NEWUSER 标识位。

6. UTS namespace

提供了主机名和域名的隔离,这样每个容器就可以拥有了独立的主机名和域名,在网络上可以被视作一个独立的节点而非宿主机上的一个进程.

七  K8s namespace

创建一个新的Kubernetes namespace时,会使用定义的系统调用(syscall-clone(2) 、 setnx(2) 、 unshare(2) 、 ioctl(2)之一)使用namespaceAPI发送请求,由于Kubernetes具有管理员权限,将会创建一个新的namespace。新的namespace将包含为其域下分配的新进程的功能规范。新的namespace的详细描述见上。

Kubernetes(和任何管理员)可以修改多个功能的规范,例如Mount或Network功能。这些功能通常通过应用Kubernetes对象(如服务)进行更改,所有与创建的namespace相关的进程都链接到该对象,以便在删除namespace时一次性清理所有进程。

Kubernetes namespace类似于Linux namespace。然而,Kubernetes以声明式风格管理namespace。简单的清理过程对于大规模操作的管理至关重要,并且集群管理附加组件(如基于角色的访问控制(RBAC))使namespace更加强大。

八 K8s namespace 最佳实践

  1 创建一个管理框架

      于许多Kubernetes管理功能是围绕namespace功能构建的,因此必须定义一个有效的管理结构。如果没有适当的管理,存储、内存、CPU和其他资源将会被混乱地请求和利用。由于缺乏管理,还会出现组织壁垒,主要是因为各团队不想互相干扰。由于缺乏共享知识和协作,开发也变得缓慢。结果就是资源不协调和资源利用不足导致高成本。缺乏方向性的文化也会导致团队在没有共享知识和协作的情况下进行开发。

  2 避免过多的使用namespace

织中不应该让任何人随意创建namespace。允许个人在没有管理的情况下创建namespace会导致许多额外的环境。 namespace应该与创建它们的应用程序或功能相关联,比如后端或前端namespace。这样可以让其他用户快速识别与该namespace相关的工作负载。

3 不要过载namespace

用户应为特定应用程序或微服务以及所有应用程序要求创建namespace。这种特殊性有多个原因:

  1. 简化整个应用程序的重建 
  2. 细粒度网络管理 
  3. 更大的可扩展性 
  4. 更大的可观察性 

不要将执行不相关任务的多个工作负载过载到命空间中。保持您的namespace精确和简单。

4 使用易扩展的命名方式

  如果您有多个需要分开的客户或应用程序,请确保开发一个可扩展的命名结构。随着公司的发展,您希望确保您的namespace可以轻松辨别。

5 道何时使用集群与namespace隔资源

  时资源的namespace分离是不够的。随着区域性考虑的出现,按照集群和区域来分离工作负载可能是必要的。不要害怕在集群之间分隔应用程序/组,因为这可能是一种更可扩展的解决方案。只要创建精确、声明式的Kubernetes对象,您的资源将是可移植和可扩展的。

6 给一切打标签

  签是您可以添加到namespace中的识别元数据,以赋予它们额外的上下文。创建或附加Kubernetes标签到您的namespace是必要的,因为它们将有助于在以后监视、记录和调试应用程序。随着应用程序的增长,它们产生的日志和指标数据的数量以及快速识别和筛选这些数据的能力将变得至关重要。 您可以在团队之间进行标签审计,以确保当前的工作负载被准确地标记,以创建命名的协调性。这可能看起来很繁琐,但整个系统的可观察性将因此而受益。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值