[K8s]Kubernetes-策略
策略
可配置的、可应用到一组资源的策略。
1 - 限制范围
默认情况下,Kubernetes 集群上的容器运行使用的计算资源没有限制。使用资源配额,集群管理员可以以名字空间为单位,限制其资源的使用与创建。在命名空间中,一个 Pod 或 Container 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。有人担心,一个 Pod 或 Container 会垄断所有可用的资源。LimitRange 是在命名空间内限制资源分配(给多个 Pod 或 Container)的策略对象。
一个 LimitRange(限制范围)对象提供的限制能够做到:
- 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
- 在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。
- 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
- 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。
启用 LimitRange
对 LimitRange 的支持自 Kubernetes 1.10 版本默认启用。
LimitRange 支持在很多 Kubernetes 发行版本中也是默认启用的。
LimitRange 的名称必须是合法的 DNS 子域名。
限制范围总览
- 管理员在一个命名空间内创建一个 LimitRange 对象。
- 用户在命名空间内创建 Pod ,Container 和 PersistentVolumeClaim 等资源。
- LimitRanger 准入控制器对所有没有设置计算资源需求的 Pod 和 Container 设置默认值与限制值,并跟踪其使用量以保证没有超出命名空间中存在的任意 LimitRange 对象中的最小、最大资源使用量以及使用量比值。
- 若创建或更新资源(Pod、Container、PersistentVolumeClaim)违反了 LimitRange 的约束,向 API 服务器的请求会失败,并返回 HTTP 状态码 403 FORBIDDEN 与描述哪一项约束被违反的消息。
- 若命名空间中的 LimitRange 启用了对 cpu 和 memory 的限制,用户必须指定这些值的需求使用量与限制使用量。否则,系统将会拒绝创建 Pod。
- LimitRange 的验证仅在 Pod 准入阶段进行,不对正在运行的 Pod 进行验证。
能够使用限制范围创建的策略示例有:
- 在一个有两个节点,8 GiB 内存与16个核的集群中,限制一个命名空间的 Pod 申请 100m 单位,最大 500m 单位的 CPU,以及申请 200Mi,最大 600Mi 的内存。
- 为 spec 中没有 cpu 和内存需求值的 Container 定义默认 CPU 限制值与需求值 150m,内存默认需求值 300Mi。
在命名空间的总限制值小于 Pod 或 Container 的限制值的总和的情况下,可能会产生资源竞争。在这种情况下,将不会创建 Container 或 Pod。
竞争和对 LimitRange 的改变都不会影响任何已经创建了的资源。
2 - 资源配额
当多个用户或团队共享具有固定节点数目的集群时,人们会担心有人使用超过其基于公平原则所分配到的资源量。
资源配额是帮助管理员解决这一问题的工具。
资源配额,通过 ResourceQuota 对象来定义,对每个命名空间的资源消耗总量提供限制。它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命令空间中的 Pod 可以使用的计算资源的总上限。
资源配额的工作方式如下:
-
不同的团队可以在不同的命名空间下工作,目前这是非约束性的,在未来的版本中可能会通过 ACL (Access Control List 访问控制列表) 来实现强制性约束。
-
集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。
-
当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会跟踪集群的资源使用情况,以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。
-
如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN),并在消息中给出有可能违反的约束。
-
如果命名空间下的计算资源 (如 cpu 和 memory)的配额被启用,则用户必须为这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。
ResourceQuota 对象的名称必须是合法的 DNS 子域名。
下面是使用命名空间和配额构建策略的示例:
- 在具有 32 GiB 内存和 16 核 CPU 资源的集群中,允许 A 团队使用 20 GiB 内存 和 10 核的 CPU 资源,允许 B 团队使用 10 GiB 内存和 4 核的 CPU 资源,并且预留 2 GiB 内存和 2 核的 CPU 资源供将来分配。
- 限制 "testing" 命名空间使用 1 核 CPU 资源和 1GiB 内存。允许 "production" 命名空间使用任意数量。
在集群容量小于各命名空间配额总和的情况下,可能存在资源竞争。资源竞争时,Kubernetes 系统会遵循先到先得的原则。
不管是资源竞争还是配额的修改,都不会影响已经创建的资源使用对象。
启用资源配额
资源配额的支持在很多 Kubernetes 版本中是默认启用的。当 API 服务器的命令行标志 --enable-admission-plugins= 中包含 ResourceQuota 时,资源配额会被启用。
当命名空间中存在一个 ResourceQuota 对象时,对于该命名空间而言,资源配额就是开启的。
计算资源配额
用户可以对给定命名空间下的可被请求的计算资源总量进行限制。
配额机制所支持的资源类型:
资源名称
|
描述
|
limits.cpu
|
所有非终止状态的 Pod,其 CPU 限额总量不能超过该值。
|
limits.memory
|
所有非终止状态的 Pod,其内存限额总量不能超过该值。
|
requests.cpu
|
所有非终止状态的 Pod,其 CPU 需求总量不能超过该值。
|
requests.memory
|
所有非终止状态的 Pod,其内存需求总量不能超过该值。
|
hugepages-<size>
|
对于所有非终止状态的 Pod,针对指定尺寸的巨页请求总数不能超过此值。
|
cpu
|
与 requests.cpu 相同。
|
memory
|
与 requests.memory 相同。
|
扩展资源的资源配额
除上述资源外,在 Kubernetes 1.10 版本中,还添加了对扩展资源的支持。
由于扩展资源不可超量分配,因此没有必要在配额中为同一扩展资源同时指定 requests 和 limits。对于扩展资源而言,目前仅允许使用前缀为 requests. 的配额项。
以 GPU 拓展资源为例,如果资源名称为 nvidia.com/gpu,并且要将命名空间中请求的 GPU 资源总数限制为 4,则可以如下定义配额:
- requests.nvidia.com/gpu: 4
存储资源配额
用户可以对给定命名空间下的存储资源总量进行限制。
此外,还可以根据相关的存储类(Storage Class)来限制存储资源的消耗。
资源名称
|
描述
|
requests.storage
|
所有 PVC,存储资源的需求总量不能超过该值。
|
persistentvolumeclaims
|
在该命名空间中所允许的 PVC 总量。
|
<storage-class-name>.storageclass.storage.k8s.io/requests.storage
|
在所有与 <storage-class-name> 相关的持久卷申领中,存储请求的总和不能超过该值。
|
<storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims
|
在与 storage-class-name 相关的所有持久卷申领中,命名空间中可以存在的持久卷申领总数。
|
例如,如果一个操作人员针对 gold 存储类型与 bronze 存储类型设置配额,操作人员可以定义如下配额:
- gold.storageclass.storage.k8s.io/requests.storage: 500Gi
- bronze.storageclass.storage.k8s.io/requests.storage: 100Gi
在 Kubernetes 1.8 版本中,本地临时存储的配额支持已经是 Alpha 功能:
资源名称
|
描述
|
requests.ephemeral-storage
|
在命名空间的所有 Pod 中,本地临时存储请求的总和不能超过此值。
|
limits.ephemeral-storage
|
在命名空间的所有 Pod 中,本地临时存储限制值的总和不能超过此值。
|
ephemeral-storage
|
与 requests.ephemeral-storage 相同。
|
说明:
如果所使用的是 CRI 容器运行时,容器日志会被计入临时存储配额。这可能会导致存储配额耗尽的 Pods 被意外地驱逐出节点。
对象数量配额
你可以使用以下语法对所有标准的、命名空间域的资源类型进行配额设置:
count/<resource>.<group>
:用于非核心(core)组的资源count/<resource>
:用于核心组的资源
这是用户可能希望利用对象计数配额来管理的一组资源示例。
- count/persistentvolumeclaims
- count/services
- count/secrets
- count/configmaps
- count/replicationcontrollers
- count/deployments.apps
- count/replicasets.apps
- count/statefulsets.apps
- count/jobs.batch
- count/cronjobs.batch
相同语法也可用于自定义资源。例如,要对 example.com API 组中的自定义资源 widgets 设置配额,请使用 count/widgets.example.com。
当使用 count/* 资源配额时,如果对象存在于服务器存储中,则会根据配额管理资源。这些类型的配额有助于防止存储资源耗尽。例如,用户可能想根据服务器的存储能力来对服务器中 Secret 的数量进行配额限制。集群中存在过多的 Secret 实际上会导致服务器和控制器无法启动。用户可以选择对 Job 进行配额管理,以防止配置不当的 CronJob 在某命名空间中创建太多 Job 而导致集群拒绝服务。
对有限的一组资源上实施一般性的对象数量配额也是可能的。此外,还可以进一步按资源的类型设置其配额。
支持以下类型:
资源名称
|
描述
|
configmaps
|
在该命名空间中允许存在的 ConfigMap 总数上限。
|
persistentvolumeclaims
|
在该命名空间中允许存在的 PVC 的总数上限。
|
pods
|
在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded) 为真。
|
replicationcontrollers
|
在该命名空间中允许存在的 ReplicationController 总数上限。
|
resourcequotas
|
在该命名空间中允许存在的 ResourceQuota 总数上限。
|
services
|
在该命名空间中允许存在的 Service 总数上限。
|
services.loadbalancers
|
在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。
|
services.nodeports
|
在该命名空间中允许存在的 NodePort 类型的 Service 总数上限。
|
secrets
|
在该命名空间中允许存在的 Secret 总数上限。
|
例如,pods 配额统计某个命名空间中所创建的、非终止状态的 Pod 个数并确保其不超过某上限值。用户可能希望在某命名空间中设置 pods 配额,以避免有用户创建很多小的 Pod,从而耗尽集群所能提供的 Pod IP 地址。
配额作用域
每个配额都有一组相关的 scope(作用域),配额只会对作用域内的资源生效。配额机制仅统计所列举的作用域的交集中的资源用量。
当一个作用域被添加到配额中后,它会对作用域相关的资源数量作限制。如配额中指定了允许(作用域)集合之外的资源,会导致验证错误。
作用域
|
描述
|
Terminating
|
匹配所有 spec.activeDeadlineSeconds 不小于 0 的 Pod。
|
NotTerminating
|
匹配所有 spec.activeDeadlineSeconds 是 nil 的 Pod。
|
BestEffort
|
匹配所有 Qos 是 BestEffort 的 Pod。
|
NotBestEffort
|
匹配所有 Qos 不是 BestEffort 的 Pod。
|
PriorityClass
|
匹配所有引用了所指定的优先级类的 Pods。
|
CrossNamespacePodAffinity
|
匹配那些设置了跨名字空间 (反)亲和性条件的 Pod。
|
BestEffort 作用域限制配额跟踪以下资源:
- pods
Terminating、NotTerminating、NotBestEffort 和 PriorityClass 这些作用域限制配额跟踪以下资源:
- pods
- cpu
- memory
- requests.cpu
- requests.memory
- limits.cpu
- limits.memory
需要注意的是,你不可以在同一个配额对象中同时设置 Terminating 和 NotTerminating 作用域,你也不可以在同一个配额中同时设置 BestEffort 和 NotBestEffort 作用域。
scopeSelector 支持在 operator 字段中使用以下值:
- In
- NotIn
- Exists
- DoesNotExist
定义 scopeSelector 时,如果使用以下值之一作为 scopeName 的值,则对应的 operator 只能是 Exists。
- Terminating
- NotTerminating
- BestEffort
- NotBestEffort
如果 operator 是 In 或 NotIn 之一,则 values 字段必须至少包含一个值。例如:
scopeSelector:
matchExpressions:
- scopeName: PriorityClass
operator: In
values:
- middle
如果 operator 为 Exists 或 DoesNotExist,则不可以设置 values 字段。
基于优先级类(PriorityClass)来设置资源配额
FEATURE STATE: Kubernetes v1.17 [stable]
Pod 可以创建为特定的优先级。通过使用配额规约中的 scopeSelector 字段,用户可以根据 Pod 的优先级控制其系统资源消耗。
仅当配额规范中的 scopeSelector 字段选择到某 Pod 时,配额机制才会匹配和计量 Pod 的资源消耗。
如果配额对象通过 scopeSelector 字段设置其作用域为优先级类,则配额对象只能跟踪以下资源:
- pods
- cpu
- memory
- ephemeral-storage
- limits.cpu
- limits.memory
- limits.ephemeral-storage
- requests.cpu
- requests.memory
- requests.ephemeral-storage
本示例创建一个配额对象,并将其与具有特定优先级的 Pod 进行匹配。该示例的工作方式如下:
- 集群中的 Pod 可取三个优先级类之一,即 "low"、"medium"、"high"。
- 为每个优先级创建一个配额对象。
将以下 YAML 保存到文件 quota.yml 中。
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-high
spec:
hard:
cpu: "1000"
memory: 200Gi
pods: "10"
scopeSelector:
matchExpressions:
- operator : In
scopeName: PriorityClass
values: ["high"]
- apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-medium
spec:
hard:
cpu: "10"
memory: 20Gi
pods: "10"
scopeSelector:
matchExpressions:
- operator : In
scopeName: PriorityClass
values: ["medium"]
- apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-low
spec:
hard:
cpu: "5"
memory: 10Gi
pods: "10"
scopeSelector:
matchExpressions:
- operator : In
scopeName: PriorityClass
values: ["low"]
使用 kubectl create 命令运行以下操作。
kubectl create -f ./quota.yml
resourcequota/pods-high created
resourcequota/pods-medium created
resourcequota/pods-low created
使用 kubectl describe quota
操作验证配额的 Used 值为 0。
kubectl describe quota
Name: pods-high
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 1k
memory 0 200Gi
pods 0 10
Name: pods-low
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 5
memory 0 10Gi
pods 0 10
Name: pods-medium
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 10
memory 0 20Gi
pods 0 10
创建优先级为 "high" 的 Pod。将以下 YAML 保存到文件 high-priority-pod.yml 中。
apiVersion: v1
kind: Pod
metadata:
name: high-priority
spec:
containers:
- name: high-priority
image: ubuntu
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10;done"]
resources:
requests:
memory: "10Gi"
cpu: "500m"
limits:
memory: "10Gi"
cpu: "500m"
priorityClassName: high
使用 kubectl create 运行以下操作。
kubectl create -f ./high-priority-pod.yml
确认 "high" 优先级配额 pods-high 的 "Used" 统计信息已更改,并且其他两个配额未更改。
kubectl describe quota
Name: pods-high
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 500m 1k
memory 10Gi 200Gi
pods 1 10
Name: pods-low
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 5
memory 0 10Gi
pods 0 10
Name: pods-medium
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 10
memory 0 20Gi
pods 0 10
跨名字空间的 Pod 亲和性配额
FEATURE STATE: Kubernetes v1.22 [beta]
集群运维人员可以使用 CrossNamespacePodAffinity 配额作用域来限制哪个名字空间中可以存在包含跨名字空间亲和性规则的 Pod。更为具体一点,此作用域用来配置哪些 Pod 可以在其 Pod 亲和性规则中设置 namespaces 或 namespaceSelector 字段。
禁止用户使用跨名字空间的亲和性规则可能是一种被需要的能力,因为带有反亲和性约束的 Pod 可能会阻止所有其他名字空间的 Pod 被调度到某失效域中。
使用此作用域操作符可以避免某些名字空间(例如下面例子中的 foo-ns)运行特别的 Pod,这类 Pod 使用跨名字空间的 Pod 亲和性约束,在该名字空间中创建了作用域为 CrossNamespaceAffinity 的硬性约束为 0 的资源配额对象。
apiVersion: v1
kind: ResourceQuota
metadata:
name: disable-cross-namespace-affinity
namespace: foo-ns
spec:
hard:
pods: "0"
scopeSelector:
matchExpressions:
- scopeName: CrossNamespaceAffinity
如果集群运维人员希望默认禁止使用 namespaces 和 namespaceSelector,而仅仅允许在特定名字空间中这样做,他们可以将 CrossNamespaceAffinity 作为一个被约束的资源。方法是为 kube-apiserver 设置标志 --admission-control-config-file,使之指向如下的配置文件:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: "ResourceQuota"
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: ResourceQuotaConfiguration
limitedResources:
- resource: pods
matchScopes:
- scopeName: CrossNamespaceAffinity
基于上面的配置,只有名字空间中包含作用域为 CrossNamespaceAffinity 且硬性约束大于或等于使用 namespaces 和 namespaceSelector 字段的 Pods 个数时,才可以在该名字空间中继续创建在其 Pod 亲和性规则中设置 namespaces 或 namespaceSelector 的新 Pod。
此功能特性处于 Beta 阶段,默认被禁用。你可以通过为 kube-apiserver 和 kube-scheduler 设置特性门控 PodAffinityNamespaceSelector 来启用此特性。
请求与限制的比较
分配计算资源时,每个容器可以为 CPU 或内存指定请求和约束。配额可以针对二者之一进行设置。
如果配额中指定了 requests.cpu 或 requests.memory 的值,则它要求每个容器都显式给出对这些资源的请求。同理,如果配额中指定了 limits.cpu 或 limits.memory 的值,那么它要求每个容器都显式设定对应资源的限制。
查看和设置配额
Kubectl 支持创建、更新和查看配额:
kubectl create namespace myspace
cat <<EOF > compute-resources.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-resources
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
requests.nvidia.com/gpu: 4
EOF
kubectl create -f ./compute-resources.yaml --namespace=myspace
cat <<EOF > object-counts.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: object-counts
spec:
hard:
configmaps: "10"
persistentvolumeclaims: "4"
pods: "4"
replicationcontrollers: "20"
secrets: "10"
services: "10"
services.loadbalancers: "2"
EOF
kubectl create -f ./object-counts.yaml --namespace=myspace
kubectl get quota --namespace=myspace
NAME AGE
compute-resources 30s
object-counts 32s
kubectl describe quota compute-resources --namespace=myspace
Name: compute-resources
Namespace: myspace
Resource Used Hard
-------- ---- ----
limits.cpu 0 2
limits.memory 0 2Gi
requests.cpu 0 1
requests.memory 0 1Gi
requests.nvidia.com/gpu 0 4
kubectl describe quota object-counts --namespace=myspace
Name: object-counts
Namespace: myspace
Resource Used Hard
-------- ---- ----
configmaps 0 10
persistentvolumeclaims 0 4
pods 0 4
replicationcontrollers 0 20
secrets 1 10
services 0 10
services.loadbalancers 0 2
kubectl 还使用语法 count/<resource>.<group>
支持所有标准的、命名空间域的资源的对象计数配额:
kubectl create namespace myspace
kubectl create quota test --hard=count/deployments.apps=2,count/replicasets.apps=4,count/pods=3,count/secrets=4 --namespace=myspace
kubectl create deployment nginx --image=nginx --namespace=myspace --replicas=2
kubectl describe quota --namespace=myspace
Name: test
Namespace: myspace
Resource Used Hard
-------- ---- ----
count/deployments.apps 1 2
count/pods 2 3
count/replicasets.apps 1 4
count/secrets 1 4
配额和集群容量
ResourceQuota 与集群资源总量是完全独立的。它们通过绝对的单位来配置。所以,为集群添加节点时,资源配额不会自动赋予每个命名空间消耗更多资源的能力。
有时可能需要资源配额支持更复杂的策略,比如:
- 在几个团队中按比例划分总的集群资源。
- 允许每个租户根据需要增加资源使用量,但要有足够的限制以防止资源意外耗尽。
- 探测某个命名空间的需求,添加物理节点并扩大资源配额值。
这些策略可以通过将资源配额作为一个组成模块、手动编写一个控制器来监控资源使用情况,并结合其他信号调整命名空间上的硬性资源配额来实现。
注意:资源配额对集群资源总体进行划分,但它对节点没有限制:来自不同命名空间的 Pod 可能在同一节点上运行。
默认情况下限制特定优先级的资源消耗
有时候可能希望当且仅当某名字空间中存在匹配的配额对象时,才可以创建特定优先级(例如 "cluster-services")的 Pod。
通过这种机制,操作人员能够将限制某些高优先级类仅出现在有限数量的命名空间中,而并非每个命名空间默认情况下都能够使用这些优先级类。
要实现此目的,应设置 kube-apiserver 的标志 --admission-control-config-file 指向如下配置文件:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: "ResourceQuota"
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: ResourceQuotaConfiguration
limitedResources:
- resource: pods
matchScopes:
- scopeName: PriorityClass
operator: In
values: ["cluster-services"]
现在在 kube-system 名字空间中创建一个资源配额对象:
apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-cluster-services
spec:
scopeSelector:
matchExpressions:
- operator : In
scopeName: PriorityClass
values: ["cluster-services"]
kubectl apply -f https://k8s.io/examples/policy/priority-class-resourcequota.yaml -n kube-system
resourcequota/pods-cluster-services created
在这里,当以下条件满足时可以创建 Pod:
- Pod 未设置 priorityClassName
- Pod 的 priorityClassName 设置值不是 cluster-services
- Pod 的 priorityClassName 设置值为 cluster-services,它将被创建于 kube-system 名字空间中,并且它已经通过了资源配额检查。
如果 Pod 的 priorityClassName 设置为 cluster-services,但要被创建到 kube-system 之外的别的名字空间,则 Pod 创建请求也被拒绝。
3 - Pod 安全策略
FEATURE STATE: Kubernetes v1.21 [deprecated]
PodSecurityPolicy 在 Kubernetes v1.21 版本中被弃用,将在 v1.25 中删除。关于弃用的更多信息,请查阅 PodSecurityPolicy Deprecation: Past, Present, and Future。
Pod 安全策略使得对 Pod 创建和更新进行细粒度的权限控制成为可能。
什么是 Pod 安全策略?
Pod 安全策略(Pod Security Policy)是集群级别的资源,它能够控制 Pod 规约中与安全性相关的各个方面。PodSecurityPolicy 对象定义了一组 Pod 运行时必须遵循的条件及相关字段的默认值,只有 Pod 满足这些条件才会被系统接受。Pod 安全策略允许管理员控制如下方面:
控制的角度
|
字段名称
|
运行特权容器
|
|
使用宿主名字空间
|
|
使用宿主的网络和端口
|
|
控制卷类型的使用
|
|
使用宿主文件系统
|
|
允许使用特定的 FlexVolume 驱动
|
|
分配拥有 Pod 卷的 FSGroup 账号
|
|
以只读方式访问根文件系统
|
|
设置容器的用户和组 ID
|
|
限制 root 账号特权级提升
|
|
Linux 权能字(Capabilities)
|
|
设置容器的 SELinux 上下文
|
|
指定容器可以挂载的 proc 类型
|
|
指定容器使用的 AppArmor 模版
|
|
指定容器使用的 seccomp 模版
|
|
指定容器使用的 sysctl 模版
|
Pod 安全策略由设置和策略组成,它们能够控制 Pod 访问的安全特征。这些设置分为如下三类:
- 基于布尔值控制:这种类型的字段默认为最严格限制的值。
- 基于被允许的值集合控制:这种类型的字段会与这组值进行对比,以确认值被允许。
- 基于策略控制:设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允许的这组值中。
启用 Pod 安全策略
Pod 安全策略实现为一种可选的准入控制器。启用了准入控制器即可强制实施 Pod 安全策略,不过如果没有授权认可策略之前即启用准入控制器将导致集群中无法创建任何 Pod。
由于 Pod 安全策略 API(policy/v1beta1/podsecuritypolicy)是独立于准入控制器来启用的,对于现有集群而言,建议在启用准入控制器之前先添加策略并对其授权。
授权策略
PodSecurityPolicy 资源被创建时,并不执行任何操作。为了使用该资源,需要对发出请求的用户或者目标 Pod 的服务账号授权,通过允许其对策略执行 use 动词允许其使用该策略。
大多数 Kubernetes Pod 不是由用户直接创建的。相反,这些 Pod 是由 Deployment、ReplicaSet 或者经由控制器管理器模版化的控制器创建。赋予控制器访问策略的权限意味着对应控制器所创建的所有 Pod 都可访问策略。因此,对策略进行授权的优先方案是为 Pod 的服务账号授予访问权限。
通过 RBAC 授权
RBAC 是一种标准的 Kubernetes 鉴权模式,可以很容易地用来授权策略访问。
首先,某 Role 或 ClusterRole 需要获得使用 use 访问目标策略的权限。访问授权的规则看起来像这样:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: <Role 名称>
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- <要授权的策略列表>
接下来将该 Role(或 ClusterRole)绑定到授权的用户:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: <binding name>
roleRef:
kind: ClusterRole
name: <role name>
apiGroup: rbac.authorization.k8s.io
subjects:
# 授权命名空间下的所有服务账号(推荐):
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:serviceaccounts:<authorized namespace>
# 授权特定的服务账号(不建议这样操作):
- kind: ServiceAccount
name: <authorized service account name>
namespace: <authorized pod namespace>
# 授权特定的用户(不建议这样操作):
- kind: User
apiGroup: rbac.authorization.k8s.io
name: <authorized user name>
如果使用的是 RoleBinding(而不是 ClusterRoleBinding),授权仅限于 与该 RoleBinding 处于同一名字空间中的 Pods。 可以考虑将这种授权模式和系统组结合,对名字空间中的所有 Pod 授予访问权限。
# 授权某名字空间中所有服务账号
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:serviceaccounts
# 或者与之等价,授权给某名字空间中所有被认证过的用户
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated
推荐实践
PodSecurityPolicy 正在被一个新的、简化的 PodSecurity 准入控制器替代。有关此变更的更多详细信息,请参阅 PodSecurityPolicy Deprecation: Past, Present, and Future。参照下述指导,简化从 PodSecurityPolicy 迁移到新的准入控制器步骤:
-
将 PodSecurityPolicies 限制为 Pod 安全性标准所定义的策略:
- Privileged
- Baseline
- Restricted
-
通过配置
system:serviceaccounts:<namespace>
组(namespace 是目标命名空间),仅将 PSP 绑定到整个命名空间。示例:apiVersion: rbac.authorization.k8s.io/v1 # This cluster role binding allows all pods in the "development" namespace to use the baseline PSP. kind: ClusterRoleBinding metadata: name: psp-baseline-namespaces roleRef: kind: ClusterRole name: psp-baseline apiGroup: rbac.authorization.k8s.io subjects: - kind: Group name: system:serviceaccounts:development apiGroup: rbac.authorization.k8s.io - kind: Group name: system:serviceaccounts:canary apiGroup: rbac.authorization.k8s.io
故障排查
控制器管理器组件必须运行在安全的 API 端口,并且一定不能具有超级用户权限。否则其请求会绕过身份认证和鉴权模块控制,从而导致所有 PodSecurityPolicy 对象都被启用,用户亦能创建特权容器。
策略顺序
除了限制 Pod 创建与更新,Pod 安全策略也可用来为其所控制的很多字段设置默认值。当存在多个策略对象时,Pod 安全策略控制器依据以下条件选择策略:
- 优先考虑允许 Pod 保持原样,不会更改 Pod 字段默认值或其他配置的 PodSecurityPolicy。这类非更改性质的 PodSecurityPolicy 对象之间的顺序无关紧要。
- 如果必须要为 Pod 设置默认值或者其他配置,(按名称顺序)选择第一个允许 Pod 操作的 PodSecurityPolicy 对象。
说明: 在更新操作期间(这时不允许更改 Pod 规约),仅使用非更改性质的 PodSecurityPolicy 来对 Pod 执行验证操作。
示例
本示例假定你已经有一个启动了 PodSecurityPolicy 准入控制器的集群并且你拥有集群管理员特权。
配置
为运行此示例,配置一个名字空间和一个服务账号。我们将用这个服务账号来模拟一个非管理员账号的用户。
kubectl create namespace psp-example
kubectl create serviceaccount -n psp-example fake-user
kubectl create rolebinding -n psp-example fake-editor --clusterrole=edit --serviceaccount=psp-example:fake-user
创建两个别名,以更清晰地展示我们所使用的用户账号,同时减少一些键盘输入:
alias kubectl-admin='kubectl -n psp-example'
alias kubectl-user='kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'
创建一个策略和一个 Pod
在一个文件中定义一个示例的 PodSecurityPolicy 对象。这里的策略只是用来禁止创建有特权要求的 Pods。PodSecurityPolicy 对象的名称必须是合法的 DNS 子域名。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: example
spec:
privileged: false # Don't allow privileged pods!
# The rest fills in some required fields.
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'
使用 kubectl 执行创建操作:
kubectl-admin create -f example-psp.yaml
现在,作为一个非特权用户,尝试创建一个简单的 Pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于:
Error from server (Forbidden): error when creating "STDIN": pods "pause" is forbidden: unable to validate against any pod security policy: []
发生了什么? 尽管 PodSecurityPolicy 被创建,Pod 的服务账号或者 fake-user 用户都没有使用该策略的权限。
kubectl-user auth can-i use podsecuritypolicy/example
no
创建角色绑定,赋予 fake-user 使用 use 访问示例策略的权限:
说明: 不建议使用这种方法!欲了解优先考虑的方法,请参见下节。
kubectl-admin create role psp:unprivileged \
--verb=use \
--resource=podsecuritypolicy \
--resource-name=example
输出:
role "psp:unprivileged" created
kubectl-admin create rolebinding fake-user:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:fake-user
输出:
rolebinding "fake-user:psp:unprivileged" created
kubectl-user auth can-i use podsecuritypolicy/example
输出:
yes
现在重试创建 Pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于:
pod "pause" created
此次尝试不出所料地成功了!不过任何创建特权 Pod 的尝试还是会被拒绝:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: privileged
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
securityContext:
privileged: true
EOF
输出类似于:
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
继续此例之前先删除该 Pod:
kubectl-user delete pod pause
运行另一个 Pod
我们再试一次,稍微有些不同:
kubectl-user create deployment pause --image=k8s.gcr.io/pause
输出为:
deployment "pause" created
kubectl-user get pods
输出为:
No resources found.
kubectl-user get events | head -n 2
输出为:
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE
1m 2m 15 pause-7774d79b5 ReplicaSet Warning FailedCreate replicaset-controller Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request
发生了什么? 我们已经为用户 fake-user 绑定了 psp:unprivileged 角色,为什么还会收到错误 Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request(创建错误:pods "pause-7774d79b5" 被禁止:没有可用来验证 pod 请求的驱动)?
答案在于源文件 - replicaset-controller。fake-user 用户成功地创建了 Deployment,而后者也成功地创建了 ReplicaSet,不过当 ReplicaSet 创建 Pod 时,发现未被授权使用示例 PodSecurityPolicy 资源。
为了修复这一问题,将 psp:unprivileged 角色绑定到 Pod 的服务账号。在这里,因为我们没有给出服务账号名称,默认的服务账号是 default。
kubectl-admin create rolebinding default:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:default
输出为:
rolebinding "default:psp:unprivileged" created
现在如果你给 ReplicaSet 控制器一分钟的时间来重试,该控制器最终将能够成功地创建 Pod:
kubectl-user get pods --watch
输出类似于:
NAME READY STATUS RESTARTS AGE
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 ContainerCreating 0 1s
pause-7774d79b5-qrgcb 1/1 Running 0 2s
清理
删除名字空间即可清理大部分示例资源:
kubectl-admin delete ns psp-example
输出类似于:
namespace "psp-example" deleted
注意 PodSecurityPolicy 资源不是名字空间域的资源,必须单独清理:
kubectl-admin delete psp example
输出类似于:
podsecuritypolicy "example" deleted
示例策略
下面是一个你可以创建的约束性非常弱的策略,其效果等价于没有使用 Pod 安全策略准入控制器:
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: privileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
privileged: true
allowPrivilegeEscalation: true
allowedCapabilities:
- '*'
volumes:
- '*'
hostNetwork: true
hostPorts:
- min: 0
max: 65535
hostIPC: true
hostPID: true
runAsUser:
rule: 'RunAsAny'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
下面是一个具有约束性的策略,要求用户以非特权账号运行,禁止可能的向 root 权限的升级,同时要求使用若干安全机制。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default'
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default'
apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default'
spec:
privileged: false
# Required to prevent escalations to root.
allowPrivilegeEscalation: false
# This is redundant with non-root + disallow privilege escalation,
# but we can provide it for defense in depth.
requiredDropCapabilities:
- ALL
# Allow core volume types.
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
# Assume that persistentVolumes set up by the cluster admin are safe to use.
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
# Require the container to run without root privileges.
rule: 'MustRunAsNonRoot'
seLinux:
# This policy assumes the nodes are using AppArmor rather than SELinux.
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
readOnlyRootFilesystem: false
策略参考
Privileged
Privileged - 决定是否 Pod 中的某容器可以启用特权模式。默认情况下,容器是不可以访问宿主上的任何设备的,不过一个“privileged(特权的)” 容器则被授权访问宿主上所有设备。这种容器几乎享有宿主上运行的进程的所有访问权限。对于需要使用 Linux 权能字(如操控网络堆栈和访问设备)的容器而言是有用的。
宿主名字空间
HostPID - 控制 Pod 中容器是否可以共享宿主上的进程 ID 空间。注意,如果与 ptrace 相结合,这种授权可能被利用,导致向容器外的特权逃逸(默认情况下 ptrace 是被禁止的)。
HostIPC - 控制 Pod 容器是否可共享宿主上的 IPC 名字空间。
HostNetwork - 控制是否 Pod 可以使用节点的网络名字空间。此类授权将允许 Pod 访问本地回路(loopback)设备、在本地主机(localhost)上监听的服务、还可能用来监听同一节点上其他 Pod 的网络活动。
HostPorts -提供可以在宿主网络名字空间中可使用的端口范围列表。该属性定义为一组 HostPortRange 对象的列表,每个对象中包含 min(含)与 max(含)值的设置。默认不允许访问宿主端口。
卷和文件系统
Volumes - 提供一组被允许的卷类型列表。可被允许的值对应于创建卷时可以设置的卷来源。卷类型的完整列表可参见卷类型。此外,* 可以用来允许所有卷类型。
对于新的 Pod 安全策略设置而言,建议设置的卷类型的最小列表包含:
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- secret
- projected
警告: PodSecurityPolicy 并不限制可以被 PersistentVolumeClaim 所引用的 PersistentVolume 对象的类型。此外 hostPath 类型的 PersistentVolume 不支持只读访问模式。应该仅赋予受信用户创建 PersistentVolume 对象的访问权限。
FSGroup - 控制应用到某些卷上的附加用户组。
- MustRunAs - 要求至少指定一个 range。使用范围中的最小值作为默认值。所有 range 值都会被用来执行验证。
- MayRunAs - 要求至少指定一个 range。允许不设置 FSGroups,且无默认值。如果 FSGroup 被设置,则所有 range 值都会被用来执行验证检查。
- RunAsAny - 不提供默认值。允许设置任意 fsGroup ID 值。
AllowedHostPaths - 设置一组宿主文件目录,这些目录项可以在 hostPath 卷中使用。列表为空意味着对所使用的宿主目录没有限制。此选项定义包含一个对象列表,表中对象包含 pathPrefix 字段,用来表示允许 hostPath 卷挂载以所指定前缀开头的路径。对象中还包含一个 readOnly 字段,用来表示对应的卷必须以只读方式挂载。例如:
allowedHostPaths:
# 下面的设置允许 "/foo"、"/foo/"、"/foo/bar" 等路径,但禁止
# "/fool"、"/etc/foo" 这些路径。
# "/foo/../" 总会被当作非法路径。
- pathPrefix: "/foo"
readOnly: true # 仅允许只读模式挂载
警告:
容器如果对宿主文件系统拥有不受限制的访问权限,就可以有很多种方式提升自己的特权,包括读取其他容器中的数据、滥用系统服务(如 kubelet)的凭据信息等。由可写入的目录所构造的 hostPath 卷能够允许容器写入数据到宿主文件系统,并且在写入时避开 pathPrefix 所设置的目录限制。readOnly: true 这一设置在 Kubernetes 1.11 版本之后可用。必须针对 allowedHostPaths 中的所有条目设置此属性才能有效地限制容器只能访问 pathPrefix 所指定的目录。
ReadOnlyRootFilesystem - 要求容器必须以只读方式挂载根文件系统来运行(即不允许存在可写入层)。
FlexVolume 驱动
此配置指定一个可以被 FlexVolume 卷使用的驱动程序的列表。空的列表或者 nil 值意味着对驱动没有任何限制。请确保 volumes 字段包含了 flexVolume 卷类型,否则所有 FlexVolume 驱动都被禁止。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: allow-flex-volumes
spec:
# spec d的其他字段
volumes:
- flexVolume
allowedFlexVolumes:
- driver: example/lvm
- driver: example/cifs
用户和组
RunAsUser - 控制使用哪个用户 ID 来运行容器。
- MustRunAs - 必须配置一个 range。使用该范围内的第一个值作为默认值。所有 range 值都被用于验证检查。
- MustRunAsNonRoot - 要求提交的 Pod 具有非零 runAsUser 值,或在镜像中(使用 UID 数值)定义了 USER 环境变量。如果 Pod 既没有设置 runAsNonRoot,也没有设置 runAsUser,则该 Pod 会被 修改以设置 runAsNonRoot=true,从而要求容器通过 USER 指令给出非零的数值形式的用户 ID。此配置没有默认值。采用此配置时,强烈建议设置 allowPrivilegeEscalation=false。
- RunAsAny - 没有提供默认值。允许指定任何 runAsUser 配置。
RunAsGroup - 控制运行容器时使用的主用户组 ID。
- MustRunAs - 要求至少指定一个 range 值。第一个 range 中的最小值作为默认值。所有 range 值都被用来执行验证检查。
- MayRunAs - 不要求设置 RunAsGroup。不过,如果指定了 RunAsGroup 被设置,所设置值必须处于所定义的范围内。
- RunAsAny - 未指定默认值。允许 runAsGroup 设置任何值。
SupplementalGroups - 控制容器可以添加的组 ID。
- MustRunAs - 要求至少指定一个 range 值。第一个 range 中的最小值用作默认值。所有 range 值都被用来执行验证检查。
- MayRunAs - 要求至少指定一个 range 值。允许不指定 supplementalGroups 且不设置默认值。如果 supplementalGroups 被设置,则所有 range 值都被用来执行验证检查。
- RunAsAny - 未指定默认值。允许为 supplementalGroups 设置任何值。
特权提升
这一组选项控制容器的allowPrivilegeEscalation 属性。该属性直接决定是否为容器进程设置 no_new_privs 参数。此参数会禁止 setuid 属性的可执行文件更改有效用户 ID(EUID),并且禁止启用额外权能的文件。例如,no_new_privs 会禁止使用 ping 工具。如果想有效地实施 MustRunAsNonRoot 控制,需要配置这一选项。
AllowPrivilegeEscalation - 决定是否用户可以将容器的安全上下文设置为 allowPrivilegeEscalation=true。默认设置下,这样做是允许的,目的是避免造成现有的 setuid 应用无法运行。将此选项设置为 false 可以确保容器的所有子进程都无法获得比父进程更多的特权。
DefaultAllowPrivilegeEscalation - 为 allowPrivilegeEscalation 选项设置默认值。不设置此选项时的默认行为是允许特权提升,以便运行 setuid 程序。如果不希望运行 setuid 程序,可以使用此字段将选项的默认值设置为禁止,同时仍然允许 Pod 显式地请求 allowPrivilegeEscalation。
权能字
Linux 权能字(Capabilities)将传统上与超级用户相关联的特权作了细粒度的分解。其中某些权能字可以用来提升特权,打破容器边界,可以通过 PodSecurityPolicy 来限制。关于 Linux 权能字的更多细节,可参阅 capabilities(7)。
下列字段都可以配置为权能字的列表。表中的每一项都是 ALL_CAPS 中的一个权能字名称,只是需要去掉 CAP_ 前缀。
AllowedCapabilities - 给出可以被添加到容器的权能字列表。默认的权能字集合是被隐式允许的那些。空集合意味着只能使用默认权能字集合,不允许添加额外的权能字。* 可以用来设置允许所有权能字。
RequiredDropCapabilities - 必须从容器中去除的权能字。所给的权能字会从默认权能字集合中去除,并且一定不可以添加。RequiredDropCapabilities 中列举的权能字不能出现在 AllowedCapabilities 或 DefaultAddCapabilities 所给的列表中。
DefaultAddCapabilities - 默认添加到容器的权能字集合。这一集合是作为容器运行时所设值的补充。关于使用 Docker 容器运行引擎时默认的权能字列表,可参阅 Docker文档。
SELinux
- MustRunAs - 要求必须配置 seLinuxOptions。默认使用 seLinuxOptions。针对 seLinuxOptions 所给值执行验证检查。
- RunAsAny - 没有提供默认值。允许任意指定的 seLinuxOptions 选项。
AllowedProcMountTypes
allowedProcMountTypes 是一组可以允许的 proc 挂载类型列表。空表或者 nil 值表示只能使用 DefaultProcMountType。
DefaultProcMount 使用容器运行时的默认值设置来决定 /proc 的只读挂载模式和路径屏蔽。大多数容器运行时都会屏蔽 /proc 下面的某些路径以避免特殊设备或信息被不小心暴露给容器。这一配置使所有 Default 字符串值来表示。
此外唯一的ProcMountType 是 UnmaskedProcMount,意味着即将绕过容器运行时的路径屏蔽行为,确保新创建的 /proc 不会被容器修改。此配置用字符串 Unmasked 来表示。
AppArmor
通过 PodSecurityPolicy 上的注解来控制。详情请参阅 AppArmor 文档。
Seccomp
Pod 对 seccomp 模版的使用可以通过在 PodSecurityPolicy 上设置注解来控制。Seccomp 是 Kubernetes 的一项 alpha 阶段特性。
seccomp.security.alpha.kubernetes.io/defaultProfileName - 注解用来指定为容器配置默认的 seccomp 模版。可选值为:
- unconfined - 如果没有指定其他替代方案,Seccomp 不会被应用到容器进程上(Kubernets 中的默认设置)。
- runtime/default - 使用默认的容器运行时模版。
- docker/default - 使用 Docker 的默认 seccomp 模版。自 1.11 版本废弃。应改为使用 runtime/default。
- localhost/<路径名> - 指定节点上路径 <seccomp_root>/<路径名> 下的一个文件作为其模版。其中 <seccomp_root> 是通过 kubelet 的标志 --seccomp-profile-root 来指定的。
seccomp.security.alpha.kubernetes.io/allowedProfileNames - 指定可以为 Pod seccomp 注解配置的值的注解。取值为一个可用值的列表。表中每项可以是上述各值之一,还可以是 *,用来表示允许所有的模版。如果没有设置此注解,意味着默认的 seccomp 模版是不可更改的。
Sysctl
默认情况下,所有的安全的 sysctl 都是被允许的。
- forbiddenSysctls - 用来排除某些特定的 sysctl。你可以在此列表中禁止一些安全的或者不安全的 sysctl。此选项设置为 * 意味着禁止设置所有 sysctl。
- allowedUnsafeSysctls - 用来启用那些被默认列表所禁用的 sysctl,前提是所启用的 sysctl 没有被列在 forbiddenSysctls 中。
4 - 进程 ID 约束与预留
FEATURE STATE: Kubernetes v1.20 [stable]
Kubernetes 允许你限制一个 Pod 中可以使用的进程 ID(PID)数目。你也可以为每个节点预留一定数量的可分配的 PID,供操作系统和守护进程(而非 Pod)使用。
进程 ID(PID)是节点上的一种基础资源。很容易就会在尚未超出其它资源约束的时候就已经触及任务个数上限,进而导致宿主机器不稳定。
集群管理员需要一定的机制来确保集群中运行的 Pod 不会导致 PID 资源枯竭,甚而造成宿主机上的守护进程(例如 kubelet 或者 kube-proxy 乃至包括容器运行时本身)无法正常运行。此外,确保 Pod 中 PID 的个数受限对于保证其不会影响到同一节点上其它负载也很重要。
说明:
在某些 Linux 安装环境中,操作系统会将 PID 约束设置为一个较低的默认值,例如 32768。这时可以考虑提升 /proc/sys/kernel/pid_max 的设置值。
你可以配置 kubelet 限制给定 Pod 能够使用的 PID 个数。例如,如果你的节点上的宿主操作系统被设置为最多可使用 262144 个 PID,同时预期节点上会运行的 Pod 个数不会超过 250,那么你可以为每个 Pod 设置 1000 个 PID 的预算,避免耗尽该节点上可用 PID 的总量。如果管理员系统像 CPU 或内存那样允许对 PID 进行过量分配(Overcommit),他们也可以这样做,只是会有一些额外的风险。不管怎样,任何一个 Pod 都不可以将整个机器的运行状态破坏。这类资源限制有助于避免简单的派生炸弹(Fork Bomb)影响到整个集群的运行。
在 Pod 级别设置 PID 限制使得管理员能够保护 Pod 之间不会互相伤害,不过无法确保所有调度到该宿主机器上的所有 Pod 都不会影响到节点整体。Pod 级别的限制也无法保护节点代理任务自身不会受到 PID 耗尽的影响。
你也可以预留一定量的 PID,作为节点的额外开销,与分配给 Pod 的 PID 集合独立。这有点类似于在给操作系统和其它设施预留 CPU、内存或其它资源时所做的操作,这些任务都在 Pod 及其所包含的容器之外运行。
PID 限制是与计算资源请求和限制相辅相成的一种机制。不过,你需要用一种不同的方式来设置这一限制:你需要将其设置到 kubelet 上而不是在 Pod 的 .spec 中为 Pod 设置资源限制。目前还不支持在 Pod 级别设置 PID 限制。
注意:
这意味着,施加在 Pod 之上的限制值可能因为 Pod 运行所在的节点不同而有差别。为了简化系统,最简单的方法是为所有节点设置相同的 PID 资源限制和预留值。
节点级别 PID 限制
Kubernetes 允许你为系统预留一定量的进程 ID。为了配置预留数量,你可以使用 kubelet 的 --system-reserved 和 --kube-reserved 命令行选项中的参数 pid=<number>
。你所设置的参数值分别用来声明为整个系统和 Kubernetes 系统守护进程所保留的进程 ID 数目。
说明:
在 Kubernetes 1.20 版本之前,在节点级别通过 PID 资源限制预留 PID 的能力需要启用特性门控 SupportNodePidsLimit 才行。
Pod 级别 PID 限制
Kubernetes 允许你限制 Pod 中运行的进程个数。你可以在节点级别设置这一限制,而不是为特定的 Pod 来将其设置为资源限制。每个节点都可以有不同的 PID 限制设置。要设置限制值,你可以设置 kubelet 的命令行参数 --pod-max-pids,或者 在 kubelet 的配置文件中设置 PodPidsLimit。
说明:
在 Kubernetes 1.20 版本之前,为 Pod 设置 PID 资源限制的能力需要启用特性门控 SupportNodePidsLimit 才行。
基于 PID 的驱逐
你可以配置 kubelet 使之在 Pod 行为不正常或者消耗不正常数量资源的时候将其终止。这一特性称作驱逐。你可以针对不同的驱逐信号配置资源不足的处理。使用 pid.available 驱逐信号来配置 Pod 使用的 PID 个数的阈值。你可以设置硬性的和软性的驱逐策略。不过,即使使用硬性的驱逐策略,如果 PID 个数增长过快,节点仍然可能因为触及节点 PID 限制而进入一种不稳定状态。驱逐信号的取值是周期性计算的,而不是一直能够强制实施约束。
Pod 级别和节点级别的 PID 限制会设置硬性限制。一旦触及限制值,工作负载会在尝试获得新的 PID 时开始遇到问题。这可能会也可能不会导致 Pod 被重新调度,取决于工作负载如何应对这类失败以及 Pod 的存活性和就绪态探测是如何配置的。可是,如果限制值被正确设置,你可以确保其它 Pod 负载和系统进程不会因为某个 Pod 行为不正常而没有 PID 可用。
5 - 节点资源管理器
Kubernetes 提供了一组资源管理器,用于支持延迟敏感的、高吞吐量的工作负载。资源管理器的目标是协调和优化节点资源,以支持对 CPU、设备和内存(巨页)等资源有特殊需求的 Pod。
主管理器,也叫拓扑管理器(Topology Manager),是一个 Kubelet 组件,它通过策略,协调全局的资源管理过程。
各个管理器的配置方式会在专项文档中详细阐述:
- CPU 管理器策略
- 设备管理器
- 内存管理器策略