Kubernetes - 到處碰壁的 K8s 架設記錄

Kubernetes - 到處碰壁的 K8s 架設記錄

jimchen5209

前言

最近開始學習 K8s,想說來架設一個環境來練習,沒想到架設過程中碰到了不少問題。
官方文件寫得有點複雜,網路上的教學也是各種版本,而且有些教學是過時的無法使用。
因此決定寫下這篇文章,來記錄一下架設過程。

K8s 簡介

Kubernetes(簡稱 K8s,由來是 K 和 s 之間有 8 個字母)。
是用於自動部署、擴充和管理「容器化(containerized)應用程式」的開源系統。
在 Production 環境中,需要管理執行應用程式的容器並確保不會出現停機。
例如,如果一個容器發生故障,則需要啟動另一個容器。
而 Kubernetes 提供了一個彈性運作分散式系統的框架。負責應用程式的擴展和故障轉移,提供部署模式等等。

提供的功能

  • 服務發現(Service discovery)與負載平衡
    Kubernetes 可以使用 DNS 名稱或使用自己的 IP 位址公開容器。
    如果容器的流量很高,Kubernetes 能夠進行負載平衡並分配網路流量,使部署穩定。
  • 儲存協調
    Kubernetes 可讓您自動掛載您選擇的儲存系統,例如:本機儲存、公有雲等。
  • 自動佈署與回滾
    您可以使用 Kubernetes 來描述已部署容器的所需狀態,並且它可以以受控的速率將實際狀態變更為所需狀態。
  • 自動資源打包
    Kubernetes 可以將容器安裝到您的節點上,以充分利用您的資源。
  • 自我修復
    Kubernetes 會重新啟動發生故障的容器、替換容器、終止不回應使用者定義的運作狀況檢查的容器。
  • 機密資料與設置管理
    Kubernetes 可讓您儲存和管理敏感資訊。
  • 批次執行
    Kubernetes 還可以管理您的批次和 CI 工作負載,並根據需要替換失敗的容器。
  • 水平縮放 (Horizontal scaling)
    使用簡單的指令、UI 或根據 CPU 使用情況自動增加、減少部屬的應用程式數量。
  • IPv4/IPv6 雙堆疊
    將 IPv4 和 IPv6 位址指派給 Pod 和服務。
  • 專為可擴充性而設計
    無需更改上游原始碼即可為 Kubernetes Cluster 新增功能。

架構

K8s 架構圖
K8s 架構圖

Control Plane

Control Plane 是 Kubernetes Cluster 的大腦。
負責制訂全域的策略(如:調度)以及 Cluster 狀態(如:工作負載)的維護。
Control Plane 由以下元件組成:

  • kube-apiserver
    API 伺服器是一個關鍵元件,使用 Kubernetes 來提供 Kubernetes 的內部和外部介面。
  • etcd
    Kubernetes 所有 Cluster 資料的備援儲存。
  • kube-scheduler
    負責 Cluster 的資源調度。
  • kube-controller-manager
    與 API 伺服器進行通訊以在需要時建立、更新和刪除他們管理的資源。

Node

Node 是 Kubernetes Cluster 的工作機器,可以是虛擬機器或物理機器。
每個 Node 包括以下元件:

  • kubelet
    在 Cluster 中的每個 Node 上執行,負責與 API 伺服器通訊並管理 Pod 和容器。
  • kube-proxy
    是 Kubernetes Service 的網路代理,負責管理 Node 的網路通訊。
  • Container Runtime Interface (CRI)
    Node 上運行容器的軟體,支援 Docker、Containerd、CRI-O。

環境準備

這邊安裝的是正式的 K8s,不是 Minikube 或 MicroK8s,因為這兩個是單一節點的架構,相對單純。
本文使用 VMware Workstation 來建立虛擬機器,並使用 Ubuntu Server 24.04 作為作業系統。
本文將不詳細介紹 VMware Workstation 的虛擬機器的安裝、網路設定與 Ubuntu 作業系統的安裝。
安裝的是最新的 K8s 版本 1.29.3,使用 kubeadm 安裝。

最低需求

Control Plane

  • 2 CPU
  • 4 GB RAM

Node

  • 1 CPU
  • 2 GB RAM

安裝 K8s

安裝前置作業(所有節點)

  1. 更新作業系統

    1
    2
    sudo apt update
    sudo apt upgrade
  2. 關閉 swap
    kubelet 目前不支援 swap,因此需要關閉 swap。

    1
    2
    sudo swapoff -a     # 關閉 swap
    sudo vim /etc/fstab # 永久關閉 swap

    將 swap 的那一行註解掉。

    註解掉 swap
    註解掉 swap

  3. 設定 hosts (可選)
    為了方便管理,可以設定 hosts。

    1
    sudo vim /etc/hosts

    加入以下內容:

    1
    <ip> <hostname>

    例如:

    1
    2
    192.168.129.21 k8s-master
    192.168.129.22 k8s-node-1
  4. 依需求設定網卡

    1. 網卡名稱需為 eth0 (for calico cni)
      可以使用以下指令查看網卡名稱:

      1
      ip a

      若網卡名稱不是 eth0,則需要修改網卡名稱。

      這邊直接修改系統的網卡命名模式:

      1
      sudo vim /etc/default/grub

      GRUB_CMDLINE_LINUX 新增 net.ifnames=0

      然後更新 grub:

      1
      sudo grub-mkconfig -o /boot/grub/grub.cfg

      接著同步更新 netplan 的網卡名稱,將原本的 ens33 改為 eth0

      1
      sudo vim /etc/netplan/00-installer-config.yaml


      之後重新啟動。

      1
      sudo reboot

      之後再次查看網卡名稱,就能發現網卡名稱已經改為 eth0

      1
      ip a

    2. 轉送 IPv4 並讓 iptables 查看橋接流量
      直接根據官方文件 執行以下指令來新增核心模組與設定:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
      overlay
      br_netfilter
      EOF

      sudo modprobe overlay
      sudo modprobe br_netfilter

      # sysctl params required by setup, params persist across reboots
      cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
      net.bridge.bridge-nf-call-iptables = 1
      net.bridge.bridge-nf-call-ip6tables = 1
      net.ipv4.ip_forward = 1
      EOF

      # Apply sysctl params without reboot
      sudo sysctl --system

      之後透過以下指令驗證模組是否載入成功:

      1
      2
      lsmod | grep br_netfilter
      lsmod | grep overlay


      以及驗證以下設定的值是否都為 1

      1
      sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

  5. 安裝 Container Runtime Interface (CRI)
    這邊使用的 CRI 是 Containerd,透過 docker 官方的 repository 來安裝:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Add Docker's official GPG key:
    sudo apt update
    sudo apt install ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc

    # Add the repository to Apt sources:
    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
    sudo apt update

    # Install containerd
    sudo apt install containerd.io

    安裝 Containerd 的 CNI Plugin

    1
    2
    3
    wget https://github.com/containernetworking/plugins/releases/download/v1.4.1/cni-plugins-linux-amd64-v1.4.1.tgz
    sudo mkdir -p /opt/cni/bin
    sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.4.1.tgz

    建立 Containerd 的設定檔。

    1
    2
    3
    4
    # 備份原本的設定檔
    sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.backup
    # 建立預設設定檔
    containerd config default | sudo tee /etc/containerd/config.toml

    編輯 /etc/containerd/config.toml,將 runc.options 裡面的 SystemdCgroup 改成 true

    1
    sudo vim /etc/containerd/config.toml
    1
    2
    3
    4
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
    ...
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    SystemdCgroup = true


    之後啟動 Containerd 並將其設定為開機啟動。

    1
    sudo systemctl enable --now containerd
  6. 安裝 kubeadm
    直接根據官方文件 安裝 kubeletkubeadm 以及 kubectl,並且鎖定版本,因為 K8s 需要特殊的升級流程。

    1
    2
    3
    4
    5
    6
    7
    sudo apt update
    sudo apt install -y apt-transport-https ca-certificates curl gpg
    curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/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.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
    sudo apt update
    sudo apt install -y kubelet kubeadm kubectl
    sudo apt-mark hold kubelet kubeadm kubectl
  7. 修改 calico 的預設 CIDR
    因為 calico 的預設 CIDR 是 192.168.0.0/16 與我們的網段衝突,因此需要修改 calico 的設定。

    先用 wget 下載 calico 的 YAML 下來更改。
    找到 192.168.0.0/16 之後,取消註解並改成 172.30.0.0/16 (或其他未使用的網段)。

    1
    2
    wget https://docs.projectcalico.org/manifests/calico.yaml
    vim calico.yaml

安裝 Control Plane (僅 Control Plane 節點)

正式初始化 Control Plane。

1
sudo kubeadm init --pod-network-cidr=172.30.0.0/16 --apiserver-advertise-address=192.168.129.21
  • pod-network-cidr 為 calico 的網段,用來分配給 Pod 使用。
  • apiserver-advertise-address 為 API Server 的 IP 位址。
    出現以下訊息代表完成安裝。

    此時可以根據提示執行以下指令,讓你的 user 可以控制 k8s。
    1
    2
    3
    mkdir -p $HOME/.kube
    sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
    sudo chown $(id -u):$(id -g) $HOME/.kube/config
    另請記下以下指令,這是用來讓 Node 加入 Cluster 的指令(此指令會在 24 小時候到期)。

安裝 Node (僅 Node 節點)

貼上剛剛在 Control Plane 上的指令(記得加上 sudo),讓 Node 加入 Cluster。

1
sudo kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>

例如:

1
sudo kubeadm join 192.168.129.21:6443 --token nvfpx7.tj0px32qh0i7a7fc --discovery-token-ca-cert-hash sha256:4c6eee8f79716a71d5f2b270b23748b4ed92d6362506048a0c74e5b0b324a781

之後,回到 Control Plane,檢查 Node 是否已經加入 。

1
kubectl get nodes -o wide


如果有出現代表成功,不過你應該發現狀態是 NotReady 了,因為我們還沒有安裝 CNI Plugin。

遇到加入時卡住怎麼辦?

有可能是因為 token 過期了,可以先在 Control Plane 上執行以下指令確認:

1
kubeadm token list
如果 token 確實過期了,可以透過以下指令重新產生一個 token:
1
kubeadm token create
會輸出類似以下訊息:
1
5didvk.d09sbcov8ph2amjw
如果你遺失了 hash,可以透過以下指令查詢:
1
2
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'
會輸出類似以下訊息:
1
4c6eee8f79716a71d5f2b270b23748b4ed92d6362506048a0c74e5b0b324a781
此時就可以套用新的 token 和 hash 再次執行 Node 加入 Cluster 的指令了。

安裝 CNI Plugin (僅 Control Plane 節點)

這邊我們使用 calico 作為 CNI Plugin。
在 Control Plane 上執行以下指令:

1
kubectl apply -f calico.yaml

然後透過以下指令監看 calico 的狀態:

1
watch kubectl get pods -n kube-system

等待 calico 開頭的 Pod 皆為 Running 狀態,代表安裝完成。

安裝完成後,再次檢查 Node 的狀態:

1
kubectl get nodes -o wide


此時 Node 的狀態應該已經是 Ready 了。

後記

部屬過程中,碰壁的地方大多都是 CRI 與 CNI。
CRI 一開始我是直接裝 Docker,後來看到文件說 Dockershim 支援已經移除,因此改裝 Containerd。
另外不少教學是用 cgroupfs 當作 cgroup driver,那時還需要修改 kubelet 的設定,但官方建議用 systemd(預設也是 systemd),並且 Containerd 內建 systemd 的 cgroup driver,因此也嘗試改為 systemd。
然後就碰到無法初始化的問題,又看了一次文件,才發現是少了「轉送 IPv4 並讓 iptables 查看橋接流量」這個步驟。
再來是 CNI 的部分,原本要用 weave-net,但沒有成功。後來又參考 calico 官方文件而改用 calico,但 pod 卻開不起來。
最後又查了另一篇教學才發現是 calico 的預設 CIDR 網段跟現有網卡衝突,另外網卡的名稱不同也不吃。因此修改 calico 的設定和網卡名稱之後才成功。
這次的架設過程碰到了不少問題,但也學到了不少東西,在這邊筆記下來,也希望這篇文章能幫助到有需要的人。

參考資料

  • Title: Kubernetes - 到處碰壁的 K8s 架設記錄
  • Author: jimchen5209
  • Created at : 2024-03-27 13:18:38
  • Updated at : 2024-04-14 22:27:41
  • Link: https://jw-blog.jimchen5209.me/2024/03/27/Kubernetes-到處碰壁的-K8s-架設記錄/
  • License: This work is licensed under CC BY-NC-SA 4.0.