K8S安全學習之叢集搭建&基本概念理解

安全頻道發表於2022-03-25

前言

最近在學習K8S相關雲原生技術,因為工作中正好接觸到,便一直想找個機會深入學習一下這個方向的利用與防禦,學習之後發現東西挺多。由於篇幅原因,決定將其整理成一個系列,拆分成幾篇文章輸出,本篇文章主要介紹K8s的搭建和概念的介紹,在理解概念之後,會逐步分享k8s中的攻擊和防禦措施。本系列文章主要為個人學習記錄總結,若有錯誤,煩請斧正。

什麼是Kubernetes?

隨著微服務架構被越來越多的公司使用,大部分單體應用正逐步被拆解成小的、獨立執行的微服務。微服務的優勢這裡不做探討,但是其帶來的服務維護問題大大增加,若想要在管理大量微服務的情況下還需要讓資源利用率更多且硬體成本相對更低,那麼基於容器部署的微服務的一些自動化設施的需求就這樣誕生了,於是就有了Kubernetes(簡稱k8s),其提供的特性有:

服務發現和負載均衡

儲存編排

自動釋出和回滾

自愈

金鑰及配置管理

透過下面架構圖可以看到其有上下兩部分對應的 Master&Node節點構成,這兩種角色分別對應著控制節點和計算節點。

Master控制節點主要出發點在於如何編排、管理、排程使用者提交的作業,一個k8s叢集中至少要有一臺master節點。

Kubernetes控制節點主要由以下幾個核心元件組成:

etcd : 儲存了整個叢集的狀態,它是一個高可用性鍵值儲存,可以在多個節點之間分佈。只有Kubernetes API伺服器可以訪問它,因為它可能具有一些敏感資訊。這是一個分散式鍵值儲存,所有人都可以訪問。簡而言之:儲存節點資訊

apiserver提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API註冊和發現等機制,是讀取與解析請求指令的中樞。

controller manager負責維護叢集的狀態,比如故障檢測、自動擴充套件、滾動更新等,簡而言之即維護k8s資源。

scheduler負責資源的排程,按照預定的排程策略將Pod排程到相應的機器上,即:負載均衡排程器。

對於計算節點:

kubelet 存在於每個node節點上,負責維護容器的生命週期,同時也負責Volume(CSI)和網路(CNI)的管理。

Container runtime負責映象管理以及Pod和容器的真正執行(CRI)

kube-proxy負責為Service提供cluster內部的服務發現和負載均衡

K8s搭建

單機安裝

關於單機安裝 k8s,我使用的相關環境如下:

  • macOS:Catalina 10.15.1
  • Docker Desktop Vesion:3.0.2
  • Kubernetes:1.19.3

由於映象的下載涉及到網路原因,因此這裡使用了開源專案 來解決這個問題,需要注意的是要修改 images的相關映象的版本,要和此時 Kubernetes配對上才行,比如我設定的是:

k8s.gcr.io/kube-proxy:v1.19.3=gotok8s/kube-proxy:v1.19.3
k8s.gcr.io/kube-controller-manager:v1.19.3=gotok8s/kube-controller-manager:v1.19.3
k8s.gcr.io/kube-scheduler:v1.19.3=gotok8s/kube-scheduler:v1.19.3
k8s.gcr.io/kube-apiserver:v1.19.3=gotok8s/kube-apiserver:v1.19.3
k8s.gcr.io/coredns:1.7.0=gotok8s/coredns:1.7.0
k8s.gcr.io/pause:3.2=gotok8s/pause:3.2
k8s.gcr.io/etcd:3.4.13-0=gotok8s/etcd:3.4.13-0

隨後開啟 Docker,進入設定介面,勾選 Enable Kubernetes即可:

不出意外,介面左下角會出現 Kubernetes running的提示,這樣就安裝成功了。

如果狀態一直處於 Kubernetes starting狀態,可在終端執行以下命令然後重啟 Docker

rm -rf ~/.kube
rm -rf ~/Library/Group\\ Containers/group.com.docker/pki/

K8叢集搭建

準備

準備三臺機器:

10.206.32.6(2核4G):Master:

執行:

hostnamectl set-hostname master echo "127.0.0.1 $(hostname)" >> /etc/hosts

10.206.32.9(2核2G):Node01

執行:

hostnamectl set-hostname node01 echo "127.0.0.1 $(hostname)" >> /etc/hosts

110.206.32.13(2核2G):Node02

執行:

hostnamectl set-hostname node02 echo "127.0.0.1 $(hostname)" >> /etc/hosts

Kubernetes版本:v1.19.3

Docker版本:19.03.12

開始前請檢查以下事項:

CentOS 版本:>= 7.6

CPU:>=2

IP:互通

關閉 swapswapoff -a

關閉防火牆:systemctl stop firewalld

將橋接的IPV4流量傳遞到iptables 的鏈:

cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
 sysctl --system
  • 安裝docker環境

配置國內kubernetes源:


cat > /etc/yum.repos.d/kubernetes.repo <<EOF

[kubernetes]

name=Kubernetes Repo

baseurl=

gpgcheck=0

enabled=1

EOF

安裝相關依賴工具:

# 安裝kubelet 1.19.3
yum install -y kubelet-1.19.3 kubeadm-1.19.3 kubectl-1.19.3
#在交換分割槽的可以設定忽略禁止使用Swap的限制,不然無法啟動Kubelet
view /etc/sysconfig/kubelet
KUBELET_EXTRA_ARGS="--fail-swap-on=false"
# 設定開機啟動
systemctl enable kubelet.service && systemctl start kubelet.service
# 檢視狀態
systemctl status kubelet.service

初始化Master

在主節點(10.206.32.6)執行以下命令:

export MASTER_IP=10.206.32.6
export APISERVER_NAME=apiserver.demo
export POD_SUBNET=10.100.0.1/16
echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts

新建指令碼 init_master.sh

vim init_master.sh


#!/bin/bash
# 只在 master 節點執行
# 指令碼出錯時終止執行
set -e
if [ ${#POD_SUBNET} -eq 0 ] || [ ${#APISERVER_NAME} -eq 0 ]; then
  echo -e "\033[31;1m請確保您已經設定了環境變數 POD_SUBNET 和 APISERVER_NAME \033[0m"
  echo 當前POD_SUBNET=$POD_SUBNET
  echo 當前APISERVER_NAME=$APISERVER_NAME
  exit 1
fi
# 檢視完整配置選項 
rm -f ./kubeadm-config.yaml
cat <<EOF > ./kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
# k8s 版本
kubernetesVersion: v1.19.3
imageRepository: registry.aliyuncs.com/k8sxio
controlPlaneEndpoint: "${APISERVER_NAME}:6443"
networking:
  serviceSubnet: "10.96.0.0/16"
  podSubnet: "${POD_SUBNET}"
  dnsDomain: "cluster.local"
EOF
# kubeadm init
# 根據您伺服器網速的情況,您需要等候 3 - 10 分鐘
kubeadm config images pull --config=kubeadm-config.yaml
kubeadm init --config=kubeadm-config.yaml --upload-certs
# 配置 kubectl
rm -rf /root/.kube/
mkdir /root/.kube/
cp -i /etc/kubernetes/admin.conf /root/.kube/config
# 安裝 calico 網路外掛
# 參考文件 
echo "安裝calico-3.13.1"
rm -f calico-3.13.1.yaml
wget 
kubectl apply -f calico-3.13.1.yaml

如果出錯:

# issue 01
# [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1
# 所有機器執行
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/bridge/bridge-nf-call-ip6tables

檢查 master初始化結果:

# 直到所有的容器組處於 Running 狀態
watch kubectl get pod -n kube-system -o wide
# 檢視 master 節點初始化結果
kubectl get nodes -o wide

如下圖:

獲得join命令引數

直接在 master執行:

kubeadm token create --print-join-command

比如此時輸出:

# 有效期兩小時
kubeadm join apiserver.demo:6443 --token uysznt.3enu78oflli383zt     --discovery-token-ca-cert-hash sha256:345664bfbdfd9178ea69edf2469387d7d224121b208bb90bad5a2c1952bcb9f3

初始化Node

在所有 node執行:

export MASTER_IP=10.206.32.6
export APISERVER_NAME=apiserver.demo
echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts
# 替換為 master 節點上 kubeadm token create 命令的輸出
kubeadm join apiserver.demo:6443 --token vh5hl9.9fccw1mzfsmsp4gh     --discovery-token-ca-cert-hash sha256:6970397fdc6de5020df76de950c9df96349ca119f127551d109430c114b06f40

檢查初始化結果

master節點執行:

kubectl get nodes -o wide

輸出結果如下:

https://image.3001.net/images/20220310/1646906060_6229cacc83ca00c03f168.png!small

Kubernetes Dashboard

可以將容器化應用程式部署到 Kubernetes叢集,對容器化應用程式進行故障排除,以及管理叢集資源。

安裝

安裝命令如下:

wget <
kubectl apply -f recommended.yaml
kubectl get pods --namespace=kubernetes-dashboard -o wide

此處執行完會發現 STATUS都是 ContainerCreating,


kubectl describe pod  dashboard-metrics-scraper-856586f554-tkm49 --namespace=kubernetes-dashboard

檢視日誌找找原因,發現是因為 metrics-scraper:v1.0.6映象下載不下來,手動執行:

docker pull kubernetesui/metrics-scraper:v1.0.1拉下來之後就妥了。

最後我們需要將訪問形式改為 NodePort訪問:

kubectl --namespace=kubernetes-dashboard edit service kubernetes-dashboard
# 將裡面的 type: ClusterIP 改為 type: NodePort

儲存後,執行:

kubectl --namespace=kubernetes-dashboard get service kubernetes-dashboard

終端輸出:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes-dashboard NodePort 10.98.194.124 <none> 443:31213/TCP 107m

Token

在瀏覽器訪問: :

https://image.3001.net/images/20220310/1646906061_6229cacdf21facf89ac79.png!small

看介面需要生成 Token

vim admin-user.yaml

# 輸入
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
# 儲存退出
vim admin-user-role-binding.yaml
# 輸入
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
# 儲存退出
# 執行命令載入配置
kubectl create -f admin-user.yaml
kubectl create -f admin-user-role-binding.yaml
# 若出現已存在
# 執行:kubectl delete -f xxx.yaml 即可

獲取令牌:

kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')

複製 token到剛才的介面登入即可,登入後介面如下:

https://image.3001.net/images/20220310/1646906064_6229cad03fd07da384e4a.png!small

如果想延長 Token的有效時間:

https://image.3001.net/images/20220310/1646906067_6229cad3e04adaf18645c.png!small

然後在 containners->args加上 --token-ttl=43200

部署映象

下拉一個你自己想部署的映象,具體命令如下(主節點執行):

# 部署
kubectl run hello --image=xxx/hello --port=5000
# 列出 pod
kubectl get pods
# 建立一個服務物件
# NodePort 在所有節點(虛擬機器)上開放一個特定埠,任何傳送到該埠的流量都被轉發到對應服務
kubectl expose po hello --port=5000 --target-port=5000 --type=NodePort  --name hello-http
# 列出服務
kubectl get services

概念理解

俗話說,磨刀不誤砍柴工。上面我們成功搭建了k8s叢集,接下來我們主要花時間瞭解一下k8s的相關概念,為後續掌握更高階的知識提前做好準備。

本文主要講解以下四個概念:

Pod Deployment Service Namespace

Deployment

讓我們從使用 Deployment執行一個無狀態應用來開始吧,比如執行一個 nginx Deployment(建立檔案: nginx-deployment.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

配置檔案第二行,有個 kind欄位,表示的是此時 yaml配置的型別,即 Deployment。什麼是 Deployment?這裡我先不做解釋,讓我們先實踐,看能不能在使用過程中體會出這個型別的概念意義。

在終端執行:

kubectl apply -f ./nginx-deployment.yaml
# 輸出
deployment.apps/nginx-deployment created

然後透過以下命令分別檢視叢集中建立的 Deployment 和 Pod 的狀態:

# 檢視 Deployment
kubectl get deployments
# 輸出
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   1/1     1            1           27m
# 檢視 Pod
kubectl get pods
# 輸出
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-585449566-cl6rv   1/1     Running   0          27m
# 檢視 Deployment 的資訊
kubectl describe deployment nginx	
# 刪除 Deployment
kubectl delete deployment nginx-deployment
# 檢視 Pod 的資訊
# kubectl describe pod <pod-name>
# 這裡的 <pod-name> 是某一 Pod 的名稱
kubectl describe pod  nginx-deployment-585449566-cl6rv
# 進入容器
kubectl exec -it nnginx-deployment-585449566-cl6rv -- /bin/bash

此時我們已經成功在k8s上部署了一個例項的nginx應用程式。k8s中的應用程式是透過 Deployment來部署的,Deployment來指導k8s完成應用程式的部署和更新維護。比如說,當Deployment在部署應用時,master節點會選擇最合適的節點建立包含相應Container(容器)的POD

又比如說,Deployment會監控應用程式例項,當執行應用程式的工作節點當機時,它將會在判斷叢集中最適宜重新部署的工作節點,並在其上面重新建立新的例項(新建立的應用程式的POD ip和pod名會與之前的不同)。

但是,等等!我們好像又看到了一個新的名詞 Pod,這又是什麼?讓我們帶著疑問繼續往下看吧。

Pod

在Kubernetes中,最小的管理元素不是一個個獨立的容器,而是pod(目的在於解決容器間緊密協作關係的難題)

Pod是一組並置的容器,代表了 Kubernetes中的基本構建模組:

  • 一個 Pod包含:

一個或多個容器(container)

容器(container)的一些共享資源:儲存、網路等

  • 一個 Pod的所有容器都執行在同一個節點

容器可以被管理,但是容器裡面的多個程式實際上是不好被管理的,所以 容器被設計為每個容器只執行一個程式

容器的本質實際上就是一個程式, Namespace 做隔離,Cgroups 做限制,rootfs 做檔案系統。在一個容器只能執行一個程式的前提下,實際開發過程中一個應用是由多個容器緊密協作才可以成功地執行起來。因此,我們需要另一種更高階的結構來將容器繫結在一起,並將它們作為一個單元進行管理,這就是 Pod出現的目的。

如何定義並建立一個Pod 

建立檔案 nginx-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
      - containerPort: 80

相關欄位解釋如下:

kind: 該配置的型別,這裡是 Pod

metadata:後設資料

  1. name:Pod的名稱
  2. labels:標籤

spec:期望Pod實現的功能

containers:容器相關配置

name:container名稱

image:映象

ports:容器埠

containerPort:應用監聽的埠

執行:

# 建立
kubectl create -f nginx-pod.yaml
# 輸出
pod/nginx created
# 檢視
kubectl get pods
# 輸出
NAME                               READY   STATUS    RESTARTS   AGE
nginx                              1/1     Running   0          10s
nginx-deployment-585449566-cl6rv   1/1     Running   0          33m
# 檢視 Pod 完整的描述性檔案 
# yaml 是你想看的格式 也可以是 json
kubectl get po nginx -o yaml
# 刪除 Pod
kubectl delete -f nginx-pod.yaml

這裡簡單介紹了用宣告式API怎麼建立 Pod,但從技術角度看, Pod又是怎樣被建立的呢?實際上 Pod只是一個邏輯概念, Pod裡的所有容器,共享的是同一個 Network Namespace,並且可以宣告共享同一個 Volume

Pod除了啟動你定義的容器,還會啟動一個 Infra容器,這個容器使用的就是 k8s.gcr.io/pause映象,它的作用就是整一個 Network Namespace方便使用者容器加入,這就意味著 Pod有以下特性:

內部直接使用 127.0.0.1通訊,網路裝置一致( Infra容器決定)

只有一個IP地址

Pod的生命週期只跟 Infra容器一致,而與使用者容器無關

label

現在我們的叢集裡面只執行了一個 Pod,但在實際環境中,我們執行數十上百個 Pod也是一件很正常的事情,這樣就引出了 Pod管理上的問題,我們可以透過標籤來組織 Pod和所有其他 Kubernetes物件。

前面 nginx-pod.yaml裡面就宣告瞭 labels欄位,標籤為 name,相關操作記錄如下:

`# 檢視標籤
kubectl get pods --show-labels
# 輸出
NAME                               READY   STATUS    RESTARTS   AGE   LABELS
nginx                              1/1     Running   0          10m   name=nginx
nginx-deployment-585449566-cl6rv   1/1     Running   0          43m   app=nginx,pod-template-hash=585449566
# 增加標籤
kubectl label pods nginx version=latest
# 輸出
pod/nginx labeled
# 檢視特定標籤
kubectl get pods -l "version=latest" --show-labels
# 更新標籤
kubectl label pods nginx version=1 --overwrite
# 刪除標籤
kubectl label pods nginx version-

Namespace

利用標籤,我們可以將 Pod和其他物件組織成一個組,這是最小粒度的分類,當我們需要將物件分割成完全獨立且不重疊的組時,比如我想單獨基於 k8s搭建一套 Flink叢集,我不用想讓我的 Flink和前面搭建的 Nginx放在一起,這個時候,名稱空間(namespace)的作用就體現出來了。

# 列出所有的名稱空間
kubectl get ns
# 輸出,我們目前都是在 default 名稱空間中進行操作
NAME                   STATUS   AGE
default                Active   124m
kube-node-lease        Active   124m
kube-public            Active   124m
kube-system            Active   124m
kubernetes-dashboard   Active   37m
local-path-storage     Active   124m

讓我們建立一個名稱空間 vim cus-ns.yaml,輸入:

apiVersion: v1
kind: Namespace
metadata:
  name: cus-ns

讓我們在終端實踐一番:

# 開始建立名稱空間
kubectl create -f cus-ns.yaml
# 輸出
namespace/cus-ns created
# 為新建資源選擇名稱空間
kubectl create -f nginx-pod.yaml -n cus-ns
#輸出
pod/nginx created

這裡我們可以暫時先做一個總結,如前面所說, Pod可以表示 k8s中的基本部署單元。經過前面的講解,你應該知道以下一些知識點:

手動增刪改查 Pod 讓其服務化( Service

但是在實際使用中,我們並不會直接人工干預來管理 Pod,為什麼呢?當 Pod健康出問題或者需要進行更新等操作時,人是沒有精力來做這種維護管理工作的,但我們擅長創造工具來自動化這些繁瑣的事情,所以我們可以使用後面介紹的 Deployment

外部訪問

此時我們已經啟動了一個 nginx,我們有哪些方法可以對 Pod進行連線測試呢?

可以使用如下命令:

kubectl port-forward nginx 8088:80
# 輸出
Forwarding from 127.0.0.1:8088 -> 80
Forwarding from [::1]:8088 -> 80
# 再開一個終端訪問測試或者開啟瀏覽器
curl <

https://image.3001.net/images/20220310/1646906069_6229cad5abe873ac09449.png!small

顯然,成功訪問,但是這個有個問題就是此埠不會長期開放,一旦一定時間內沒有訪問,就會自動斷掉,我們需要其他的方式來進行訪問,比如後面會提到的 Service,這裡就簡單執行個命令,大家感受一下:

# 建立一個服務物件
# NodePort 在所有節點(虛擬機器)上開放一個特定埠,任何傳送到該埠的流量都被轉發到對應服務
kubectl expose po nginx --port=80 --target-port=80 --type=NodePort  --name nginx-http
# 輸出
service/nginx-http exposed
# 檢視服務
kubectl get svc
# 輸出
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        130m
nginx-http   NodePort    10.96.114.244   <none>        80:30236/TCP   21s
# 終端訪問測試
curl <
# 輸出 html, 表示成功埠成功開放給外部

Service

Service 服務的主要作用就是替代 Pod 對外暴露一個不變的訪問地址

在本文中 Pod部分的 外部訪問小節,就已經提到並演示了 Service,它很方便地將我們的服務埠成功開放給外部訪問。

介紹

我們的 Pod是有生命週期的,它們可以被建立、銷燬,但是一旦被銷燬,這個物件的相關痕跡就沒有了,哪怕我們用 ReplicaSet讓他又復生了,但是新 PodIP我們是沒法管控的。

很顯然,如果我們的後端服務的介面地址總是在變,我們的前端人員心中定然大罵,怎麼辦?這就輪到 Service出場了。

定義 Service

前面我們建立了一個名為 nginx-httpServices,用的是命令列;接下來我們介紹一下配置檔案的形式,在 nginx-deployment.yaml後面增加以下配置:

---
kind: Service
apiVersion: v1
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  type:  NodePort
  ports:
    - nodePort: 30068
      port: 8068
      protocol: TCP
      targetPort: 80

相信上述配置,大部分的欄位看起來都沒什麼問題了吧,先說一下埠這塊的含義:

nodePort:透過任意節點的 30068埠來訪問 Service

port:叢集內的其他容器組可透過 8068埠訪問 Service

targetPort: Pod內容器的開發埠

這裡我想強調的是 type欄位,說明如下:

ClusterIP:預設型別,服務只能夠在叢集內部可以訪問

NodePort:透過每個 Node 上的 IP 和靜態埠( NodePort)暴露服務

LoadBalancer:使用雲提供商的負載均衡器,可以向外部暴露服務。

關於 LoadBalancer,基本上是雲商會提供此型別,如果是我們自行搭建的,就沒有此型別可選,但是很多開源專案預設是啟用這種型別,我們可以自行打一個補丁來解決這個問題:

kubectl patch svc {your-svc-name} -n default -p '{"spec": {"type": "LoadBalancer", "externalIPs":["0.0.0.0"]}}'

執行生效命令:

kubectl apply -f ./nginx-deployment.yaml
# 輸出
deployment.apps/nginx-deployment unchanged
service/nginx created
# 檢視服務
kubectl get services -o wide
# 輸出
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE     SELECTOR
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        134m    <none>
nginx-http   NodePort    10.96.114.244   <none>        80:30236/TCP   4m41s   name=nginx,version=latest
# 終端測試
curl <

除了前面提的兩種方法( NodePortLoadBalancer),還有另外一種方法—— Ingress資源。我們為什麼需要引入 Ingress,最主要的原因是 LoadBalancer需要公有的IP地址,自行搭建的就不要考慮了。

Ingress非常強大,它位於多個服務之前,充當叢集中的 智慧路由器或入口點:

https://image.3001.net/images/20220310/1646906071_6229cad72b5183ddf7379.png!small

窺一斑而知全豹,好好了解完 Pod之後,再繼續瞭解 k8s的概念也就水到渠成了。我們一般不會直接建立 Pod,畢竟透過建立 Deployment資源可以很方便的建立管理 Pod(水平擴充套件、伸縮),並支援宣告式地更新應用程式。

我們一開始就是以 Deployment舉例,當時啟動配置檔案我們看到了一個 Deployment資源和一個 Pod,檢視命令如下:

kubectl get deployments
# 輸出
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   0/1     1            0           4s
kubectl get pods
# 輸出 如果名字有變化不用在意,只是我重新建立了一個 Deployment 
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-585449566-mnrtn   1/1     Running   0          2m1s

這裡我們再增加一條命令:

kubectl get replicasets.apps
# 輸出
NAME                         DESIRED   CURRENT   READY   AGE
nginx-deployment-585449566   1         1         1       10m

嗯嗯~,讓我們捋一捋,當我們建立一個 Deployment物件時, k8s不會只建立一個 Deployment資源,還會建立另外的 ReplicaSet以及1個 Pod物件。所以問題來了,  ReplicaSet又是個是什麼東西?

ReplicaSet

如果你更新了 DeploymentPod模板,那麼 Deployment就需要透過滾動更新(rolling update)的方式進行更新。

而滾動更新,離不開 ReplicaSet,說到 ReplicaSet就得說到 ReplicationController(棄用)。

ReplicationController是一種k8s資源,其會持續監控正在執行的pod列表,從而保證Pod的穩定(在現有Pod丟失時啟動一個新Pod),也能輕鬆實現Pod的水平伸縮

ReplicaSet的行為與 ReplicationController完全相同,但 Pod選擇器的表達能力更強(允許匹配缺少某個標籤的Pod,或包含特定標籤名的Pod)。所以我們可以將 Deployment當成一種更高階的資源,用於部署應用程式,並以宣告的方式管理應用,而不是透過 ReplicaSet進行部署,上述命令的建立關係如下圖:

https://image.3001.net/images/20220310/1646906072_6229cad84f1cdbccf0848.png!small

如上圖, Deployment的控制器,實際上控制的是 ReplicaSet的數目,以及每個 ReplicaSet的屬性。我們可以說 Deployment是一個兩層控制器:

Deployment–>ReplicaSet–>Pod

這種形式下滾動更新是極好的,但這裡有個前提條件那就是 Pod是無狀態的,如果執行的容器必須依賴此時的相關執行資料,那麼回滾後這些存在於容器的資料或者一些相關執行狀態值就不存在了,對於這種情況,該怎麼辦?此時需要的就是 StatefulSet(部署有狀態的多副本應用)。

StatefulSet

如果透過 ReplicaSet建立多個 Pod副本(其中描述了關聯到特定持久卷宣告的資料卷),那麼這些副本都將共享這個持久卷宣告的資料卷。

https://image.3001.net/images/20220310/1646906073_6229cad9107ef43b07be2.jpeg!small

那如何執行一個pod的多個副本,讓每個pod都有獨立的儲存卷呢?對於這個問題,之前學習的相關知識都不能提供比較好的解決方案。 k8s提供了 Statefulset資源來執行這類Pod,它是專門定製的一類應用,這類應用中每一個例項都是不可替代的個體,都擁有穩定的名字和狀態。

對於有狀態的應用(例項之間有不對等的關係或者依賴外部資料),主要需要對以下兩種型別的狀態進行復刻:

儲存狀態:應用的多個例項分別繫結了不同的儲存資料,也就是讓每個Pod都有自己獨立的儲存卷

拓撲狀態:應用的多個例項之間不是完全對等的關係,各個Pod需要按照一定的順序啟動


來自 “ freebuf ”, 原文作者:Pamela@塗鴉智慧安全實驗室;原文連結:https://www.freebuf.com/articles/container/324421.html,如有侵權,請聯絡管理員刪除。

相關文章