Kubernetes 教學 第六篇:正式進入 Kubernetes 的世界

在之前的篇章中,我們已經學會基本操作 Kubernetes 的方法了,也瞭解了一點 k8s 的觀念。

剛好最近有個 Project 需要我架設正式的 Kubernetes Cluster,因此就順便整理一篇文章來教導大家,所以在這一篇章中,我們要來正式架設 Kubernetes Cluster,而我們使用的方式是最原始的 kubeadm

Kubernetes Cluster 架構:帶你瞭解 Kubernetes 的各個元件

這是一張從 Kubernetes 官方偷來的架構圖:

我們可以看到節點基本上分爲兩大類:

接下來來看看 Control-Plane 上面的節點會跑的 Components:

每個節點(包括 Master)都會跑的 Component:

Kubernetes 實操

建立 Kubernetes Node 的前置作業(每個 Node 都要)

首先你要清楚知道以下事項:

  1. Kubernetes 不負責直接對容器進行操作,他只負責管理容器,因此我們需要安裝 Container Runtime Interface(CRI) 來協助它操作容器。
  2. Container Network Interface(CNI) 只是一個標準界面,CNI plugin 才是遵循着 CNI 實作出來的工具,爲 Kubernetes 實作各種網路需求。因此,我們還需要安裝 CNI 這個網路界面,那些 CNI plugin 才能銜接這個界面去操作 Container 網路。
  3. CRI 不負責容器虛擬化技術,他只是提供一個界面給使用者進行容器操作、管理與調度,容器虛擬化技術的核心是控制 namespace、cgroups,以及虛擬文件系統與掛載。因此我們需要使用 runc,它會去使用 namespace 和 cgroups 等技術,來開啓一個容器。
  4. 當一臺主機運行着許多 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

他會安裝以下程式:

然而我還發現了一件有趣的事情,我花費了數小時在 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。

具體方法如下:

建立 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 可能過期了;或是你沒留意,就把記錄刷掉了。此時你就必須要查詢 tokencert-hash

如果你需要查詢,請到你的 master 上:

至於 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%:

因此,如果你定偶數台的 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 的使用。