[超重要]如何進行自建Kubernetes叢集核心憑證年度更新

[超重要]如何進行自建Kubernetes叢集核心憑證年度更新

最近讀 infoQ 翻譯的一篇Kubernetes 开源 9 年,但我们已经有了 8 年的踩坑血泪史(原文:Lessons From Our 8 Years Of Kubernetes In Production — Two Major Cluster Crashes, Ditching Self-Managed, Cutting Cluster Costs, Tooling, And More),看得心驚膽跳,其中 Kubernetes 憑證沒換把 Kubernetes 叢集搞掛這件事,我已經不只一次在文章裡看到。一般的教學文件會教你如何架 Kubernetes 叢集,教你 Pod、Service、Deployment、Network等等物件的基礎,但很少好好跟你談談憑證這件事。但如果你跟我一樣走自架 Kubernetes 叢集的路,這件事就無比重要,它關係到你的 Kubernetes 叢集的持續運作。這篇我們來解一下 Kubernetes 叢集憑證。

  • 當我們利用 kubeadm 架設 Kubernetes 叢集時,預設會產生一張有效期一年的憑證。也就是說,預設 365 天之後,憑證會過期
  • 憑證會用於各項 Kubernetes 叢集核心服務之間。憑證過期,代表全部核心服務無法正常運作。也就是為何,一張憑證就能讓整個Kubernetes 叢集 Crash

我們要了解 Kubernetes 叢集憑證可以從 kubeadm certs 下手:

$ sudo kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Feb 23, 2025 09:46 UTC   354d            ca                      no
apiserver                  Feb 23, 2025 09:46 UTC   354d            ca                      no
apiserver-etcd-client      Feb 23, 2025 09:46 UTC   354d            etcd-ca                 no
apiserver-kubelet-client   Feb 23, 2025 09:46 UTC   354d            ca                      no
controller-manager.conf    Feb 23, 2025 09:46 UTC   354d            ca                      no
etcd-healthcheck-client    Feb 23, 2025 09:46 UTC   354d            etcd-ca                 no
etcd-peer                  Feb 23, 2025 09:46 UTC   354d            etcd-ca                 no
etcd-server                Feb 23, 2025 09:46 UTC   354d            etcd-ca                 no
front-proxy-client         Feb 23, 2025 09:46 UTC   354d            front-proxy-ca          no
scheduler.conf             Feb 23, 2025 09:46 UTC   354d            ca                      no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Feb 21, 2034 09:46 UTC   9y              no
etcd-ca                 Feb 21, 2034 09:46 UTC   9y              no
front-proxy-ca          Feb 21, 2034 09:46 UTC   9y              no

可以明確看到現有憑證會影響那些服務,EXPIRES 能知道過期日是那天,記得釘到行事曆上。

官方提供「主動」與「被動」更新的方式。

主動更新 Kubernetes 叢集憑證

就是定期更新你的 Control Plane 核心版本,例如 v1.29.0 升級為 v1.29.1。基本上,目前每季都會有小版號會推出,更新之後會自動更新憑證。當然前提是,你的更新間隔必須少於一年。

被動更新 Kubernetes 叢集憑證

就是上面說的「行事曆」,每年特定日期進行手動更新。Kubernetes 叢集更新憑證的難度很低,唯一缺點就是怕忘記沒做。

$ sudo kubeadm certs renew all

[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed

Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.

更新指令設計的很輕巧,一行,沒了,並且訊息也非常好心提示你 kube-apiserverkube-controller-managerkube-scheduleretcd 這幾個核心服務必須重啟,它們才會套用新憑證。可以發現,這幾個都是 Kubernetes 叢集的 Static Pod

$ ls /etc/kubernetes/manifests/
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

Static Pod 也是 Pod,其實並沒有 restart 這種選項,可以暴力的用 kubectl delete 去刪除 Pod,來去觸發重新建立 Pod 的動作,來達到 restart 這個目的。

$ kubectl get pods -n kube-system
NAME                                 READY   STATUS    RESTARTS       AGE
coredns-5dd5756b68-mfgsn             1/1     Running   8 (3h1m ago)   10d
coredns-5dd5756b68-zcqj4             1/1     Running   8 (3h1m ago)   10d
etcd-k8s-master                      1/1     Running   8 (3h1m ago)   10d
kube-apiserver-k8s-master            1/1     Running   8 (3h1m ago)   10d
kube-controller-manager-k8s-master   1/1     Running   8 (3h1m ago)   10d
kube-proxy-65954                     1/1     Running   6 (3h1m ago)   10d
kube-proxy-g9nfh                     1/1     Running   8 (3h1m ago)   10d
kube-proxy-hf56r                     1/1     Running   6 (3h1m ago)   10d
kube-scheduler-k8s-master            1/1     Running   8 (3h1m ago)   10d

但懂 Static Pod 運作機制的讀者會知道,當 Yaml 不存在 /etc/kubernetes/manifests/ 目錄下時,Static Pod 會被刪除,當 Yaml 建立在 /etc/kubernetes/manifests/ 目錄下時,Static Pod 會被建立。我們應該學習優雅的方式。

mkdir -p /tmp/manifests
sudo mv /etc/kubernetes/manifests/*yaml /tmp/manifests

# 查詢 containerd 裡的容器狀態
sudo crictl ps

# 約過 20-30 秒後
sudo cp /tmp/manifests/*.yaml /etc/kubernetes/manifests

在我們將 Yaml 移出 /etc/kubernetes/manifests/ 目錄後,因為核心服務的 Pod 被刪除,因此 kubectl 也會無法使用,因此可以暫時改用 crictl 來查詢容器狀態,以確定 Static Pod 已被刪除。

例如,我們在移出 Yaml 與移入 Yaml 前後進行查詢 containerd 的容器。

# 移出
$ sudo crictl ps
CONTAINER           IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID              POD
b19db98978e8a       ead0a4a53df89       3 hours ago         Running             coredns             8                   c0bcac8a2acef       coredns-5dd5756b68-zcqj4
8bdfc01810f35       ead0a4a53df89       3 hours ago         Running             coredns             8                   41b5ae3144211       coredns-5dd5756b68-mfgsn
b4ebf0346af23       f9c73fde068fd       3 hours ago         Running             kube-flannel        8                   933d15c9c2c3f       kube-flannel-ds-j22v5
889e4a5ed20ff       123aa721f941b       3 hours ago         Running             kube-proxy          8                   e078feb5da8cc       kube-proxy-g9nfh

# 移入
$ sudo crictl ps
CONTAINER           IMAGE               CREATED             STATE               NAME                      ATTEMPT             POD ID              POD
49ba95ea22f7e       309c26d006295       1 second ago        Running             kube-scheduler            0                   e10e9cf8a00de       kube-scheduler-k8s-master
0b23f9a006398       4d9d9de55f196       1 second ago        Running             kube-controller-manager   0                   3387d4f99f1be       kube-controller-manager-k8s-master
1f2983169aea9       73deb9a3f7025       1 second ago        Running             etcd                      0                   dfb1848f8312d       etcd-k8s-master
a36b0f2949fba       eeb80ea665767       1 second ago        Running             kube-apiserver            0                   491bcf049e723       kube-apiserver-k8s-master
b19db98978e8a       ead0a4a53df89       3 hours ago         Running             coredns                   8                   c0bcac8a2acef       coredns-5dd5756b68-zcqj4
8bdfc01810f35       ead0a4a53df89       3 hours ago         Running             coredns                   8                   41b5ae3144211       coredns-5dd5756b68-mfgsn
b4ebf0346af23       f9c73fde068fd       3 hours ago         Running             kube-flannel              8                   933d15c9c2c3f       kube-flannel-ds-j22v5
889e4a5ed20ff       123aa721f941b       3 hours ago         Running             kube-proxy                8                   e078feb5da8cc       kube-proxy-g9nfh

你可能會說,重開機就好了。這是對的,但這就少了練功的機會,而且一般來說,Control Plane 應該不是想重開機就能重開機的。

特別注意:執行此步驟執行時要非常小心,最好是安排在維護時間進行,因為等於這段時間你整個 Control Plane 是處於無法提供服務的狀態。尤其是單節點 Control Plane。

$ sudo kubeadm certs check-expiration

[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Mar 06, 2025 06:10 UTC   364d            ca                      no
apiserver                  Mar 06, 2025 06:10 UTC   364d            ca                      no
apiserver-etcd-client      Mar 06, 2025 06:10 UTC   364d            etcd-ca                 no
apiserver-kubelet-client   Mar 06, 2025 06:10 UTC   364d            ca                      no
controller-manager.conf    Mar 06, 2025 06:10 UTC   364d            ca                      no
etcd-healthcheck-client    Mar 06, 2025 06:10 UTC   364d            etcd-ca                 no
etcd-peer                  Mar 06, 2025 06:10 UTC   364d            etcd-ca                 no
etcd-server                Mar 06, 2025 06:10 UTC   364d            etcd-ca                 no
front-proxy-client         Mar 06, 2025 06:10 UTC   364d            front-proxy-ca          no
scheduler.conf             Mar 06, 2025 06:10 UTC   364d            ca                      no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Feb 21, 2034 09:46 UTC   9y              no
etcd-ca                 Feb 21, 2034 09:46 UTC   9y              no
front-proxy-ca          Feb 21, 2034 09:46 UTC   9y              no

重新確認憑證狀態會發現 RESIDUAL 被重設為 364d 的滿血狀態。

另外官網文件提到,最後請重新執行以下指令,將更新後的憑證重新套用一下,這樣 User 執行 kubectl 才不會有問題。 :

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

我個人在 kubeadm v1.28.0 這版實驗的結果,沒有更新 .kube/config 也能正常執行 kubectl

$ ll .kube/
-rw------- 1 kkbruce kkbruce 5645 Mar  3 14:15 config
$ ll /etc/kubernetes/
-rw-------  1 root root 5649 Mar  6 06:10 admin.conf

這點有點奇怪,可以看到 admin.conf 是 Mar 6 有被更新過。User 本身的 config 還是 Mar 3 的版本。我有進一步去比對兩個檔案的內容,Hash 確實不一樣。為避免麻煩,我最後還是乖乖執行,我就沒有往下去追為什麼舊版 config 的內容還能正常執行 kubectl 了。

自動更新 Kubernetes 叢集憑證指令碼

這裡我們想做兩件事:

  • 可以將前面執行步驟收集到的指令整理成指令碼加以利用。
  • 透過排程的方式,每 1 個月執行檢查一次。(再次提醒,重啟核心服務會造成 Control Plane 服務中斷哦。)

自動化排程很方便,但就持續服務的角度來看,會造成服務會中斷就很麻煩,請讀者自行考慮。

echo "[Task 1] Run certs renew all"
sudo kubeadm certs renew all
 
echo "[Task 2] Stop static pods"
sudo mkdir -p /tmp/manifests
sudo mv /etc/kubernetes/manifests/*yaml /tmp/manifests
 
echo "[Task 3] Wait for static pods closed (watch kube-apiserver container)..."
while true; do
  numLines=$(sudo crictl ps | grep "kube-apiserver" | wc -l)
  if [ "$numLines" = "0" ]; then
    break
  fi
  sleep 1
done
 
echo "===== static pod has been removed! ====="
 
# start static pod
echo "[Task 4] Start static pod"
sudo cp /tmp/manifests/*.yaml /etc/kubernetes/manifests
 
# montior static pod
echo "Wait for static pods start(watch kube-apiserver container)..."
while true; do
  numLines=$(sudo crictl ps | grep "kube-apiserver" | wc -l)
  if [ "$numLines" != "0" ]; then
    break
  fi
  sleep 1
done
echo "===== static pod has started! ====="
 
# update kubeconfig
echo "[Task 5] update kubeconfig"
sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
 
 
# display certs
echo "[Task 6] Run certs check-expiration"
sudo kubeadm certs check-expiration

有了 Shell Bash 指令碼,Linux 排程有很多選擇,Crontab 或 Systemd timer 就看讀者個人選擇吧。

參考資料:

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。