Kubernetes 资源

资源

Kubernetes 将所有的工作单元都抽象为资源,并通过资源路径来与这些资源进行交互。当资源配置被应用(实例化)后,它就变成了一个对象。例如,定义一个 Pod 类型的配置文件并执行它后,Kubernetes 会创建一个 Pod 对象。每种资源都有一个特定的 URL 路径用于访问和操作这些对象。

比如,通过 /api/v1/pods 路径可以获取所有 v1 版本的 Pod 对象列表,列表包含了同一类型对象的集合,所有 Pod 组成的集合被称为 PodList。如果你需要访问特定的 Pod 对象,则可以通过 /api/v1/namespaces/<namespace-name>/pods/<pod-name> 路径来获取该 Pod 的详细信息。

[root@k8s-master1 ~]# kubectl get --raw /api/v1/namespaces/default/pods/nginx-db749865c-n25rj | python -m json.tool
{
    "apiVersion": "v1",
    "kind": "Pod",
    ......
}

这种抽象方式使得 Kubernetes 的资源管理更加模块化和灵活,同时 API 路径也为开发者和管理员提供了对集群资源的标准化访问方式。这些路径不仅用于查询资源,也可以用于更新、删除和其他 CRUD 操作。例如:
获取所有 Pod:GET /api/v1/pods
获取指定命名空间下的某个 Pod:GET /api/v1/namespaces/default/pods/my-pod
创建 Pod:POST /api/v1/namespaces/default/pods
删除 Pod:DELETE /api/v1/namespaces/default/pods/my-pod

资源类型

通过kubectl api-resources命令进行查看所有api资源

[root@k8s-master2 ~]# kubectl api-resources
NAME        SHORTNAMES      APIVERSION    NAMESPACED                              KIND
资源名称     资源名称简写     版本信息      是否可使用命名空间隔离,true是,false否    资源类型

通过 kubectl api-resources --namespaced=true命令查看位于命名空间下的资源,名称空间级别资源:
工作负载型资源:Pod、 ReplicaSet、 Deployment、 StatefulSet、 DaemonSet、Job、CronJob
服务发现及负载均衡型资源: Service、 Ingress
配置与存储型资源:Volume、CSI(容器存储接口,可以扩展各种各样的第三方存储卷)
特殊类型的存储卷:ConfigMap、 Secret、DownwardAPI

集群级资源,通过kubectl api-resources --namespaced=false命令,可以查看不在命名空间下的资源:Namespace、node、 Role ClusterRole、 RoleBinding、 ClusterRoleBinding、…

我们来看一个标准的kubernetes的yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

在上述的yaml中,我们指定了 apiVersion: apps/v1,其中就包括了Group(apps)和 Version(v1),即GV,也用kind字段标识了资源类型Deployment(Kind),集合为GVK。同时第一个spec下定义了众多字段资源(即Resource,是Kind的对象标识,存储的是Kind的 API 对象的一个集合),即GVR。比如我们来查看deployments资源的GVK,则通过以下命令进行查看:

[root@k8s-master2 ~]# kubectl api-resources
deployments                       deploy             apps/v1                                true         Deployment

也可以通过 kubectl api-versions命令查看版本信息

[root@k8s-master2 ~]# kubectl api-versions
admissionregistration.k8s.io/v1				#组名/版本
admissionregistration.k8s.io/v1beta1
......
v1											#组名没写默认为core,核心组

资源清单

在Docker环境中,可以直接通过 docker run 命令来运行应用,在Kubernetes环境下同样也可以用类似kubectl run命令行的方式来运行应用。但是在Kubernetes中却不推荐使用命令行的方式,而是希望使用我们称为资源清单的东西来描述应用。资源清单可以用YAML或者JSON文件来编写,一般来说YAML文件更方便阅读和理解,所以推荐使用YAML文件来进行描述,这样的yaml文件我们一般称为资源清单。

一些简单的资源对象我们可能可以凭借记忆写出对应的资源清单,但是 Kubernetes 发展非常快,版本迭代也很快,每个版本中资源对象可能又有很多变化,那么有没有一种办法可以让我们做到有的放矢呢?实际上最简单的方法就是查找 Kubernetes API 文档,比如我们现在使用的是 v1.22.2 版本的集群,可以通过地址 https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22 查找到对应的 API 文档,在这个文档中我们可以找到所有资源对象的一些字段。

但是如果平时我们编写资源清单的时候都这样去查找文档势必会效率低下,所以我们可以直接通过 kubectl 命令行工具来获取这些字段信息。比如我们要获取 Deployment 的字段信息,我们可以通过 kubectl explain 命令来了解

[root@master ~]# kubectl explain deployment
KIND:     Deployment
VERSION:  apps/v1

通过命令行查看的信息和我们在 API 文档中查看到的信息基本一致,比如我们看到其中 spec 字段是一个 <Object> 类型的,证明该字段下面是一个对象,我们可以继续去查看这个字段下面的详细信息。对象类型object格式都有二级字段,如果字段显示的是 []object表示列表对象:由对象组成的列表,也就意味着该字段下面的字段需要以横线引导。如果字段显示的是 required表示必选字段,这就证明该字段是必填的,在创建这个资源对象的时候必须声明这个字段

[root@master ~]# kubectl explain deployment.spec
KIND:     Deployment
VERSION:  apps/v1

RESOURCE: spec <Object>
......

通过 kubectl 命令可以直接从配置文件加载配置数据去操作kubernetes资源,比如运行一个定义好的资源清单文件

kubectl apply -f xxxx.yaml

kubectl 是直接操作 APIServer 的,所以就相当于把我们的清单提交给了 APIServer,然后集群获取到清单描述的应用信息后存入到 etcd 数据库中,然后 kube-scheduler 组件发现这个时候有一个Pod还没有绑定到节点上,就会对这个Pod进行一系列的调度,把它调度到一个最合适的节点上,然后把这个节点和Pod绑定到一起(写回到etcd) ,然后节点上的kubelet 组件这个时候watch到有一个Pod被分配过来了,就去把这个Pod的信息拉取下来,然后根据描述通过容器运行时把容器创建出来,最后当然同样把Pod状态再写回到etcd中去,这样就完成了一整个的创建流程。

资源清单模板

五个一级字段:apiVersion、kind、metadata、spec、status。status字段并非由用户定义,而是通过集群管理,资源的实际状态
spec是期望状态,status是实际状态。下面是资源清单模板文件,仅供参考

apiVersion:[group]/version		# 群组/版本号 如果没有给定group名称,默认为core,可以使用 kubectl api-versions 获取当前 k8s 版本上所有的 apiVersion 版本信息

kind: 		#用来指定要管理的资源类型,首字母大写

metadata:	#元数据对象,表示资源的标识信息
  name:		#资源名称,在同一个类别中这个名称必须是唯一的
  namespace:	#名称空间,资源所属的名称空间
  labels:		#标签列表,每个标签都是一对键值对,一个资源可以拥有多个标签,一个标签也可以对应多个资源
    - key: value	#标签可以在资源创建时指定,也可以在资源创建之后来管理标签
  annotations:      #自定义注解列表
    - key: value	#可以定义多个注解的键值

spec: 				#详细定义的对象
  containers:		#指定容器列表,可以有多个容器
  - name:			#要创建的容器名称,如果不指定会随机创建
    image:			#要运行的镜像名称
    imagePullPolicy: [Always | Never | IfNotPresent] 	#镜像的下载策略,当不指定此配置时,如果镜像标签是 :latest 的时候,默认采用Always的方式拉取镜像,否则默认采用IfNotPresent方式拉取镜像
     # Always: 表示无论本地是否有镜像文件,每次创建资源时都去镜像仓库中拉取镜像
     # Never:  表示使用被绑定节点的本地镜像,如果本地没有也不会从镜像仓库中拉取镜像
     # IfNotPresent: 表示如果被绑定节点的本地有镜像时就使用本地镜像,如果本地没有就去镜像仓库拉取
    command:	#指定容器启动命令,将替换镜像打包时使用的启动命令,command会覆盖Dockerfile中的Entrypoint,如果不指定该参数,那么就会采用镜像文件中的ENTRYPOINT指令
    args:		#指定容器启动命令参数,command指令的参数列表,如果不指定这个参数,则使用镜像中的CMD指令
    # command和args参数分别对应镜像中的ENTRYPOINT和CMD指令,此时就出现如下几种情况:
    # command和args都未指定:运行镜像中的ENTRYPOINT 和 CMD指令
    # 指定command而未指定args:只运行command指令,镜像中的ENTRYPOINT和CMD指令都会被忽略
    # 未指定command而指定args:运行镜像中的ENTRYPOINT指令且将args当做参数传给ENTRYPOINT指令且镜像中的CMD指令被忽略
    # command和args都指定:运行command指令,并把args当做参数传递给command,镜像中的ENTRYPOINT和CMD指令都会被忽略
    ports:		#容器暴露的端口信息。在此处暴露端口可为系统提供有关容器使用的网络连接的信息,但仅仅是参考信息。如果在此处没有指定端口,也并不能保证容器没有暴露端口。任何监听容器中“0.0.0.0”地址的端口都可以被访问到。其下级还有如下字段:
    - name:		#指定端口名称
      containerPort:	#暴露的容器端口号
      protocol:			#指定端口协议,默认TCP协议,可选UDP,TCP,SCTP
    workingdir: string	#指定容器的工作目录
    livenessProbe:  	#POD容器存活状态监测,检测探针有三种,ExecAction、TCPSockerAction、HttpGetAction
      exec: 			#命令类型探针
      command:			#执行的探测命令,命令运行路径是容器内系统的/,且命令并不会运行在shell中,所以,需要我们手动指定运行的shell,当命令返回值是0时表示状态正常,反之表示状态异常
      httpGet:		#http请求型探针
        host:		#请求的主机地址,默认是POD IP
        httpHeaders:	#HTTP请求头
        path: 			#请求的URL
        port: 			#请求的端口,必填项
        scheme: 		#请求协议,默认是http
      tcpSocket:TCP socket型探针
        host:			#请求的主机地址,默认是POD IP
        port:			#请求的端口号,端口范围是1-65535
        failureThreshold:	#连续错误次数,默认3次,即默认连续3次检测错误才表示探测结果为异常
        successThreshold:	#连续成功次数,默认1次,即当出现失败后,出现连续1次检测成功就认为探测结果是正常
        periodSeconds:		#探测时间间隔,默认10秒
        timeoutSeconds:	#探测超时时间,默认1秒
        initialDelaySeconds:	#起始探测时间间隔,表示pod启动后,该间隔之后才开始进行探测
    readinessProbe:			#主容器内进程状态监测,此检测和service调度有很强的关联性,当新调度一个pod时,如果没有指定就绪性检测,此时一旦pod创建就会立即被注册到service的后端。如果此时pod内的程序尚无法对外提供服务,就会造成部分请求失败。所以,我们应该让一个pod在注册到service中区之前,已经通过了可用性检测,保证可以对外提供服务
    lifecycle:		#生命周期钩子方法
      postStart:	#容器启动后执行的命令,可用检测探针和livenessProbe是一致的
      preStop:		#容器启动前执行的命令
  restartPolicy:	#重启策略,Always, OnFailure,Never. Default to Always.
  nodeSelector:		#node选择器,可以根据node的标签选择POD运行在某些指定的node上
  nodeName:		    #使pod运行在指定nodeName的节点之上

spec.containers[].env			list		指定容器运行前需要设置的环境变量列表
spec.containers[].env.name		string		指定环境变量名称
spec.containers[].env.value		string		指定环境变量值
spec.containers[].resources		object		指定资源限制和资源请求的值(设置容器的资源上线)
spec.containers[].resources.limits.cpu		string	指定cpu的限制,单位数为core数,将用于docker run –cpu-shares参数
spec.containers[].resources.limits.memory	string	指定MEM内存的限制
spec.containers[].resources.requests		object	指定容器启动和调度时的资源设置
spec.containers[].resources.requests.cpu	string	cpu请求,单位为core数,容器启动时初始化可用数量
spec.containers[].resources.requests.memory	string	内存请求,单位为MIB,GIB,容器启动的初始化可用数量
spec.containers[].ports[].hostPort	string		指定容器所在主机需要监听的端口,默认跟containerPort相同,注意设置了hostPort同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)

YAML

YAML是一个可读性高,用来表达数据序列的格式。YAML的意思其实是:仍是一种标记语言,但为了强调这种语言以数据做为中心,而不是以标记语言为重点。在 Kubernetes 中,我们只需要了解两种结构类型就行了:Lists(列表)、Maps(字典)

基本语法:
1、使用缩进表示层级关系,缩进时不允许使用Tab键,只允许使用空格,缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
2、#代表注释,从这个字符一直到行尾,都会被解释器忽略。json语法严格,不能注释,可读性较差,比较适用于API返回值

列表

在 YAML 文件中定义一个列表,可以有任何数量的项在列表中,每个项的定义以破折号-开头,与父元素之间可以缩进也可以不缩进

args:
  - Cat
  - Dog
  - Fish

将上面的 YAML 文件转换成 JSON 格式:

{
    "args": [ 'Cat', 'Dog', 'Fish' ]
}

字典

字典就是一个 key:value 的键值对

apiVersion: v1
kind: Pod
#---是分隔符,在单一文件中可用连续三个连字号---区分多个文件
---
apiVersion: v1
kind: Pod
metadata:			# KEY 对应的值不是字符串而是一个 Maps
  name: ydzs-site
  labels:
    app: web

将上面的 YAML 文件转换成 JSON 格式:

{
    "apiVersion": "v1",
    "kind": "pod"
}
---
{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "kube100-site",
        "labels": {
            "app": "web"
        }
    }
}

字典列表组合

复合结构:对象和列表结合使用,形成复合结构。Lists 的子项也可以是 Maps,Maps 的子项也可以是 Lists

apiVersion: v1
kind: Pod
metadata:
  name: ydzs-site
  labels:
    app: web
spec:
  containers:
    - name: front-end
      image: nginx
      ports:
        - containerPort: 80
    - name: flaskapp-demo
      image: cnych/flaskapp
      ports:
        - containerPort: 5000

转成如下 JSON 格式文件:

{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "ydzs-site",
        "labels": {
            "app": "web"
        }
    },
    "spec": {
        "containers": [{
            "name": "front-end",
            "image": "nginx",
            "ports": [{
                "containerPort": "80"
            }]
        }, {
            "name": "flaskapp-demo",
            "image": "cnych/flaskapp",
            "ports": [{
                "containerPort": "5000"
            }]
        }]
    }
}

Pod资源限制

容器中的程序运行需要占用一定的资源,例如 CPU 和内存等。如果不对容器的资源进行限制,容器可能会占用过多资源,从而影响其他容器的正常运行,甚至导致节点不稳定。在 Kubernetes 中,资源分为 可压缩资源不可压缩资源 两种:
在kubernetes中,像cpu这样的资源被称为可压缩资源,应用程序不运行是不会占用cpu的,进程支持抢占。 当cpu资源不足时pod不会退出,只会饥饿。
可压缩资源是指当应用程序不运行时,不会占用这些资源,且进程支持抢占。在kubernetes中,像CPU这样的资源被称为可压缩资源。CPU 资源的占用是动态的,只有在程序运行时才会消耗 CPU 时间。如果 CPU 资源不足,Pod 不会退出,而是进入饥饿状态,即无法获得足够的 CPU 时间来运行,但它依然存在。资源饥饿会导致 Pod 的性能下降,但 Pod 并不会被杀死或退出。
不可压缩资源比如内存,内存是容器运行过程中必须占用的资源,一旦分配给容器,程序生成的内存就会被占用。当内存不足时,Kubernetes 会触发 OOM(Out of Memory) 错误,并导致内存被内核回收,最终可能会导致 Pod 被杀死。

在 Kubernetes 中,Pod 是最小的调度单位。调度和资源管理的所有配置,通常都是针对 Pod 来设置的。由于一个 Pod 可以包含多个容器,资源的限制通常是针对每个容器进行配置的。所以,Pod 的 CPU 和内存资源的总配置,是由其中每个容器的配置值累加得出的。因此,如果想要控制 Pod 的总资源消耗,必须在 Pod 内的每个容器中进行资源限制的配置。这种设计让 Kubernetes 在资源管理上更为灵活,同时确保了容器可以在资源不足的情况下尽量避免影响到其他容器的运行。

Kubernetes 提供了对内存和 CPU 等资源的配额机制,主要通过 Pod 中的 resources 选项来实现。resources 选项包括两个子选项:requestslimits,用于控制容器的资源使用。
requests:表示容器在启动时至少能够获得的资源大小。容器可能不会实际使用这么多资源,但 Kubernetes 在调度 Pod 时会确保容器被调度到能够提供至少这些资源的节点上。换句话说,requests 设定了调度时的资源保证,如果集群中没有足够的资源来满足请求的资源,Pod 就无法启动。
limits:表示容器能够使用资源的最大值。当容器的资源使用超过了 limits 所设定的值时,Kubernetes 会终止该容器并进行重启。limits 用来确保容器不会消耗过多资源,避免影响集群中其他容器的正常运行。

requestslimits这两个选项的配合使用,能够帮助 Kubernetes 实现资源的有效调度和控制,避免资源浪费或过度使用,从而保障集群的稳定性。

内存

对于内存资源,Kubernetes 使用 字节(bytes) 作为单位,但支持多种更易读的单位表示方式。常用的单位有:Ei、Pi、Ti、Gi、Mi、Ki(或者 E、P、T、G、M、K)的方式来作为 bytes 的值。注意 Mi 和 M 的区别:Mi(Mebibyte)是基于 1024 的单位,而 M(Megabyte)是基于 1000 的单位。具体来说,1 Mi = 1024 * 1024 字节,而 1 M = 1000 * 1000 字节。Kubernetes 中常使用 Mi 和 Gi 这种基于 1024 的单位,因为它更符合计算机内存和存储的二进制分配方式

为了了解如何用这些值是来控制容器进程,我们来创建一个没有配置内存限制的 Pod

kubectl run limit-test --image=busybox --command -- /bin/sh -c "while true; do sleep 2; done"

用 Kubectl 命令我们可以验证这个 Pod 是没有资源限制的

[root@master ~]# kubectl get pods limit-test -o=jsonpath='{.spec.containers[0].resources}'
{}

Kubernetes 最酷的一点是你可以跳到系统以外的角度来观察每个构成部分,所以我们登录到运行 Pod 的节点,看看 docker 是如何运行这个容器的

[root@node2 ~]# docker inspect `docker ps | grep busy | cut -d' ' -f1` -f "{{.HostConfig.Memory}}"
0

这个容器的.HostConfig.Memory域对应了docker run时的--memory参数,0 值表示未设定。Docker 会对这个值做什么?为了控制容器进程能够访问的内存数量,Docker 配置了一组 control group,或者叫 Cgroup。Cgroup 可以通过/proc/sys 伪文件系统轻松查看到,所以检查容器如何配置内存的 Cgroup 就很简单了。在容器的 Pid namespace 里,根进程的 pid 为 1,但是 namespace 以外它呈现的是系统级 pid,我们可以用来查找它的 Cgroup

[root@node2 ~]# ps ax | grep /bin/sh 
   4726 ?        Ss     0:00 /bin/sh -c while true; do sleep 2; done
[root@node2 ~]# cat /proc/4726/cgroup
......
8:memory:/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pode2b4385e_3ca1_4f6b_a523_e88cefa34b12.slice/docker-98fc68395110e75f6b65833c645c636caba8c1bbbb6d564200963f48298bc4bb.scope
......

我列出了内存 cgroup,这正是我们所关注的。你在路径里可以看到前面提到的 cgroup 层级。一些比较重要的点是:首先,这个路径是以 kubepods 开始的 cgroup,所以我们的进程继承了这个 group 的每个属性,还有 burstable 的属性(Kubernetes 将 Pod 设置为burstable QoS类别)和一组用于审计的 Pod 表示。最后一段路径是我们进程实际使用的 cgroup,我们可以把它追加到/sys/fs/cgroups/memory后面查看更多信息

[root@node2 ~]# ls -l /sys/fs/cgroup/memory/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pode2b4385e_3ca1_4f6b_a523_e88cefa34b12.slice/docker-98fc68395110e75f6b65833c645c636caba8c1bbbb6d564200963f48298bc4bb.scope
......
-rw-r--r--. 1 root root 0 Jun  6 17:00 memory.limit_in_bytes
-rw-r--r--. 1 root root 0 Jun  6 17:00 memory.soft_limit_in_bytes

memory.limit_in_bytes属性,它设置了内存限制。它等价于 Docker 命令中的--memory参数,也就是 Kubernetes 里的内存资源限制

[root@node2 ~]# cat /sys/fs/cgroup/memory/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pode2b4385e_3ca1_4f6b_a523_e88cefa34b12.slice/docker-98fc68395110e75f6b65833c645c636caba8c1bbbb6d564200963f48298bc4bb.scope/memory.limit_in_bytes
9223372036854771712

这是没有设置资源限制时我的节点上显示的情况,我得到值 9223372036854771712,该值来自内存管理层中的 cgroup 设置,默认情况下,它被设置为PAGE_COUNTER_MAX,这是LONG_MAX / PAGE_SIZE在 64 位平台上,并且在读取时再次乘以PAGE_SIZE。所以我们看到如果没有在 Kubernetes 里设置内存限制的话,会导致 Docker 设置HostConfig.Memory值为 0,并进一步导致容器进程被放置在默认值为no limitmemory.limit_in_bytes内存 cgroup 下。我们现在创建使用 100MiB 内存限制的 Pod

kubectl run limit-test --image=busybox --limits "memory=100Mi" --command -- /bin/sh -c "while true; do sleep 2; done"

我们再一次使用 kubectl 验证我们的资源配置

[root@master ~]# kubectl get pods limit-test -o=jsonpath='{.spec.containers[0].resources}'
{"limits":{"memory":"100Mi"},"requests":{"memory":"100Mi"}}

你会注意到除了我们设置的limits外,Pod 还增加了 requests。当你设置limits而没有设置requests时,Kubernetes 默认让requests等于 limits。当这个 Pod 启动后,我们可以看到 Docker 如何配置的容器以及这个进程的内存cgroup

[root@node2 ~]# docker inspect `docker ps | grep busy | cut -d' ' -f1` -f "{{.HostConfig.Memory}}"
104857600
[root@node2 ~]# ps ax | grep /bin/sh
  19381 ?        Ss     0:00 /bin/sh -c while true; do sleep 2; done
[root@node2 ~]# cat /proc/19381/cgroup
......
8:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podc4d4e63a_ad55_4aba_914d_8992efb01390.slice/docker-01f15eea815c8483f9a9c0205335f3171f844dcd777f8c933359c8807cf3017b.scope
[root@node2 ~]# cat /sys/fs/cgroup/memory/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podc4d4e63a_ad55_4aba_914d_8992efb01390.slice/docker-01f15eea815c8483f9a9c0205335f3171f844dcd777f8c933359c8807cf3017b.scope/memory.limit_in_bytes 
104857600

正如你所见,Docker 基于我们的 containerSpec 正确地设置了这个进程的内存 cgroup。但是这对于运行时意味着什么?Linux 内存管理是一个复杂的话题,Kubernetes 工程师需要知道的是:当一个宿主机遇到了内存资源压力时,内核可能会有选择性地杀死进程。如果一个使用了多于限制内存的进程会有更高几率被杀死。因为 Kubernetes 的任务是尽可能多地向这些节点上安排 Pod,这会导致节点内存压力异常。如果你的容器使用了过多内存,那么它很可能会被 oom-killed。如果 Docker 收到了内核的通知,Kubernetes 会找到这个容器并依据设置尝试重启这个 Pod。

让我们看看memory.soft_limit_in_bytes,软限制仍然被设置为默认值no limit。即使 Docker 支持通过参数--memory-reservation进行设置,但 Kubernetes 并不支持这个参数。这是否意味着为你的容器指定内存requests并不重要?不,不是的。requests要比limits更重要。limits 告诉 Linux 内核什么时候你的进程可以为了清理空间而被杀死。requests帮助 Kubernetes 调度找到合适的节点运行 Pod。如果不设置它们,或者设置得非常低,那么可能会有不好的影响。

[root@node2 ~]# cat /sys/fs/cgroup/memory/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podc4d4e63a_ad55_4aba_914d_8992efb01390.slice/docker-01f15eea815c8483f9a9c0205335f3171f844dcd777f8c933359c8807cf3017b.scope/memory.soft_limit_in_bytes 
9223372036854771712

CPU

Kubernetes里为 CPU 设置的单位是CPU的个数。比如 cpu=1 指的就是,这个 Pod 的 CPU 限额是 1 个 CPU。具体 1个CPU 在宿主机上如何解释,是 1个CPU核心 还是 1个vCPU,还是 1个CPU 的超线程(Hyperthread),完全取决于宿主机的 CPU 实现方式。Kubernetes 只负责保证 Pod 能够使用到 1个CPU 的计算能力。此外 Kubernetes 还允许你将 CPU 限额设置为分数,比如 CPU limits 的值为500m,指的就是 500 millicpu,也就是 0.5个CPU 的意思,这样这个 Pod 就会被分配到 0.5个CPU 的计算能力。当然你也可以直接把这个配置写成 cpu=0.5。但在实际使用时,推荐使用 500m 的写法,毕竟这才是 Kubernetes 内部通用的 CPU 表示方式。

为了了解 Docker 和 cgroup 如何使用这些值来控制容器,我们首先创建一个只配置了 CPUrequests的 Pod

kubectl run limit-test --image=busybox --requests "cpu=50m" --command -- /bin/sh -c "while true; do sleep 2; done"

通过 kubectl 命令我们可以验证这个 Pod 配置了 50m 的 CPU requests

[root@master ~]# kubectl get pods limit-test -o=jsonpath='{.spec.containers[0].resources}'
{"requests":{"cpu":"50m"}}

我们还可以看到 Docker 为容器配置了相同的资源限制

[root@node2 ~]# docker inspect `docker ps | grep busy | cut -d' ' -f1` --format '{{.HostConfig.CpuShares}}'
51

这里显示的为什么是 51,而不是 50?这是因为 Linux cgroup 和 Docker 都将 CPU 核心数分成了 1024 个时间片(shares),而 Kubernetes 将它分成了 1000 个shares。shares用来设置 CPU 的相对值,并且是针对所有的 CPU(内核),默认值是 1024,假如系统中有两个 cgroup,分别是 A 和 B,A 的shares值是 1024,B 的shares值是 512,那么 A 将获得 1024/(1204+512)=66% 的 CPU 资源,而 B 将获得 33% 的 CPU 资源。

shares有两个特点:
如果 A 不忙,没有使用到 66% 的 CPU 时间,那么剩余的 CPU 时间将会被系统分配给 B,即 B 的 CPU 使用率可以超过 33%。
如果添加了一个新的 cgroup C,且它的shares值是 1024,那么 A 的限额变成了 1024/(1204+512+1024)=40%,B 的变成了 20%。

从上面两个特点可以看出:
在闲的时候,shares 基本上不起作用,只有在 CPU 忙的时候起作用,这是一个优点。
由于shares是一个绝对值,需要和其它 cgroup 的值进行比较才能得到自己的相对限额,而在一个部署很多容器的机器上,cgroup 的数量是变化的,所以这个限额也是变化的,自己设置了一个高的值,但别人可能设置了一个更高的值,所以这个功能没法精确的控制 CPU 使用率。

与配置内存资源限制时 Docker 配置容器进程的内存 cgroup 的方式相同,设置 CPU 资源限制时 Docker 会配置容器进程的 cpu,cpuacct cgroup:

[root@node2 ~]# ps ax | grep /bin/sh
  44617 ?        Ss     0:00 /bin/sh -c while true; do sleep 2; done
[root@node2 ~]# cat /proc/44617/cgroup
......
5:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod633fe3bc_510f_4d8a_a549_1e9c0da50bb3.slice/docker-29acedcf5a94367bac50efd9122b2bf1135402a9efd83497e6909637a12e9298.scope
[root@node2 ~]# ls -l /sys/fs/cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod633fe3bc_510f_4d8a_a549_1e9c0da50bb3.slice/docker-29acedcf5a94367bac50efd9122b2bf1135402a9efd83497e6909637a12e9298.scope
......
-rw-r--r--. 1 root root 0 Jun  6 17:53 cpu.shares

Docker 容器的 HostConfig.CpuShares 属性映射到 cgroup 的 cpu.shares 属性,可以验证一下:

[root@node2 ~]# cat /sys/fs/cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod633fe3bc_510f_4d8a_a549_1e9c0da50bb3.slice/docker-29acedcf5a94367bac50efd9122b2bf1135402a9efd83497e6909637a12e9298.scope/cpu.shares 
51

你可能会很惊讶,设置了 CPUrequests竟然会把值传播到 cgroup,而在设置内存requests时并没有将值传播到 cgroup。这是因为内存的 soft limit 内核特性对 Kubernetes 不起作用,而设置了 cpu.shares 却对 Kubernetes 很有用。后面我会详细讨论为什么会这样。现在让我们先看看设置 CPUlimits时会发生什么

kubectl run limit-test --image=busybox --requests "cpu=50m" --limits "cpu=100m" --command -- /bin/sh -c "while true; do sleep 2; done"

再一次使用 kubectl 验证我们的资源配置

[root@master ~]# kubectl get pods limit-test -o=jsonpath='{.spec.containers[0].resources}'
{"limits":{"cpu":"100m"},"requests":{"cpu":"50m"}}

查看对应的 Docker 容器的配置

[root@node2 ~]# docker inspect `docker ps | grep busy | cut -d' ' -f1` --format '{{.HostConfig.CpuShares}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}'
51 10000 100000

可以明显看出,CPUrequests对应于 Docker 容器的 HostConfig.CpuShares 属性。而 CPUlimits就不太明显了,它由两个属性控制:HostConfig.CpuPeriod 和 HostConfig.CpuQuota。

实例演示

下面我们运行一个pod,指定了内存request和limit的值

cat <<EOF | kubectl apply -f - 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    resources:      # 资源配额
      limits:       # 限制资源(上限)
        cpu: "2"    # CPU限制,单位是CPU核心数。也可以使用小数,1核=1000m核。 例如0.1,它等价于表达式100m(表示100milicore),只负责保证pod能够使用0.1个cpu的计算能力
        memory: "200Mi" # 内存限制,单位可以为MiB/GiB/MB/GB,1Mi=1024*1024;1M=1000*1000
      requests:         # 请求资源(下限)
        cpu: "1000m"    # CPU限制,单位是CPU内核数
        memory: "100Mi" # 内存限制
EOF

查看当前pod运行所在节点,我们这里运行在了k8s-node7节点
在这里插入图片描述
下面我们进入到k8s-node7节点,查看当前容器的值,首先找到该容器memory.limit_in_bytes和cpu.cfs_period_us
在这里插入图片描述
查看该容器memory.limit_in_bytes和cpu.cfs_quota_us的值
在这里插入图片描述
pod中指定了 limits.memory=200Mi,相当于将 Cgroups 的 memory.limit_in_bytes 设置为 200 * 1024 * 1024=209715200。指定了 limits.cpu=2 ,则相当于将 Cgroups 的 cpu.cfs_quota_us 的值设置为 (2000/1000)*100ms,而 cpu.cfs_period_us 的值始终是 100ms。
在这里插入图片描述
下面我们来创建一个request请求资源不足导致pod Pending状态的案例,创建一个pod,设置它的memory为500Gi

kubectl create deployment nginx --image=nginx --replicas=1
kubectl set resources deployment nginx --limits=memory=500Gi

创建pod并查看pod的状态,发现pod状态一直都是Pending

[root@k8s-master2 ~]# kubectl get pod nginx-5ffcb5bf-bvxbv
NAME                   READY   STATUS    RESTARTS   AGE
nginx-5ffcb5bf-bvxbv   0/1     Pending   0          39s

查看pod的详细信息,集群中没有任何一台机器能满足该pod的内存要求

[root@k8s-master2 ~]# kubectl describe pod pod-resources -n dev
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  51s (x2 over 53s)  default-scheduler  0/10 nodes are available: 1 node(s) were unschedulable, 2 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 7 Insufficient memory.

案例演示三,这里我们使用stress这个镜像进行演示,该镜像是一种专门用来测试容器性能和压力的工具

cat <<EOF | kubectl apply -f - 
apiVersion: v1
kind: Pod
metadata:
  name: pod-resources
  namespace: test
spec:
  containers:
  - name: resourcescontainer
    image: vish/stress
    imagePullPolicy: IfNotPresent
    #-mem-total表示将容器的内存占用量增加到250MiB。-mem-alloc-size表示每次增加10MiB内存占用。-mem-alloc-sleep表示内存每1秒增加一次
    args: ['-mem-total','250Mi','-mem-alloc-size','10Mi','-mem-alloc-sleep','1s']
    resources:      # 资源配额
      limits:       # 限制资源(上限)
        cpu: "2"    # CPU限制,单位是CPU核心数。也可以使用小数,1核=1000m核。 例如0.1,它等价于表达式100m(表示100milicore),只负责保证pod能够使用0.1个cpu的计算能力
        memory: "200Mi" # 内存限制,单位可以为MiB/GiB/MB/GB,1Mi=1024*1024;1M=1000*1000
      requests:         # 请求资源(下限)
        cpu: "1000m"    # CPU限制,单位是CPU内核数
        memory: "100Mi" # 内存限制
EOF

创建pod并查看pod的状态,发现pod状态为running

[root@master ~]# kubectl get pod pod-resources -n test
NAME            READY   STATUS    RESTARTS   AGE
pod-resources   1/1     Running   0          13m

随着压力测试工具的不断施压,当内存占用量超过了limits属性的限制后,容器会被自动终止,发现状态变为OOMKilled

[root@master ~]# kubectl get pod pod-resources -n dev
NAME            READY   STATUS      RESTARTS      AGE
pod-resources   0/1     OOMKilled   1 (26s ago)   14m

内存不足会根据重启策略重启

[root@k8s-master2 ~]# kubectl get pod -n test
NAME            READY   STATUS             RESTARTS   AGE
pod-resources   0/1     CrashLoopBackOff   5          24m

我们还可以看到 Docker 为容器配置了相同的资源限制,我们也可以通过kubectl top pod -n test、docker stats命令来观察容器cpu内存使用情况

资源回收

服务质量QoS

Kubernetes 中如果一个 Node 节点上的 Pod 占用资源过多并且不断飙升导致 Node 节点资源不足,可能会导致为了保证节点可用,将容器被杀掉。在遇见这种情况时候,我们希望先杀掉那些不太重要的容器,确保核心容器不会首先被杀掉。为了衡量先杀掉哪个程序,所以推出了优先级机制 QoS (Quality of Service)来做判断,Kubernetes 将容器划分为三种 QoS 等级:完全可靠的Guaranteed、
较可靠的Burstable、不太可靠的BestEffort

当 Pod 中的container同时设置了 requests 和 limits,并且这两个值相等时,Pod 属于 Guaranteed 类别。示例:设置 requests=limits=200Mi 和 requests=limits=700m 的容器。
当 Pod 只设置了 limits,没有设置 requests,Kubernetes 会自动将 requests 设置为与 limits 相同的值,这样 Pod 仍然会被划分为 Guaranteed 类别。示例:只设置了 limits: memory: 200Mi 和 limits: cpu: 700m。
当 Pod 只设置了 requests,没有设置 limits 时,Kubernetes 会将其划分为 Burstable 类别。示例:设置 requests: memory: 200Mi 和 requests: cpu: 700m,但没有设置 limits。
当 Pod 同时设置了 requests 和 limits,但 limits 的值大于 requests,那么该 Pod 仍然会被划分为 Burstable 类别。示例:设置 requests.memory=200Mi,limits.memory=400Mi。
当 Pod 既没有设置 requests 也没有设置 limits 时,Kubernetes 会将其划分为 BestEffort 类别。示例:没有设置 requests 或 limits 的 Pod。

Guaranteed保障型

系统用完了全部内存,且没有其他类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉,也就是说最后才会被考虑 kill 掉,属于该级别的 pod 有以下两种情况:
Pod 中的每个容器,包含初始化容器都且仅设置了 CPU 和内存的 limits。
Pod 中的每个容器,包含初始化容器都设置了 CPU 和内存的 requests 和 limits ,且单个容器内的 requests=limits(requests不等于0)

如果一个容器只指明 limit 而未设定 requests,则 requests 的值等于 limit 值

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: qos-demo
  namespace: qos-example
spec:
  containers:
  - name: qos-demo
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
EOF

当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 Guaranteed

[root@master ~]# kubectl describe pod -n qos-example qos-demo | grep QoS
QoS Class:                   Guaranteed
[root@master ~]# kubectl get pod qos-demo -n qos-example -oyaml | grep qosClass
  qosClass: Guaranteed

下面我们运行一个 requests 与 limits 相等 的示例容器,其中:内存请求(requests)和内存限制(limits)均为 200MiB,CPU 请求(requests)和 CPU 限制(limits)均为 700m(0.7 核)

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: qos-demo2
  namespace: qos-example
spec:
  containers:
  - name: qos-demo
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"
EOF

当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 Guaranteed

[root@master ~]# kubectl describe pod -n qos-example qos-demo2 | grep QoS
QoS Class:                   Guaranteed
[root@master ~]# kubectl get pod qos-demo2 -n qos-example -oyaml | grep qosClass
  qosClass: Guaranteed

Burstable弹性型

当pod中至少有一个 Container 内存或CPU设置了 requests,那么这个 Pod 就会被划分到 Burstable 类别

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: qos-demo3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo
    image: nginx
    resources:
      requests:
        memory: "200Mi"
        cpu: "700m"
EOF

当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 Burstable

[root@master ~]# kubectl describe pod -n qos-example qos-demo3 | grep QoS
QoS Class:                   Burstable
[root@master ~]# kubectl get pod qos-demo3 -n qos-example -oyaml | grep qosClass
  qosClass: Burstable

那么我pod中的Container设置了内存和CPU,但是requests和limis的值不一样,也会被 Kubernetes 自动设置为 Burstable

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: qos-demo4
  namespace: qos-example
spec:
  containers:
  - name: qos-demo
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "200m"
      requests:
        memory: "100Mi"
        cpu: "100m"
EOF

当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 Burstable

[root@master ~]# kubectl describe pod -n qos-example qos-demo4 | grep QoS
QoS Class:                   Burstable
[root@master ~]# kubectl get pod qos-demo4 -n qos-example -oyaml | grep qosClass
  qosClass: Burstable

BestEffort尽力而为型

当 Pod 既没有设置 requests 也没有设置 limits 时,Kubernetes 会将其划分为 BestEffort 类别

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: qos-demo5
  namespace: qos-example
spec:
  containers:
  - name: qos-demo
    image: nginx
EOF

当这个 Pod 创建之后,它的 qosClass 字段就会被 Kubernetes 自动设置为 BestEffort

[root@master ~]# kubectl describe pod -n qos-example qos-demo5 | grep QoS
QoS Class:                   BestEffort

驱逐

API-initiated 驱逐

主动调用 API 触发的驱逐也称为 API-initiated 驱逐。API-initiated 驱逐是一个创建 Eviction 对象从而触发 pod 优雅终止的过程。我们可以直接请求Eviction API 或者使用客户端来发起请求,例如 kubectl drain 命令。这将会创建一个 Eviction 对象,并导致 APIServer 终止对应 Pod。

调用 Kube-Apiserver 创建 eviction 对象,就像这样:

{
  "apiVersion": "policy/v1",
  "kind": "Eviction",
  "metadata": {
    "name": "demotest",
    "namespace": "default"  }
}

创建一个 nginx deploy

kubectl create deployment nginx --image=nginx --replicas=1

查看 Pod 所在节点,可以看到,当前在 demo-2 节点

[root@demo-1 ~]# kubectl get po -owide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
nginx-77b4fdf86c-vpkdf   1/1     Running   0          6s    172.25.86.70   demo-2   <none>           <none>

使用 kubectl proxy 开启代理:

kubectl proxy --port=8888

新开一个终端执行 curl 命令,发起 POST 请求创建一个 Eviction 对象,名字和命名空间必须和要驱逐的 Pod 一致

podName="nginx-6799fc88d8-7kvwh"
namespace="default"
curl -X POST -H "Content-Type: application/json" -d "{
  \"apiVersion\": \"policy/v1\",
  \"kind\": \"Eviction\",
  \"metadata\": {
    \"name\": \"${podName}\",
    \"namespace\": \"${namespace}\"
  }
}" http://localhost:8888/api/v1/namespaces/${namespace}/pods/${podName}/eviction

由于该 Pod 是被 deploy 管理的,因此 Pod 会再次被创建出来,使用 kubectl get pod -w 可以看到驱逐过程:

[root@demo-1 ~]# kubectl get po -w
NAME                     READY   STATUS    RESTARTS   AGE
nginx-77b4fdf86c-djvsl   1/1     Running   0          21s
nginx-77b4fdf86c-djvsl   1/1     Running   0          32s
nginx-77b4fdf86c-djvsl   1/1     Terminating   0          32s
nginx-77b4fdf86c-djvsl   1/1     Terminating   0          33s
nginx-77b4fdf86c-vfqbc   0/1     Pending       0          1s
nginx-77b4fdf86c-vfqbc   0/1     Pending       0          1s
nginx-77b4fdf86c-vfqbc   0/1     ContainerCreating   0          1s
nginx-77b4fdf86c-djvsl   1/1     Terminating         0          33s
nginx-77b4fdf86c-djvsl   0/1     Terminating         0          33s
nginx-77b4fdf86c-vfqbc   0/1     ContainerCreating   0          1s
nginx-77b4fdf86c-djvsl   0/1     Terminating         0          34s
nginx-77b4fdf86c-djvsl   0/1     Terminating         0          34s
nginx-77b4fdf86c-djvsl   0/1     Terminating         0          34s
nginx-77b4fdf86c-vfqbc   1/1     Running             0          4s

旧 Pod 先被终止,然后因为该 Pod 是由 Deploy 管理的,因此 kube-controller-manager 会创建出来一个新 Pod,接着 scheduler 对该 Pod 进行调度,当然新建的 Pod 也有可能被再次调度到原节点。

一般是节点下线维护时会手动把对应节点上 Pod 全部驱逐开,以避免突然的节点下线对业务造成影响。kubectl drain 命令就是使用 Eviction API 实现的。一般节点下线流程为:
1、Kubectl drain 驱逐该节点上所有 Pod,drain 命令也会先把该节点更新为 Unschedulable 状态,因此不需要先手动执行 kubectl cordon 了。当然也可以手动一个一个 Pod 驱逐以降低对业务的影响
2、kubectl delete node 删除该节点
3、节点关机下线

节点压力驱逐

节点压力驱逐是 kubelet 主动终止 Pod 以回收节点资源的过程,特别是在处理内存和磁盘等不可压缩资源时显得尤为重要。在 Kubernetes 中,requests 和 limits 的设置会将 Pod 划分为不同的 QoS 等级。当宿主机不可压缩资源短缺时,kubelet 会根据资源回收策略对 Pod 进行驱逐(Eviction),回收的资源包括可用内存、宿主机磁盘空间、容器运行时镜像存储空间等。

每个node上的kubelet负责定期采集资源占用数据,并与预设的阈值进行比对,如果超过阈值,kubelet就会尝试杀掉一些pod以回收相关资源,对Node进行保护。根据不同资源,kubelet 中定义了 5 种驱逐信号:

memory.available = node.status.capacity[memory] - node.stats.memory.workingSet
nodefs.available = node.stats.fs.available
nodefs.inodesFree = node.stats.fs.inodesFree
imagefs.available = node.stats.runtime.imagefs.available
imagefs.inodesFree = node.stats.runtime.imagefs.inodesFree
pid.available = node.stats.rlimit.maxpid - node.stats.rlimit.curproc

除了用驱逐信号来判断是否需要驱逐 Pod 之外,Kubelet 还会把驱逐信号反应为节点的状态。即:更新到 node 对象的 status. condition 字段里,对应关系如下:

MemoryPressure	memory.available	节点可用内存余量满足驱逐阈值
DiskPressure	nodefs.available, nodefs.inodesFree, imagefs.available, or imagefs.inodesFree	节点主文件系统或者镜像文件系统剩余磁盘空间或者 inodes 数量满足驱逐阈值
PIDPressure	pid.available	节点上可用进程标识符(processes identifiers) 低于驱逐阈值

总的来说就是节点上对应资源不足时 kubelet 就会被节点打上对应的标记。同时 control plane(具体为 node controller) 会自动把节点上相关 condition 转换为 taint 标记,具体见:https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/#taint-nodes-by-condition,这样可以避免把 Pod 调度到本来就资源不足的 Pod 上。

整体运作流程:
首先 Kubelet 检测到节点上剩余磁盘空间不足,更新 Node Condition 增加 DiskPressure 状态
然后 node controller 自动将 Condition 转换为污点 node.kubernetes.io/disk-pressure
最后 Pod 调度的时候,由于没有添加对应的容忍,因此会优先调度到其他节点,或者最终调度失败

有时候节点状态处于阈值附近上下波动,导致软驱逐条件也在 true 和 false 之前反复切换,为了过滤掉这种误差, kubelet 提供了 eviction-pressure-transition-period 参数来限制,节点状态转换前必须要等待对应的时间,默认为 5m。

Kubelet 默认每 10s 会检测一次节点状态,即驱逐判断条件为 10s 一次,当然也可以通过housekeeping-interval 参数进行配置。

Eviction 在 Kubernetes 里其实分为 Soft 和 Hard 两种模式
每种threshold又分为eviction-soft和eviction-hard两组值。soft和hard的区别在于前者在到达threshold值时会给pod一段时间优雅退出,而后者则崇尚暴力,直接杀掉pod,没有任何优雅退出的机会。

软驱逐 eviction-soft

一般驱逐条件比较保守,此时还可以等待 Pod 优雅终止,需要配置以下参数
eviction-soft:软驱逐条件,例如 memory.available<1.5Gi
eviction-soft-grace-period:软驱逐宽限期,需要保持驱逐条件这么久之后才开始驱逐 Pod。必须指定,否则 kubelet 启动时会直接报错
eviction-max-pod-grace-period:软驱逐最大 Pod 宽限期,驱逐 Pod 的时候给 Pod 优雅终止的时间。在 Pod 生命周期部分我们可以配置 spec.terminationGracePeriodSeconds来控制 Pod 优雅终止时间,就像这样:

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: my-container
    image: my-image
  terminationGracePeriodSeconds: 60  # 设置终止期限为 60 秒

然后驱逐这里又配置了一个 eviction-max-pod-grace-period,实际驱逐发生时,Kubelet 会取二者中的较小值来作为最终优雅终止宽限期。

硬驱逐 eviction-hard

只要满足驱逐条件 kubelet 就会立马将 pod kill 掉,而不是发送 SIGTERM 信号。Kubelet 提供了以下的默认硬驱逐条件:

memory.available<100Mi
nodefs.available<10%		#nodefs指node自身的存储,存储daemon的运行日志等,一般指root分区/;
nodefs.inodesFree<5%
imagefs.available<15%		#imagefs指docker daemon用于存储image和容器可写层(writable layer)的磁盘;

例如下面这个配置

eviction-soft: memory.available < 1 Gi
eviction-soft-grace-period: 60s
eviction-max-pod-grace-period: 30s
eviction-hard: memory.available < 100 Mi

当节点可用内存余量低于 1Gi 连续持续 60s 之后,kubelet 就会开始软驱逐,给被选择的 Pod 发送 SIGTERM,使其优化终止,如果终止时间超过 30s 则强制 kill。如果节点内存可用余量低于 100Mi,则 kubelet 进入硬驱逐,立马 Kill 掉被选中的 Pod。当 Eviction 发生的时候,kubelet会参考这些 Pod 的 QoS 类别来选择删除哪些pod。首先删除BestEffort 类别的 Pod,其次Burstable 类别,最后才是 Guaranteed 类别

对于磁盘资源可以通过驱逐 Pod 以外的方式进行回收,如果节点有一个专用的 imagefs 文件系统供容器运行时使用,kubelet 会执行以下操作:
如果 nodefs 文件系统满足驱逐条件,会自动干掉本机上的pod和pod对应的容器(这里是k8s的驱逐机制,删除本节点pod,在其他节点启动,在不放守护机制的前提下是不顺滑的,存在一定时间的服务中断)。
如果 imagefs 文件系统满足驱逐条件,kubelet 将删除所有未使用的镜像。
如果nodefs和imagefs是同一个分区,即/分区,占用率很高(96%),k8s会首先驱逐本机上的pod到其他节点,然后资源仍然不够,会删除没有被使用到的镜像,直到剩余空间低于设定的阈值,甚至会删除k8s的系统镜像。解决办法
1、修改这几个指标的阈值(不推荐)
2、添加监控,在阈值到达之前提前处理

烈建议你将 DaemonSet 的 Pod 都设置为 Guaranteed 的 QoS 类型。否则,一旦 DaemonSet 的 Pod 被回收,它又会立即在原宿主机上被重建出来,这就使得前面资源回收的动作,完全没有意义了

inode & pid 导致的驱逐
当 kubelet 因 inode 或 进程标识符(pid) 不足而驱逐 Pod 时, 它使用 Pod 的相对优先级来确定驱逐顺序,因为 inode 和 PID 没有对应的请求字段。相对优先级排序方式如下:
节点有 imagefs:
如果 nodefs 触发驱逐, kubelet 会根据 nodefs 使用情况(本地卷 + 所有容器的日志)对 Pod 进行排序。
如果 imagefs 触发驱逐,kubelet 会根据所有容器的可写层使用情况对 Pod 进行排序。
节点没有 imagefs
如果 nodefs 触发驱逐, kubelet 会根据磁盘总用量(本地卷 + 日志和所有容器的可写层)对 Pod 进行排序。

即:inode、pidk 导致的驱逐,Kubelet 会优先驱逐磁盘消耗大的 Pod,而不是根据 Pod Qos 来。

OOM Killer

这里也顺带提一下和资源相关的另一个功能OOM Killer,注意:OOM Killer 实际上是一个 Linux 内核特性,并不是 kubernetes 的一部分。

OOM Killer 内核进程会监控系统里的所有进程,并根据公式有效 oom_score = oom_score + oom_score_adj 计算出每个进程的的 OOM 得分,随后内存不足时直接 Kill 掉得分最高的进程。其中 oom_score_adj 就是内核为用户提供的灵活性,k8s 中则是根据 Pod 的 QoS 为 Pod 中的容器进程设置了不同的 oom_score_adj 来调整优先级,具体 oom_score_adj 计算公式如下:

服务质量oom_score_adj
Guaranteed-997
BestEffort1000
Burstablemin(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)

可以看到 Pod 的 QoS 对这个还是有影响的,Guaranteed 级别的 Pod oom_score_adj 固定为 -997,保证了该级别 Pod 中的容器不会被轻易 Kill 掉。然后根据 Pod 在节点上使用的内存百分比计算出一个 oom_score。最后 oom_score加上 oom_score_adj 得到每个容器有效的 oom_score。 内存快消耗完时,OOM Killer 会根据该算法找出得分最高的容器并 Kill 掉。

这意味着低 QoS Pod 中相对于其调度请求消耗内存较多的容器,将首先被杀死。当进程被 OOM Killer Kill 掉之后,会生成内核事件,然后 kubelet 会识别该事件并将对应 Pod 设置为 OOMKilled 状态。注意:容器最终是被 OOM Killer 这个内核进程 Kill 的,而不是 kubelet。就像这样:

State:          Running
       Started:      Thu, 10 Oct 2019 11:14:13 +0200
       Last State:   Terminated
       Reason:       OOMKilled
       Exit Code:    137
       ...

Eviction 是指 kubelet 对该节点上的 Pod 进行驱逐,OOM 是指 cgroup 对进程进行 kill。OOM 与 Pod 驱逐不同,如果容器被 OOM 杀死, kubelet 可以根据其 restartPolicy 重新启动它。

kube-scheduler 调度抢占驱逐

在高优先级 Pod 无法调度时,kube-scheduler 也会驱逐低优先级 Pod 以为高优先级 Pod 腾出空间完成调度。我们可以通过 PriorityClass 来设置 Pod 的优先级,首先是创建 PriorityClass 对象,然后在 Pod 对象上 priorityClassName 参数指定,当然更多情况下是在 Deployment 的 Pod 模板中指定该字段。类似于 pvc 指定 storageclass

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "此优先级类应仅用于 XYZ 服务 Pod。"

preemptionPolicy 字段控制抢占策略,默认为 PreemptLowerPriority,即:允许抢占低优先级 Pod,如果不希望抢占则可以配置为 Never。

Pod 创建后就会进入等待调度队列,Scheduler 从队列中取出 Pod 并尝试将其调度到一个合适的节点上。如果没有找到一个满足 Pod 所有条件的节点,就会触发抢占逻辑。假设等待调度的 Pod 为 A,抢占逻辑试图找到一个节点, 在该节点中删除一个或多个优先级低于 A 的 Pod,则可以将 A 调度到该节点上。如果找到这样的节点,一个或多个优先级较低的 Pod 会被从节点中驱逐。 被驱逐的 Pod 消失后,A 可以被调度到该节点上。

Kube-controller-manger 节点离线驱逐

Kube-controller-manager 周期性检查节点状态,每当节点状态为 NotReady,并且超出 podEvictionTimeout 时间后,就把该节点上的 pod 全部驱逐到其它节点。具体驱逐速度还受驱逐速度参数,集群大小等的影响。相关参数如下:
pod-eviction-timeout:即当节点宕机该事件间隔后,开始 eviction 机制,驱赶宕机节点上的 Pod,默认为 5min
node-eviction-rate: 驱赶速率,即驱赶 Node 的速率,由令牌桶流控算法实现,默认为 0.1,即每秒驱赶 0.1 个节点,注意这里不是驱赶 Pod 的速率,而是驱赶节点的速率。相当于每隔 10s,清空一个节点
secondary-node-eviction-rate: 二级驱赶速率,当集群中宕机节点过多时,相应的驱赶速率也降低,默认为 0.01
unhealthy-zone-threshold:不健康 zone 阈值,会影响什么时候开启二级驱赶速率,默认为 0.55,即当该 zone 中节点宕机数目超过 55%,而认为该 zone 不健康
large-cluster-size-threshold:大集群阈值,当该 zone 的节点多于该阈值时,则认为该 zone 是一个大集群。大集群节点宕机数目超过 55% 时,则将驱赶速率降为 0.0.1,假如是小集群,则将速率直接降为 0

比如 kubelet 来不及驱逐,Node 上的资源就被恶意 Pod 耗尽导致 Node 最终宕机,或者别的原因导致 Node 进入 NotReady 状态。此时 control-plane 无法正常和该节点通信,拿不到节点上的 Pod 的状态,因此会做最坏的打算,认为这些 Pod 都挂了,因此会将该 Node 上的所有 Pod 都驱逐到其他节点重新跑起来,以保证高可用。

资源预留

在 Kubernetes 集群中,默认情况下,Pod 可以使用节点的全部可用资源容量。然而,这种默认行为可能会引发问题:如果某些 Pod 消耗了过多的资源(如内存或 CPU),可能会导致系统本身的守护进程无法获得足够的资源,从而影响整个节点的正常运行。这种现象类似于在宿主机上运行 Java 服务时,Java 程序可能会耗尽系统资源,导致系统卡顿或无法登录。

在 Kubernetes 集群中,类似的资源耗尽问题可能会引发一系列不可控的故障。因此,为系统守护进程(如 kubelet、kube-proxy 等)预留一定的资源是非常必要的。为了解决这个问题,Kubernetes 提供了 Node Allocatable 特性。该特性由 kubelet 提供,Kubelet Node Allocatable用来为Kube组件和System进程预留资源,从而保证当节点出现满负荷时也能保证Kube和System进程有足够的资源。目前支持cpu, memory, ephemeral-storage三种资源预留。计算方式:Node Allocatable Resource = Node Capacity - Kube-reserved - system-reserved - eviction-threshold节点上可配置值 = 总量 - 预留值 - 驱逐阈值
Node Capacity:Node的所有硬件资源
Kube-reserved:kube 组件预留的资源
system-reserved:给system进程预留的资源
eviction-threshold(阈值):kubelet eviction(收回)的阈值设定
Allocatable:真正scheduler调度Pod时的参考值(保证Node上所有Pods的request resource不超过Allocatable)

我们先通过 kubectl describe node 命令查看节点可分配资源的数据:

...
Capacity:
  cpu:                8
  ephemeral-storage:  206292644Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             15731900Ki
  pods:               110
Allocatable:
  cpu:                8
  ephemeral-storage:  190119300396
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             15629500Ki
  pods:               110
...

可以看到其中有 Capacity 与 Allocatable 两项内容,其中的 Allocatable 就是节点可被分配的资源,我们这里没有配置资源预留,所以默认情况下 Capacity 与 Allocatable 的值基本上是一致的。

配置资源预留

比如我们现在需要为系统预留一定的资源,我们可以使用如下的几个 kubelet 参数来进行配置:

--enforce-node-allocatable=pods
--kube-reserved=memory=...
--system-reserved=memory=...
--eviction-hard=...

这里我们暂时不设置对应的 cgroup,比如我们这里先只对 master1 节点添加资源预留,我们可以直接修改 /var/lib/kubelet/config.yaml 文件来动态配置 kubelet,添加如下所示的资源预留配置:

[root@k8s-master1 ~]# cat /var/lib/kubelet/config.yaml
......
enforceNodeAllocatable:
 1. pods
kubeReserved:  # 配置 kube 资源预留
  cpu: 500m
  memory: 1Gi
  ephemeral-storage: 1Gi
systemReserved:  # 配置系统资源预留
  memory: 1Gi
evictionHard:  # 配置硬驱逐阈值
  memory.available: "300Mi"
  nodefs.available: "10%"

修改完成后重启 kubelet,启动完成后,通过 kubectl describe node 命令重新对比 Capacity 及 Allocatable 的值:

...
Capacity:
  cpu:                8
  ephemeral-storage:  206292644Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             15731900Ki
  pods:               110
Allocatable:
  cpu:                7500m
  ephemeral-storage:  189045558572
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             13327548Ki
  pods:               110
...

仔细对比可以发现其中的 Allocatable的值恰好是 Capacity 减去上面我们配置的预留资源的值:Allocatable = Capacity - kubeReserved - systemReserved - evictionHard
内存: 13327548Ki = 15731900Ki - 110241024Ki - 110241024Ki - 300*1024Ki
CPU: 7500m = 8000m - 500m (单位为m是以CPU的时间分片计量的,1C为1000m)

再通过查看 kubepods.slice(systemd 驱动是以 .slice 结尾)cgroup 中对节点上所有 Pod 内存的限制,该值决定了 Node 上所有的 Pod 能使用的资源上限:

cat /sys/fs/cgroup/memory/kubepods.slice/memory.limit_in_bytes
13961981952      #单位是byte

得到的 Pod 资源使用上限为:

13961981952 Bytes = 13634748 Ki = Allocatable(13327548 Ki) + eviction_hard(300*1024Ki)   # 13961981952 / 1024 = 13634748

也可以通过计算验证我们的配置是正确的:

kubepods.slice/memory.limit_in_bytes = capacity - kube_reserved - system_reserved

Namespace

Namespace的主要作用是通过逻辑划分来实现多套环境的资源隔离或者多租户的资源隔离。当你在 Kubernetes 集群中创建 Pod 时,你可以指定将它们放置在特定的命名空间Namespace中。命名空间为 Kubernetes 中的资源提供了作用域,可以将资源分类到不同的命名空间中进行管理和隔离。这可以使得不同的团队、项目或用户在同一个 Kubernetes 集群中使用它们所需要的资源而不会相互干扰。
在这里插入图片描述
kubernetes在集群启动之后,会默认创建几个namespace

[root@k8s-master1 ~]# kubectl get ns			#查看名称空间
NAME                   STATUS   AGE
default                Active   2m39s		#所有未指定Namespace的对象都会被分配在default命名空间
kube-node-lease        Active   2m40s		#集群节点之间的心跳维护,v1.13开始引入
kube-public            Active   2m40s		#此命名空间下的资源可以被所有人访问(包括未认证用户)
kube-system            Active   2m40s		#K8s集群自身组件及其它系统组件使用的名称空间

大多数Kubernetes资源(例如,Pod、Service、控制器等)在命名空间中,但是命名空间资源本身并不在命名空间中,并且低级资源(例如,节点和持久存储卷等)也不在任何命名空间中。通过 kubectl api-resources --namespaced=true命令查看位于命名空间下的资源,通过kubectl api-resources --namespaced=false命令,可以查看不在命名空间下的资源。

命名空间、授权机制和资源配额机制一起为 Kubernetes 提供了多租户资源隔离的功能:
授权机制可以让你指定哪些用户可以访问哪些命名空间或资源,保证不同团队或用户之间资源的安全隔离。例如,你可以为不同的租户分配不同的命名空间,并且通过授权机制限制他们只能访问自己的命名空间。
资源配额机制可以限制每个命名空间可以使用的资源数量,例如 CPU、内存等等。这样可以避免某个命名空间中的 Pod 占用过多的资源导致其他命名空间中的 Pod 无法正常运行。

创建名称空间

命名空间的创建十分简单,可以通过命令或者模板文件来创建命名空间,首先我们使用create命令创建一个dev名称空间

kubectl create ns dev

通过模板文件来创建命名空间

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: test
  labels:
    nsname: test
EOF

创建完成后,通过kubectl get namespace命令可以查看各个命名空间

[root@k8s-master1 ~]# kubectl get namespace dev test
NAME   STATUS   AGE
dev    Active   75s
test   Active   12s

删除名称空间

删除namespace,删除namespace资源会级联删除其包含的所有其它资源对象

kubectl delete ns test

如果被删除的名称空间一直处于Terminating状态,则调用 api-server 接口进行删除

[root@k8s-master1 ~]# kubectl get ns test
NAME   STATUS       AGE
test   Terminating  2d

打开一个新的终端,切记不要ctrl c关掉,等最后成功删除后再停

[root@k8s-master1 ~]# kubectl proxy --port=8081
Starting to serve on 127.0.0.1:8001

将test名称空间导出为json文件

kubectl get ns test -o json > test.json

编辑命名空间json文件,删除spec字段
在这里插入图片描述
通过调用api-server接口发送请求的方式进行删除,test.json 是我们导出的文件,test是需要删除的命名空间

curl -k -H "Content-Type:application/json" -X PUT --data-binary @test.json 127.0.0.1:8001/api/v1/namespaces/test/finalize

切换名称空间

切换名称空间,这里切换到kube-system名称空间后,不需要指定-n,查看的就是kube-system命名空间的资源

kubectl config set-context --current --namespace=kube-system

命名空间的资源配额ResourceQuota

命名空间的设计初衷是实现多租户的资源隔离。这只是逻辑隔离,实际上所有租户都共用同一个Kubernetes集群,所以还需要规划命名空间的资源配额ResourceQuota,以免某个命名空间滥用集群资源而影响整个Kubernetes集群。可以通过ResourceQuota来定义资源配额,设置命名空间下能够使用的各类资源总量,一个命名空间下最多只能存在一个ResourceQuota。

资源配额分为3种类型:
计算资源配额:指定可用的计算机资源总量,如总内存或CPU等
存储资源配额:指定可用的存储资源总量,如PVC总数等
对象数量配额:指定Kubernetes资源对象的可用总量,如Pod总数和Service总数等

计算资源配额

用户可以对给定命名空间下的可被请求的计算资源总量进行限制。配额机制所支持的资源类型:
limits.cpu:所有非终止状态的 Pod,其 CPU 限额总量不能超过该值。
limits.memory:所有非终止状态的 Pod,其内存限额总量不能超过该值。
requests.cpu:所有非终止状态的 Pod,其 CPU 需求总量不能超过该值。
requests.memory:所有非终止状态的 Pod,其内存需求总量不能超过该值。
hugepages-<size>:对于所有非终止状态的 Pod,针对指定尺寸的巨页请求总数不能超过此值。
cpu:与 requests.cpu 相同。
memory:与 requests.memory 相同。

下面我们来创建一个命名空间,绑定一个ResourceQuota资源

kubectl create namespace quota-mem-cpu-example

将命名空间和资源限制对象进行绑定,在该命名空间中的每个 Pod 的所有容器都必须要有内存请求和限制,以及 CPU 请求和限制

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
  namespace: quota-mem-cpu-example
spec:
  hard:
    requests.cpu: "1"       #在该命名空间中所有 Pod 的 CPU 请求总和不能超过 1 cpu
    requests.memory: 1Gi    #在该命名空间中所有 Pod 的内存请求总和不能超过 1 GiB
    limits.cpu: "2"         #在该命名空间中所有 Pod 的 CPU 限制总和不能超过 2 cpu
    limits.memory: 2Gi      #在该命名空间中所有 Pod 的内存限制总和不能超过 2 GiB
EOF

查看该命名空间中的资源配额(ResourceQuota)的详细信息

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example --output=yaml

下面我们来创建一个pod进行演示

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: quota-mem-cpu-example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        resources:
          limits:
            memory: "2Gi"
            cpu: "2"
          requests:
            memory: "2Gi"
            cpu: "2"
EOF

创建完成后发现没有pod

[root@k8s-master2 ~]# kubectl get pod -n quota-mem-cpu-example
No resources found in quota-mem-cpu-example namespace.

咦,这是为啥呢,下面我们来看一下控制器的message信息

[root@k8s-master2 ~]# kubectl get deploy -n quota-mem-cpu-example nginx -oyaml
......
    message: 'admission webhook "resourcesquotas.quota.kubesphere.io" denied the request:
      pods "nginx-56c67c7fdb-gx4l8" is forbidden: exceeded quota: mem-cpu-demo, requested:
      requests.cpu=2,requests.memory=2Gi, used: requests.cpu=0,requests.memory=0,
      limited: requests.cpu=1,requests.memory=1Gi'

错误信息指出 Pod 的创建被拒绝,原因是超过了名为 “mem-cpu-demo” 的配额限制:
pods "nginx-56c67c7fdb-gx4l8" is forbidden:试图创建名为 “nginx-56c67c7fdb-gx4l8” 的 Pod 时被拒绝
exceeded quota: mem-cpu-demo:超过了 “mem-cpu-demo” 这个配额的限制
requested: requests.cpu=2,requests.memory=2Gi, used: requests.cpu=0,requests.memory=0,limited: requests.cpu=1,requests.memory=1Gi:在请求中指定了cpu、内存的配额,当前已经使用了cpu和内存都是0,但是请求的配额超过了该配额的限制,所以 Pod 创建被拒绝了

解决这个问题的方法有两种:
调整 Pod 的资源配额: 将请求的内存减少到 “1Gi” 以下,然后再次尝试运行 kubectl apply 命令。例如,将 requests.memory 的值设置为 “500M”。
调整配额限制: 修改名为 “mem-cpu-demo” 的配额限制,允许更高的内存请求。您可以使用 kubectl edit resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example 命令来编辑该配额,并增加 requests.memory 的限制值,然后保存退出。

我们这里选择方案二,将配额进行调整

[root@k8s-master2 ~]# kubectl edit resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example
......
spec:
  hard:
    limits.cpu: "2"
    limits.memory: 2Gi
    requests.cpu: "2"
    requests.memory: 2Gi

资源扩展后,pod可以正常创建,如果上时间没创建,可以将rs控制器删除,重新触发创建

[root@k8s-master2 ~]# kubectl get pod -n quota-mem-cpu-example
NAME                     READY   STATUS    RESTARTS   AGE
nginx-56c67c7fdb-psd4s   1/1     Running   0          5s

查看ResourceQuota

[root@k8s-master2 ~]# kubectl get resourcequota mem-cpu-demo -n quota-mem-cpu-example -o jsonpath='{ .status.used }' | python -m json.tool
{
    "limits.cpu": "2",
    "limits.memory": "2Gi",
    "requests.cpu": "2",
    "requests.memory": "2Gi"
}
[root@k8s-master2 ~]# kubectl get resourcequota mem-cpu-demo -n quota-mem-cpu-example 
NAME           AGE   REQUEST                                       LIMIT
mem-cpu-demo   26m   requests.cpu: 2/2, requests.memory: 2Gi/2Gi   limits.cpu: 2/2, limits.memory: 2Gi/2Gi
[root@k8s-master2 ~]# kubectl describe resourcequota mem-cpu-demo -n quota-mem-cpu-example 
Name:            mem-cpu-demo
Namespace:       quota-mem-cpu-example
Resource         Used  Hard
--------         ----  ----
limits.cpu       2     2
limits.memory    2Gi   2Gi
requests.cpu     2     2
requests.memory  2Gi   2Gi

查看quota-mem-cpu-example空间中的pod的资源消耗

[root@k8s-master2 ~]# kubectl get pods --namespace=quota-mem-cpu-example -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].resources.requests.cpu}{"\t"}{.spec.containers[0].resources.requests.memory}{"\n"}{end}'
nginx-56c67c7fdb-psd4s	2	2Gi

存储资源配额

用户可以对给定命名空间下的存储资源总量进行限制。此外,还可以根据相关的存储类(Storage Class)来限制存储资源的消耗:
requests.storage:所有 PVC,存储资源的需求总量不能超过该值
persistentvolumeclaims在该命名空间中所允许的 PVC 总量
<storage-class-name>.storageclass.storage.k8s.io/requests.storage:在所有与 相关的持久卷申领中,存储请求的总和不能超过该值。
<storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims:在与 storage-class-name 相关的所有持久卷申领中,命名空间中可以存在的持久卷申领总数。

下面我们来创建一个命名空间,绑定一个ResourceQuota资源

kubectl create namespace quota-storage

当设置存储资源配额时,可以根据具体的需求和实际情况进行调整。以下是更多详细的存储资源配额的例子:

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage
  namespace: quota-storage
spec:
  hard:
    requests.storage: "10Gi"    # 设置存储请求的总和不超过10Gi
    limits.cpu: "4"             # 设置CPU限制总和不超过4核
    limits.memory: "8Gi"        # 设置内存限制总和不超过8Gi
    persistentvolumeclaims: "3" # 设置PersistentVolume数量不超过3个
    services: "5"      # 设置Service数量不超过5个
    configmaps: "5"    # 设置ConfigMap和Secret的总和不超过5个
    secrets: "5"
EOF

对象数量配额

你可以使用以下语法对所有标准的、命名空间域的资源类型进行配额设置:
count/<resource>.<group>:用于非核心(core)组的资源
count/<resource>:用于核心组的资源

以下是一组可于管理的资源类型的对象计数配额的示例:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: example-object-quota
  namespace: test
spec:
  hard:
    count/pods: 10 #在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded) 为真
    count/persistentvolumeclaims: 5 #在该命名空间中允许存在的 PVC 的总数上限
    count/services: 10              #在该命名空间中允许存在的 Service 总数上限
    count/services.loadbalancers: 10 #在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限
    count/services.nodeports: 10     #在该命名空间中允许存在的 NodePort 类型的 Service 总数上限
    count/secrets: 20               #在该命名空间中允许存在的 Secret 总数上限
    count/configmaps: 15            #在该命名空间中允许存在的 ConfigMap 总数上限
    count/deployments.apps: 2       #在该命名空间中允许存在的 deployments 总数上限
    count/replicasets.apps: 4
    count/statefulsets.apps: 1
    count/jobs.batch: 5
    count/cronjobs.batch: 2

命名空间单个资源限额LimitRange

通过设置资源配额,可以限定一个命名空间下使用的资源总量。但这仅是总量设置,对于单个资源没有限制,很有可能单个 Pod 或容器就会消耗完整个命名空间下资源配额所指定的CPU 或内存总量。默认情况下如果创建一个 Pod 没有设置 Limits 和 Requests 对其加以限制,那么这个 Pod 可能能够使用 Kubernetes 集群中全部资源, 但是每创建 Pod 资源时都加上这个动作是繁琐的。为了避免单个资源对象消耗所有的命名空间资源,Kubernetes 提供了 LimitRange 对象来对单个资源对象的资源占用量进行限定,它能够对一个 Namespace 下的全部 Pod 使用资源设置默认值、并且设置上限大小和下限大小等操作。害人之心不可有,防人之心不可无,防止他人乱用资源。

通过LimitRange对象可以实现以下功能:
1、设置命名空间下单个Pod或容器的最小和最大计算资源使用量
2、设置命名空间下单个PVC的最小和最大存储请求
3、设置命名空间下请求(request)资源量和上限(limit)资源量的比例
4、设置命名空间下默认的计算资源请求与上限,并在运行时自动将其注入容器中

如果在命名空间下对CPU或内存设置了请求与上限,那么在定义Pod资源时,必须明确在模板中指定这两个值。否则,系统会拒绝创建Pod,除非在定义LimitRange 时设置了默认值。如果在设置LimitRange之前就创建了Pod,即使之后设置了LimitRange,那么对正在运行的Pod也没有影响,除非Pod重建。

这里演示将使用 LimitRange 来限制某个 namespace 下的资源的测试用例,创建名称空间

kubectl create namespace limit-test

将 LimitsRange 应用到一个 Kubernetes 的命名空间中,需要先定义一个 LimitRange ,定义最大及最小范围、Requests 和 Limits 的 默认值、Limits 与 Requests 的最大比例上限等,资源配额模板的定义如下所示

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: LimitRange
metadata:
  name: limtit
  namespace: limit-test
spec:
  limits:
  - type: Container       #限制的资源类型
    max:
      cpu: "1"            #限制单个容器的最大CPU
      memory: "1Gi"       #限制单个容器的最大内存
    min:
      cpu: "100m"         #限制单个容器的最小CPU
      memory: "100Mi"     #限制单个容器的最小内存
    default:
      cpu: "900m"         #默认CPU限定
      memory: "800Mi"     #默认内存限定
    defaultRequest:
      cpu: "200m"         #默认CPU请求
      memory: "200Mi"     #默认内存请求
    maxLimitRequestRatio:
      cpu: 2              #限定CPU limit/request比值最大为2  
      memory: 1.5         #限定内存limit/request比值最大为1.5
  - type: Pod
    max:
      cpu: "2"            #限定Pod最大CPU
      memory: "2Gi"       #限定Pod最大内存
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi        #限定PVC最大的requests.storage
    min:
      storage: 1Gi        #限定PVC最小的requests.storage
EOF

查看创建的limitranges

[root@master ~]# kubectl get limitranges -n limit-test
NAME     CREATED AT
limtit   2023-02-07T14:21:12Z
[root@master ~]# kubectl describe limitranges -n limit-test
Name:                  limtit
Namespace:             limit-test
Type                   Resource  Min    Max  Default Request  Default Limit  Max Limit/Request Ratio
----                   --------  ---    ---  ---------------  -------------  -----------------------
Container              memory    100Mi  1Gi  200Mi            800Mi          1500m
Container              cpu       100m   1    200m             900m           2
Pod                    cpu       -      2    -                -              -
Pod                    memory    -      2Gi  -                -              -
PersistentVolumeClaim  storage   1Gi    2Gi  -                -              -

限制案例1:CPU与内存 RequestRatio比例限制

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: limit-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: my-container
        image: nginx
        resources:
          requests:
            cpu: 200m
            memory: 100Mi
          limits:
            cpu: 600m
            memory: 200Mi
EOF

创建完后无法发现创建的pod

[root@master ~]# kubectl get pod -n limit-test
No resources found in limit-test namespace.

那么我们该如何查看是因为什么原因导致pod无法创建呢

kubectl get deployments nginx -n limit-test -o yaml

在这里插入图片描述
由报错信息可以得知。请求的cpu最大限制每个容器的比率为2,但提供的比率为3.000000,内存最大限制为每个集装箱的请求比率为1500m,但提供的比率为2.000000

限制案例2:CPU与内存或超分限制

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: limit-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: my-container
        image: nginx
        resources:
          requests:
            cpu: 2
            memory: 2Gi
          limits:
            cpu: 2
            memory: 2Gi
EOF

创建完后无法发现创建的pod

[root@master ~]# kubectl get pod -n limit-test
No resources found in limit-test namespace.

查看报错信息

kubectl get deployments nginx -n limit-test -o yaml

在这里插入图片描述
大致意思是每个容器的最大cpu使用率是1,但我们创建请求的是2,每个容器的最大内存使用量是1Gi,但我们创建请求的是2Gi

限制案例3:创建一个未定义request或Limits的Pod,我们这里演示的是都不定义

kubectl create deployment nginx --image=nginx --namespace=limit-test --replicas=1

查看创建的pod发现没有创建成功,查看报错信息

kubectl get deployments nginx -n limit-test -o yaml

在这里插入图片描述
cpu最大请求限制每个容器的比率为2,但提供的比率为4.500000,内存最大限制为每个集装箱的请求比率为1500m,但提供的比率为4.000000,因为我们这里没有指定request和limit,则使用默认的Default Request(cpu: 200m,内存200Mi)和Default Limit(cpu: 900m,内存800Mi)

标签、选择器及注解

在同一个命名空间下,还可以对资源进行更细粒度的划分,对各个资源的身份进行标识(例如,可以区分各个应用的版本、层级、环境等),可以使用标签(label)来区分这些资源。通过标签选择器(selector),我们可以快速查找具备指定标签值的资源,或者将某些高层Kubernetes资源通过标签条件关联到低层资源。注解(annotation)也是一种类似标签的机制。相比标签,注解更自由,可以包含少量结构化数据。一般来说,注解只是向对象中添加更多信息的一种方式,并没有实际功能。

标签

在 Kubernetes 中,标签(Label)是用来对资源对象进行分类和标识的一种工具。简单来说,就是给资源贴上标签,让我们更好地管理和查找这些资源。标签是由 键值对(key-value)组成的,每个资源可以有多个标签,但对于同一个键,每个资源只能有一个值。

对于每一种资源对象,都可以设置标签。方法是在模板中的metadata属性中设置,如下所示:

metadata:
  labels:    #标签列表,可定义多个标签的键/值对
    key1: value1
    key2: value2
    ......
    keyN: valueN

对于已有资源,可以通过以下命令为其创建或删除标签

kubectl label <资源类型> <资源名称> <标签名>=<标签值>
kubectl label <资源类型> <资源名称> <标签名>-

以下是一些标签的应用场景
根据发布版本划分为release: beta、release: stable、release:canary
根据环境划分为environment: dev、environment: qa、environment: production
根据应用层级划分为tier: frontend、tier: backend、tier: cache
根据用户群划分为partition: customerA、partition: customerB
根据维护频率划分为track: daily、track: weekly

下面我们来定义一个带标签的Pod

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
 name: nginx
 labels:
   app: nginx
   release: beta
   environment: dev
spec:
 containers:
 - name: nginx
   image: nginx:1.17
   ports:
   - containerPort: 80
EOF

查看pod所带的标签

[root@k8s-master2 ~]# kubectl get pod --show-labels
NAME    READY   STATUS    RESTARTS   AGE   LABELS
nginx   1/1     Running   0          45s   app=nginx,environment=dev,release=beta

通过标签来查找指定的pod

[root@k8s-master2 ~]# kubectl get pod -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          91s

删除标签,比如我们这里删除key为environment的标签

[root@k8s-master2 ~]# kubectl label pod nginx environment-
pod/nginx labeled
[root@k8s-master2 ~]# kubectl get pod --show-labels
NAME    READY   STATUS    RESTARTS   AGE     LABELS
nginx   1/1     Running   0          2m27s   app=nginx,release=beta

添加标签,给pod添加一个environment=test的标签

[root@k8s-master2 ~]# kubectl label pod nginx environment=test
pod/nginx labeled
[root@k8s-master2 ~]# kubectl get pod --show-labels
NAME    READY   STATUS    RESTARTS   AGE     LABELS
nginx   1/1     Running   0          3m56s   app=nginx,environment=test,release=beta

选择器

Kubernetes 提供了一种称为 标签选择器(Label Selector)的功能。通过标签选择器,可以快速查找具备指定标签值的资源。如通过kubelet命令查找指定资源时,加上-l参数,后面带上选择器表达式。

在查询时可以使用=(或==)、!=操作符,使用逗号可分隔并连接多个表达式以进行匹配。例如,可以使用以下命令查询environment标签不为dev、release标签为beta的所有Pod

[root@k8s-master2 ~]# kubectl get pods -l environment!=dev,release=beta
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          3m19s

还可以使用in、notin等方式进行查询。在使用这种方式时需要将选择器表达式放置在单引号之间,使用逗号分隔并连接多个表达式进行匹配。例如,可以使用以下命令查询environment标签的取值在test和dev之间且release标签取值不在canary中的所有Pod

[root@k8s-master2 ~]# kubectl get pods -l 'environment in (test,dev),release notin (canary)'
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          5m49s

还可以使用!{label}、{label}等方式进行查询。如果使用!{label},需要将选择器表达式放置在单引号之间,使用逗号分隔并连接多个表达式进行匹配。例如,可以使用以下命令查询带environment标签(任何值皆可)但不带deadline标签的所有Pod。

[root@k8s-master2 ~]# kubectl get pods -l 'environment,!deadline'
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          7m13s

标签选择器可以将高层 Kubernetes 资源通过标签条件关联到低层资源。例如,Deployment 控制器可以使用标签选择器查找符合条件的 Pod。在控制器模板的 spec 属性中,通过 selector 指定选择器条件,匹配相应标签的 Pod,从而实现高层资源对低层资源的动态管理。以下是一个 Deployment 的示例,它通过标签选择器关联到特定的 Pod

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 3
  selector:          # 定义标签选择器
    matchLabels:
      app: my-app    # 选择所有带有 app=my-app 标签的 Pod
  template:          # 定义 Pod 模板
    metadata:
      labels:
        app: my-app  # Pod 的标签
    spec:
      containers:
      - name: my-container
        image: nginx
EOF

查看创建的pod

[root@k8s-master2 ~]# kubectl get pod -l app=my-app --show-labels
NAME                            READY   STATUS              RESTARTS   AGE     LABELS
my-deployment-fc467b48d-8gg5h   1/1     Running             0          2m23s   app=my-app,pod-template-hash=fc467b48d
my-deployment-fc467b48d-dvkkk   1/1     Running             0          2m23s   app=my-app,pod-template-hash=fc467b48d
my-deployment-fc467b48d-p52tc   1/1     Running             0          2m23s   app=my-app,pod-template-hash=fc467b48d

注解

在 Kubernetes 中,注解(Annotation)是一种类似标签的机制,用于为资源对象添加额外的信息。与标签不同的是,注解的主要作用是存储说明性的信息,而不是用来识别或筛选资源对象。注解非常灵活,可以包含少量结构化数据或简单的文本信息。这些信息通常用于描述资源对象的额外属性,或者为系统组件、运维人员提供一些补充说明。需要注意的是,注解仅仅起到说明作用,并不具备实际功能,也不会影响资源对象的行为。

同标签一样,对于每一种资源对象都可以设置注解,在模板的metadata属性中设置即可。下面我们来定义一个带注解的Pod

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginxzj
  annotations:
    app: "nginxzj"
    versionInfo: "v1.17"
spec:
  containers:
  - name: nginxzj
    image: nginx:1.17
EOF

查看Pod的详细信息,可以发现Pod已成功指定注解。除了我们设置的几个注解信息外,在创建Pod时,Kubernetes还自动增加了其它注解信息

[root@k8s-master2 ~]# kubectl describe pod nginxzj
......
Annotations:  app: nginxzj
              versionInfo: v1.17
......
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qianshuaiblog.cn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值