~xdavidwu/cskloudv3-thesis

cskloudv3-thesis/Sections/4.Architecture.tex -rw-r--r-- 43.1 KiB
b4127bf2Pinghao Wu appendix: survey: describe design 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
\chapter{設計方法與實作}
\label{ch:architecture}

本章節敘述本研究吸收 CScloud 的想法,重新設計出的 CSKloud v3 架構,其實作方法,以及網頁界面的功能性。

\section{設計需求}

為達成本研究目標設想的 Kubernetes-as-a-Service,我們將 CScloud 的想法衍生改良,保留其共用叢集、基於 Namespace 的多租戶設計以及透過串接既有單一登入服務簡化身份驗證實作的特色,重新設計架構並著重於改善下列兩點:

\begin{onehalfspacing}
\begin{itemize}
    \item \textbf{安全性}:改善對於系統元件的切分以及實作方法
    \item \textbf{通用性}:跳脫 PaaS 的設計框架、充分發揮 Kubernetes 的潛力
\end{itemize}
\end{onehalfspacing}

由於架構變更,使得平台上自行開發的元件必須重新實做。以此為契機,我們將採用可上線運行的高標準撰寫,並且著重其完備性,而非僅只進行機制的概念驗證。在實做過程中,我們也將反覆審視此架構下對於易用性與使用者體驗的影響,並且透過擴展網頁界面功能以其他方式彌補。

\subsection{安全性}

二代架構設計將需要高權限、低使用量的使用者開通元件以及低權限、高使用量的使用者互動界面實做為一個單體式網頁服務,不同權限層級的操作間欠缺隔離手段。另外在實作上也欠缺資訊安全意識,其根據使用者輸入在伺服器端呼叫 Helm CLI 的方式也容易產生漏洞。完全仰賴自行定義的 admission control 邏輯進行叢集本身的加固,由於 Kubernetes API 的複雜性,也容易由於未充分理解而有所缺失。

以系計中在運作方面上的特色而言,其主力開發人員來自於碩士班研究生以及部份學士班的打工,更迭極為頻繁快速,且提供的服務眾多,難以分配專屬人力,一人經常身兼多職維護多個產品,歷史上僅有少數人能夠深入了解既有服務實作細節。面對這樣的場景,以及考量到本平台的重要性,由初步規劃即必須嚴謹考量資訊安全,從架構起手進行防禦,以免在日後他人迭代維護時產生紕漏。

\subsection{通用性}

二代並沒有達成一代對於開放直接使用 Kubernetes API 的想法,欠缺通用性,在網頁控制界面的實作亦是以新的 PaaS 平台角度出發,Kubernetes 僅只屬於達成平台的手段,這樣的實作脈絡容易侷限於自身的使用經驗,而忽略其他潛在的需求。同樣由於系計中人力更迭迅速的特性,較不利於累積經驗,這個方式難以產出功能全面的產品。Kubernetes 本身經過時間的洗禮,已發展為高度彈性且經過廣泛採用歷練的服務部屬框架,應由直接利用 Kubernetes 的角度設計,即可低成本的達成高通用性。

\section{CSKloud v3 架構設計}

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/CSKloudV3.png}
    \caption{CSKloud v3 架構簡易示意圖}
\end{figure}

針對這些需求,我們提出新的架構,稱為 CSKloud v3(以下簡寫 CSKloud)。我們將權限開通元件獨立,改採類似於一代的方式,透過自訂 Kubernetes resource 類型撰寫。一代的開通流程仰賴於管理者手動觸發,CSKloud 則是採取全自動流程。透過 Kubernetes 對於身份驗證的設計,OIDC 驗證前不須針對新使用者進行設定,即可進行身份驗證,我們設計賦予所有使用者創立此自訂 resource 的權限,在網頁界面首次登入時自動創立,作為開通流程的觸發機制。同時權限開通元件本身並不會從外部來源查詢使用者身份資訊,而是透過創立者的驗證資訊判斷。

對於叢集本身的加固,二代採用 webhooks 的形式自行實做 admission control,而 CSKloud 改以 Kubernetes 本身內建的 admission controllers 為主,有利於確保相關防禦的完備性,並且隨著 Kubernetes 的更迭,內建的 admission controllers 也會有對應的邏輯更新,大幅減少後續在維護上的負擔,同時其行為也更為通用、更容易對使用者描述,使用者得以對平台所提供的功能有更明確的認知。整個系統難免還是有內建 admission controllers 無法涵蓋的其餘需求,例如域名使用權的管控,這部份 CSKloud 以 CEL 重新實做取代 webhooks,以達到更好的效能及更低的運作成本。

CSKloud 達成一代願景中的開放使用者直接存取 Kubernetes API,使用者可以透過標準 Kubernetes 客戶端(如 kubectl\footnote{kubectl: Kubernetes 官方提供的 CLI (指令列界面)型客戶端。})存取叢集,但系統仍然提供一個網頁界面。除了用以觸發權限開通流程,這個網頁界面同時具備作為 Kubernetes 的通用型界面的功能,由 Kubernetes 的角度出發設計,用意主要在輔助與簡化使用,以及提供 kubectl 所缺乏的配額視覺化等在 CLI 場景難以實做的功能。

此網頁界面預計不論是具備 Kubernetes 經驗的老手,或是剛入門的新手都能從中獲益。我們將透過提供編輯器與 resource 種類定義整合,提供更好的 resources 撰寫與瀏覽體驗,彌補此場域在整合式開發環境的希缺,對於探索學習 Kubernetes 功能或者日常使用皆有所幫助。另外我們以 Helm 達成完整的應用程式安裝管理功能,提供高層次、步驟式的圖形化操作界面,有利於新手快速的達成安裝常見軟體的基本任務。

在實作方面,由於 CSKloud 開放存取 Kubernetes API,可以將其直接作為網頁後端利用,因此採用 SPA 架構重新實做網頁界面,免除另外維護網頁後端伺服器所產生的維運成本及資安顧慮,同時也利用 Kubernetes API 對資料更新的推送機制,搭配 SPA 動態控制內容的特性,達成即時的資訊更新,大幅地增強使用者體驗。另外將對於 Helm 的支援透過 WebAssembly 改由直接在瀏覽器端運行,並且將其功能完善,開放更多的 Helm Charts 提供安裝。

\section{叢集加固實作}

Kubernetes 功能廣泛,有許多功能牽涉較大的作業系統權限,間接具備控制整座叢集的能力。共有的平台有被部屬不受信任的工作負載之可能,使用 admission control 限制功能是必要的。對此我們以引入 Kubernetes 內建的 admission controllers 為主,輔以 CEL 進一步實做缺乏的邏輯。我們主要引入下列內建的 admission controllers:

\begin{onehalfspacing}
\begin{itemize}
    \item \verb|PodSecurity|
    \item \verb|PodTolerationRestriction|
    \item \verb|AlwaysPullImages|
\end{itemize}
\end{onehalfspacing}

\subsection{PodSecurity}
\label{sec:PodSecurity}

容器一般被視為一個與宿主端 (host) 隔離的環境,但由於容器具有自成一體 (self-contained) 的特性,能夠大幅地簡化部屬管理,在 Kubernetes 的場合更可以統一管理叢集內部各節點的容器,因此容器也常被用來部屬與宿主端有關的底層基礎設施。此場景需要局部地關閉容器的隔離功能,來達成如管理宿主端網路環境等操作。對此,Kubernetes 在 Pod 的 \verb|spec| 欄位設計多個開關,提供解除部份隔離機制的途徑。

即使並未關閉任何預設的隔離機制,對於一般的工作負載,我們也應遵循最小權限原則,例如以非 root 的身份執行等。針對容器內的執行身份,在 Pod 內也可以進行設定。另外,Kubernetes 除了傳統容器隔離技術,也支援進一步設定額外的安全與 mandatory access control (MAC)\footnote{MAC: 針對特定行程的操作與其目標(如讀寫特定檔案、使用特定網路資源)進行限制的機制。} 機制,例如:seccomp、SELinux、AppArmor 等。

面對如此多元的安全性相關設定,以及其產生的無數組合,我們需要一個標準評判 Pod 的安全性。Kubernetes 提出了 Pod Security Standards,並且將可能的設定分為三個政策層級:privileged、baseline 與 restricted。Privileged 可以放寬隔離機制,baseline 包含未特別指定任何設定的場景,restricted 則是進一步要求提升安全性設定。對於執行這些政策檢查,Kubernetes 提供了 \verb|PodSecurity| admission controller。

大多數政策類型的 admission controllers,包含 \verb|PodSecurity|,皆能夠以 Namespace 為單位設定政策,增加管理上的彈性。為了確保平台安全性,我們針對使用者的 Namespaces 實施 restricted 政策,再另外個案處理平台基礎建設所使用的 Namespaces。

\subsection{PodTolerationRestriction}
\label{sec:PodTolerationRestriction}

在典型的 Kubernetes 叢集規劃下,節點分為兩大類:control plane 與 worker nodes。Control plane 負責運行叢集本身的元件,例如 kube-apiserver 與各式 controllers 等;而 worker nodes 負責執行一般的工作負載。Control plane 的元件通常以容器的形式部屬,而其節點也會納入叢集本身的管轄下,然而我們不希望 control plane 節點執行一般的工作負載,避免影響到叢集控制邏輯的運行。Kubernetes 提供 taints 機制,用來標示 Node 的屬性,以及屬性應帶來的效果。以這個場景為例,我們標示此 Node 為 control plane 並且不允許納入排程。此外,taints 機制也用來自動標記異常的節點,例如節點資源即將耗盡,並且藉此調整排程機制,優先使用其他節點。

與 taints 相對,Pod 可以設定 tolerations 來抑制 taints 的效果。例如在擴充 Kubernetes API 時,我們需要部屬實做自訂 resource 種類邏輯的 controller,這個 controller 屬於叢集控制邏輯的一部分,我們會希望規劃在 control plane 運行。部屬此 controller 時,我們便會設定與前述 taints 相對的 tolerations 解除限制,並且搭配其他排程設定強制排程至 control plane。

為了避免一般使用者濫用這個機制,我們採用 \verb|PodTolerationRestriction| 來限制使用者能填寫的 tolerations。\verb|PodTolerationRestriction| 可以針對 Namespace 設定其中 Pods 可使用的 tolerations 白名單,我們限制為只允許系統內部會自動填入的項目。

\subsection{AlwaysPullImages}

Kubernetes 中容器 images 的下載策略(\verb|imagePullPolicy| 欄位)有三種:\verb|Always|、\verb|IfNotPresent| 與 \verb|Never|。\verb|Always| 在啟動容器之前總是會檢查容器的 image 的更新,若有更新即重新下載;\verb|IfNotPresent| 是指當節點上尚未有該 image 時才會下載;\verb|Never| 則是不主動下載,仰賴節點上的存放狀況。在容器結束執行後,Kubernetes 並不會主動刪除 image 資料,而是只有在儲存空間有壓力時才會刪除,藉此減少下載的負擔。

在容器生態下,images 主要可分為公開存取以及私人的兩種。下載公開存取的 images 不須與儲存庫進行身份驗證,而私人的 images 則是需要。由於 \verb|IfNotPresent| 與 \verb|Never| 在節點已經存有指定 image 時,不會進行任何更新檢查,因此也不需進行身份驗證,如此在共用叢集的狀況下,便可能被用來存取他人遺留在節點上的私人 images。對此,我們採用 \verb|AlwaysPullImages| 將下載策略一律複寫為 \verb|Always|。

\subsection{CEL}
\label{sec:CEL}

對於防止使用者在 control plane 上運行容器,\ref{sec:PodTolerationRestriction} \verb|PodTolerationRestriction| 仍然不足。預設在 control plane 上的 taint 只有防止排程,使用者實際上可以繞過排程直接指定節點。一個解決方法是將此 taint 改為防止執行而非單純防止排程,但由於這個 taint 來自於 Kubernetes 官方安裝工具的預設行為,實際上已被廣泛利用,修改容易造成管理上的負擔,若欠缺注意更有可能造成部屬叢集元件時意外安排到 worker nodes 上,間接影響叢集服務品質。於是我們採取另外撰寫邏輯,防止使用者繞過排程。這同時也有附加的優勢:使用者提交的工作負載一律會經過平台的排程機制,使資源的充分、平均利用更有保障。

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/code-pod-policy.png}
    \caption{以 CEL 防止使用者繞過排程機制}
    \label{fig:code-pod-policy}
\end{figure}

以 CEL 實做 validating admission control 邏輯需要由 ValidatingAdmissionPolicy resource 先行定義,再以 ValidatingAdmissionPolicyBinding resource 將之啟用。圖 \ref{fig:code-pod-policy} 即為實做防止繞過排程的 ValidatingAdmissionPolicy。首先我們只對開放給使用者的 Namespace 進行限制,透過 \ref{sec:provisioner} 權限開通的設計,以具有 \verb|u-| 前綴來判斷。再來由於排程機制對 Pod 指定節點與使用者直接指定修改的欄位相同,我們需要避免限制到叢集系統本身。這點由於我們的權限規劃,使用者沒有列舉 Nodes 的權限,我們以此分別使用者與系統。最後我們檢查欄位狀況,確保在更新時值保持不變,或者在創立時未被指定。

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/ingress.png}
    \caption{以 Kubernetes Ingress 基於域名的 HTTP 服務分流規劃示意圖}
\end{figure}

另一個需要自訂邏輯的場景是域名使用權的管控。針對用來設定反向代理的 Ingresses,我們一樣先判斷操作的 Namespace 是否為使用者面向的,再進一步進行限制。需要限制的有:禁止使用 \verb|defaultBackend| 欄位(全域、不符合任何規則時的 fallback route),以及所有反向代理規則中的 \verb|host| 必須要符合域名白名單。白名單參考其餘政策相關內建 admission controllers 的設計,採用在 Namespace 上標記作為設定手段,後續再藉由 \ref{sec:provisioner} 權限開通設定此白名單。

\section{權限開通實作}
\label{sec:provisioner}

使用者權限開通的主要流程為:建立一個 Namespace,增加 admission controllers 政策相關標記,對其設定資源配額,最終調整 RBAC 授予使用者此 Namespace 下的權限。為了將其自動化,我們規劃為拓展 Kubernetes API,使用自訂 resource 種類進行實做,此種類稱為 User。使用者登入網頁界面時會自動嘗試創立代表自身的 User,若已存在代表已開通完成不須另外動作,而面對新創立的 User,我們撰寫的 controller 會實行上述的開通流程。

\begin{figure}[htb]
    \centering
    \includegraphics[width=0.5\textwidth]{assets/provisioner-user-spec.png}
    \caption{權限開通自訂 resource 種類 User spec 範例}
\end{figure}

每個使用者會創立一個與其系計中帳號名稱相同的 User,其中 \verb|spec| 的 \verb|profile| 欄位帶有身份組資訊,影響資源配額的大小,由網頁界面自動判斷填入。對於創立代表自身 User 所需的權限,我們透過 RBAC 允許所有使用者創立 User,再透過 admission control 將其限制名稱必須與身份相同。兩階段的設計有助於簡化 RBAC 實作,使 RBAC 實作上不須枚舉使用者名稱一一事先對應,同時也大幅降低叢集上相關設定的 resources 個數,有助於減少管理上的負擔。對於 \verb|profile| 欄位的真實性,我們一樣透過 admission control 與身份資訊比對證實。

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/provisioning.png}
    \caption{權限自動開通整體流程圖}
\end{figure}\verb|profile| 欄位而言,還有另一個可行的方法,即是以 mutating admission control 由叢集端判斷寫入,而不仰賴網頁界面邏輯。這個方式目前有個缺點:admission control 邏輯我們希望透過 CEL 實做,而以 CEL 實做 mutating 相關機制尚未開發完成。另外,透過網頁界面預填也使得 admission control 層只須實做 validating,相對於 mutating,validating 因不能修改 resource 內容,較為單純且有平行化處理的空間,在效能與管理上皆有些許優勢。

對於管理需求,我們在 admission control 實作保留了彈性,若操作者為管理者即不加以限制。管理者可以自行創立代表他人的 User,為其修改身份組,提供人工處理政策上例外的空間。另外,由 User 所產生的每個 resource 皆會採用 Kubernetes 的 \verb|ownerReferences| 機制。\verb|ownerReferences| 表現了一個 resource 被一個 owner resource 所管轄,當 owner resource 被刪除時,Kubernetes 的回收機制也會一併刪除此 resource。在我們的場景,由於使用者的資料皆為於個人專屬的 Namespace 底下,而 User 又為此 Namespace 的 owner resource,於是當使用者行為極端異常,管理者需要對其制裁時,只須刪除其 User 便可徹底清除此使用者的所作所為。

在實作方面,User 種類的定義以及 controller 邏輯採用 Kubebuilder\cite{kubebuilder} 框架撰寫。Kubebuilder 採用 Go 程式語言與 \verb|sig.k8s.io/controller-runtime| 函式庫開發,由 Go 語言的資料結構定義輔以註解補充,搭配程式碼生成技術實現 Kubernetes resource 種類的定義,並且提供實做 controller 時所需的監控 resources 變更等基本邏輯。另外,\verb|sig.k8s.io/controller-runtime| 亦具備 \verb|envtest| 測試框架,實做 kube-apiserver 等元件的自動運行,在不需要架設完整叢集的前提下,提供 controller 較為完整的測試環境。

\subsection{Namespace}

為了避免命名與叢集上既有、負責叢集本身運行的 Namespaces 衝突,我們將使用者的 Namespace 命名為 \verb|u-| 前綴加上系計中帳號名稱(即相關 User resource 的名稱)。

在創立 Namespace 時,根據個別的 admission controllers 設計,需要在 \verb|labels| 或 \verb|annotations| 上加註政策。\verb|labels| 與 \verb|annotations| 皆是 Kubernetes resources 共通的 key-value 標記欄位,常用於輔助管理辨識,或是以較高的修改彈性提供次要的功能,其中 \verb|labels| 在透過 Kubernetes API 列舉 resources 時可作為條件過濾,因此也常用於 resources 間的關聯建立,但能夠儲存的內容也具有相關限制。需要設定政策的有 \ref{sec:PodSecurity} \verb|PodSecurity|、\ref{sec:PodTolerationRestriction} \verb|PodTolerationRestriction| 與 \ref{sec:CEL} 以 CEL 實做的域名使用權管控。為了方便管理者透過過濾進行清查,域名管控我們設計以 \verb|labels| 設定白名單,一個域名一條 label。平台預設開放如帳號名稱加上 \verb|.u.cloud.cs.nycu.edu.tw| 後綴\footnote{平台有預先配置 *.u.cloud.cs.nycu.edu.tw 的 wildcard 域名紀錄與 TLS 憑證。}的域名供使用者使用。

Namespace 上 \verb|u-| 以及平台提供域名中 \verb|u.| 的命名切分設計使得平台可以進一步擴充,除了以使用者為租戶單位以外,未來可以增加其他種類的命名切分,提供以如實驗室或課程為租戶的服務。

\subsection{ResourceQuota}

ResourceQuota 是 Kubernetes 的一個 resource 種類,用來實做單一 Namespace 下的資源配額,其運作邏輯大致分兩大塊:admission controller 與一般的 controller。ResourceQuota 的 admission controller 屬於 validating,負責在創立或更新 resource 時推算其資源用量的變化值,並且拒絕用量將超出配額的請求。一般的 controller 端則是以更為全面的觀點,監控整體 resources 的變更,並且統計資源用量總額。

ResourceQuota 所能管控的資源大致有兩種:運算資源以及 resources 的數量。運算資源具有量值,例如部屬容器時設定的 CPU 或者記憶體用量限制,以及 PersistentVolumeClaim (PVC)\footnote{PVC: Resource 種類,空間請求,需自動或手動與 PersistentVolume(PV,空間分配實體)耦合使用。} 中的永久儲存空間請求。Resources 的數量則是滿足特定條件的 resources 的個數,例如正在運行的 Pods。

除了使用 ResourceQuota 來限制運算資源用量,我們運用個數特定條件邏輯來限制 resource 內容,其中最為代表性的是 Service。Service 作為具備負載均衡的網路存取點,根據其 \verb|spec| 的 \verb|type| 欄位,主要又分為 \verb|ClusterIP|、\verb|NodePort|、\verb|LoadBalancer| 三大型別。\verb|ClusterIP| 為最基本、最常見的叢集內部存取點,\verb|NodePort| 額外在每個節點宿主端的網路環境提供存取點,\verb|LoadBalancer| 則是進一步串接叢集外部的負載均衡器。在我們的叢集規劃下,節點架設於內部網路,使用者無法直接存取節點,\verb|NodePort| 對使用者來說並無用途,同時 \verb|NodePort| 使用節點的有限網路資源,不適合對使用者開放。\verb|LoadBalancer| 則是需佔用外部負載均衡器的 IP 位址分配池,其大小非常有限,也不適合開放給使用者。ResourceQuota 正有根據型別分開統計 Service 數量的邏輯,我們利用此特性,設置對應配額為零,防止使用者使用 \verb|NodePort| 與 \verb|LoadBalancer|。

除了運算資源,以及上述針對 resource 內容的管控,我們也對所有使用者能夠創立的 resource 種類進行總量限制,避免濫用。對其限額設計,我們仍盡量保持讓使用者可以在運算資源限制內合理地自由運用。

\subsection{LimitRange}

在使用 ResourceQuota 限制總運算資源用量的情境,所有 Pods 必須設定容器的相關資源限制。這點十分容易影響使用者體驗,資源限制在 API 上並非必填,以 Kubernetes 通常經由高層次 resources (如 Deployment)去控管 Pods 的方式,如果在高層次 resources 中的 Pod 模板並未填寫資源限制,創立當下並不受限制,而是在相關 controller 創立 Pod 時才發生錯誤,對使用者來說較難除錯。另外,部份常用代為呼叫 API 的手段如 \verb|kubectl create deployment| 也沒有途徑可以填寫限制。

面對這個場景,我們使用 LimitRange resource 種類。LimitRange 透過 \verb|LimitRanger| admission controller 限制使用者能夠填寫的資源限制大小範圍,同時也能夠在未填寫的狀況下套用預設值協助補足。LimitRange 如同 ResourceQuota,以單一 Namespace 為作用範圍。我們使用 LimitRange 實做資源限制的欄位預設值,以提昇使用者體驗。

\subsection{測試}

由於權限開通過程較為複雜,並且牽涉多個 admission control 安全性相關設定,因此這方面的測試顯得相當重要。\verb|envtest| 提供的環境涵蓋 kube-apiserver,足以讓我們測試 admission control 的行為,我們用其實做整合測試,以整體的行為作為測試目標,例如開通後,使用者應能在其 Namespace 下創立 Pods,但不能帶有關閉隔離措施的設定等。同樣測試流程我們也進一步重複利用,將 \verb|envtest| 抽離,提供可以直接對真實叢集測試的變種,適用於完整部屬後的驗證場合。

相對於一般用途的 Kubernetes 叢集,CSKloud 也需要額外進行如啟用特定 admission controller 等設定。高層次的行為測試雖然可以直觀地撰寫,對於失敗的除錯卻需要對系統有相當深入的認知。對此,我們也撰寫叢集設定驗證測試,確保相關功能有被啟用,且其設定有經過儲存,以底層的角度防止安裝上的人為疏失。

\section{網頁界面實作}

進一步對網頁界面的需求分析,可以將網頁界面切分為兩大塊:支援 Helm 的通用 Kubernetes 網頁型客戶端,以及 CSKloud 特定的部份,包含權限開通元件的整合以及 CSKloud 平台面向使用者的文件等。其中 Kubernetes 客戶端很容易的就可以利用於其他場景,我們採取 open-core\cite{hall2016open} 的策略,將其開放原始碼,以 MIT 授權條款\footnote{MIT license: 一個在網頁技術場域廣受歡迎的寬鬆型開放原始碼條款。}釋出,命名為 Sparkles\footnote{Sparkles 釋出於 \url{https://github.com/xdavidwu/sparkles}},回饋於社會,使得非平台使用者也能受益,同時也利用開放原始碼社群的力量來茁壯平台的發展。

\begin{figure}[htb]
    \centering
    \includegraphics[scale=1.1]{assets/sparkles-and-web.png}
    \caption{CSKloud v3 網頁界面與 Sparkles 關係示意圖}
\end{figure}

網頁界面採用 Vue.js\cite{vue} 框架,以 TypeScript 語言\footnote{TypeScript: 一個轉譯為 JavaScript 的語言,比起 JavaScript 多了型別標示以利開發。}撰寫,使用 Vuetify 做為元件庫。Vuetify 的元件設計採用基於 Google 提出的 Material Design 設計語彙,同時 Kubernetes 也是由 Google 發跡的,在些許程度上,選擇 Vuetify 帶給了 Kubernetes 使用者更契合的親切感。由於界面希望帶有在容器內執行指令的功能,所需的終端機傳統上採用黑底白字配色,為避免其元件顯得突兀,整體界面亦採暗色系設計。在流行上,近期資訊界生產力相關的網頁服務也有逐漸導入暗色系設計的趨勢。

\begin{figure}[htb]
    \centering
    \includegraphics[scale=1.11]{assets/kubernetes-watch.png}
    \caption{Kubernetes watch 機制示意圖}
\end{figure}

對於 Kubernetes API 的操作,我們採用由 Kubernetes 提供的 OpenAPI\footnote{OpenAPI: 一種用於描述 RESTful API 行為的框架。}\cite{openapi} 定義檔,透過 openapi-generator\footnote{openapi-generator: 由 OpenAPI 定義檔產生適用於各種不同語言的函式庫的工具。} 自動產生的函式庫輔助開發。在網頁的各個功能,我們皆有採用 Kubernetes 的 watch 機制達到資料的即時更新。Kubernetes 的 watch 是一個特有的 HTTP API 呼叫方法,透過維持長期不中斷的 HTTP response,藉由 HTTP 的 chunked encoding\footnote{Chunked encoding: 一個將內容分塊傳送的 HTTP 編碼機制,常用於串流等持續產生資料的場合\cite{swaminathan2011low}} 編碼,即時的將一個 resources 列表的內容更新以新增、移除、修改 resource 等事件傳出,並且每個事件帶有一個版本號,如果 HTTP 通訊意外中止,客戶端可以透過這個版本號代表目前對 resources 列表的認知,向 Kubernetes 重發一個 watch 呼叫獲取由該版本後的變更。這個機制也廣泛運用在 Kubernetes 元件內部的溝通。

\begin{figure}[hbt]
    \centering
    \includegraphics[scale=0.15]{assets/web-navigation.png}
    \caption{CSKloud v3 網頁界面導覽設計}
\end{figure}

網頁界面設計以單一 Kubernetes Namespace 為主軸,符合 CSKloud 對使用者提供獨立 Namespace 的場景。網頁的導覽透過頁面左方的導覽列達成,由此存取各個子功能,並且顯示目前所操作的 Namespace。在 Sparkles 上,Namespace 可由可搜尋的下拉式選單選擇,在 CSKloud 上,則是固定為使用者對應的 Namespace 而停用選單,保留在界面上是為了往後開發出使用者以外的租戶單位時,切換對應的 Namespace 使用。

\subsection{Pods 頁面與 Web Terminal}
\label{sec:web-terminal}

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/web-pods.png}
    \caption{CSKloud v3 網頁界面 Pods 頁面}
\end{figure}

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/kubectl-pods.png}
    \caption{kubectl 表格式列舉 Pods}
    \label{fig:kubectl-get-pods}
\end{figure}

Pods 作為 Kubernetes 中的重要角色之一,表示叢集上運行的容器組合,我們特別為其提供了特製一覽頁面,也展示了網頁在資料呈現與互動上的顯著優於 kubectl 的 CLI 界面。網頁界面可以很容易的繪製較複雜的表格結構,也可以透過顏色幫助使用者快速的分辨異同。同時也能夠在資料上進一步的提供互動,例如點擊容器的 image 連結會導向至相關的資訊頁面,也可以針對項目顯示功能按鈕。

在容器的功能按鈕中,有瀏覽容器的運行紀錄 (log) 以及在容器中動態執行指令的功能。執行指令需要有雙向的即時資料流來傳送輸入與輸出,在這方面 Kubernetes 提供兩個解決方案:SPDY 與 WebSocket。SPDY 是 Google 早期提出的協定,用來改善 HTTP 1.x 的效能,如今在 HTTP 的場域已經被為廣泛的 HTTP/2 取代,Kubernetes 則是持續利用其雙向即時傳輸的功能。WebSocket\cite{rfc6455} 是特別設計在網頁場景提供雙向即時資料流的協定,並且連線由 HTTP 請求觸發進行進一步的協定轉移,對於 HTTP 既有的代理生態有一定的相容性而被廣泛採納。Kubernetes 原先以 SPDY 為主,因長期發展考量逐步轉換為 WebSocket。因為瀏覽器支援因素,我們採用 WebSocket 進行實做。

% 或許可以來個架構圖, 沒有很重要但文字偏抽象, 要避免牽扯到前面都沒提的 kubernetes components
有了雙向的資料流,我們還需要一個終端機模擬器負責進一步的處理,與叢集上的 pty\footnote{pty: Unix-like 系統常見的元件,主要負責處理文字行編輯、訊號快捷鍵(如 Ctrl+C)等。} 溝通。終端機模擬器繪製整個文字界面,並且處理 pty 輸出中的控制訊息,達到文字界面的顏色與游標位置等的控制。這部份我們使用既有的 Xterm.js 開源函式庫達成。

\subsection{Helm 頁面與 Helm 實作}
\label{sec:helm}

對於 Helm 功能,我們希望保持 SPA 架構中,將部份邏輯推往瀏覽器端在運行成本上的優勢,規劃將其在瀏覽器內實做。起初我們將 Helm 的函式庫直接透過 WebAssembly 包裝,並提供其以指令操作為切分單位的函式接口與 TypeScript 串接,但我們發現以此方式實做效能不佳,於列出已安裝的 Helm Releases 等基本操作,在低資料量下已產生足以影響使用者體驗的延遲。

Helm Release 預設的 Secret 儲存方式需要經過 Base64\footnote{Base64: 一個編碼格式,可將任何形式資料轉為文字表示。} 與 gzip\footnote{gzip: 一個被廣泛利用的資料壓縮演算法。} 等操作,如果將 Helm Release 的解讀透過 WebAssembly 處理,在不經調整的情況下,呼叫的是來自 Go 語言基本函式庫的 Base64 與 gzip 實作。WebAssembly 雖然已經較為接近硬體,但設計上仍有些許限制,以及於瀏覽器場景即時編譯成原生指令迫於時間壓力無法進行較耗時的最佳化,導致效能與原生程式還是有落差\cite{jangda2019not}。而 Base64 與 gzip 作為常用的算法,瀏覽器本身有提供高效能的原生實作,在此情景卻未被利用。另外將資料傳送進出 WebAssembly 環境也有一定的執行成本。

關於這個情景,我們面臨兩個可能的解法:抽換相關演算法改為呼叫瀏覽器的實作,或者將容易影響使用者體驗的區塊以 TypeScript 重新實做。抽換的方法不但無法避免資料傳輸的負擔,反而會增加傳送的次數,不甚理想。而分析 Helm 架構可發現,除了模板引擎因基於 Go 基本函式庫的模板框架,設計容易牽涉語言特性而不易重新實做外,剩餘的區塊皆為一般的資料處理與 Kubernetes API 的串接。其中需要模板引擎的操作只有安裝、升級等使用者傾向預期需要時間的情景,不包含對使用者體驗影響重大的列出 Helm Releases。綜上考量,我們將 Helm 模板引擎外的部份抽離,由 TypeScript 重新實做。

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/web-helm-impl.png}
    \caption{CSKloud v3 網頁界面實作 Helm 原理示意圖}
\end{figure}

除此之外,瀏覽器一般以單一執行緒運行程式碼,不論 JavaScript 或 WebAssembly 邏輯皆是在與畫面繪製相同的執行緒執行,唯 JavaScript 語言上具有異步 IO 相關設計,使得一般負擔主要在 IO 與事件處理的網頁程式碼具備可接受的效能。但我們的場景不同,Helm 的模板引擎運算量較多,在執行時容易拖累到畫面繪製,造成卡頓。對此,我們進一步採用 Web Workers 技術。Web Workers 提供一個較侷限的執行環境,但在獨立的執行緒運行,可以避面上述影響繪製的窘境,使用上開發者需自行處理 Web Workers 與瀏覽器一般執行緒間的溝通。我們將安裝、升級、回滾、解除安裝的耗時的操作,包含 WebAssembly 的使用,在 Web Workers 環境下實做以改善使用者體驗。

在安裝的部份,由於我們是在瀏覽器內實做,能存取的 HTTP 服務受到對端的跨域政策影響。除非對端主動的表示允許,瀏覽器會限制網頁邏輯對其他域名服務的存取,作為增強安全性的一個手段。由於 Helm 傳統上並非在瀏覽器環境執行,Helm Repositories 大多不會特別放寬跨域政策,因此我們無法直接使用市面上大多數的 Helm Repositories。針對 CSKloud 的場景,因為可以預期部份 Charts 會受到平台加固等策略影響而無法安裝,我們採用自行架設 Helm Repositories,並只納入批量測試成功安裝的 Helm Charts,目前共提供 32 個來自 Bitnami 的 Helm Charts。

\begin{figure}[htb]
    \centering
    \includegraphics[width=\textwidth]{assets/web-helm-list.png}
    \caption{CSKloud v3 網頁界面 Helm Releases 列表頁面}
\end{figure}

\begin{figure}[htb]
    \centering
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-helm-install-repo.png}
        \caption{選擇 Helm Chart}
    \end{subfigure}%
    ~
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-helm-install-values.png}
        \caption{填寫 Helm Values}
    \end{subfigure}
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-helm-install-name.png}
        \caption{命名 Helm Release}
    \end{subfigure}
    \caption{CSKloud v3 網頁界面 Helm Releases 安裝流程}
\end{figure}

界面上以 Helm Releases 表格呈現為主,可以展開觀看版本歷史,在每個項目旁有解除安裝、回滾、更新等按鈕,解除安裝會保留歷史,仍然可以將其裝回,也可以事後刪除歷史。畫面的右下角則是有浮動式的按鈕,可以觸發安裝新 Helm Release 的流程。

對於安裝的流程,按下浮動式按鈕後,會跳出對話框進行三個步驟:第一步是選擇要安裝的 Helm Chart,由一個可搜尋的列表選取。第二步是填入 Helm Values,如同 Helm CLI 以 YAML\cite{yaml}\footnote{YAML: 資料序列化語言,語法為 JSON 的超集,於 Kubernetes 常用來表達 resources。} 的形式填入,界面上提供三個頁簽,一是填寫 Helm Values 的編輯器,二是來自 Helm Chart 的 README 檔案,通常會在此提供常用選項的說明,三是 Helm Chart 內的 Helm Values 預設值檔案,慣例在此以註解的形式完整地對選項欄位說明。以 CSKloud 的場景,編輯器會自動帶入針對 CSKloud 的第二層預設值,這個預設值來自測試安裝時所帶入的參數,調整如資源配額用量等選項,以確保在平台的限制下得以安裝,使用者也可以進一步進行修改。最後的步驟則是為此 Helm Release 命名,界面會自動預設一個隨機的名字。

調整 Helm Values 的編輯器採用 CodeMirror 開源編輯器專案,除了 YAML 的基本語法高亮以外,我們採用 codemirror-json-schema 擴充元件提供進一步的資料驗證、欄位說明與名稱自動補全。codemirror-json-schema 收取 JSON Schema 作為欄位定義,JSON Schema\cite{pezoa2016foundations} 是一個描述 JSON\cite{rfc8259}\footnote{JSON: 資料序列化語言,語法比 YAML 容易以程式解析。} 資料的定義框架,但由於大多數 YAML 應用場景描述的資料也都可以由 JSON 的形式表達,也常用來描述 YAML 資料。部份 Helm Chart 會提供對其 Helm Values 的 JSON Schema 定義檔。

\subsection{Quotas 視覺化}
\label{sec:quota}

\begin{figure}[htb]
    \centering
    \includegraphics[width=0.7\textwidth]{assets/web-quotas.png}
    \caption{CSKloud v3 網頁界面 Quotas 頁面}
\end{figure}

此頁面針對平台透過 ResourceQuota 對 Namespace 下實行的資源配額進行視覺化,使用圓餅圖顯示每個取用單位(如 Pod 下的一個容器)在相關資源(如記憶體)允許配額下所佔用的比例。使用者可以透過將游標移動到圓餅區塊上,查看取用單位的名稱以及佔用量,方便使用者了解何處取用最多。在總取用比例高到一定程度時,會改以黃色或紅色顯示提醒使用者注意。

在實作上,由於 ResourceQuota 只帶有配額與佔用總量資訊,須以程式邏輯額外列舉相關 resources,解析取用單位資訊以判斷細項。另外,圓餅圖的繪製透過 Chart.js 開源函式庫實做。

\subsection{Tokens 管理}
\label{sec:tokens-mgmt}

CSKloud 開放使用者直接存取 Kubernetes API,並且提供 kubectl 連線設定檔。因為叢集本身使用 OIDC 驗證方式,客戶端需要有相關 OIDC 流程邏輯,在網頁界面我們可以直接實做,而對於 kubectl 的場景,我們則是使用 kubelogin 擴充程式,在 kubectl 發出 Kubernetes API 請求時,kubelogin 會檢查當下是否持有有效的 token,在有必要時執行 OIDC 流程。OIDC 需要透過瀏覽器進行網頁登入,導致這個驗證方式雖然在使用者的互動使用時可行且較為安全,在自動化場景卻相當不方便。

對此,我們採用 ServiceAccount tokens 彌補對自動化場景的不足。Tokens 管理頁面幫助使用者設定一個具有當下 Namespace 權限的 ServiceAccount,再經由此 ServiceAccount 簽發指定效期、綁定 Secret 的 token。使用者可以紀錄相關的用途筆記,存放在對應的 Secret 內。這些 Secrets 使得網頁界面可以紀錄追蹤簽發的 tokens,同時作為撤銷 tokens 的手段。

\subsection{Resource Explorer}

\begin{figure}[htb]
    \centering
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-resources-list.png}
        \caption{Resources 列表}
    \end{subfigure}%
    ~
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-resources-single.png}
        \caption{單一 resource 檢視}
    \end{subfigure}
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-resources-single-menu.png}
        \caption{單一 resource 檢視功能表}
    \end{subfigure}%
    ~
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth]{assets/web-resources-single-new.png}
        \caption{創立 resource 的模板預填}
    \end{subfigure}
    \caption{CSKloud v3 網頁界面 Resource Explorer 頁面}
\end{figure}

Resource Explorer 提供對 Kubernetes resource 列舉、創立、編輯、刪除的操作界面,作為使用者與 Kubernetes 互動中最基礎的功能。這個頁面透過 Kubernetes discovery API 在運行時查詢叢集所有支援的 resource 種類,未來如果隨著 Kubernetes 演進而對種類有所增減,這個頁面不須經過修改可以直接沿用。

Resources 的列舉採用 Kubernetes 內建的製表功能,可以產出基本的概要,kubectl 的表格呈現亦是基於此機制。使用者可以將游標移動到表格欄位名稱上方,透過提示框了解欄位意義,也可以點擊針對欄位排序。右下方的浮動按鈕可以撰寫新的 resource,點擊表格列則是查看對應的 resource 的完整內容,以 YAML 的形式呈現。在單一 resource 完整呈現中右下方的浮動按鈕功能列表,也有編輯與刪除等功能。

在 YAML 檢視與編輯的方面,則與 \ref{sec:helm} Helm 頁面功能相似,採用 CodeMirror,並且一樣有基於 JSON Schema 的資料驗證、欄位提示與名稱補全。這裡的欄位定義則是來自於 Kubernetes API 的 OpenAPI 定義檔,由描述相關 API endpoint 的定義萃取轉換而成。Kubernetes 對於 OpenAPI 定義亦具有 discovery 機制,可以從 API 獲取定義檔,這樣的形式同樣使得網頁不須修改,即可適用於未來的 Kubernetes 版本。

創立新 resource 時,網頁會針對目前選擇的 resource 種類,預先填好必填或者常用的欄位,也會取好一個暫定名稱。預填欄位較為難以進行動態判斷,因此採用人工逐一事先設計的方式。如果選定的種類未先設計模板,仍會自動預填 Kubernetes resource 共通的 \verb|apiVersion|、\verb|kind|、\verb|metadata| 欄位。

這個頁面功能可以涵蓋 Kubernetes 上的大多數操作,但仍然有限制,主要為不支援 subresources。Subresources 為單一 resource 實例下的額外 API endpoints,使 Kubernetes 得以跳脫 resource 讀寫操作的框架,並且不具有固定的形式,其中較為代表的有:Pod 的 log subresource,用以查看 Pod 下容器的運行紀錄;Pod 的 exec subresources,用以在 Pod 下容器執行指令;ServiceAccount 的 tokens subresource,用以簽發 tokens。由於 subresources 的形式、用途皆不固定,我們無法直接提供通用的操作界面,而是經由如 \ref{sec:web-terminal} Pod 頁面等,針對不同場景另外實做。

\subsection{kubectl shell}
\label{sec:kubectl-shell}

隨著 Kubernetes 的更迭,即使我們盡力擴增網頁界面對應的功能,難免還是會有跟不上的時候。就算是現在,雖然我們已經實做了通用型的 Resource Explorer,仍然有少數 Kubernetes 功能無法直接透過網頁界面使用。對此,我們提供一個逃生口 (escape hatch):在網頁界面直接使用 kubectl 指令。雖然使用者可以自行安裝設定 kubectl,在網頁界面直接提供這個功能還是有免安裝、隨時帶著走的優勢。

實做這個功能有兩個可行方向:使用 WebAssembly 將 kubectl 移植到瀏覽器,或者在叢集內執行 kubectl。前者實際上除了移植 kubectl 指令外,還需提供一個 shell 環境以便使用,在需將環境內各種工具一一移植的前提下,容易產生缺漏使用者想要的項目的窘境,開發成本相當高,於是我們選擇了後者。

kubectl shell 頁面類似於 \ref{sec:tokens-mgmt} Tokens 管理頁面,都會先產生一個具有當下 Namespace 權限的 ServiceAccount,但取用的方式不同。接著會採用 Kubernetes 設計上將 ServiceAccount 對 Pod 做的特殊整合,創立一個使用該 ServiceAccount 的 Pod 時,叢集會自動簽發綁定此 Pod 的 token,並將 token 與相關連線資訊放置於 Pod 內容器的檔案系統中。容器內具備的 kubectl 指令會自動採用這些資訊進行連線。再來只須重複利用 \ref{sec:web-terminal} 所述的虛擬終端機連線機制,便可讓使用者操作容器內的 kubectl 指令。

這個方式雖然因為在叢集內執行容器,會佔用到使用者的資源配額,但實際此場景預期的資源使用極小,對於使用者權益的影響不大。運作機制主要又是基於其他既有的功能做組合,實際開發成本極低,卻十分有效。圖 \ref{fig:kubectl-get-pods} 即是來自於此功能。