系列文章:

Kubernetes - 建置Cluster


此為2024年最新版本建置K8s Cluster的步驟。


建置版本環境

Linux Ubuntu - 24.04
Docker - 27.2.0
Containerd - 1.7.21
K8s - 1.31
K8s - CNI(Calico) - 3.28.1


在Control Plane和Node上安裝K8s

安裝起手勢

sudo su
apt update


VM工具包

apt install -y open-vm-tools-desktop
apt update
💡 針對VMware虛擬機環境優化的工具包,專門用於Linux桌面環境。它是open-vm-tools的延伸包,提供了與VMware桌面虛擬化平台(如:VMware Workstation 和VMware Fusion)集成所需的額外功能。若是使用VMware可以安裝此工具。


安裝工具套件

apt update
apt install -y vim openssh-server net-tools nfs-common
apt update
💡 其中net-tools和nfs-common分別為可選的,他們分別用於:網路管理的工具包和使用NFS文件系統必要工具包。


關閉Swap

在Ubuntu24.04中,將swap.img關閉。
swapoff -a
vim /etc/fstab


修改HostName(選擇性)

vim /etc/hostname
💡 正確的命名HostName有利於我們後續管理K8s的集群。


安裝Docker和Containerd

apt update
apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin


設定Docker的Private Registry受信任名單(可選)

vim /etc/docker/daemon.json
在daemon.json裡面加入信任名單清單。
{
  "insecure-registries": [
    "xxx.xxx.com"
  ]
}
設定完成後,需要重啟docker服務。
systemctl daemon-reload
systemctl restart docker


設定Containerd與cgroup驅動程式

mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
vim /etc/containerd/config.toml
找到「[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]」設定,將SystemdCgroup改為「true」
version = 2
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
   [plugins."io.containerd.grpc.v1.cri".containerd]
      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          runtime_type = "io.containerd.runc.v2"
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            SystemdCgroup = true
設定完成後,需要重啟Containerd服務。
systemctl restart containerd


設定Containerd的Private Registry受信任名單(可選)

vim /etc/containerd/config.toml
將信任的名單加在「[plugins."io.containerd.grpc.v1.cri".registry.mirrors]」底下。
version = 2
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    [plugins."io.containerd.grpc.v1.cri".registry]
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."xxx.xxx.com"]
          endpoint = ["https://xxx.xxx.com"]          
設定完成後,也是需要重啟Containerd服務。
systemctl restart containerd
💡 若有設定Containerd的受信任名單,建議可以和cgroup驅動程式一起設定好再重啟。


安裝K8s

apt update
apt install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo 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.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt update
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl


初始化Control Plane和工具安裝

初始化Control Plane

kubeadm init --pod-network-cidr=172.16.0.0/16
💡 注意!初始化時,pod-network-cidr的設定不要和主機的網域相同。例如:主機IP地址位於192.168.0.0/16的範圍區間內,那麼pod-network-cidr就請避開這個區間。

當Pod的IP地址範圍與主機的IP地址範圍相重疊時,K8s的網路系統會無法正確的路由流量。Pod和主機之間的流量在轉發時,如果目標IP屬於重疊的CIDR區域,系統無法辨別該流量是否應該轉發至Pod還是主機本身,導致數據包無法正確送達。


Kubectl命令使用權限設定

非Root權限使用權限設定:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
💡 設定完成後,非Root用戶可以不需要sudo就可以直接使用kubectl的命令。
Root權限使用權限設定:
export KUBECONFIG=/etc/kubernetes/admin.conf
💡 匯出後即可直接使用kubectl的命令,但要注意!每次重開終端機都要再執行一次該命令。

安裝CNI(使用Calico)

在K8s集群中,我們需要額外安裝CNI容器網路接口,Container Network Interface)來實現容器的網路功能,這是因為K8s本身不提供具體的網路實現,而是依賴CNI插件來管理和配置集群中的網路。
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/<指定版本>/manifests/tigera-operator.yaml
💡 指定版本可以到Calico的GitHub上查看最新的Release版本【點我前往】。
接著需要安裝Calico的Custom Resource,我們需要找到和上方Calico CNI相同版本的Custom Resource,並進行安裝。
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/<指定版本>/manifests/custom-resources.yaml
如果一開始kubeadm init時,有指定--pod-network-cidr的設定值時,我們就需要將Custom Resource中下載下來,並修改ipPools中的cidr設定值。
該值需要和初始化時的設定一致,最後使用此修改後的檔案進行安裝。
kubectl create -f <修改cidr設定值後的檔案>.yaml
安裝完成後可以使用watch命令監看容器的啟動狀態,直到確定所有的Calico容器都是Running為止就算成功安裝。
watch kubectl get pods -n calico-system
最後,我們要允許K8的Control Plane可以運行CNI的Pod來管理集群網路。
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
💡 移除Control Plane節點上的污點可以適用於所有需要在Control Plane節點上運行的CNI插件。這麼做能確保集群中的網路元件(通常是通過DaemonSet部署的)能夠在所有節點上正常運行,包括Control Plane節點。


節點安裝

kubeadm token create --print-join-command


Ingress-Nginx Controller安裝

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-<指定版本>/deploy/static/provider/cloud/deploy.yaml
💡 指定版本可以到GitHub上查看最新的Release版本【點我前往】。
💡 注意!若Ingress-Nginx要使用snippet功能,例如:「nginx.ingress.kubernetes.io/configuration-snippet」,必須在Ingress-Nginx的Controller中,將「allow-snippet-annotations」改為true。

由於此設定在多租戶叢集中可能很危險,出於安全原因,預設是關閉的,此時若使用snippet功能,部署時就會發生錯誤:「denied the request: nginx.ingress.kubernetes.io/configura tion-snippet annotation cannot be used. Snippet directives are disabled by the Ingress administrator」。

相關的CVE-2021-25742安全性漏洞問題回報可以【點我前往
如果想要查看所有關於Ingress-Nginx的資源則可以使用以下指令。
kubectl get all -n ingress-nginx


非雲端K8s集群負載平衡器的解決方案 - MetalLB安裝

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/<指定版本>/config/manifests/metallb-native.yaml
💡 指定版本可以到GitHub上查看最新的Release版本【點我前往】。
接著建立一個metallb-addressconfig.yaml的檔案,這個檔案將會分配負載平衡器的實際連線池,設定完成就可以直接部署。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.80-192.168.1.90
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
💡 注意!負載平衡器的IP範圍請不要設定和Control Plane或Node節點重複的IP,例如Control Plane是192.168.1.10,Node是192.168.1.11,那麼負載平衡的IP範圍請避開10和11,並設定在192.168.1.12-192.168.1.15。

另外,如果你的叢集只有Ingress一個服務要分配IP,我們可以將範圍設定為:192.168.1.80-192.168.1.80,不過通常都還會有其它的LoadBalancer類型的服務會需要使用,因此實物上比較不會這麼設定。


MetalLB進階設定1

如果我們希望可以手動分配Ingress在固定的IP上,例如希望將Ingress的IP分配在192.168.1.80,其餘的192.168.1.81-90則是開給其它LoadBalancer類型的服務時,我們可以使用以下的設定。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: ingress-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.80-192.168.1.80
  autoAssign: false
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: ingress-advertisement
  namespace: metallb-system
spec:
  ipAddressPools:
    - ingress-pool
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.81-192.168.1.90
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
    - default
💡 注意!由於192.168.1.80是專門分配給Ingress使用的,因此需要將autoAssign設定為「false」,以防止這個IP被自動分配給其他服務。
接著我們還需要修改Ingress的服務,我們可以調整「https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-<指定版本>/deploy/static/provider/cloud/deploy.yaml」檔案中的Service,並新增以下設定。
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.11.2
  name: ingress-nginx-controller
  namespace: ingress-nginx
  annotations:
    #在這裡指定Ingress使用我們設定的負載平衡器:「ingress-pool」
    metallb.universe.tf/address-pool: ingress-pool 
spec:
  externalTrafficPolicy: Local
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  ports:
    - appProtocol: http
      name: http
      port: 80
      protocol: TCP
      targetPort: http
    - appProtocol: https
      name: https
      port: 443
      protocol: TCP
      targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: LoadBalancer
這樣就可以手動分配指定的IP給Ingress使用,其餘的則是當使用LoadBalancer類型的服務時,就會從中自動分配IP。


MetalLB進階設定2

MetalLB預設情況下,會為每個LoadBalancer類型的服務分配一個唯一的IP,也就是說,192.168.1.81-192.168.1.90只能夠分配給9組服務供使用,若是IP不夠用的情況下就很麻煩。此時,就需要使用IP位置共享的方式來進行設定。
若要啟用IP位置共享,我們需要在LoadBalancer服務中加上「metallb.universe.tf/allow-shared-ip」的詮釋資料。加上詮釋資料後還需要滿足以下條件:
  • 它們都有相同的共享鍵。
  • 它們請求使用不同的埠(例如,一個是 tcp/80,另一個是 tcp/443)。
  • 它們都使用 Cluster external traffic policy,或者它們都指向完全相同的一組 pod(即 pod 的選擇器是相同的)。
滿足以上條件後,還需要指定LoadBalancer使用的IP位置,我們可以使用「status.loadBalancer.ingress」設定。
apiVersion: v1
kind: Service
metadata:
  name: test1-service
  namespace: test-group
  annotations:
    metallb.universe.tf/allow-shared-ip: "test-group-192.168.1.81" # 共享IP的key
spec:
  type: LoadBalancer
  ports:
    - name: api
      port: 1234
      targetPort: 6666
  selector:
    app: test1-api
status:
  loadBalancer:
    ingress:
      - ip: 192.168.1.81
---
apiVersion: v1
kind: Service
metadata:
  name: test2-service
  namespace: test-group
  annotations:
    metallb.universe.tf/allow-shared-ip: "test-group-192.168.1.81" # 共享IP的key
spec:
  type: LoadBalancer
  ports:
    - name: api
      port: 1235
      targetPort: 7777
  selector:
    app: test2-api
status:
  loadBalancer:
    ingress:
      - ip: 192.168.1.81
這樣就可以在192.168.1.81的位置上讓兩個服務共享,並且使用不同的Port囉!


建立自簽名CA憑證

# 創建一個新的 CA Issuer,使用之前創建的 ca-tls-secret 作為根證書,這個 Issuer 用於簽發其他證書
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: ca-issuer
  namespace: test-ca
spec:
  ca:
    secretName: ca-tls-secret
---
# 創建一個 TLS 憑證,使用上面定義的 CA Issuer 來簽發,這個憑證適用於 *.xxx.xxx.com 的子域名,並存儲在 test-ca-tls secret 中
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test-ca-tls
  namespace: test-ca
spec:
  secretName: test-ca-tls
  duration: 876000h # 憑證有效期為100年
  renewBefore: 720h # 憑證過期前30天自動更新
  dnsNames:
    - "*.xxx.xxx.com" # 包含 xxx.xxx.com 所有子域名
  commonName: "*.xxx.xxx.com"
  subject:
    organizations:
      - MomoChenIsMe Co., Ltd. # 公司名稱
    countries:
      - TW # 國家代碼:台灣
    localities:
      - Tainan # 當地城市:台南
    organizationalUnits:
      - R&D Department # 部門名稱:研發部門
  issuerRef:
    name: ca-issuer # 使用之前創建的 CA Issuer 來簽發此憑證
    kind: Issuer
  usages:
    - digital signature # 用於數字簽名
    - key encipherment # 用於密鑰加密
    - server auth # 用於服務器身份驗證
    - client auth # 用於客戶端身份驗證
接著在Ingress中就可以使用自簽名憑證。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ca-ingress
  namespace: wad-k8s
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - abc.xxx.xxx.com
      secretName: test-ca-tls # TLS 憑證
  rules:
    - host: abc.xxx.xxx.com
      http:
        paths:
          - path: /testca(/|$)(.*)
            pathType: Prefix
            backend:
              service:
                name: test-ca-service
                port:
                  number: 80 # 服務端口


讓Containerd信任的Private Registry使用自簽名憑證

如果Private Registry是架設在K8s集群中,而且Private Registry端點剛好也使用K8s集群中的Ingress,此時Registry上的Image是無法被本身集群或是其它集群拉取的,Pod中會出現「x509: certificate signed by unknown authority」的自簽名憑證不被信任的錯誤。

為了解決這個問題,首先,我們需要從K8s集群中導出這個Registry的自簽名CA憑證。我們可以從上面創建的CA憑證Secret中提取出憑證。
kubectl get secret test-ca-tls -n test-ca -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
接著將導出的憑證擺放到要信任的K8s集群伺服器底下的「/usr/local/share/ca-certificates/」路徑。若是相同的主機則可以直接使用指令複製。
sudo cp ca.crt /usr/local/share/ca-certificates/
擺放到正確路徑後需要更新伺服器憑證。
sudo update-ca-certificates
接著修改Containerd中的憑證設定。
vim /etc/containerd/config.toml
將信任的憑證路徑加在「[plugins."io.containerd.grpc.v1.cri".registry.configs]」底下。
version = 2
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    [plugins."io.containerd.grpc.v1.cri".registry]
      [plugins."io.containerd.grpc.v1.cri".registry.configs]
        [plugins."io.containerd.grpc.v1.cri".registry.configs."xxx.xxx.com".tls]
          ca_file = "/usr/local/share/ca-certificates/ca.crt"
          insecure_skip_verify = false         
設定完成後,需要重啟Containerd服務才能夠運作。
systemctl restart containerd
💡 注意!若要使用自簽名CA憑證,就必須在所有的K8s集群中都註冊指定的自簽名CA憑證(Control Plane和所有的Node都要)。


以上就是2024年版本的K8上集群安裝方式。