Kubernetes 教學 第六篇:正式進入 Kubernetes 的世界
在之前的篇章中,我們已經學會基本操作 Kubernetes 的方法了,也瞭解了一點 k8s 的觀念。
剛好最近有個 Project 需要我架設正式的 Kubernetes Cluster,因此就順便整理一篇文章來教導大家,所以在這一篇章中,我們要來正式架設 Kubernetes Cluster,而我們使用的方式是最原始的 kubeadm
。
Kubernetes Cluster 架構:帶你瞭解 Kubernetes 的各個元件
這是一張從 Kubernetes 官方偷來的架構圖:
我們可以看到節點基本上分爲兩大類:
- Control-Plane(控制平面):負責回應使用者請求、進行調度、管理資料庫等工作。在非官方語境中,我們習慣稱呼在 Control-Plane 的節點爲 Master。
- 其它:負責運行要被執行的 Pods,也就是被作爲運算資源的節點,也被成爲 Workers。
接下來來看看 Control-Plane 上面的節點會跑的 Components:
- kube-apiserver:負責與使用者交互,你下
kubectl
就是在發送 API 請求給它。 - etcd:著名的分散式 HA 鍵值資料庫,在 k8s 中被用於儲存和 Cluster 相關的所有資訊。etcd 的運作會直接決定 k8s 的可靠性。
- kube-scheduler:基本上上負責調度 pod,當 pod 被新創建的時候,它就會被分到某個 node。注意,這個被新創建,未必指的是你新 deploy 上去,可能是某個節點下線,replicas 機制讓它會替你重新創建 pods;亦或是調整 replicas 數量。
- kube-controller-manager:controller-manager 只是一隻程式,但邏輯上,他是非常多個的 controller 整合成一隻程式,包括 node controller(負責檢測 node 狀態並且當它掛掉的時候進行處理)、job controller(管理 job 的 pod,如果各位還記得第五篇寫的 controller,應該會有印象 job 和 cronjob,注意:此 controller 非彼 controller)……等等。
每個節點(包括 Master)都會跑的 Component:
- kubelet:基本上就是 Kubernetes Cluster 在該節點上的在節點上管理人。會和 k8s api server 溝通,來獲得創建、更新或是刪除 Pod 的指令,並且確保這些 Pod 的狀態。
- kube-proxy:處理 Service 的元件,會負責 Cluster 內部 Pods 的通訊,以及 Pods 對外部的溝通。(不過它的功能可以被某些 CNI Plugin 取代)
Kubernetes 實操
建立 Kubernetes Node 的前置作業(每個 Node 都要)
首先你要清楚知道以下事項:
- Kubernetes 不負責直接對容器進行操作,他只負責管理容器,因此我們需要安裝 Container Runtime Interface(CRI) 來協助它操作容器。
- Container Network Interface(CNI) 只是一個標準界面,CNI plugin 才是遵循着 CNI 實作出來的工具,爲 Kubernetes 實作各種網路需求。因此,我們還需要安裝 CNI 這個網路界面,那些 CNI plugin 才能銜接這個界面去操作 Container 網路。
- CRI 不負責容器虛擬化技術,他只是提供一個界面給使用者進行容器操作、管理與調度,容器虛擬化技術的核心是控制 namespace、cgroups,以及虛擬文件系統與掛載。因此我們需要使用 runc,它會去使用 namespace 和 cgroups 等技術,來開啓一個容器。
- 當一臺主機運行着許多 Container 時,那臺主機就相當於是一臺 Router 了,因爲他要負責 Container 與外界以及 Container 之間的通訊,其中就需要用到 NAT 和 Forwarding。
因此,我撰寫了一個跑在 Debian Bookworm 的 Kubernetes 環境建置腳本(請在 root 權限下執行):
# 配置網路
modprobe br_netfilter
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
sysctl --system
# 下載 containerd
wget https://github.com/containerd/containerd/releases/download/v2.0.0-rc.5/containerd-2.0.0-rc.5-linux-amd64.tar.gz
tar Cxzvf /usr/local/ containerd-2.0.0-rc.5-linux-amd64.tar.gz
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
mv containerd.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now containerd
# 安裝 runc
wget https://github.com/opencontainers/runc/releases/download/v1.2.0-rc.3/runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc
# 下載 CNI
wget https://github.com/containernetworking/plugins/releases/download/v1.5.1/cni-plugins-linux-amd64-v1.5.1.tgz
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.5.1.tgz
# 啓動 containerd
systemctl restart --now containerd
rm cni-plugins-linux-amd64-v1.5.1.tgz runc.amd64 containerd-2.0.0-rc.5-linux-amd64.tar.gz
# 安裝 kubeadm、kubelet 和 kubectl
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
systemctl enable --now kubelet
他會安裝以下程式:
- containerd:v2.0.0-rc.5(這是 pre-release,因此可能不穩定,有需要建置 production 者自行改版本號)
- runc:v1.2.0-rc.3
- CNI:v1.5.1
- Kubernetes:v1.30
然而我還發現了一件有趣的事情,我花費了數小時在 debug 上面:etcd 在預設的 Debian Bookworm 上面無法在 containerd 中正常運行。原因疑似是 Debian Bookwarm 預設採用 cgroups v2,而 containerd 對 cgroups v2 的支援度不足以運行 etcd。因此要把 cgroups 的版本降爲 v1。
但事實上,我測試的結果是,幾乎所有 container 都能在使用 cgroups v2 的 containerd 上良好運行,就唯獨 etcd 不行。而且也沒有產生任何 logs,讓人無從下手,是最後翻到了這篇文章 Kubernetes Forums - Why does etcd fail with Debian/bulleye kernel? 才成功讓我架起來 Kubernetes Cluster。
具體方法如下:
- 修改
/etc/default/grub
,在GRUB_CMDLINE_LINUX_DEFAULT
這個參數上再加上systemd.unified_cgroup_hierarchy=0
,如下圖: - 更新 Grub 設定:執行
sudo update-grub
。 - 重啓機器。
建立 Kubernetes Cluster
創建 Kubernetes Cluster 非常簡單,執行以下指令即可:
kubeadm init --service-dns-domain=k8s.sandb0x.tw
其中的 --service-dns-domain
是 Optional 的,代表着 Cluster 預設的 domain,如果沒有特別設定,那它就會是我們在第四篇所提到的 cluster.local
。
基本上它還有其它參數可以做設定,建議建立 Cluster 之前先去 [Kubernetes Docs: kubeadm-init] 這裏看一看,例如可以設定預設 Pod 網段、Service 網段……等等。
接着等待幾分鐘,它建立完成之後就會噴出一條指令給你,類似這樣:
kubeadm join {ip}:6443 --token {token} \
--discovery-token-ca-cert-hash {cert-hash}
這條指令可以讓別台機器作爲 worker 加入你的 cluster。
操作 Kubernetes Cluster
在你創建完 Cluster 之後,你的 Cluster 就會有第一臺 Master,在那臺機器上面會產生一個 /etc/kubernetes/admin.conf
檔案,基本上可以就是管理員登入資訊。
所以,將它複製到你的家目錄 ~/.kube/config
這個位置,並確保他的 owner 是你;爲了安全,權限最好設置成 600。
之後你便可以透過 kubectl 來操作這座 cluster 了,例如:
$ kubectl get node -A
NAME STATUS ROLES AGE VERSION
k8s-master1 Ready control-plane 114m v1.30.5
安裝 CNI plugin
此時如果你下 kubectl get pods -A
,你會發現有些 Pods 在 pending
,仔細看會發現是 coredns 的相關 pods,那個原因是因爲沒有 CNI plugin,因此那些 K8S 沒辦法準備 IP 位置分配給它們。結論就是我們看到的,那些 pods 始終處於 pending
狀態,直至你安裝了 CNI plugin。
你可能又會好奇了,那爲什麼有一些 pods 還能正常運作?例如 kube-apiserver?其實那是因爲,爲了保證 k8s 始終能正確運行,不會被錯誤撰寫的 CNI plugin 或是錯誤的配置弄壞,也是爲了一開始在沒有安裝 CNI plugin 時可以彼此溝通,因此如 kube-apiserver、kube-scheduler 和 etcd 等的核心 components 都是使用主機網路(HostNetwork)模式。
那我們就直接來安裝 Calico:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/calico.yaml
安裝之後,等待一段時間,所有的 Pods 應該就會就緒。
加入 Worker 到 Kubernetes Cluster
請先確保這臺機器已經做好前置準備,包括系統網路設定、CNI、CRI 和 kube 套件。
基本上就是剛才提到的指令:
kubeadm join {ip}:6443 --token {token} \
--discovery-token-ca-cert-hash {cert-hash}
但是由於你使用的時候, token
可能過期了;或是你沒留意,就把記錄刷掉了。此時你就必須要查詢 token
和 cert-hash
。
如果你需要查詢,請到你的 master 上:
- 利用
kubeadm token list
來查詢所有有效 token。 - 如果沒有有效的 token 了,可以使用
kubeadm token create
來新增一個。
至於 cert-hash
,請下此指令:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 | awk '{print $2}'
它就會幫你重新計算 cert-hash
值。
最後,下達 kubectl get nodes
指令來確認。
$ kubectl get node -A
NAME STATUS ROLES AGE VERSION
k8s-master1 Ready control-plane 114m v1.30.5
k8s-worker1 Ready <none> 100m v1.30.5
可以注意到 k8s-worker1 的 Role 是 <none>
,這其實是正常的,因爲 Worker 本來預設就不會被分配任何 Role,你也可以對它做更進階的設定,給它分配一個 Role,不過這暫且不提。
加入 Control-Plane(Master) 到 Kubernetes Cluster
請先確保這臺機器已經做好前置準備,包括系統網路設定、CNI、CRI 和 kube 套件。
指令是:
kubeadm join {ip}:6443 --token {token} \
--discovery-token-ca-cert-hash {cert-hash}
--control-plane --certificate-key {certificate-key}
我們必須爲它產生一個 certificate-key
,方法如下:
kubeadm init phase upload-certs --upload-certs
它就會告訴你一組 key,塞入 certificate-key
那個欄位即可。
最後,下達 kubectl get nodes
指令來確認,應該要出現一個新的 node 並且其 role 是 control-plane
。
我這邊只是閱讀一些文件撰寫了上面加入新的 Control-Plane Node,因爲沒有實質需求,並沒有實際測試。因此如果有問題,請務必聯繫我勘誤,非常感謝。
Additional Information
此外,這邊想補充,基本上在建置 Kubernetes Cluster 的時候,我們的 Control-Plane Node 數量都會訂在奇數,這是因爲 etcd 的選舉需要某個節點獲得過半數的票,這裏我們簡化模型,假設某台機器壞掉的機率是 5%:
- 5 台 Master:允許你壞 2 台,壞 3 到 5 台導致 Cluster 故障的機率是 2.25%。
- 6 台 Master:允許你壞 2 台,壞 3 到 6 台導致 Cluster 故障的機率是 3.27%。
因此,如果你定偶數台的 Master,那麼 HA 反而會下降,因爲允許壞掉的數量沒有提高,壞掉的機率沒有下降,可能壞掉的數量上升了。因此前面才提及:etcd 的運作會直接決定 k8s 的可靠性。
結語
在做完以上全部的事項(除了增加 Master Node 以外),我們最後應該會看到這樣子的結果:
$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
calico-apiserver calico-apiserver-6cbcf97b6-7p9kt 1/1 Running 0 147m
calico-apiserver calico-apiserver-6cbcf97b6-ldcck 1/1 Running 0 147m
calico-system calico-kube-controllers-7bddf486cb-llxl5 1/1 Running 0 148m
calico-system calico-node-2hfb4 1/1 Running 0 147m
calico-system calico-node-55pbl 1/1 Running 1 (138m ago) 140m
calico-system calico-typha-647c8849b8-8gqnl 1/1 Running 0 147m
calico-system csi-node-driver-298fw 2/2 Running 0 148m
calico-system csi-node-driver-5b9md 2/2 Running 2 (138m ago) 140m
kube-system coredns-55cb58b774-42fnz 1/1 Running 0 153m
kube-system coredns-55cb58b774-66cwx 1/1 Running 0 153m
kube-system etcd-k8s-master1 1/1 Running 599 153m
kube-system kube-apiserver-k8s-master1 1/1 Running 35 153m
kube-system kube-controller-manager-k8s-master1 1/1 Running 594 153m
kube-system kube-proxy-2x75p 1/1 Running 0 153m
kube-system kube-proxy-dskvn 1/1 Running 2 (138m ago) 140m
kube-system kube-scheduler-k8s-master1 1/1 Running 639 153m
tigera-operator tigera-operator-576646c5b6-s75x9 1/1 Running 0 148m
那麼恭喜你,你建立了一套功能正常的 Kubernetes Cluster。
如果正常的話,我們會介紹 Kubernetes 的 RBAC(Role-Based Access Control),是 K8S 提供的授權機制,提供不同使用者或是服務對不通過資源的操作權限。
我會選擇這個主題的原因是,我最近在開發的專案,恰好就是要管理一個平臺,各個使用者可以在上面創建、銷毀 Containers,因此才想要建立一個正式的 Kubernetes 環境,以及來仔細研究一下 RBAC 的使用。
