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 簽署的才會被許可。
因此,我們可以做的事情是:
- 自建 CA,再讓 Kubernetes 使用此 CA。
- 用此 CA 來簽署憑證,搭配
admin.conf
獲得的 Cluster 資料,組成新的 config 檔。
然而,這樣子的使用者是沒有任何權限的,他不似 Linux,只要使用者的 Login Shell 不是 nologin,能登入就可以有一定權限執行一些程式;Kubernetes 對於沒有額外設定權限的使用者,它不會允許他進行任何操作。
利用 Role-Based-Access-Control(RBAC) 來授權使用者
RBAC,這個大家聽到爛掉的詞彙,基本上的意思就是:
- 創建一個 Role,規定說這個 Role 可以對哪些 Resource 做哪些操作。
- 創建一個 RoleBinding,指定說某個 Namespace 下有着這個 Role 的權限。
以下是一個範例,假設我們剛剛用產生一個 Username
爲 user
的 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
小總結
- RBAC 專注於針對資源的基本權限控制,不會考慮資源存取外的內容,例如使用量、使用者行爲。
- RBAC 是靜態的規則,因此沒辦法基於條件來控制,例如你不能設置 Pods
Labels
來控制使用者對它的控制權限。 - 因此,我們需要更進階的控制系統,而在衆多選擇當中,我選擇了 OPA Gatekeeper 和 Kyverno 來分別嘗試。
Kyverno
前面有稍微提及,我有嘗試過 Gatekeeper 作爲更進階的控制系統方案,但它的上手難度太高了,它是基於 rego 語言來進行 policy 制訂,我暫時沒辦法花那麼多時間學習;反而 Kyverno 的功能也很強大,並且語法簡單,不需要學特別的語言,只需要上官網查詢需要的 function,它的模板引擎就可以做完計算了。
需求
我這邊有兩個小需求:
- 每位使用者只能開不超過 2 個 Deployments;
- 每位使用者開的 Deployment 以及其啓動的 Pods 上面都帶有標籤
owner
,值爲使用者名稱,我希望每位使用者不能去刪除標籤owner
值不爲自己的資源。
初版
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"
上面可以看到,資源項有分:
requests
:要滿足這個條件,才會把 Pod 分配到那個節點。否則會處於 Pending 狀態。limits
:會限制這個條件,不會讓 Pod 超過這個資源使用量。
然後 CPU 的單位是核心,意思是最少要 0.25 顆空閒核心,然後最多可以使用 0.5 顆核心的運算量。
Cluster 資源管理:限制硬碟用量
Container 的 Filesystem 很有趣,分爲兩層:
- ReadOnly Layer:除了使用者額外 Mount 上去的部分,其它預設同 Image 的部分,都是 ReadOnly Layer。
- Writable Layer:當使用者對 ReadOnly Layer 的文件、目錄進行修改、新增操作時,它就會被寫入 Writable Layer。因此我們在 Container 當中進行的操作,都會被存放在此 Layer。
而我們如果要限制 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/,其中:
utils.py
:與 k8s 交互的程式碼。test_1
:測試新增出來的 kube_config 能否正常使用,以及 RBAC 的設定。test_2
:測試 Kyverno Policies。
這個專案是什麼?
這是一個平臺,各個使用者可以 ssh 上來這個特製工作站,連上來之後會進入一個功能受限的 Shell,在上面可以創建、銷毀 Containers,而真正的操作便是讓你進入 Containers,以 Container 中的 root 權限去做,你可以得到一個絕對自由的環境。
由於容器化最著名的一套工具叫做 Docker,因此得名 Docker Shell,簡稱 Dosh。原是交大資工系計中推出的服務,但由於開發者畢業,無人維護,僅上線不到一年就下架。
本人對 Kubernetes 技術一直頗感興趣,於是便希望能重啓這個專案,來磨練本人的 Kubernetes 能力,而本系列教學也會根據本人的開發進展推進;如果中途使用了其它技術,值得一提的也會整理出來分享給大家。
本專案的連接在此:GitHub: Rin0913/Dosh
