2020 IT邦幫忙鐵人賽 Kubernetes 原理分析系列文章
有興趣的讀者歡迎到我的網站 https://hwchiu.com 閱讀其他技術文章,有任何錯誤或是討論歡迎直接留言或寄信討論
本篇文章作為 Container Runtime Interface
系列文的最後一篇,這次的主題也是延續前兩篇,繼續探討各種不同的 CRI
解決方案。
藉由瞭解這些不同目的需求的解決方案,可以幫助我們去探索對於 kubernetes
的各種應用,畢竟有需求才會有開發,有人遇到了痛點是當前的 kubernetes
沒有辦法解決的,所以才會開始著手開發符合自己需求的方式。
如同最一開始闡述過,透過 Container Runtime Inteface
的架構,可以讓開發者更自由且彈性的去開發與發布這些功能,而不用跟 kubernetes
本身綁定導致開發週期過長。
前兩篇我們探討了兩種 CRI
使用情境
CRI
解決方案,譬如 containerd
與 cri-o
, 但是底層都是基於 runc
這個 OCI Runtime
來產生 Container
.OCI Runtime
, 譬如 gVisor
與 kata container
等。 透過 OCI
的標準架構,開發者與使用者都可以於 containerd
或是 cri-o
中將 runc
切換成這些更加安全的 container
。而今天要探討的則是第三種情形,是真正的 Virtual Machine
,目的非常簡單,就是透過 kubernetes
去管理 Virtual Machine
,統一透過 Pod/Deployment
等習慣的方式同事去管理 Container
與 Virtual Machine
.
不同於 Kata Container
這種基於 Virtual Machine
的 Container
解決方案,本文探討的就是實實在在的 Virtual Machine
,一點 Container
的概念都不存在的環境,使用者在建立該資源的時候甚至不是提供 Container Image
,而是提供 VM Image
來創立服務。
如同前篇文中所述, Container
與 Virtual Machine
彼此的比較從來沒有中斷過,然而隨者微服務概念的普及與發展,愈來愈多的使用情境都在嘗試使用容器來管理,在這種前提下,為什麼會有這種純 Virtual Machine
的需求被提出?
如果今天的使用情境都是基於自己公司內部開發,那我覺得通常不會有這個問題,因為不同組別之間可以互相協調,規劃出一個對於開發,維運與測試都接受的模式與架構。
但是有些情況是運行的服務並非自行開發,而是第三方廠商的解決方案,並且將該解決方案給整合到 Virtual Machine
之中。
畢竟 Virtual Machine
發展的時間更久,有很多已經開發許久的軟體都是基於 Virtual Machine
的環境進行設計與使用,同時在販售與合作方面也都是基於 VM Image
的方式在發布,因此這會造成服務提供商的服務部分來自 Container
,部分來自 Virtual Machine
,那 kubernetes
能不能解決這個問題 ?
事實上就我目前接觸到的所有案例裡面,幾乎全部都跟 Network Function Virtualization(NFV)
有關。隨者 NFV
與 OpenStaack
的發展,很多的廠商開始將自己的服務從軟硬體綁在一起到當純只賣軟體,使用 VM
方式釋出該 VM Image
供他人使用。
對於一般的研究人員或是開發者來說,這個問題更加嚴重,因為支援不足與地位差距,你今天根本沒有辦法要求原開發商將軟體從 VM
轉移到 Container
,這種情況下你會覺得很難處理,因為根本沒有辦法運行。
所以就漸漸有不同的方式被提出來解決這個問題,譬如 OpenStack
堆疊在 Kubernetes
上,或是 Kubernetes
堆疊在 OPenStack
上,然後重新開發其他的管理工具同時掌管 Kubernetes
以及 OpenStack
。
這樣的架構也許可行,但是卻將複雜度提升到一個難以理解的地方,對於開發者,使用者,維運者都帶來難以除錯與管理的問題,所以問題就退回到,能不能用 kubernetes
直接管理 Virtual Machine
?
曾經覺得困難的問題,現在如果瞭解了 CRI
的架構與運作模式,對這個問題似乎就不會覺得太困難了,可以直接打造一個滿足 CRI
介面的程式,背後全部都用 Virtual Machine
去實現。
這個情況下可以完全不需要去管 OCI 標準了,因為我們的目標是純 Virtual Machine,不太需要管 Container。
除了上述的難以容器化的原因外,還有一些原因是打造這種專案的契機
Linux
環境架設的 Kubernetes
叢集,要如何在其中運行一些基於 Windows
環境的服務?gVisror
與 Kata Container
相關的解決方案,也未必能夠說服所有人目前這兩個專案的開發以經處於 Production Ready
,所以如果可以繼續研究已經運行長期的 Virtual Machine
環境是再好不過。這方面我目前知道比較有名的專案為 kubevirt
以及 virtlet
,那接下來會針對 virtlet
為主去介紹探討一下這種架構的設計,以及怎麼使用
就跟前述的慣例一下,先看一下 官方 GitHub 是如何描述自己這個專案的
Virtlet is a Kubernetes runtime server which allows you to run VM workloads, based on QCOW2 images.
這邊值得注意的是其用的詞是 Kubernetes runtime server
, 這邊所指的就是 Container Runtime Interface
,該專案本身額外實現了一個全新的應用程式,該應用程式本身支援 CRI
的 gRPC
介面,但是底下實現這些功能時全部都使用基於 QCOW2 Images
格式的 Virtual Machine
。
virtlet
開發的初衷並不是要用 VM
取代所有的 Container
, 而是希望能夠提供另外一種選擇。為了達成這個目的,則 CRI
的部分勢必要重新撰寫,不能使用原生的 containerd
或是 cri-o
。
同時這個全新設計的 CRI
處理程式也要能夠根據情況決定使用 Virtual Machine
或是 Container
來創建對應的運算資源。
於是乎, CRI Proxy
這個專案就因應這個需求而生
CRI Proxy Server
的功能分成簡單,就是根據條件轉發 CRI
請求到不同的後端,針對 container
的部分,目前支援 dockershim
或是 containerd
,而針對 Virtual Machine
的部分則是 virtlet
server 。
本圖節錄自GitHub CRIProxy
CRI Proxy
要用什麼條件來判斷到底該怎麼處理這個請求,這部分就使用上了 kubernetes
本身針對其資源內部提供的標記欄位,也就是所謂的 annotation
對於每個創建的 Pod
,只要於 metadata.annotations.kubernetes.io/target-runtime
設定為 virtlet.cloud
, 則 CRI Proxy
就會認得這個 Pod
要走 VM
去處理,而非傳統的 Container
。
apiVersion: v1
kind: Pod
metadata:
name: cirros-vm
annotations:
kubernetes.io/target-runtime: virtlet.cloud
...
透過這種標記的方式與架構,可以讓使用者方便的去根據需求來決定要使用 VM
還是 Container
。
此外 CRI Proxy
會被 kubelet
呼叫,所以本身也是每個節點上都要存在,因此一開始會先用 systemd
的方式在每台節點上都運行安裝,這樣基本的 kubelet
才可以啟動。 接者所有沒有設定的 Pod
就會走 kubelet
-> CRI Proxy
-> dockershim/containerd
的方式以 Container
被創建出來。
除了上述的 CRI Proxy
之外,我們接下來看一下其完整的運作架構。
本圖節錄自 Architecture
當 CRI Proxy
收到創建 VM
的請求後,就會將該 CRI
的請求轉發到後端處理,這個處理的角色就是 Virtlet Container
,也是俗稱的 virtlet Manager
。
當整個 kubernetes
系統起來後,會透過 daemonset
的方式去部署 virtlet Manager
vagrant@k8s-dev:~$ sudo kubectl -n kube-system get daemonset
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-proxy 3 3 3 3 3 <none> 8h
virtlet 1 1 1 1 1 <none> 8h
vagrant@k8s-dev:~$ sudo kubectl get pods --all-namespaces -l runtime=virtlet
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system virtlet-gghd4 3/3 Running 0 8h
然而該 DaemonSet 本身其實也有設定節點的選擇條件,並非所有的節點都會部署,畢竟該節點要有能力產生 VM
,目前使用的規則是該節點必須要含有個標籤 extraRuntime: virtlet
即可,值得注意的是其使用的條件是 In
, 所以只要含有 virtlet
這個字的節點都會被部署 virtlet Manager
。
...
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: extraRuntime
operator: In
values:
- virtlet
...
當 virtlet Manager
收到指令後,會透過 libvirt
API 的方式進行後續的處理,叫起 vmwrapper
來產生對應的 VM
環境
vmrapper is run by libvirt and wraps the emulator (QEMU/KVM). It requests tap file descriptor from Virtlet, adds command line arguments needed by the emulator to use the tap device and then execs the emulator.
其完整架構非常複雜,其中自行設計了不少元件來處理資源的處理,譬如使用 tapmanager
來處理整個 CNI
,這部分幾乎沒有文件,只能依賴閱讀原始碼的方式來理解其實作方法。
vm-pod-lifecycle 這邊描述了關於 Pod
創造與刪除時整體處理流程,非常的長,有興趣的可以自行閱讀。
官方文件 中有提供兩種安裝方式,一種是使用 kubernetes-dind-cluster
去安裝整個測試環境,另外一種則是按部就班的描述要安裝的所有元件,只是單純測試跟研究的話,我認為選擇第一種會比較方便
我自己的電腦環境是MAC Pro
,平常都會透過 Vagrant + VirtualBox
產生一個 Linux 環境來測試,這次就基於這個 Linux
的環境使用 kubernetes-dind-cluster 安裝 kubernetes 並且在裡面使用 virtlet 產生 VM。
架構如下,非常的有趣
Container
, 以這三個 Container
組成一個 Kubernetes Cluster
Virtlet
作為其 CRI
解決方案,最後在裡面產生一個基於 Virtual Machine
的 Pod
gocubsgo
.可以看到創建出來的 pod
, 有特別標注一個 annotations
,這樣 CRIProxy
就會根據需求使用 containerd
或是 VM
來創建服務
這時候透過 kubectl get pods -o wide
取得該 Pod
運行的節點位置,並且透過 docker exec -it $name bash
到節點裡面進行觀察
透過觀察真的發現該節點內透過 qemu
創建了一個 VM
除了上述最簡單的範例之外,GitHub 這邊還有提供其他不同的 Yaml
, 其中我覺得非常有趣的就是 k8s.yml
可以讓你在 kubernetes
裡面透過 VM
產生另外一個 kubernetes
cluster, 我暫時想不到應用情境,但是就是一個很有趣的架構。
節錄一下裡面的內容,可以看到該 VM
起來後會透過 kubeadm
的方式去初始化一個 cluster.
- path: /usr/local/bin/provision.sh
permissions: "0755"
owner: root
content: |
#!/bin/bash
set -u -e
set -o pipefail
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
apt-get update
apt-get install -y docker.io kubelet kubeadm kubectl kubernetes-cni
sed -i 's/--cluster-dns=10\.96\.0\.10/--cluster-dns=10.97.0.10/' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
systemctl daemon-reload
if [[ $(hostname) =~ -0$ ]]; then
# master node
kubeadm init --token adcb82.4eae29627dc4c5a6 --pod-network-cidr=10.200.0.0/16 --service-cidr=10.97.0.0/16 --apiserver-cert-extra-sans=127.0.0.1,localhost
export KUBECONFIG=/etc/kubernetes/admin.conf
export kubever=$(kubectl version | base64 | tr -d '\n')
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$kubever"
while ! kubectl get pods -n kube-system -l k8s-app=kube-dns|grep ' 1/1'; do
sleep 1
done
mkdir -p /root/.kube
chmod 700 /root/.kube
cp "${KUBECONFIG}" /root/.kube/config
echo "Master setup complete." >&2
else
# worker node
kubeadm join --token adcb82.4eae29627dc4c5a6 --discovery-token-unsafe-skip-ca-verification k8s-0.k8s:6443
echo "Node setup complete." >&2
fi
Container Runtime Interface(CRI)
的文章到這邊告了一個段落,我個人對於 CRI
這種介面的設計是滿喜歡的,透過介面將實作與主體抽離,能夠讓社群開發者自己開發想要的功能,同時又能夠簡單且順利的與 kubernetes
整合。
也正是因為如此才可以看到各式各樣針對不同議題而努力的專案,每個專案都有自己的特色與優劣,所以對於一個管理者來說,如果能夠理解這些不同的解決方案的優劣之處,不論是基於 CRI
標準的方案,或是更底下相容於 OCI Runtime
的實作,對於未來遇到任何不同的使用情境與問題時,腦中就可以很快的反射出是不是有相關的議題與資源可以去研究,而不會只用一套 docker
打天下。
接下來將針對網路的部分,從 Container Network Interface(CNI)
為出發點介紹其概念與架構,也包含了 ipam
的介紹,讓大家知道到底 IP
是怎麼被分配與指派的,接者也會探討常見的 flannel
其實作概念與運作流程。
接者所有沒有設定的 Pod 就會走 kubelet -> CRI Proxy -> dockershim/containerd 的方式以 Container 被創建出來。
接者是接著 ?
cirrOS 被創建出來會有自己的 kernel 嗎 ? 我對 qemu
不了解。
那這可以一台虛擬做 scale 平均在不同 Node 上 ?
其實你把它當作 Pod來想就好, Pod 可以怎麼運作,它就可以怎麼運作。 只是這個 Pod 就是個 VM產生的,不是單純的容器
隨者 NFV 與 OpenStaack
的發展 --> OpenStaack 有誤
在實驗環境中 k8s cluster 的三個 Node 基於同一個 ubuntu host,可以把它變成 1 個 Node 1 個 ubuntu host,這是可行的嗎 ?
上述文章似乎沒提到,而且我還在理解中QQ
完全可行,安裝過程中請確定每一台機器的 kubelet 都有設定正確,相關的參數以及相關的 daemon 都有安裝即可。
我選用一台只是因為單純因為 demo 方便
hi 大牛我想問一下你的實驗環境
- 先疊一層 VM
- 裡面創三個 Container, 以這三個 Container 組成一個 Kubernetes Cluster
- Kubernetes 使用 Virtlet 作為其 CRI 解決方案,最後在裡面產生一個基於 Virtual Machine 的 Pod
關於先疊一層 VM 接者在立面建立三個 Container 組成 Kubernetes Cluster 這邊有點疑問,這邊是要三台 VM 組成 Kubernetes Cluster 吧?
沒有唷,是真的用三個 container 組成環境,這個是依賴 kubernetes-dind-cluster (kind) 這個專案架設的 k8s 環境