Kubernetes 教學 第七篇:Cluster 權限、資源管理篇

帶你用 RBAC 和 Kyverno 來管理 Cluster 權限

大家從第一篇開始,就很仰賴的工具 kubectl,它本質是和 kube-apiserver 溝通,而身份認證資訊,正是我們第六篇提及的 kube_config

而人難以避免團隊合作,在多人工作的時候,不可能讓衆人共用同一份 kube_config,這就相當於是一間大公司每個人都使用同一隻 Email 一樣混亂。

因此我們勢必要創建更多 kube_config 檔,再分別給他們設置權限。

Kubernetes 的認證機制

Kubernetes 都只能靠 kube_config 來分配權限運作嗎?

其實不然,Kubernetes 和 Unix-Like Systems 不同。在 Kubernetes 中,有一種特別的機制,叫做 ServiceAccount,可以把它想象成是發給應用程式的 API Key。

它的效果其實和 kube_config 類似,但 ServiceAccount 是 Kubernetes 專門爲 Cluster 內運行的 Pods 提供的身份認證機制,包括核心服務也是透過 ServiceAccount。

而 kube_config 通常是給外部使用的,因爲它額外儲存了 Cluster 資訊,包括 API Server 位置、CA 資訊、身份認證資訊等。

因此它們的劃分並不是給程式與給人類使用,而是給內部與給外部使用。

如何創建有效的 kube_config 檔?

其實相當簡單,我們只需要稍微瞭解一下整個 kube_config 的結構即可。

他本質上雖然是 YAML 檔,但我這邊把它轉換成 JSON,可能會比較直觀:

{
	"apiVersion": "v1",
	"kind": "Config",
	"clusters": [
		{
			"cluster": {
				"certificate-authority-data": "{cluster CA 資訊}",
				"server": "{cluster kube-apiserver 位置}",
			},
			"name": "{cluster 名稱}"
		}
	],
	"contexts": [
		{
			"context": {
				"cluster": "{cluster 名稱}",
				"user": username,
			},
			"name": username + "-context"
		}
	],
	"current-context": username + "-context",
	"users": [
		{
			"name": username,
			"user": {
				"client-certificate-data": cert_pem,
				"client-key-data": private_key
			}
		}
	]
}

各位可以發現,基本上那些 cluster 資訊,都是共用的,基本上只要你知道其中一個 kube_config(其實就是 admin.conf 啦!),你可以複製那些部分,再產生 user 資訊,生成一個新的 kube_config。

你可能會想:如果那麼簡單,那麼每個得到 config 檔的人,都可以自己再 issue 新的 config 檔給別人啊?

所以一定是有限制的,限制就在 user 欄位中的 client-certificate-data,那個憑證是必須要經過 Kubernetes Cluster 信任的 Client-CA 簽署的才會被許可。

因此,我們可以做的事情是:

  1. 自建 CA,再讓 Kubernetes 使用此 CA。
  2. 用此 CA 來簽署憑證,搭配 admin.conf 獲得的 Cluster 資料,組成新的 config 檔。

然而,這樣子的使用者是沒有任何權限的,他不似 Linux,只要使用者的 Login Shell 不是 nologin,能登入就可以有一定權限執行一些程式;Kubernetes 對於沒有額外設定權限的使用者,它不會允許他進行任何操作。

利用 Role-Based-Access-Control(RBAC) 來授權使用者

RBAC,這個大家聽到爛掉的詞彙,基本上的意思就是:

  1. 創建一個 Role,規定說這個 Role 可以對哪些 Resource 做哪些操作。
  2. 創建一個 RoleBinding,指定說某個 Namespace 下有着這個 Role 的權限。

以下是一個範例,假設我們剛剛用產生一個 Usernameuser 的 kube_config 檔,那我們可以定義以下 RBAC 資源,來授權此使用者在 namespace default 中對於所有 Pods 的 ReadOnly 權限:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-ro-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: "sss"
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-ro-role
  apiGroup: rbac.authorization.k8s.io

這就是 RBAC 的最簡單範例。

除此之外,RBAC 也可以規定使用者對 Cluster 的控制權,對應到的資源是 ClusterRole 和 ClusterRoleBinding。

例如下面的例子,我們授權使用者 user 查看所有節點的權限:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-nodes-cluster-wide
subjects:
- kind: User
  name: "user"
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: node-reader
  apiGroup: rbac.authorization.k8s.io

小總結

Kyverno

前面有稍微提及,我有嘗試過 Gatekeeper 作爲更進階的控制系統方案,但它的上手難度太高了,它是基於 rego 語言來進行 policy 制訂,我暫時沒辦法花那麼多時間學習;反而 Kyverno 的功能也很強大,並且語法簡單,不需要學特別的語言,只需要上官網查詢需要的 function,它的模板引擎就可以做完計算了。

需求

我這邊有兩個小需求:

初版

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: limit-deployment-policy
spec:
  background: false
  validationFailureAction: Enforce
  rules:
  - name: limit-deployments-per-user
    match:
      resources:
        kinds:
        - Deployment
    preconditions:
      all:
      - key: "{{ request.userInfo.username }}"
        operator: NotEquals
        value: "Kubernetes-admin"
      - key: "{{ request.operation }}"
        operator: Equals
        value: CREATE
    context:
    - name: deploymentCount
      apiCall:
        urlPath: "/apis/apps/v1/namespaces/{{ request.namespace }}/deployments"
        jmesPath: "items[?metadata.labels.owner=='{{ request.userInfo.username }}'] | length(@)"
    validate:
      message: "User '{{ request.userInfo.username }}' cannot create more than 2 deployments with label 'owner: {{ request.userInfo.username }}' in this namespace."
      deny:
        conditions:
        - key: "{{ deploymentCount }}"
          operator: GreaterThanOrEquals
          value: 2

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-unauthorized-access
spec:
  background: false
  validationFailureAction: Enforce
  rules:
  - name: restrict-deployment-operations
    match:
      resources:
        kinds:
        - Deployment
        - Pod
    preconditions:
      all:
      - key: "{{ request.userInfo.username }}"
        operator: NotEquals
        value: "kubernetes-admin"
      - key: "{{ request.operation }}"
        operator: In
        value:
          - CONNECT
          - DELETE
    validate:
      message: "User '{{ request.userInfo.username }}' is not allowed to {{ request.operation }} the deployment unless they are the owner."
      deny:
        conditions:
        - key: "{{ request.object.metadata.labels.owner }}"
          operator: NotEquals
          value: "{{ request.userInfo.username }}"

這個邏輯就很清晰,基本上不需要什麼背景只是也可以讀懂。

直到我在測完程式之後查看了當前 namespace 還剩下哪些 Pods,我發現所有的 Pods 都沒有隨着 Deployment 被回收。

正常當 Deployment 被刪除的時候,Kubernetes 會把其餘的 Pods 也刪除。

而我這個 Policy,允許使用者刪除 Deployment,也允許使用者刪除 Pods,更允許管理員刪除那些資源,唯獨少了 Kubernetes 系統。

因此我們要授權 Kubernetes 系統,讓它不受這些規則限制;其實不回收 Pods,只是慢性死亡而已。另一條規則更可怕,每個使用者只能創 2 個 Deployment:

$ kubectl get deployment -A
NAMESPACE          NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
calico-apiserver   calico-apiserver                2/2     2            2           28h
calico-system      calico-kube-controllers         1/1     1            1           29h
calico-system      calico-typha                    1/1     1            1           29h
kube-system        coredns                         2/2     2            2           29h
kyverno            kyverno-admission-controller    1/1     1            1           4h33m
kyverno            kyverno-background-controller   1/1     1            1           4h33m
kyverno            kyverno-cleanup-controller      1/1     1            1           4h33m
kyverno            kyverno-reports-controller      1/1     1            1           4h33m
tigera-operator    tigera-operator                 1/1     1            1           29h

在這條規則套用之後,kyverno 甚至無法正常啓動,會引起非預期行爲,而 Calico 也會無法正常啓動,這都是可能導致整座 Cluster 損壞的。

終版

precondition 加上以下內容即可:

- key: "{{ regex_match('^system:serviceaccount:.*', '{{request.userInfo.username}}') }}"
  operator: NotEquals
  value: true

Cluster 資源管理:限制 CPU、記憶體資源

apiVersion: v1
kind: Pod
metadata:
  name: resource-limits-demo
spec:
  containers:
  - name: demo-container
    image: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "0.25"
      limits:
        memory: "128Mi"
        cpu: "0.5"

上面可以看到,資源項有分:

然後 CPU 的單位是核心,意思是最少要 0.25 顆空閒核心,然後最多可以使用 0.5 顆核心的運算量。

Cluster 資源管理:限制硬碟用量

Container 的 Filesystem 很有趣,分爲兩層:

而我們如果要限制 Writable Layer 的內容,我們可以限制 ephemeral-storage 的資源用量:

apiVersion: v1
kind: Pod
metadata:
  name: ephemeral-storage-limits-demo
spec:
  containers:
  - name: demo-container
    image: nginx
    resources:
      requests:
        ephemeral-storage: "1Gi"
      limits:
        ephemeral-storage: "2Gi"

Appendix

今天這篇文章內容雖然少,但是我確花了很多時間研究,研究 OPA、研究爲何 Pods 不被回收,以及撰寫專案。

用 Python 產生 kube_config 檔

自動化流程我已經在我的程式中寫好了,目前放在 GitHub Dosh: /kube_manager/user_manager.py

用 Python 控制 Kubernetes Cluster 進行簡單操作

目前我是拿來測試 RBAC 和 Kyverno Policies,目前放在 GitHub Dosh: /tests/,其中:

這個專案是什麼?

這是一個平臺,各個使用者可以 ssh 上來這個特製工作站,連上來之後會進入一個功能受限的 Shell,在上面可以創建、銷毀 Containers,而真正的操作便是讓你進入 Containers,以 Container 中的 root 權限去做,你可以得到一個絕對自由的環境。

由於容器化最著名的一套工具叫做 Docker,因此得名 Docker Shell,簡稱 Dosh。原是交大資工系計中推出的服務,但由於開發者畢業,無人維護,僅上線不到一年就下架。

本人對 Kubernetes 技術一直頗感興趣,於是便希望能重啓這個專案,來磨練本人的 Kubernetes 能力,而本系列教學也會根據本人的開發進展推進;如果中途使用了其它技術,值得一提的也會整理出來分享給大家。

本專案的連接在此:GitHub: Rin0913/Dosh