Kubernetes 研究筆記

2023-05-29 12:00:44

Kubernetes 研究筆記

在接下來的這篇筆記中,我將會介紹 Kubernetes 這一強大的容器編排工具,並學習其基本使用方法。該筆記將會被儲存在https://github.com/owlman/study_note專案的Software/Container目錄下一個名為的K8s子目錄中。其具體內容將包含:

  • 瞭解 Kubernetes 的核心設計理念和它的基本組成結構;
  • 掌握使用 Kubernetes 構建伺服器叢集的基本工作流程;
  • 掌握如何在伺服器叢集中實現應用程式的容器化運維;

學習規劃

  • 學習基礎:
    • 有一兩門程式語言的使用經驗。
    • 有一定的 Web 開發及維護經驗。
  • 視訊資料:
  • 閱讀資料:
  • 學習目標:
    • 使用 Docker+Kubernetes 釋出並維護自己的私人專案。

Kubernetes 簡介

在實際生產環境中,許多企業級規模的應用程式為了獲得更好的執行效能和負載能力,經常會選擇在多臺裝置組成的伺服器叢集上進行分散式部署,其中涉及到的容器數量可能會多達上百個。如果我們需要在這種伺服器叢集環境中實現應用程式的自動化部署與維護,容器編排工作的難度將會得到進一步增加。為了更好地應對這項工作,我們在這裡會更新於推薦讀者使用 Kubernetes(以下簡稱為 K8s [1])這個更為強大的容器編排工具。

K8s 是 Google 公司於 2014 年推出的一個開源的容器編排工具,它近年來一直被公認為是在伺服器叢集環境中對應用程式進行容器化部署的最佳解決方案。該工具最核心的功能是能實現容器的自主管理,這可以保證我們在伺服器叢集環境中部署的應用程式能按照指定的容器編排規則來實現自動化的部署和維護。換而言之,如果我們想部署一個名為「線上簡歷」的應用程式,就只需要在容器編排檔案中定義好部署該應用程式中各項微服務時所需要建立的容器,以及這些容器之間通訊方案、資料持久化方案、負載均衡方案等規則。然後,K8s、就會和 Docker Compose 一樣自動去範例化並啟動這些容器以及相關網路、資料儲存等基礎設施,並持續確保這些容器的執行狀態,以及按照預定方式對其進行負載均衡,但不同的是,K8s還會根據應用程式中各項微服務的具體負載狀態自動調整相關容器範例在伺服器叢集中的具體執行節點。總而言之,K8s更著重於為應用程式的使用者提供不間斷的服務狀態。

為了更好地實現基於微服務架構的應用程式部署方案,K8s的開發者在設計上對伺服器裝置上計算資源的排程單元進行了一系列高層次的抽象。正是因為有了這些抽象化的資源排程物件,運維人員才能得以像管理單一主機的不同部件一樣管理一個伺服器叢集,因為他們只需要基於一些抽象的資源排程物件來定義應用程式的部署和維護方案,然後交由k8s自行決定如何在物理層面上執行這些方案。所以在具體學習K8s的使用方法之前,我們有必要先了解一下該工具的核心組成結構及其背後的軟體架構。

核心組成結構

K8s 相較於其他容器編排工具的獨到之處在於,它同時在物理組織和軟體架構這兩個層面上對伺服器叢集環境進行了抽象化設計。首先,在面對伺服器叢集中的多臺物理主機時,K8s將應用程式的部署環境抽象化成了一個分散式的軟體管理系統,它在邏輯上將伺服器叢集中的所有物理主機定義為一個主控節點和若干個工作節點。其中,主控節點(Master)用於排程並管理部署在 K8s 系統中的應用程式,而工作節點(Worker)則用於執行具體的容器範例,可被視為供K8s系統排程的計算資源。其具體組成結構如下圖所示。

從上述結構圖中,我們可以看出 K8s 被設計成了一個與 Linux 有幾分相似的分層系統,其核心層包含了以下一系列功能元件。

  • kubelet 元件,用於管理部署在伺服器叢集環境中的所有容器及其映象,同時也負責資料卷和內部網路的管理;
  • proxy 元件:用於對 K8s 中的排程單元執行反向代理、內部通訊、負載均衡等作業;
  • etcd 元件:用於儲存整個伺服器叢集的執行狀態,這些資料通常儲存於主控節點中;
  • API Server 元件:用於負責對外提供伺服器叢集中各類計算資源的操作介面,它同時也是叢集中各元件資料互動和通訊的樞紐,主要用於處理 REST 操作,並在 etcd 元件中驗證、更新相關資源物件的狀態(並儲存);
  • Scheduler 元件:用於負責伺服器叢集中計算資源的排程,其基本原理是先通過監聽 API Server 元件來獲取可排程的計算資源,然後再基於一系列篩選和評優演演算法來對這些資源進行任務分配;
  • Controller Manager 元件: 該元件會基於一種被稱為 Controller 的資源排程概念(我們稍後會詳細介紹它)來實現對伺服器叢集中所有容器的編排作業;
  • Container Runtime 元件:用於管理容器的映象及它們在 K8s 排程單元中的範例化與執行;

除了上述核心元件之外,K8s 在外層還設計有一個開放性的外掛體系,我們還可以根據自己的需要為其安裝不同的外掛。例如:kube-dns 可以用於為整個伺服器叢集提供域名解析服務、Ingress Controller 可為應用程式提供外網入口、coredns 外掛可用於建立伺服器叢集內部網路等。通過利用該外掛系統帶來的可延伸性,運維人員就能實現在邏輯層面上像操作一臺主機中的不同元件一樣對伺服器叢集進行管理,任意為其新增相關的功能。

總而言之,為了給運維人員提供一個可以在多臺伺服器裝置上部署、維護和擴充套件應用程式的自動化機制,K8s 被定義成了一系列鬆耦合的構建模組和具有高度可延伸性的分散式系統。但從某種程度上來說,如果我們想用 K8s 靈活地應對各種工作場景對應用程式負載能力的要求,還必須要要在上述組成結構的基礎上理解 K8s 的軟體架構。也就是說,在具體介紹如何在跨伺服器環境中進行應用程式的部署和維護之前,我們還需要先來了解一下 K8s 在軟體層面上的架構設計。

軟體架構設計

在軟體的架構設計上,K8s 的設計者也針對伺服器叢集中可排程的計算資源進行了抽象。換而言之,我們在 K8s 中所進行的所有運維工作實際上都需要通過以下一系列基於這些抽象的資源物件來完成。

  • Pod:這是在 K8s 中部署應用程式時可排程的最小資源物件,它本質上是針對容器分組部署工作所進行的一種抽象。在K8s的設計中,被部署在同一個 Pod 中的容器將會始終被部署到同一個物理伺服器上,並且每個 Pod 都將會被整個叢集的內部網路自動分配一個唯一的 IP 地址,這樣就可以允許應用程中的不同元件序使用同一埠,不必擔心會發生埠衝突的問題。另外,某些 Pod 還可以被定義成一個獨立的資料卷,並將其對映到某個本地磁碟目錄或網路磁碟,以供其他Pod中的容器存取。

  • ReplicationController:這是一種針對 Pod 的執行狀態進行抽象的資源物件。該物件是早期版本的 K8s 中 ReplicaSet 物件的升級,這兩種物件主要用於確保在任何時候都有特定數量的 Pod 範例處於執行狀態。和 Pod 一樣,我們通常不會直接手動建立和管理這一級的抽象物件,而是直接通過 deployment 等 Controller 物件來對它們進行自動化管理、

  • Controller:在通常情況下,我們雖然也可以通過定義基於 Pod 的容器編排規則和相關的 K8s 使用者端命令來實現對 Pod 的手動排程,但如果想最大限度地發揮 K8s 的優勢,運維人員更多時候會選擇使用更高層次的抽象機制來實現自動化排程。其中,Controller 是一種針對 Pod 或 ReplicationController 的執行狀態進行控制的資源物件。在 K8s 中,內建的 Controller 物件主要有以下五種。

    • deployment:適合用於部署無狀態的服務,例如 HTTP 服務;
    • StatefullSet:適合用於部署有狀態的服務,例如資料庫服務;
    • DaemonSet:適合需要在伺服器叢集的所有節點上部署相同範例的服務,例如分散式儲存服務。
    • Job:適合用於執行一次性的任務,例如離線資料處理、視訊解碼等任務;
    • Cronjob:適合用於執行週期性的任務,例如資訊通知、資料備份等任務;
  • Service:它可以被視為是一種以微服務架構的視角來組織和排程 Pod 的資源物件,K8s 會通過給 Service 分配靜態 IP 地址和域名,並且以輪循排程的方式對應用程式的流量執行負載均衡作業。在預設情況下,Service 既可以被暴露在伺服器叢集的內部網路中,也可以被暴露給伺服器叢集的外部外部網路。

  • namespace:如果我們希望將一個物理意義上的伺服器叢集劃分成若干個虛擬的叢集環境,用於部署不同的應用程式,就可以使用 namespace 這一抽象概念對物理層面上的計算資源加以劃分。

正如之前所說,有了上面介紹的這些資源排程物件,運維人員就可以根據具體的需求來定義應用程式的部署和維護方案了,k8s 將會自行決定如何在物理層面上執行這些方案。接下來,我們的任務就是要帶領大家構建一個基於 K8s 的伺服器叢集,然後演示如何在該叢集環境中定義容器編排規則,並實際部署應用程式。

構建 K8s 伺服器叢集

接下來,我們要為大家演示如何構建一個用於部署「線上簡歷」範例程式的K8s三機叢集。為此,我們需要準備三臺安裝了 Ubuntu 20.04 系統的計算機裝置。在實際生產環境中,我們通常會選擇去實際購買相應的物理裝置或者雲主機。但即使對於一些企業級使用者來說,採用這種方案也會是一筆不小的開銷,用來本書所需要的演示環境就顯得更不經濟了,因此使用虛擬機器器軟體可能是一個更具有可行性的選擇。於是,我們使用 Vagrant+VirtualBox 工具構建出了一個以下設定的伺服器叢集。

主機名 IP地址 記憶體 處理器數量 作業系統
k8s-master 192.168.100.21 4G 2 Ubuntu 20.04
k8s-worker1 192.168.100.22 2G 2 Ubuntu 20.04
k8s-worker2 192.168.100.23 2G 2 Ubuntu 20.04

在完成裝置方面的準備之後,我們接下來的工作是要在上述三臺裝置上安裝與設定 Docker+K8s 環境,並將名為 k8s-master 的主機設定成伺服器叢集的主控節點,而 k8s-worker1 和 k8s-worker2 這兩臺主機則設定為工作節點。為此,我們需要執行以下步驟的操作。

安裝 Docker+K8s 環境

為了讓 K8s 伺服器叢集的搭建過程成為一個可重複的自動化工作流程,我決定使用 Shell 指令碼的方式來完成相關的安裝與設定工作。為此,我們首先需要分別進入到上述三臺主機中,並通過執行以下指令碼檔案來完成 Docker+K8s 環境的安裝與基本設定。

#! /bin/bash

# 指定要安裝哪一個版本的K8s
KUBERNETES_VERSION="1.21.1-00"

# 關閉swap分割區
sudo swapoff -a
sudo sed -ri 's/.*swap.*/#&/' /etc/fstab 

echo "Swap diasbled..."

# 關閉防火牆功能
sudo ufw disable

# 安裝一些 Docker+k8s 環境的依賴項
sudo apt update -y
sudo apt install -y apt-transport-https ca-certificates curl wget software-properties-common

echo "Dependencies installed..."

# 安裝並設定 Docker CE
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update -y
sudo apt install -y docker-ce

cat <<EOF | sudo tee /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"],
"exec-opts":["native.cgroupdriver=systemd"]
}
EOF

# 啟動 Docker
sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker

echo "Docker installed and configured..."

# 安裝 k8s 元件:kubelet, kubectl, kubeadm
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubenetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
sudo apt update -y
sudo apt install -y kubelet=$KUBERNETES_VERSION kubectl=$KUBERNETES_VERSION kubeadm=$KUBERNETES_VERSION

# 如果想禁止K8s的自動更新,可以鎖住上述元件的版本
sudo apt-mark hold kubeadm kubectl kubelet

# 啟動 K8s 的服務元件:kubelet
sudo systemctl start kubelet  
sudo systemctl enable kubelet   

echo "K8s installed and configured..."

在上述指令碼執行完成之後,使用者可以通過執行kubeadm versionkubectl version這兩個命令來確認一下安裝成果,如果這些命令正常輸出了相應的版本資訊,就說明 K8s 已經可以正常使用了。另外在該指令碼檔案中,我們可以看到除了之前已經熟悉了的、用於安裝和設定 Docker CE 的操作之外,它執行的主要操作就是安裝 kubeadm、kubectl 和 kubelet 三個軟體包。其中,kubeadm 是 K8s 叢集的後臺管理工具,主要用於快速構建 k8s 叢集並管理該叢集中的所有裝置,kubectl 是 K8s 叢集的使用者端工具,主要用於在 K8s 叢集中對應用程式進行具體的部署與維護工作,而 kubelet 則是 K8s 叢集部署在其每一臺主機上的伺服器端元件,主要用於響應使用者端的操作並維持應用程式在叢集上的執行狀態。

設定主控節點與工作節點

接下來的工作是為 K8s 叢集設定主控節點與工作節點。為此,我們需要先單獨進入到名為 k8s-master 的主機中,並通過執行以下指令碼檔案來將其設定成叢集的主控節點。

#! /bin/bash

# 指定主控節點的IP地址
MASTER_IP="192.168.100.21"
# 指定主控節點的主機名
NODENAME=$(hostname -s)
# 指定當前 K8s 叢集中 Pod 所使用的 CIDR
POD_CIDR="10.244.0.0/16"
# 指定當前 K8s 叢集中 Service 所使用的 CIDR
SERVICE_CIDR="10.96.0.0/12"
# 指定當前使用的 K8s 版本
KUBE_VERSION=v1.21.1

# 特別預先載入 coredns 外掛
COREDNS_VERSION=1.8.0
sudo docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:$COREDNS_VERSION
sudo docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:$COREDNS_VERSION registry.cn-hangzhou.aliyuncs.com/google_containers/coredns/coredns:v$COREDNS_VERSION

# 使用 kubeadm 工具初始化 K8s 叢集
sudo kubeadm init \
--kubernetes-version=$KUBE_VERSION \
--apiserver-advertise-address=$MASTER_IP \
--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers \
--service-cidr=$SERVICE_CIDR \
--pod-network-cidr=$POD_CIDR \
--node-name=$NODENAME \
--ignore-preflight-errors=Swap

# 生成主控節點的組態檔
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 將主控節點的組態檔備份到別處
config_path="/vagrant/configs"

if [ -d $config_path ]; then
sudo rm -f $config_path/*
else
sudo mkdir -p $config_path
fi

sudo cp -i /etc/kubernetes/admin.conf $config_path/config
sudo touch $config_path/join.sh
sudo chmod +x $config_path/join.sh       

# 將往 K8s 叢集中新增工作節點的命令儲存為指令碼檔案
kubeadm token create --print-join-command > $config_path/join.sh

# 安裝名為 flannel 的網路外掛
sudo wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
sudo kubectl apply -f kube-flannel.yml

# 針對 Vagrant+VirtualBox 虛擬機器器環境的一些特定處理
sudo -i -u vagrant bash << EOF
mkdir -p /home/vagrant/.kube
sudo cp -i /vagrant/configs/config /home/vagrant/.kube/
sudo chown 1000:1000 /home/vagrant/.kube/config
EOF

在上述指令碼中,除了因國內網路環境而使用了基於阿里雲的映象來對 coredns 外掛進行的預載入操作之外,我們的主要工作就是使用 kubeadm 工具對 K8s 叢集進行初始化。在這裡,kubeadm init命令會自動將當前主機設定為整個叢集的主控節點,我們在執行該命令時需提供以下引數。

  • kubernetes-version引數:該引數用於指定當前使用的K8s版本。
  • apiserver-advertise-address引數:該引數用於指定存取當前K8s叢集的API Server時需要使用的IP地址,通常就是主控節點所在主機的IP地址。
  • image-repository引數:該引數用於指定當前K8s叢集所使用的遠端容器映象倉庫,在這裡,我們使用的是位於中國境內的阿里雲映象倉庫。
  • service-cidr引數:該引數用於指定當前K8s叢集中Service物件的CIDR,這決定了這些Service物件在該叢集內部網路中可被分配的IP地址段。
  • pod-network-cidr引數:該引數用於指定當前K8s叢集中Pod物件的CIDR,這決定了這些Pod物件在該叢集內部網路中可被分配的IP地址段。
  • node-name引數:該引數用於指定當前節點在K8s叢集中的名稱,通常情況下,我們會將其設定為當前主機的名稱。
  • ignore-preflight-errors引數:該引數用於指定要忽略的預檢錯誤。

如果一切順利的話,在kubeadm init命令執行完成之後,當前主機就成功地被設定成為了當前 K8s 叢集的主控節點。接下來,我們需要繼續執行兩項善後工作。首先要做的是將當前 K8s 叢集的組態檔備份至別處,並複製一份到我們在主控節點的$HOME/.kube/目錄下,這樣一來,我們就可以在主控節點中使用 kubectl 使用者端工具操作整個叢集了。

其次,我們將用於往當前K8s叢集中新增工作節點的命令儲存成為了一個名為join.sh的 Shell 指令碼檔案,並將其備份至別處(在這裡,就是將其備份至/vagrant/configs/目錄中)。然後,我們就只需要再分別進入到 k8s-worker1 和 k8s-worker2 這兩臺主機中,並通過執行以下指令碼檔案來將其設定成 K8s 伺服器叢集的工作節點。

#! /bin/bash

# 執行之前儲存的,用於往K8s叢集中新增工作節點的指令碼
/bin/bash /vagrant/configs/join.sh -v

# 如果希望在工作節點中也能使用kubectl,可執行以下命令
sudo -i -u vagrant bash << EOF
mkdir -p /home/vagrant/.kube
sudo cp -i /vagrant/configs/config /home/vagrant/.kube/
sudo chown 1000:1000 /home/vagrant/.kube/config
EOF

如果讀者仔細檢視一下join.sh檔案的內容,就會看到往當前 K8s 叢集中新增工作節點的操作是通過kubeadm join命令來實現的,該命令在當前 K8s 叢集中的使用方式,會在kubeadm init命令執行成功之後,以返回資訊的形式提供給使用者,其大致形式如下。

kubeadm join 192.168.100.21:6443 --token 6e2oxk.affn2w8jqe4vkr0p --discovery-token-ca-cert-hash sha256:c6c928b4f4e6403b9d05bde57511aa1742e0254344219c7ca94848175bbab1fe 

正如讀者所見,我們在執行kubeadm join命令時通常需要提供以下引數。

  • [K8s API Server]:在該引數中,我們會指定當前 K8s 叢集的 API Server 所使用的 IP 地址和埠號,通常情況下就是主控節點的 IP 地址,預設埠為6443
  • token引數:該引數用於指定加入當前 K8s 叢集所需要使用的令牌,該令牌會在kubeadm init命令執行成功之後,以返回資訊的形式提供給使用者。
  • discovery-token-ca-cert-hash引數:該引數是一個 hash 型別的值,主要用於驗證加入令牌的 CA 公鑰,該 hash 值也會在kubeadm init命令執行成功之後,以返回資訊的形式提供給使用者。

使用 kubectl 遠端操作叢集

到目前為止,我們在操作 K8s 叢集的時候,都需要先進入到該叢集的主控節點中,然後使用 kubectl 等工具對其進行操作。但在現實生產環境中,我們能直接進入到主控節點的機會並不多,因為該伺服器裝置大概率位於十萬八千里之外的某個機房裡,我們甚至都不知道它是一臺實體裝置還是虛擬雲主機。當然,我們也可以在以 Windows 或 macOS 為作業系統的個人工作機上先使用 SSH 等遠端登入的方式進入到叢集的主控節點中,然後再執行 K8s 的相關操作,但更為專業的做法是直接在工作機上使用 kubectl 使用者端工具遠端操作 K8s 叢集,為此,我們需要在工作機上進行如下設定。

  1. 通過在搜尋引擎中搜尋「kubectl」找到該使用者端工具的官方下載頁面,然後根據自己工作機使用的作業系統下載相應的安裝包,並將 kubectl 安裝到工作機中。

  2. 進入到 Windows 或 macOS 的系統使用者目錄中。如果讀者使用的是 Windows 10/11 系統,該目錄就是C:\Users\<你的使用者名稱>;如果使用的是 macOS 系統,該目錄就是/user/<你的使用者名稱>;如果使用的是 Ubuntu 這樣的 Linux 系統,該目錄就是/home/<你的使用者名稱>

  3. 在系統目錄中建立一個名為.kube目錄,並將之前儲存的、名為config的K8s叢集組態檔複製到其中。

  4. 在個人工作機上開啟 Powershell 或 Bash 這樣的命令列終端環境,並執行kubectl get nodes命令,如果得到如下輸出,就說明我們已經可以在當前裝置上對之前建立的 K8s 叢集進行操作了。

$ kubectl get nodes

NAME          STATUS     ROLES                  AGE   VERSION
k8s-master    Ready      control-plane,master   22h   v1.21.1
k8s-worker1   Ready      <none>                 20h   v1.21.1
k8s-worker2   Ready      <none>                 21h   v1.21.1

專案實踐

在完成了 K8s 叢集的環境構建之後,我們就可以正式地在該伺服器叢集中開展應用程式的運維工作了。下面,就讓我們來具體介紹一下在使用 K8s 對「線上簡歷」應用程式進行部署的基本步驟、容器編排檔案的編寫規則、運維工作時會遇到的使用場景,以及在這些場景中會使用到的相關命令吧。

部署應用的基本步驟

現在,讓我們先來演示一下如何將應用程式部署到 K8s 叢集中,並將其執行起來。正如之前所說,K8s 的核心設計目標就將物理上由多臺主機組成的伺服器叢集抽象成一臺邏輯層面上的單機環境,以便使用者可以像管理一臺主機中的不同元件一樣管理伺服器叢集中的計算資源。因此,使用 K8s 部署應用程式的步驟其實和我們之前使用 Docker Compose 在單一伺服器環境中部署應用程式的步驟是大同小異的。接下來,我們就來具體演示一下如何使用 K8s 來完成「線上簡歷」應用程式的部署。

  1. 在 K8s 叢集的主控節點上建立一個名為online_resumes的目錄,並使用 Git 或 FTP 等工具將我們之前已經編寫好了的、「線上簡歷」應用程式的原始碼複製到該目錄中。

  2. 進入到online_resumes目錄中,並根據已有的Dockerfile檔案來執行sudo docker image build -t online_resumes .命令。該命令會將應用程式的核心業務模組打包成一個新的Docker映象。待命令執行完成之後,我們就可以在docker image ls命令返回的本地映象列表中看到這個名為online_resumes的映象了。

  3. 由於我們使用的是一個三機組成的叢集環境,所以還需要繼續在主控節點中使用docker image save -o /vagrant/k8s_yml/resumes.img online_resumes命令將剛才建立的映象以檔案的形式匯出並儲存到別處(這裡的/vagrant目錄是Vagrant設定的虛擬機器器共用目錄)。然後分別進入到另外兩個工作節點中,通過執行docker image load -i /vagrant/k8s_yml/resumes.img命令將該映象載入到 Docker 映象列表中。當然,如果讀者註冊了 Docker Hub 這樣的遠端倉庫服務,也可以使用docker push命令將映象推播到遠端倉庫中,讓 K8s 自動拉取它們。

  4. 在K8s叢集的主控節點上執行sudo kubectl create namespace online-resumes命令,以便在該叢集中單獨建立一個用於部署「線上簡歷」應用程式的namespace。

  5. 由於「線上簡歷」應用程式的核心業務模組是一個基於 HTTP 協定的無狀態服務,所以我們打算使用 Deployment 型別的控制器編排容器,並將其部署成 K8s 叢集的一個 Service。為此,我們需要在online_resumes目錄下建立一個名為express-deployment.yml的資源定義檔案,其具體內容如下。

    apiVersion: apps/v1 # 指定Deployment API的版本,
                                    # 可用kubectl api-versions命令檢視
    kind: Deployment   # 定義資源物件的型別為Deployment
    metadata:              # 定義Deploynent物件的後設資料資訊
    name: express-deployment # 定義Deploynent物件的名稱
    namespace: online-resumes # 定義Deploynent物件所屬的名稱空間
    spec:    # 定義Deploynent物件的具體特徵
    replicas: 3 # 定義Deploynent物件要部署的數量
    selector: # 定義Deploynent物件的選擇器,以便其他物件參照
        matchLabels: # 定義該選擇器用於匹配的標籤
        app: resumes-web # 定義該選擇器的app標籤
    template:  # 定義Deploynent物件中的Pod物件模板
        metadata: # 定義該Pod物件模板的後設資料
        labels: # 定義Pod物件模板的標籤資訊
            app: resumes-web # 定義Pod物件模板的app標籤
        spec:      # 定義Pod物件模板的具體特徵
        containers: # 定義Pod物件模板中要部署的容器列表
        - name: resumes-web # 定義第一個容器的名稱
            image: online_resumes:latest # 定義該容器使用的映象
            imagePullPolicy: Never # 定義拉取容器的方式,主要有:
                                                # Always:始終從遠端倉庫中拉取
                                                # Never:始終使用本地映象
                                                # IfNotPresent:優先使用本地映象,
                                                #      映象不存在時從遠端倉庫拉取
            ports:               # 定義容器的埠對映
            - containerPort: 3000 # 定義容器對外開放的埠
    
    ---
    apiVersion: v1 # 指定Service API的版本
                            # 可用kubectl api-versions命令檢視
    kind: Service    # 定義資源物件的型別為Service
    metadata:        # 定義Service物件的後設資料資訊
    name: express-service # 定義Service物件的名稱
    namespace: online-resumes # 定義Service物件所屬的名稱空間
    labels:             # 定義Service物件的標籤資訊
        app: resumes-web # 定義Service物件的app標籤
    spec:                  # 定義Service物件的具體屬性
    type: ClusterIP # 定義Service物件的型別為 ClusterIP,這也是其預設型別
    ports:              # 定義Service物件的埠對映
        - port: 80    # 定義Service物件對外開放的埠
        targetPort: 3000 # 定義Service物件要轉發的內部埠
    selector:         # 使用選擇器定義Service物件要部署的資源物件
        app: resumes-web # 該app標匹配的是稍後定義的Deployment物件
    
  6. 由於「線上簡歷」應用程式的資料庫模組是一個有狀態的 MongoDB 服務,所以它適合用 StatefullSet 型別的控制器編排容器,並用 StorageClass 物件定義一個資料持久化方案,最後再將其部署成 K8s 叢集的另一個 Service。為此,我們需要在online_resumes目錄下建立一個名為mongodb-statefulset.yml的資源定義檔案,其具體內容如下。

    # 用StorageClass物件定義一個資料持久化方案
    apiVersion: storage.k8s.io/v1 # 指定StorageClass API的版本
    kind: StorageClass  # 定義資源物件的型別為StorageClass
    metadata:               # 定義StorageClass物件的後設資料資訊
    name: cluster-mongo # 定義StorageClass物件的名稱
    provisioner: fuseim.pri/ifs # 定義StorageClass物件採用nfs檔案系統
    
    ---
    # 用StatefulSet物件來組織用於部署MongoDB資料庫的Pod物件
    apiVersion: apps/v1  # 指定StatefulSet API的版本
    kind: StatefulSet   # 定義資源物件的型別為StatefulSet
    metadata:              # 定義StatefulSet物件的後設資料資訊
    name: mongodb-statefulset # 定義StatefulSet物件的名稱
    namespace: online-resumes # 定義StatefulSet物件所屬的名稱空間
    spec:                     # 定義StatefulSet物件的具體屬性
    selector  :            # 定義StatefulSet物件的選擇器,以便其他物件參照
        matchLabels: # 定義該選擇器用於匹配的標籤
        role: mongo # 定義該選擇器的role標籤,用於匹配相應的認證規則
        environment: test # 定義該選擇器的環境標籤為test
    serviceName: mongo-service
    replicas: 2  # 定義StatefulSet物件要部署的數量
    template:  # 定義StatefulSet物件中的Pod物件模板
        metadata: # 定義該Pod物件模板的後設資料
        labels: # 定義該Pod物件模板的標籤資訊
            role: mongo
            environment: test
        spec: # 定義Pod物件模板的具體屬性
        containers: # 定義Pod物件模板中要部署的容器列表
        - name: mongo  # 定義第一個容器的名稱
            image: mongo:latest # 定義第一個容器使用的映象
            command: # 設定啟動該容器的命令引數
            - mongod
            - "--replSet"
            - rs0
            - "--bind_ip"
            - 0.0.0.0
            - "--smallfiles"
            - "--noprealloc"
            ports:   # 定義該容器對外開放的埠
            - containerPort: 27017
            volumeMounts: # 定義該容器所要掛載的資料卷
            - name: mongo-storage
                mountPath: /data/db
        - name: mongo-sidecar  # 定義第二個容器的名稱及相關引數
            image: cvallance/mongo-k8s-sidecar:latest # 定義第二個容器使用的映象
            env:
            - name: MONGO_SIDECAR_POD_LABELS
                value: "role=mongo,environment=test"
    volumeClaimTemplates: # 定義StatefulSet物件所要使用的資料卷模板
        - metadata:
            name: mongo-storage
        spec:
            storageClassName: cluster-mongo # 採用之前已定義的StorageClass
            accessModes: ["ReadWriteOnce"] # 定義資料卷的讀寫模式
            resources: # 定義該模板要申請的儲存資源
            requests:
                storage: 10Gi # 資料卷的容量
    
    ---
    # 將上述StatefulSet控制器物件組織的Pod匯出為本地服務
    apiVersion: v1
    kind: Service
    metadata:
    name: mongo-service
    namespace: online-resumes
    labels:
        name: mongo-service
    spec:
    clusterIP: None # 定義該Service物件的網路型別為本地存取
    ports:
        - port: 27017
        targetPort: 27017
    selector:
        role: mongo
    
    ---
    # 將上述StatefulSet控制器物件組織的Pod匯出為外部服務
    apiVersion: v1
    kind: Service
    metadata:
    name: mongo-cs
    namespace: online-resumes
    labels:
        name: mongo
    spec:
    type: NodePort # 定義該Service物件的網路型別為NodePort
    ports:
        - port: 27017
        targetPort: 27017
        nodePort: 30717
    selector:
        role: mongo
    
  7. 將上面定義的兩個容器編排檔案複製到 K8s 叢集的主控節點中,並分別執行kubectl create -f express-deployment.yml命令和kubectl create -f mongodb-statefulset.yml命令建立相關的 Pod 範例和 Service 範例,並啟動它們。如果一切順利,我們就可以通過以下操作來確認應用程式的部署情況。

    $ sudo kubectl get services -n online-resumes
    NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
    express-service   ClusterIP   10.104.174.250   <none>        80/TCP      18m
    mongo-cs          NodePort    10.109.1.28      <none>        27017:30717/TCP   88s
    mongo-service     ClusterIP   None             <none>        27017/TCP         88s
    
    $ sudo kubectl get deployments -n online-resumes
    NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
    express-deployment   3/3     3            3           18m
    
    $ sudo kubectl get statefulsets -n online-resumes
    NAME                  READY   AGE
    mongodb-statefulset   2/2     46m
    
    $ sudo kubectl get pods -n online-resumes
    NAME                                  READY   STATUS    RESTARTS   AGE 
    express-deployment-75d7c69766-266hq   1/1     Running   0          23m
    express-deployment-75d7c69766-kxfhr   1/1     Running   0          23m
    express-deployment-75d7c69766-lh5kr   1/1     Running   0          23m
    mongodb-statefulset-0                 2/2     Running   0          46m
    

只要看到了與上面類似的輸出,就說明我們已經成功完成了「線上簡歷」應用程式在 K8s 叢集環境中的容器化部署。接下來就可以利用 kubectl 這一 K8s 叢集的使用者端工具對應用程式進行日常維護工作了。

編寫資源定義檔案

和使用 Docker Compose 時一樣,我們在 K8s 中部署一個應用程式的主要任務也是編寫用於定義各類資源物件的 YAML 檔案。而 YAML 檔案的格式可以被視為是 JSON 的一種子集格式,由於它只需憑藉簡單的縮排和鍵/值對格式就可以描述出一個內容頗為複雜的分層資料結構,因而相對於 JSON 而言更適用於執行軟體的設定與管理工作。下面,就讓我們來簡單介紹一下使用 YAML 檔案在 K8s 中定義資源物件的基本規則。

在 K8s 中,資源物件在本質上就是伺服器叢集狀態在軟體系統中的抽象化表述,它們會以執行時記憶體實體的形式始終存在於 K8s 系統的整個生命週期中,並用於描述如下資訊:

  • 在伺服器叢集中執行的應用程式(以及它們所在的伺服器節點);
  • 上述應用程式可以使用的計算資源,例如網路、資料卷等;
  • 上述應用程式所採用的的運維策略,比如重啟策略、升級策略以及容錯策略;

因此和軟體在執行時管理的其他記憶體實體一樣,K8s 中的這些資源物件的建立、修改、刪除等操作也都需要通過呼叫 K8s API 來完成。也就是說,我們在編寫定義資源物件的 YAML 檔案時實際上在做的就是擬定 K8s API 的呼叫方法及其呼叫引數,因此所有的 K8s 資源物件定義檔案中應該都至少會包含以下四個必須欄位:

  • apiVersion欄位:用於宣告當前檔案建立資源物件時所需要使用的 K8s API 的版本,當前系統中可用的 K8s API 版本可用kubectl api-versions命令進行查詢;
  • kind欄位:用於宣告當前檔案要建立的資源物件所屬的型別,例如 Pod、Deployment、StatefulSet 等;
  • metadata欄位:用於宣告當前資源物件的後設資料,以便唯一標識被建立的物件,該後設資料中通常會包括一個名為name的子欄位,用於宣告該資源物件的名稱,有時候還會加上一個namespace子欄位,用於宣告該資源物件所屬的名稱空間;
  • spec欄位:用於宣告當前資源物件的具體屬性,用於具體描述被建立物件的各種細節資訊;

需要特別注意的是,K8s中 不同型別的資源物件在spec欄位中可設定的子欄位是不盡相同的,我們需要在 K8s API 參考檔案中根據要建立的資源型別來了解其spec欄位可設定的具體選項,例如,Pod 物件的spec欄位中可設定的是我們在該物件中所要建立的各個容器及其要使用的映象等資訊;在 Deployment、StatefulSet 這一類控制器物件中,spec欄位中設定的通常是它在組織相關資源物件時所需要使用的 Pod 物件模板;而 Service 物件的spec欄位中可設定的則是被匯出為服務的資源物件,及其使用網路型別、埠對映關係等資訊。

對於上述資源物件的定義細節,我們在上一節中就已經以「線上簡歷」應用程式為例、分別針對無狀態的 Web 服務和有狀態的資料庫服務在 K8s 中的部署做了具體的示範,並在定義這些物件的 YAML 檔案中新增了詳細的註釋資訊,以供讀者參考。當然了,同樣基於篇幅方面的考慮,我們在本書中介紹的依然只是在編寫K8s資源定義檔案時可能會用到的最基本寫法。如果讀者希望更全面地瞭解在使用這類 YAML 檔案定義 K8s 中各種型別的資源物件時所有可設定的內容及其設定方法,可以自行在 Google 等搜尋引擎中搜尋「Kubernetes API」關鍵字,然後檢視更為詳盡的文獻資料。[2]

使用kubectl使用者端

在 K8s 叢集中,對應用程式的日常維護工作大部分都是通過 kubectl 這個使用者端命令列工具來完成的。在接下來的內容中,我們就結合維護工作中常見的使用場景來介紹一下該命令列工具的具體使用方法。

首先是基於 YAML 格式的資源定義檔案的操作,我們在執行這一類操作時經常會用到以下命令。

  • kubectl create -f <YAML檔名>命令:該命令會根據<YAML檔名>引數指定的資源定義檔案建立相關的資源物件,並將其部署到K8s叢集中。
  • kubectl apply -f <YAML檔名>命令:該命令會根據<YAML檔名>引數指定的資源定義檔案修改相關的資源物件,並將其重新部署到K8s叢集中。
  • kubectl delete -f <YAML檔名>命令:該命令會根據<YAML檔名>引數指定的資源定義檔案刪除相關的資源物件,並解除其K8s叢集中的部署。

在上述命令中,kubectl createkubectl apply命令都可以用於根據指定的資源定義檔案來建立資源物件(利用-f引數),區別在於:kubectl apply命令可以根據目標資源的存在情況來調整要執行的操作。如果資源物件已經存在,則根據資源定義檔案建立該物件;如果資源物件已經存在,但資源定義檔案已經被修改,就將修改應用於該物件中,如果資源定義檔案沒有變化,則什麼也不做。簡而言之,kubectl apply命令是一個可在運維工作中反覆使用的命令,而kubectl create命令通常只能用於一次性地建立不存在的資源物件。

接下來,我們需要了解的是對已經部署到K8s叢集中的資源物件可以自行的常用操作,在執行這一類操作時經常會用到以下命令。

  • kubectl get <資源型別> <參數列>命令:該命令用於列出部署在K8s叢集中的所有資源物件及其相關資訊。在該命令中,<資源型別>可以是podsdeploymentsstatefulsetsservices等我們之前介紹過的資源物件型別;而<參數列>中則可以為該命令指定一些具體條件,例如-n引數可用於指定資源物件所屬的名稱空間,預設情況下使用的是default名稱空間,而-o引數則可以指定返回資訊的呈現樣式。

  • kubectl describe <資源物件> <參數列>命令:該命令用於檢視K8s叢集中指定資源物件的資訊。在該命令中,<資源物件>需指定資源物件的名稱及其所屬的資源型別,例如,如果想檢視一個名為express-pod的 Pod 物件。該命令就該是kubectl describe pod express-pod。同樣的,我們也可以在<參數列>中使用-n引數來指定資源物件所屬的名稱空間,預設情況下使用的是default名稱空間。

  • kubectl delete <資源物件> <參數列>命令:該命令用於刪除部署在 K8s 叢集中的資源物件。在該命令中,<資源物件><參數列>部分的編寫語法與kubectl describe命令相同。

  • kubectl edit <資源物件> <參數列>命令:該命令用於修改部署在 K8s 叢集中的資源物件,它會使用 VIM 編輯器開啟指定資源物件的 YAML 檔案,以便我們修改該物件的定義。在該命令中,<資源物件><參數列>部分的編寫語法也與kubectl describe命令相同。

  • kubectl exec <Pod物件> <參數列>命令:該命令用於進入到指定<Pod物件>的容器中,它的編寫語法與docker exec命令基本相同,預設情況下會進入到Pod物件中的第一個容器中,如果需要進入其他容器,就需要使用-c引數指定容器名稱。例如kubectl exec -it express-pod -c resumes-web /bin/bash命令的作用就是進入名為express-pod的Pod物件中的resumes-web容器中,並執行/bin/bash程式。

  • kubectl scale <資源物件> <參數列>命令:該命令用於對指定<資源物件>的數量進行動態伸縮,它的編寫語法與docker-compose scale命令基本相同,例如kubectl scale deployment express-deployment --replicas=5命令的作用就是名為express-deployment的Deployment控制器物件在 K8s 叢集中的執行範例數量修改為五個。

  • kubectl set image <資源型別/資源物件名稱> <映象名稱="版本標籤">命令:該命令用於更改指定容器映象的版本,例如,如果我們想將「線上簡歷」應用程式中使用的mongo映象的版本改為3.4.22,就可以通過執行kubectl set image statefulset/mongodb-statefulset mongo="mongo:3.4.22"命令來實現。

  • kubectl rollout undo <資源型別/資源物件名稱>命令:該命令用於回滾被修改的資源物件,將其恢復到被修改之前的狀態。例如,如果我們在更新了上述mongo映象之後除了問題,就可以通過執行kubectl rollout undo statefulset/mongodb-statefulset命令來將其回滾到之前的版本。

最後,再來了解一下可對 K8s 叢集本身執行的執行的操作,我們在執行這一類操作時經常會用到以下命令。

  • kubectl get nodes <參數列>命令:該命令用於列出當前 K8s 叢集中的所有節點,其<參數列>部分的編寫語法與之前用於檢視資源物件的kubectl get命令相同。

  • kubectl api-versions命令:該命令用於檢視當前系統所支援的 K8s API 及其版本,我們可以根據其返回的資訊來編寫資源定義檔案。

  • kubectl cluster-info命令:該命令用於檢視當前 K8s 叢集的相關資訊。


  1. 在這裡,「K8s」這個簡稱是由將kubernetes中間的「ubernete」八個字母縮寫為「8」而來。 ↩︎

  2. 在搜尋參考文獻時最好不要使用「K8s」這樣的縮寫形式,這會讓我們錯過一些正式的官方檔案。 ↩︎