# 使用 kubeadm 安裝 K8s
- 本文將使用 Kubeadm 建立有兩個節點的 k8s 叢集
- 一個 master 節點和一個 worker 節點
- 使用 **Containerd** 作為 k8s 的 CRI
## 硬體環境
- 需要多台實體機器,或是使用 VM 模擬多台機器
- 至少需要兩個節點,才能形成完整的叢集
- 最小硬體需求:
- CPU: 2 cores
- RAM: 2 GB
- 網路環境
- 必須可以連上網際網路
- 所有節點之間可以互相連線 (在相同的 LAN 中、或有公網 IP、或是可以透過 router 互連)
- 可以用 `ping` 指令檢驗所有節點是否可以互連
- 作業系統
- 以 Linux 為佳,以下將以 Ubuntu 環境示範
### 網路資訊
:::warning
- 下列為本文操作中的兩個節點的網路資訊
- 於自己的環境操作時,請記得代換成你自己環境上的資訊
:::
- Hostname
- Master Node: `k8s-master`
- Worker Node: `k8s-1`
- IP address:
- Master Node: `192.168.56.80`
- Worker Node: `192.168.56.81`
- 網卡名稱: `enp0s8`
## 設定 DNS (所有 Node)
- 使用文字編輯器修改 */etc/hosts* 設定檔
```bash
sudo nano /etc/hosts
# 或
sudo vim /etc/hosts
```
- 加入其他節點的名稱和對應的 IP
- 以我的環境為例
- Master Node: 名稱為 k8s-master,IP 為 192.168.56.80
- Worker Node: 名稱為 k8s-1,IP 為 192.168.56.81
- 在設定檔中應加入
```
192.168.56.80 k8s-master
192.168.56.81 k8s-1
```
## 關閉 Swap (所有 Node)
### 關閉 Swap 功能
```bash
sudo swapoff -a
```
檢查是否成功關閉,若執行下面指令後沒有任何輸出,則代表關閉成功
```bash
sudo swapon --show
```
:::warning
- `swapoff` 的效果並非永久
- 系統重新啟動後,swap 仍會重新開啟
- 需要執行下方的操作,避免開機時啟用 Swap
:::
### 永久關閉 Swap
#### 修改 fstab,避免開機時掛載 swap 磁區
- 使用文字編輯器 (`nano`, `vim`, ...)開啟 */etc/fstab*
```bash
sudo vim /etc/fstab
# 或
sudo nano /etc/fstab
```
- 將所有包含 `swap` 的行都加上註解 (在該行前加上 `#`)
- 以下面的內容為例,若 */etc/fstab* 的內容如下 (內容僅供參考,實際內容以你自己的機器為主)
```bash=
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sda3 during curtin installation
/dev/disk/by-uuid/85e6b527-dc3f-4921-8004-fbea382a88e0 / ext4 defaults 0 1
/dev/disk/by-uuid/f52bade4-4ae1-44ec-8342-38cea61fd2b0 none swap sw 0 0
# /boot was on /dev/sda2 during curtin installation
/dev/disk/by-uuid/4280fffd-6ec8-4d4e-99fb-90238597a1f1 /boot ext4 defaults 0 1
```
- 第 10 行是一個 swap 磁區的設定 (該行的最右邊有寫著 swap 的欄位),所以要在該行的開頭加上 `#` 將此行註解,結果如下
```bash=
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sda3 during curtin installation
/dev/disk/by-uuid/85e6b527-dc3f-4921-8004-fbea382a88e0 / ext4 defaults 0 1
#/dev/disk/by-uuid/f52bade4-4ae1-44ec-8342-38cea61fd2b0 none swap sw 0 0
# /boot was on /dev/sda2 during curtin installation
/dev/disk/by-uuid/4280fffd-6ec8-4d4e-99fb-90238597a1f1 /boot ext4 defaults 0 1
```
- 記得修改完檔案後要存檔
- 使用 `vim`,先按 Esc 後輸入 `:wq`
- 使用 `nano`,先按 Ctrl+O 再按 Ctrl+X
#### 透過 Systemd 關閉所有 Swap Unit
- 使用 `systemctl` 查詢所有 swap unit
```
sudo systemctl --type=swap -a
```
- 假設輸出如下,代表系統中有一個被啟用的 swap unit,名稱為 ***dev-sda4.swap***
```
UNIT LOAD ACTIVE SUB DESCRIPTION
dev-sda4.swap loaded inactive dead Swap Partition
LOAD = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB = The low-level unit activation state, values depend on unit type.
1 loaded units listed.
To show all installed unit files use 'systemctl list-unit-files'.
```
- 實際名稱可能不同,請以操作時拿到的實際輸出為準
- 使用 `systemctl` 關閉該 swap
```
sudo systemctl mask <swap unit>
```
- `<swap unit>` 要改成實際的 unit 名稱
- 以上面例子為例,指令應為:
```
sudo systemctl mask dev-sda4.swap
```
- 再次使用 `systemctl` 查詢,檢查 swap 是否被關閉
```
sudo systemctl --type=swap -a
```
## 安裝 Docker (所有 Node)
```bash
sudo apt-get update
sudo apt-get 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
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-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
## 啟用 Kernel Module 並設定 Kernel 參數 (所有 Node)
- 在 K8s 的所有節點中,需要執行下列指令載入指定的 Kernel Module,以保證網路傳輸正常
```bash
# 建立 Kernel Module 設定檔
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 載入 Kernel Module
sudo modprobe overlay
sudo modprobe br_netfilter
```
- 在 K8s 的所有節點中,需要執行下列指令設定 Kernel 參數,以保證網路傳輸正常
```bash
# 建立 Kernel 參數設定檔
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
# 載入 Kernel 參數
sudo sysctl --system
```
## 安裝 K8s 套件 (所有 Node)
### 安裝網路工具
```=
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl
```
### 取得 K8s 安裝設定
**下面以 1.32 版為例**,其他版本可參考官方說明文件: https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
```bash
sudo mkdir /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/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.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
```
### 安裝 kubelet, kubeadm, kubectl
- 安裝預設版本
```
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
```
- 可以在套件名稱之後加上 `=<version>` 指定安裝的版本
```
sudo apt install -y kubelet=1.32.2-1.1 kubeadm=1.32.2-1.1 kubectl=1.32.2-1.1
```
- 查詢可用的版本號
```
sudo apt list -a kubelet
```
- (可選) 鎖定版本,避免自動更新或升級
```
sudo apt-mark hold kubelet kubeadm kubectl
```
## 設定 Containerd (所有 Node)
- 建立 Containerd 的設定檔資料夾
```
sudo mkdir /etc/containerd
```
- 將預設的設定內容寫入設定檔案中
```bash
sudo sh -c "containerd config default > /etc/containerd/config.toml"
```
- 修改設定檔的內容,啟用 SystemdCgroup
```bash
sudo sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml
```
- 重啟 containerd 和 kubelet
```
sudo systemctl restart containerd
sudo systemctl restart kubelet
```
## 建立 K8s Cluster (Master Node)
### Pull 必要的 Image
- 以下操作僅在 Master Node 執行
- Pull 所有 kubeadm 需要的 Image
```
sudo kubeadm config images pull
```
:::warning
- 這個步驟需要下載一些檔案,可能需要較長時間
- 實際需要的時間取決於網路速度,請耐心等待
:::
### 初始化 Cluster
```bash
sudo kubeadm init \
--apiserver-advertise-address=<node ip address> \
--pod-network-cidr=<pod network cidr>
```
- `<node ip address>` 為 master 節點的 IP address (且此 IP address 必須能和其他節點溝通)
- `<pod network cidr>` 為日後 pod 所使用的 IP 區段,可以自己定義
- 格式為 `XXX.XXX.XXX.XXX/OO`,需符合 [CIDR](https://zh.wikipedia.org/zh-tw/%E6%97%A0%E7%B1%BB%E5%88%AB%E5%9F%9F%E9%97%B4%E8%B7%AF%E7%94%B1)
- 須注意使用的 IP 區段不能和機器實際使用的 IP 區段重疊
- 建議使用私有區段,例如 `10.XX.0.0/16`
- 舉例來說
- 假設 master node 的 IP 為 192.168.56.81
- 希望 pod 使用的 IP 區段為 10.244.0.0/16 (即 10.20.0.0~10.244.255.255)
- 初始化的指令應為
```
sudo kubeadm init \
--apiserver-advertise-address=192.168.56.81 \
--pod-network-cidr=10.244.0.0/16
```
- 初始化完成後,輸出的最後兩行是加入 worker 節點的指令,請先複製此指令並存在任意檔案中,在之後可以用到
- 該指令應類似:
```
kubeadm join 10.0.2.81:6443 --token XXXXXXX \
--discovery-token-ca-cert-hash sha256:XXXXXXXXX
```
### 建立管理員設定檔
- 初始化完成後,會跳出如何設定的提示
```
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
- 完成設定後,即可使用 `kubectl` 指令控制 cluster
- 查看節點狀態
```bash
kubectl get node
```
- 輸出應類似:
```
NAME STATUS ROLES AGE VERSION
k8s-master NotReady control-plane 1m v1.27.6
```
- NotReady 是因為還未安裝 CNI,在下一篇筆記中會提到
## Worker Node 加入 Cluster (Worker Node)
- 在 Worker Node 中,執行剛剛複製的指令,即可將 worker node 加入 cluster 中
- 由於加入 node 的 token 是有時效性的,若已過期或是沒有存到指令,可以在<font color='red'> master node </font> 執行下面指令,產生新的 join 指令
```
kubeadm token create --print-join-command
```
- 執行下面指令,查看 cluster 的所有節點,現在應有兩個節點
```
kubectl get node
```
## 安裝 Calico 作為 CNI
- 在建立 cluster 之後,需要在 cluster 中部屬 CNI (container networking interface) 才能讓 cluster 開始運作
- 本文將使用 Calico 作為 CNI
### 下載 yaml (Mater Node)
- 到 Calico 的 Github 找到 calico 的 yaml 檔,Calico 的所有 yaml 會放在 manifests 底下
- https://github.com/projectcalico/calico/tree/master/manifests
- 找到 [calico.yaml](https://github.com/projectcalico/calico/blob/master/manifests/calico.yaml),打開該檔案的原始檔,複製它的 URL

- 在 master node 使用 `wget` 下載 yaml 檔
```
wget https://raw.githubusercontent.com/projectcalico/calico/master/manifests/calico.yaml
```
### 修改 yaml
- 使用文字編輯器修改 calico.yaml
```bash
nano ./calico.yaml
# 或
vim ./calico.yaml
```
- 找到以下區段
- 可以搜尋 `# Auto-detect the BGP IP address.`

- 在相同的層級插入以下兩行
```yaml
- name: IP_AUTODETECTION_METHOD
value: "interface=<network interface>"
```
- `<network interface>` 請替換成你正在使用的網卡名稱,請選擇可以和其他節點互連的那張網卡
- 縮排要對齊 `# Auto-detect the BGP IP address.`

- 以我的為例,所有節點上的網卡 `enp0s8`,都有 IP 位址 192.168.56.XX,所以我要填入 `enp0s8`
- 修改完成後存檔
### 部屬 Calico (Master Node)
- 使用 `kubectl` 部屬修改好的 yaml 檔
```
kubectl apply -f ./calico.yaml
```
- 完成部屬後(可能需要等幾分鐘),所有節點的狀態應為 `Ready`
```
kubectl get node
```