先前參加SRE Conference時,認識了CNTUG(Cloud Native Taiwan User Group)這個開源社群,除了推廣雲端原生的相關技術以外,也提供Lab讓大家能夠申請、在上面做一些很難在自己本機上面的實驗(例如kubernetes cluster的建立),很幸運地前陣子遞交的Lab申請通過了,就也打算來寫一篇文章記錄整個實驗的架設與心得。
環境準備
參考了CNTUG網站與kubernetes官方網站上面關於VM硬體條件的文件,這次在openstack上架了四台VM,一台bastion host、一台control plane(m0)、兩台worker node(n0, n1)。 四台VM分別的硬體條件如下:
IP Address | Server Name | Role | CPU | Ram | OS |
---|---|---|---|---|---|
192.168.200.100 | bastion-host | Bastion Host | 2 | 2G | Ubuntu 22.04 |
192.168.200.101 | k8s-m0 | Master Node - 0 | 4 | 4G | Ubuntu 22.04 |
192.168.200.102 | k8s-n0 | Worker Node - 0 | 4 | 4G | Ubuntu 22.04 |
192.168.200.103 | k8s-n1 | Worker Node - 1 | 4 | 4G | Ubuntu 22.04 |
網路設定
準備兩張網卡:public與private。本實驗環境會將kubernetes cluster都放在內網,僅讓bastion host做對外的連線。
在拿到openstack的帳號時,public網卡已經先幫我們建立好了,接下來要自己手動新增內網,並且能讓內網去連接到外網。在設定內網時,記得勾選「啟用DHCP」讓每個加進這個網路的VM都會被自動分配到唯一的ip位址。
- Public Network:
- Public IPv4: 103.122.XXX.0/23
- Private Network:
- 子網路名稱:private-net
- 網路位址:192.168.200.0/24
- IP版本:IPv4
- 閘道IP(Gateway IP):192.168.200.1
- 靜態路由(內網所有的設備要訪問任何不在內網的位址時,都把封包送到下一跳點的位址):
- 目標CIDR:0.0.0.0/0
- 下一跳點:103.122.117.XXX (Public gateway IP)
安全性群組設定
方才我們透過網路來設定各個網路設備的連接,那麼接下來進一步地透過安全性群組來管理端口與每台VM的流量。
除了kubernetes本身需要開啟的端口以外,我會希望可以透過bastion host來ssh到cluster裡面的每個node,在內網的所有intance也需要互相連結。除此之外,bastion host也要開一個對外的22 port讓我可以從本地進行操作。
Control Plane和Worker Node的port全部都參考官網建議去設置:kubernetes: Ports and Protocols
最後,會有四個安全性群組:(所有的outbound都設置為 0.0.0.0/0,故以下僅列出inbound rule)
Security Group | Ether Type | IP protocol | Port Range | Remote IP Prefix | Remote Security Group | Description |
---|---|---|---|---|---|---|
Public SSH | IPv4 | TCP | 22 (SSH) | (自己的電腦IP) | NULL | NULL |
Private SSH | IPv4 | TCP | 22 (SSH) | 192.168.200.0/24 | NULL | NULL |
Master node | IPv4 | TCP | 2379 - 2380 | 192.168.200.0/24 | etcd server client API | |
IPv4 | TCP | 6443 | 192.168.200.0/24 | Kubernetes API server | ||
IPv4 | TCP | 10250 | NULL | Master node | Kubelet API | |
IPv4 | TCP | 10257 | NULL | Master node | kube-controller-manager | |
IPv4 | TCP | 10259 | NULL | Master node | kube-schedule | |
Worker node | IPv4 | TCP | 10250 | NULL | Worker node | Kubelet API Self |
Worker node | IPv4 | TCP | 10256 | NULL | Worker node | kube-proxy |
IPv4 | TCP | 30000 - 32767 | 0.0.0.0/0 | Worker node | NodePort Services |
Master Node中的etcd
元件是一個分散式的key-value資料庫,會保存kubernetes cluster裡面的所有資料,cluster裡面的所有狀態——例如Pod, Volume, Service的當前狀態——都會保存在這個資料庫中,由於在Worker Node的狀態會被回傳到etcd元件,因此開放讓內網裡所有的IP都能連線。
在Master Node中另一個會需要開放給內網所有instance的原件還有Kubernetes API server
,它讓使用者可以直接與cluster進行互動,包含查詢狀態、建立、更新、刪除cluster內部的資源等。同時由於我們先前已讓bastion host來處理所有來自外部的連線,因此在這邊的IP也是設定為內網IP即可。
VM設置
網路與安全性群組都設定好後,cluster裡面的VM全都插入private網卡,再根據master/worker node分別分派各自的安全性群組。bastion host則插入public與private兩張網卡,以便與外部和內部網路進行連接。
openstack的網路設定有個小坑:bastion-host加入private網卡後,依然沒有辦法ping到內網的任何一台VM。經由ip addr
確認後證實雖然openstack的UI介面顯示已經加入網卡了,但實際上private網卡並沒有設定到。
這時候可以手動修改設定檔:
sudo vim /etc/netplan/50-cloud-init.yaml
在設定檔中手動加入網卡:
network:
ethernets:
enp3s0:
dhcp4: true
match:
macaddress: fa:16:3e:4f:f7:6c
set-name: enp3s0
enp3s1: # 新增的配置
dhcp4: true
match:
macaddress: fa:16:3e:1f:77:9f
set-name: enp3s1 # 指定新的網卡名稱
version: 2
(以上做法是看tico大大的部落格解決的:https://ithelp.ithome.com.tw/articles/10293768)
Key generation
我們希望可以從本地電腦連線到bastion-host,也希望bastion-host可以連線到cluster裡面的任一個node。
- 先在本地產一個key,把public key上傳到openstack的密鑰對。接著在設置VM時都加入這個密鑰對
- 把本地的private key用ssh的方式傳到bastion host
ssh -i <PRIVATE_KEY_PATH> ubuntu@<PUBLIC_IP>
到這邊,確定四台VM的網路設置沒問題後,才真正可以來架設kubernetes cluster了。
kubespray
kubespray是一個開源專案,用來進行kubernetes cluster的創建。自version 2.3開始,kubespray的內部採用與kubeadm相同的生命週期管理,再透過Ansible Playbook來調用kubeadm來做叢集的建置。 kubeadm則是由官方開發維護,用來建立原生kubernetes環境的工具。因此,使用kubespray不僅兼顧了部署的方便性,也兼顧了kubernetes生命週期管理的穩定性。
因此在安裝kubespray之前,要先在機器上面安裝Ansible以及Ansible所需要的Python環境。相關安裝步驟我是參考kubespray官方文件:Installing Ansible
版本資訊
- Python3 v3.10.4
- Ansible v9.5.1
- kubespray v2.25.0
安裝步驟
以下的安裝步驟,若無特別標註,就都是在bastion host來做執行。
由於從openstack拿到的VM裡面已有內建Python,在這邊可以先裝Python的虛擬環境。
ubuntu@bastion-host:~$ sudo apt update
ubuntu@bastion-host:~$ sudo apt install python3-virtualenv
ubuntu@bastion-host:~$ virtualenv --version
virtualenv 20.13.0+ds from /usr/lib/python3/dist-packages/virtualenv/__init__.py
接下來就可以把kubespray專案clone下來,這邊我裝的版本是撰寫當下的最新版本2.25.0。
git clone --depth 1 --branch v2.25.0 https://github.com/kubernetes-sigs/kubespray.git
安裝Ansible
VENVDIR=kubespray-venv #指定virtual env位置
KUBESPRAYDIR=kubespray # kubespray資料夾位置
python3 -m venv $VENVDIR
source $VENVDIR/bin/activate
cd $KUBESPRAYDIR
pip install -U -r requirements.txt # 安裝Ansible所需套件
如果順利的話,到這邊就可以看到(kubespray-env)已經被啟動了!
在這邊我們會先把cluster的配置文件拷貝出來,創建一個自己的叢集目錄,方便配置與管理。
cp -rfp inventory/sample inventory/mycluster
接著,進入到mycluster目錄中,編輯inventory.ini。裡面的設定檔就是我們server相關資訊的設定,其餘的參數代表意義可以參考kubespray的官方文件。
# ## Configure 'ip' variable to bind kubernetes services on a
# ## different ip than the default iface
# ## We should set etcd_member_name for etcd cluster. The node that is not a etcd member do not need to set the value, or can set the empty string value.
[all]
# 根據文件範例設定,server_name ansiblehost=<IP> ansible_user=<USERNAME>
k8s-m0 ansible_host=192.168.200.101 ansible_user=ubuntu
k8s-n0 ansible_host=192.168.200.102 ansible_user=ubuntu
k8s-n1 ansible_host=192.168.200.103 ansible_user=ubuntu
[kube_control_plane]
k8s-m0
[etcd]
# etcd的位置,在這邊就是與master node是在同一個位置
k8s-m0
[kube_node]
k8s-n0
k8s-n1
[calico_rr]
[k8s_cluster:children]
kube_control_plane
kube_node
calico_rr
目前搭建的實驗環境資源有限,因此也將etcd放在master node上。master node目前也沒有做到HA的設計,是以單節點的方式存在。
也由於master node以單節點的方式存在,不需要Load Balancer API server,我們進到inventory/mycluster/group_vars/all/all.yml
把Line 20的loadbalancer_apiserver_localhost
修改為False
。
接著,再進到inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
把Line 160 的cluster_name
修改成自己的cluster name。
最後啟動ansible進行部署。
# ansible-playbook -i <INVENTORY_FILE> --private-key=<PRIVATE_KEY> --become --become-user=root cluster.yml
ansible-playbook -i inventory/mycluster/inventory.ini --private-key=~/.ssh/private.key --become --become-user=root cluster.yml
這邊的private key就是在前面文章中,我們要從bastion host連線到cluster node的那把private key。
在bastion host完成叢集的創建後,日後想要存取叢集需要拿取master node上面的token才行,因此我們先從bastion host SSH到master node。
ssh -i ~/.ssh/private.key ubuntu@192.168.200.101
先前提到kubespray是透過ansible做自動化部署,但底層仍舊遵循kubeadm的lifecycle。/etc/kubernetes/admin.conf
在kubeadm初始化叢集時便會創建,可以算是使用者與Kubernetes API Server進行溝通的token。
我們在操作叢集資源時一般都是使用kubectl
來做到,為了要讓kubectl可以使用這個token來做資源操作,我們要修改一下權限,並且把token丟回bastion host,這樣在bastion host也可以使用kubectl 操作資源。
ubuntu@k8s-m0:~$ sudo cp /etc/kubernetes/admin.conf ~/
ubuntu@k8s-m0:~$ sudo chown ubuntu:ubuntu ~/admin.conf # 將user/group修改成當前用戶,這樣不需要sudo 也能操作token
ubuntu@k8s-m0:~$ mkdir -p .kube #kubectl 會從這個目錄裡面來讀取相關的config文件
ubuntu@k8s-m0:~$ mv ~/admin.conf ~/.kube/config #將原本kubeadm創建的token移到能被kubectl 讀取的地方
確定能讀取到cluster的訊息後,再把~/.kube/config scp到bastion host的相同目錄下,讓kubectl可以讀取。
(kubespray-venv) ubuntu@bastion-host:~$ mkdir -p ~/.kube
(kubespray-venv) ubuntu@bastion-host:~$ scp -i ~/private.key ubuntu@192.168.200.101:~/.kube/config ~/.kube/config
另外要記得的是,config檔的line 5記錄著API Server的位置,因為我們是直接從master node將文件複製到bastion host,在這邊要記得把127.0.0.1
改成192.168.200.101
也就是master node的內網IP。
安裝kubectl
這邊要注意的是kubectl的版本要和cluster的版本相同(可以差到一個minor version)。 查看完cluster版本後就直接按普通方式安裝即可。
# 從kubectl get node就能查看cluster版本為v1.29.5
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-m0 Ready control-plane 24h v1.29.5
k8s-n0 Ready <none> 24h v1.29.5
k8s-n1 Ready <none> 24h v1.29.5
# download a specific version of kubectl
curl -LO "https://dl.k8s.io/release/v1.29.5/bin/linux/amd64/kubectl"
# Download the kubectl checksum file:
curl -LO "https://dl.k8s.io/v1.29.5/bin/linux/amd64/kubectl.sha256"
# validate the binary file
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
# check ok後就可以正式安裝
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# 安裝後,不需要的檔案可以刪除
rm kubectl*
在bastion host下kubectl get node
指令,大功告成!
踩坑紀錄
前面才剛提供一個private網卡踩坑的解決辦法,結果隔天上bastion host就突然沒辦法讀取cluster的資源了。一查看發現是private那張網卡的設定又跑掉了。
ubuntu@bastion-host:~$ ip addr show enp3s1
3: enp3s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1442 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:1f:77:9f brd ff:ff:ff:ff:ff:ff
inet6 fe80::f816:3eff:fe1f:779f/64 scope link
valid_lft forever preferred_lft forever
由上面資訊可以發現它的IPv4配置不見了。因此我又手動加了回去。
ubuntu@bastion-host:~$ sudo ip addr add 192.168.200.100/24 dev enp3s1
ubuntu@bastion-host:~$ ip addr show enp3s1
3: enp3s1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1442 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:1f:77:9f brd ff:ff:ff:ff:ff:ff
inet 192.168.200.100/24 scope global enp3s1
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe1f:779f/64 scope link
valid_lft forever preferred_lft forever
成功!但不確定是不是openstack的網路設置本就有一些問題,先把這個坑紀錄,之後看看會不會再發生。
ubuntu@bastion-host:~$ ping 192.168.200.101
PING 192.168.200.101 (192.168.200.101) 56(84) bytes of data.
64 bytes from 192.168.200.101: icmp_seq=1 ttl=64 time=5.44 ms
64 bytes from 192.168.200.101: icmp_seq=2 ttl=64 time=1.41 ms
64 bytes from 192.168.200.101: icmp_seq=3 ttl=64 time=1.06 ms
64 bytes from 192.168.200.101: icmp_seq=4 ttl=64 time=0.696 ms
Reference
Installing kubeadm https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#before-you-begin
關於我怎麼把一年內學到的新手 IT/SRE 濃縮到 30 天筆記這檔事 https://ithelp.ithome.com.tw/users/20112934/ironman/5640
Comparison - Kubespray vs Kubeadm https://github.com/kubernetes-sigs/kubespray/blob/master/docs/getting_started/comparisons.md
Installing Ansible https://github.com/kubernetes-sigs/kubespray/blob/v2.19.1/docs/ansible.md#inventory
Install and Set Up kubectl on Linux https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
CNTUG Infra Labs 說明文件 https://docs.cloudnative.tw/docs/category/%E5%9F%BA%E7%A4%8E%E6%95%99%E5%AD%B8
OpenStack 上利用 kubeadm 搭建 K8S Cluster https://docs.cloudnative.tw/docs/self-paced-labs/kubeadm/