Kubernetes中的多租戶


多租戶

多租戶集群由多個用戶和/或工作負載共享,這些用戶和/或工作負載被稱為“租戶”。多租戶集群的運營方必須將租戶彼此隔離,以最大限度地減少被盜用的租戶或惡意租戶可能對集群和其他租戶造成的損害。此外,必須在租戶之間公平地分配集群資源。

在規划多租戶架構時,應該考慮 Kubernetes 中的資源隔離層:集群、命名空間、節點、Pod 和容器。還應該考慮在租戶之間共享不同類型資源的安全隱患。例如,將來自不同租戶的 Pod 調度到同一節點上可以減少集群中所需的機器數量。另一方面,可能需要阻止某些工作負載共置。例如可能不允許來自組織外部的不受信任的代碼與處理敏感信息的容器在同一節點上運行。

雖然 Kubernetes 不能保證租戶之間完全安全地隔離,但它為特定使用場景提供了足夠的相關功能。可以將每個租戶及其 Kubernetes 資源分隔到各自的命名空間中。然后,可以使用一些限制策略來強制執行租戶隔離。現在策略通常按命名空間划分,可用於限制 API 訪問、資源使用以及允許容器執行的操作。

多租戶集群的租戶共享以下資源:

  • 擴展程序、控制器、插件和自定義資源定義 (CRD)。
  • 集群控制平面。這意味着集群操作、安全性和審核是集中管理的。

與運營多個單租戶集群相比,運營多租戶集群有幾個優點:

  • 減少管理開銷
  • 減少資源碎片
  • 新租戶無需等待集群創建

多租戶使用用例

企業多租戶

在企業環境中,集群的租戶是組織內的不同團隊。通常,每個租戶對應一個命名空間,同一個命名空間內的網絡流量不受限制,但不同命名空間之間的網絡流量必須明確列入白名單。可以使用 Kubernetes 網絡策略來實現這些隔離。

集群的用戶根據其權限分為三種不同的角色:

  • 集群管理員:此角色適用於在整個集群中管理所有租戶的管理員。集群管理員可以創建、讀取、更新和刪除任何策略對象。他們可以創建命名空間並將其分配給命名空間管理員。
  • 命名空間管理員:此角色適用於特定單一租戶的管理員,命名空間管理員可以管理其命名空間中的用戶。
  • 開發者:此角色的成員可以創建、讀取、更新和刪除命名空間內的非策略對象,如 Pod、Job 和 Ingress。開發者只在他們有權訪問的命名空間中擁有這些權限。

image

SaaS提供商多租戶

SaaS 提供商集群的租戶是應用的各個客戶專用實例,以及 SaaS 的控制平面。要充分利用按命名空間划分的政策,應將各個應用實例安排到其各自的命名空間中,SaaS 控制平面的組件也應如此。最終用戶無法直接與 Kubernetes 控制平面交互,而是使用 SaaS 的界面,由后者與 Kubernetes 控制平面進行交互。

例如,博客平台可以在多租戶集群上運行,在這種情況下,租戶是每個客戶的博客實例和平台自己的控制平面。平台的控制平面和每個托管博客都將在不同的命名空間中運行。客戶將通過平台的界面來創建和刪除博客、更新博客軟件版本,但無法了解集群的運作方式。

多租戶策略

命名空間

Kubernetes 中的多租戶都是根據 Namespace 來進行划分的,Namespace 是一組邏輯的集群,可以大概類似於租戶的概念,可以做到一定程度的資源隔離、Quota。

如下面兩條命令:

$ kubectl run nginx -image=nginx
$ kubectl run nginx -image=nginx -namespace=dev

這兩條命令雖然都是 run 起來一個 nginx,但是作用域卻不一樣。第一條命令則是在 Default 這個命名空間中,第二條命令是在一個叫 dev 的 namespace 里運行nginx。

訪問權限控制

對於多租戶來說,訪問權限控制是非常重要的,我們可以使用 Kubernetes 內置的 RBAC 來進行權限控制,可以為集群中的特定資源和操作授予細化的權限。

網絡策略

通過集群網絡策略,我們可以控制集群的 Pod 之間的通信,策略可以指定 Pod 可以與哪些命名空間、標簽和 IP 地址范圍進行通信。

資源配額

資源配額用於管理命名空間中對象使用的資源量,我們可以按 CPU 和內存用量或對象數量來設置配額。通過資源配額,可以確保租戶不會使用超過其分配份額的集群資源。

資源配額是通過 ResourceQuota 資源對象來定義的,可以對每個 namespace 的資源消耗總量提供限制。它可以按類型限制 namespace 下可以創建的對象的數量,也可以限制可被該項目以資源形式消耗的計算資源的總量。

資源配額的工作方式如下:

​ 1、管理員為每個 namespace 創建一個或多個資源配額對象

​ 2、用戶在 namespace 下創建資源 (pods、 services 等),同時配額系統會跟蹤使用情況,來確保其不超過資源配額中定義的硬性資源限額

​ 3、如果資源的創建或更新違反了配額約束,則請求會失敗,並返回 HTTP 狀態碼 403 FORBIDDEN,以及說明違反配額約束的信息

​ 4、如果 namespace 下的計算資源(如 cpu 和 memory)的配額被啟用,則用戶必須為這些資源設定請求值(request) 和約束值(limit),否則配額系統將拒絕 Pod 的創建。

Kubernetes 中主要有3個層級的資源配額控制:

​ 1、容器:可以對 CPU 和 Memory 進行限制

​ 2、POD:可以對一個 Pod 內所有容器的的資源進行限制

​ 3、Namespace:為一個命名空間下的資源進行限制

其中容器層次主要利用容器本身的支持,比如 Docker 對 CPU、內存等的支持;

Pod 方面可以限制系統內創建 Pod 的資源范圍,比如最大或者最小的 CPU、memory 需求;

Namespace 層次就是對用戶級別的資源限額了,包括 CPU、內存,還可以限定 Pod、RC、Service 的數量。

要使用資源配額的話需要確保 apiserver--enable-admission-plugins= 參數中包含 ResourceQuota,當 namespace 中存在一個 ResourceQuota 對象時,該 namespace 即開始實施資源配額的管理工作了,另外需要注意的是一個 namespace 中最多只應存在一個 ResourceQuota 對象。

資源配額控制器支持的配額控制資源主要包括:計算資源配額、存儲資源配額、對象數量資源配額以及配額作用域。

計算資源配額

用戶可以對給定 namespace 下的計算資源總量進行限制,支持的資源類型如下所示:

資源名稱 描述
CPU 所有非終止狀態的Pod中,其CPU需求總量不能超過該值
limits.cpu 所有非終止狀態的Pod中,其CPU限額總量不能超過該值
limits.memory 所有非終止狀態的Pod中,其內存限額總量不能超過該值
memory 所有非終止狀態的Pod中,其內存需求總量不能超過該值
request.cpu 所有非終止狀態的Pod中,其CPU需求總量不能超過該值
request.memory 所有非終止狀態的Pod中,其內存需求總量不能超過該值

比如我們現在來為一個命名空間創建內存和 CPU 配額,首先創建一個測試用的命名空間:

$ kubectl create namespace dev

然后定義一個如下所示的資源配額資源對象:(quota-mem-cpu.yaml)

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
  namespace: dev
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi

創建完成后,我們查看ResourceQuota這個對象:

$ kubectl describe quota mem-cpu-demo -n dev
Name:            mem-cpu-demo
Namespace:       dev
Resource         Used  Hard
--------         ----  ----
limits.cpu       0     2
limits.memory    0     2Gi
requests.cpu     0     1
requests.memory  0     1Gi

現在我們來創建一個如下所示的 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: quota-mem-cpu-demo
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "800Mi"
        cpu: "800m" 
      requests:
        memory: "600Mi"
        cpu: "400m"

創建上面的Pod,查看運行狀態:

$ kubectl get pod -n dev
NAME                 READY   STATUS    RESTARTS   AGE
quota-mem-cpu-demo   1/1     Running   0          78s

可以看到Pod已經運行起來了,這時候我們再次查看我們定義的資源配額對象:

$ kubectl describe quota mem-cpu-demo -n dev
Name:            mem-cpu-demo
Namespace:       dev
Resource         Used   Hard
--------         ----   ----
limits.cpu       800m   2
limits.memory    800Mi  2Gi
requests.cpu     400m   1
requests.memory  600Mi  1Gi

我們可以看到已經明確告訴我們已經使用了多少計算資源了,比如內存的請求值只剩 400Mi(1Gi-600Mi)資源了,我們現在來創建一個大於 400Mi 請求內存的資源測試下:

apiVersion: v1
kind: Pod
metadata:
  name: quota-mem-cpu-demo-2
  namespace: quota-mem-cpu-example
spec:
  containers:
  - name: quota-mem-cpu-demo-2-ctr
    image: redis
    resources:
      limits:
        memory: "1Gi"
        cpu: "800m"      
      requests:
        memory: "700Mi"
        cpu: "400m"

我們創建這個Pod,結果發現不成功:

$ kubectl apply -f quota_nginx2.yaml 
Error from server (Forbidden): error when creating "quota_nginx2.yaml": pods "quota-mem-cpu-demo-2" is forbidden: exceeded quota: mem-cpu-demo, requested: requests.memory=700Mi, used: requests.memory=600Mi, limited: requests.memory=1Gi

可以看到已經被拒絕了,因為 requests.memory 已經超過了我們的資源配額的限制了。

從上面的案例看我們可以使用 ResourceQuota 來限制命名空間中運行的所有容器的 CPU 和 內存的資源配額總數,如果限制單個容器而不是所有容器的總數,就需要使用 LimitRange 資源對象了。另外如果在一個命名空間下面計算資源(如 CPU 和內存)的配額被啟用了,則用戶必須為這些資源設置請求值(request)和約束值(limit),否則配額系統將拒絕 Pod 的創建,除非我們配置了 LimitRange 資源對象。

要使用 LimitRange 同樣需要在 --enable-admission-plugins= 參數中開啟 LimitRanger。比如現在我們來配置一個命名空間中容器的最小和最大的內存限制,我們創建一個命名空間來進行配置:

$ kubectl create namespace mem-example

然后創建一個LimitRange的配置資源對象:

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-min-max-demo-lr
  namespace: constraints-mem-example
spec:
  limits:
  - max:
      memory: 1Gi
    min:
      memory: 500Mi
    type: Container

創建成后,我們查看詳細信息:

$ kubectl get limitrange mem-min-max-demo-lr --namespace=mem-example -o yaml
......
spec:
  limits:
  - default:
      memory: 1Gi
    defaultRequest:
      memory: 1Gi
    max:
      memory: 1Gi
    min:
      memory: 500Mi
    type: Container

上面輸出顯示了最小和最大的內存約束,但是要注意即使我們沒有指定默認值,也會自動創建的。現在,只要在 mem-example 命名空間中創建容器,Kubernetes 就會執行下面的步驟:

  • 如果 Container 未指定自己的內存請求和限制,將為它指定默認的內存請求和限制
  • 驗證 Container 的內存請求是否大於或等於 500 MiB
  • 驗證 Container 的內存限制是否小於或等於1 GiB

下面我們這里來創建一個 Pod,其中容器聲明了 600 MiB 的內存請求和 800 MiB 的內存限制,這些滿足了 LimitRange 的最小和最大內存約束:

apiVersion: v1
kind: Pod
metadata:
  name: mem-demo
  namespace: mem-example
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "800Mi"
      requests:
        memory: "600Mi"

然后直接創建查看狀態:

$ kubectl get pod  -n mem-example
NAME       READY   STATUS    RESTARTS   AGE
mem-demo   1/1     Running   0          38s

可以看到是可以正常運行的。

然后我們再創建一個超過最大內存限制的 Pod 測試下:

apiVersion: v1
kind: Pod
metadata:
  name: mem-demo-2
  namespace: mem-example
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "1.5Gi"
      requests:
        memory: "800Mi"

現在我們創建,發現不成功:

Error from server (Forbidden): error when creating "memdemo2.yaml": pods "mem-demo-2" is forbidden: maximum memory usage per Container is 1Gi, but limit is 1536Mi

輸出結果顯示 Pod 沒有創建成功,因為容器聲明的內存限制太大了。我們也可以去嘗試下創建一個小於最小內存限制的 Pod 或沒有聲明內存請求和限制的 Pod。

存儲配額

用戶可以對給定 namespace 下的存儲資源總量進行限制,此外,還可以根據相關的存儲類(Storage Class)來限制存儲資源的消耗。

資源名稱 描述
requests.storage 所有的PVC中,存儲資源的需求不能超過該值
persistentvolumeclaims namespace中所允許的PVC總量
.storageclass.storage.k8s.io/requests.storage 所有該storage-class-name相關的PVC中,存儲資源的需求不能超過該值
.storageclass.storage.k8s.io/persistentvolumeclaims namespace中所允許的該sorage-calss-name相關的PVC總量

對象數量配置

資源名稱 描述
configmaps namespace下允許存在的configmap的數量
persistentvolumeclaims namespace下允許存在的PVC數量
pods namespace下允許存在的非終止狀態的Pod數量,如果Pod的status.phase為Failed或Succeeded,那么其處於終止狀態。
replicationcontrollers namespace下允許存在的replication controllers的數量
resourcequotas namespace下允許存在的resource quotas的數量
services namespace下允許存在的service的數量
services.loadbalancers namespace下允許存在的load balancer類型的service的數量
servicesnodeports namespace下允許存在的node port類型的service的數量
secrets namespace下允許存在的secret的數量

Qos服務質量

當 Kubernetes 創建 Pod 時,它會將以下 QoS 中的一類分配給 Pod:

​ 1、Guaranteed

​ 2、Burstable

​ 3、BestEffort

Guaranteed

要為 Pod 提供 Guaranteed 的 QoS 類必須滿足以下需求:

​ 1、Pod 中的每個 Container 都必須有內存限制和內存請求。

​ 2、對於 Pod 中的每個 Container,內存限制必須等於內存請求。

​ 3、Pod 中的每個 Container 都必須有一個 CPU 限制和一個 CPU 請求。

​ 4、對於 Pod 中的每個 Container,CPU 限制必須等於 CPU 請求。

這些限制同樣適用於 init 容器和 app 容器。

下面我們來創建Pod,Container 有一個內存限制和一個內存請求,兩者都等於 200 MiB。Container 有一個 CPU 限制和一個 CPU 請求,都等於 700 milliCPU:

apiVersion: v1
kind: Namespace
metadata:
  name: qos-example

---
apiVersion: v1
kind: Pod
metadata:
  name: qos-demo
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

創建Pod后,查看Pod的詳細信息:

$ kubectl get pod qos-demo -n qos-example -oyaml
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: qos-demo-ctr
    resources:
      limits:
        cpu: 700m
        memory: 200Mi
      requests:
        cpu: 700m
        memory: 200Mi
    ...
status:
  qosClass: Guaranteed

輸出顯示 Kubernetes 為 Pod 提供了 Guaranteed 的 QoS 類。輸出還驗證 Pod 容器是否有與其內存限制相匹配的內存請求,並且它有一個與其 CPU 限制相匹配的 CPU 請求。

注意:

如果 Container 指定了自己的內存限制,但沒有指定內存請求,Kubernetes 會自動分配與限制匹配的內存請求。同樣,如果一個Container指定了自己的CPU限制,但沒有指定CPU請求,Kubernetes會自動分配一個匹配該限制的CPU請求。

Burstable

在以下情況下,Pod 會被賦予 Burstable 的 QoS 類:

​ 1、Pod 不滿足 QoS 等級 Guaranteed 的標准。

​ 2、Pod 中至少有一個 Container 有內存或 CPU 請求。

這是具有一個容器的 Pod 的配置文件。Container 的內存限制為 200 MiB,內存請求為 100 MiB。

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-2
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"

同樣,創建Pod后,查看Pod的詳細信息:

$  kubectl get pod -n qos-example qos-demo-2 -oyaml
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: qos-demo-2-ctr
    resources:
      limits:
        memory: 200Mi
      requests:
        memory: 100Mi
	...
status:
  qosClass: Burstable

輸出顯示 Kubernetes 為 Pod 提供了 Burstable 的 QoS 類。

創建一個包含兩個Container的Pod

這是具有兩個容器的 Pod 的配置文件。一個容器指定 200 MiB 的內存請求。另一個 Container 沒有指定任何請求或限制。

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-4
  namespace: qos-example
spec:
  containers:

  - name: qos-demo-4-ctr-1
    image: nginx
    resources:
      requests:
        memory: "200Mi"

  - name: qos-demo-4-ctr-2
    image: redis

此 Pod 符合 QoS 類 Burstable 的標准。也就是說,它不滿足 QoS 類 Guaranteed 的標准,並且它的一個 Container 有內存請求。

創建后查看Pod的詳情:

$ kubectl get pod qos-demo-4 -n qos-example -oyaml
spec:
  containers:
    ...
    name: qos-demo-4-ctr-1
    resources:
      requests:
        memory: 200Mi
    ...
    name: qos-demo-4-ctr-2
    resources: {}
    ...
status:
  qosClass: Burstable
BestEffort

要為 Pod 提供 BestEffort 的 QoS 類,Pod 中的容器不得有任何內存或 CPU 限制或請求。

這是具有一個容器的 Pod 的配置文件。容器沒有內存或 CPU 限制或請求:

apiVersion: v1
kind: Pod
metadata:
  name: qos-demo-3
  namespace: qos-example
spec:
  containers:
  - name: qos-demo-3-ctr
    image: nginx

同樣,創建Pod后進行查看詳情:

$ kubectl get pod qos-demo-3 -n qos-example -oyaml
spec:
  containers:
  - image: nginx
    imagePullPolicy: Always
    name: qos-demo-3-ctr
    resources: {}
	...
status:
  qosClass: BestEffort

輸出顯示 Kubernetes 為 Pod 提供了 BestEffort 的 QoS 類。

Pod安全策略

Pod 安全策略是控制 pod 規范的安全敏感方面的集群級資源。PodSecurityPolicy對象定義了一組條件,Pod 必須滿足這些條件才能被系統接受,以及相關字段的默認值。它們允許管理員控制以下內容:

控制方面 字段名稱
運行特權容器 privileged
主機命名空間的使用 hostPID, hostIPC
主機網絡和端口的使用 hostNetwork, hostPorts
卷類型的使用 volumes
主機文件系統的使用 allowedHostPaths
允許特定的 FlexVolume 驅動程序 allowedFlexVolumes
分配擁有 pod 卷的 FSGroup fsGroup
要求使用只讀根文件系統 readOnlyRootFilesystem
容器的用戶和組 ID runAsUser, runAsGroup,supplementalGroups
限制升級到 root 權限 allowPrivilegeEscalation, defaultAllowPrivilegeEscalation
Linux 功能 defaultAddCapabilities, requiredDropCapabilities,allowedCapabilities
容器的 SELinux 上下文 seLinux
容器的 Allowed Proc Mount 類型 allowedProcMountTypes
容器使用的 AppArmor 配置文件 注釋
容器使用的 sysctl 配置文件 forbiddenSysctls,allowedUnsafeSysctls
容器使用的 seccomp 配置文件 注釋

注意:PodSecurityPolicy 自 Kubernetes v1.21 起已棄用,並將在 v1.25 中刪除。

啟用 Pod 安全策略

Pod 安全策略實現為一種可選的 准入控制器啟用了准入控制器 即可強制實施 Pod 安全策略,不過如果沒有授權認可策略之前即啟用 准入控制器 將導致集群中無法創建任何 Pod

由於 Pod 安全策略 API(policy/v1beta1/podsecuritypolicy)是獨立於准入控制器 來啟用的,對於現有集群而言,建議在啟用准入控制器之前先添加策略並對其授權。

授權策略

PodSecurityPolicy 資源被創建時,並不執行任何操作。為了使用該資源,需要對 發出請求的用戶或者目標 Pod 的 服務賬號 授權,通過允許其對策略執行 use 動詞允許其使用該策略。

大多數 Kubernetes Pod 不是由用戶直接創建的。相反,這些 Pod 是由 Deployment、 ReplicaSet或者經由控制器管理器模版化的控制器創建。 賦予控制器訪問策略的權限意味着對應控制器所創建的 所有 Pod 都可訪問策略。 因此,對策略進行授權的優先方案是為 Pod 的服務賬號授予訪問權限 。

通過RBAC授權

首先,某 RoleClusterRole 需要獲得使用 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

策略順序

除了限制 Pod 創建與更新,Pod 安全策略也可用來為其所控制的很多字段 設置默認值。當存在多個策略對象時,Pod 安全策略控制器依據以下條件選擇 策略:

  1. 優先考慮允許 Pod 保持原樣,不會更改 Pod 字段默認值或其他配置的 PodSecurityPolicy。 這類非更改性質的 PodSecurityPolicy 對象之間的順序無關緊要。
  2. 如果必須要為 Pod 設置默認值或者其他配置,(按名稱順序)選擇第一個允許 Pod 操作的 PodSecurityPolicy 對象。

說明: 在更新操作期間(這時不允許更改 Pod 規約),僅使用非更改性質的 PodSecurityPolicy 來對 Pod 執行驗證操作。

具體內容可以參考官方文檔

Pod反親和性

注意:惡意租戶可以規避 Pod 反親和性規則。以下示例應僅用於具有受信任租戶的集群,或租戶無法直接訪問 Kubernetes 控制平面的集群。 我們可以利用 Pod 反親和性來防止不同租戶的 Pod 被調度到同一節點上。例如,下面的 Pod 規范描述了一個標簽為 team: billing 的 Pod,以及阻止該 Pod 與沒有該標簽的 Pod 調度到一起的反親和性規則。

apiVersion: v1
kind: Pod
metadata:
  name: bar
  labels:
    team: "billing"
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
      - topologyKey: "kubernetes.io/hostname"
        labelSelector:
          matchExpressions:
          - key: "team"
            operator: NotIn
            values: ["billing"]

上面這個資源清單的意思就是 bar 這個 Pod 不能調度到不具有 team: billing 這樣的標簽的 Pod 所在的節點,不過這種做法的缺點是惡意用戶可以通過將 team: billing 標簽添加到任意 Pod 來規避規則,僅使用 Pod 反親和性機制不足以在具有不受信任的租戶的集群上安全地強制執行政策。

污點和容忍

注意:惡意租戶可以規避由節點污點和容忍機制強制執行的政策。以下示例應僅用於具有受信任租戶的集群,或租戶無法直接訪問 Kubernetes 控制平面的集群。

節點污點是控制工作負載調度的另一種方法,可以使用節點污點來將專用節點留給某些租戶使用。例如,可以將配備 GPU 的節點專門留給那些工作負載需要 GPU 的特定租戶。要將某個節點池專門留給某個租戶,請將具有 effect: "NoSchedule" 的污點應用於該節點池,然后,只有具備相應容忍設置的 Pod 可以被調度到該節點池中的節點。

這種做法的缺點是惡意用戶可以通過為其 Pod 添加相應容忍設置來訪問專用節點池,所以僅使用節點污點和容忍機制不足以在具有不受信任的租戶的集群上安全地強制執行政策。

如果一個節點標記為污點(Taints),除非 Pod 也被標識為可以容忍污點節點,否則該 Taints 節點不會被調度 pod。

比如用戶希望把 Master 節點保留給 Kubernetes 系統組件使用,或者把一組具有特殊資源預留給某些 Pod,則污點就很有用了,Pod 不會再被調度到 taint 標記過的節點。我們使用 kubeadm 搭建的集群默認就給 master 節點添加了一個污點標記,所以我們創建的普通Pod都沒有被調度到Master上去:

$ kubectl describe node master
Name:               master
Roles:              master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=master
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/master=
......
Taints:             node-role.kubernetes.io/master:NoSchedule
Unschedulable:      false
.....

我們可以使用上面的命令查看 master 節點的信息,其中有一條關於 Taints 的信息:node-role.kubernetes.io/master:NoSchedule,就表示給 master 節點打了一個污點的標記,其中影響的參數是NoSchedule,表示 Pod 不會被調度到標記為 taints 的節點,除了 NoSchedule 外,還有另外兩個選項:

  • PreferNoSchedule:NoSchedule 的軟策略版本,表示盡量不調度到污點節點上去
  • NoExecute:該選項意味着一旦 Taint 生效,如該節點內正在運行的 Pod 沒有對應 Tolerate 設置,會直接被逐出

污點 taint 標記節點的命令如下:

$ kubectl taint nodes node1 test=node1:NoSchedule
node/node1 tainted

上面的命名將 node1 節點標記為了污點,影響策略是 NoSchedule,只會影響新的 Pod 調度,如果仍然希望某個 Pod 調度到 taint 節點上,則必須在 Spec 中做出 Toleration 定義,才能調度到該節點,比如現在我們想要將一個 Pod 調度到 master 節點:(taint-demo.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: taint
  labels:
    app: taint
spec:
  selector:
    matchLabels:
      app: taint
  replicas: 2
  template:
    metadata:
      labels:
        app: taint
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - name: http
          containerPort: 80
      tolerations:
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule"

由於 master 節點被標記為了污點節點,所以我們這里要想 Pod 能夠調度到 master 節點去,就需要增加容忍的聲明:

tolerations:
- key: "node-role.kubernetes.io/master"
  operator: "Exists"
  effect: "NoSchedule"

然后創建上面的資源,查看結果:

kubectl get pod -o wide
NAME                     READY   STATUS              RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
busybox                  1/1     Running             0          2d    10.244.2.3   node2    <none>           <none>
taint-5779c44f78-lq9gg   0/1     ContainerCreating   0          27s   <none>       master   <none>           <none>
taint-5779c44f78-m46lm   0/1     ContainerCreating   0          27s   <none>       master   <none>           <none>
test-np                  1/1     Running             0          2d    10.244.1.6   node1    <none>           <none>

我們可以看到兩個 Pod 副本被調度到了 master 節點,這就是容忍的使用方法。

對於 tolerations 屬性的寫法,其中的 key、value、effect 與節點的 Taint 設置需保持一致, 還有以下幾點說明:

  1. 如果 operator 的值是 Exists,則 value 屬性可省略
  2. 如果 operator 的值是 Equal,則表示其 key 與 value 之間的關系是 equal(等於)
  3. 如果不指定 operator 屬性,則默認值為 Equal

另外,還有兩個特殊值:

  1. 空的 key 如果再配合 Exists 就能匹配所有的 key 與 value,也就是能容忍所有節點的所有 Taints
  2. 空的 effect 匹配所有的 effect

最后,如果我們要取消節點的污點標記,可以使用下面的命令:

$ kubectl taint nodes node1 test-
node/node1 untainted

參考資料

https://www.qikqiak.com/k8strain/tenant/#pod_1

https://kubernetes.io/zh/docs/home/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM