1. 程式人生 > 其它 >如何配置CLion來編譯執行Qt專案

如何配置CLion來編譯執行Qt專案

Pod介紹

Pod結構

每個Pod中都可以包含一個或者多個容器,這些容器可以分為兩類:

  • 使用者程式所在的容器,數量可多可少

  • Pause容器,這是每個Pod都會有的一個根容器,它的作用有兩個:

    • 可以以它為依據,評估整個Pod的健康狀態

    • 可以在根容器上設定Ip地址,其它容器都此Ip(Pod IP),以實現Pod內部的網路通訊

      這裡是Pod內部的通訊,Pod的之間的通訊採用虛擬二層網路技術來實現,當前環境用的是Flannel

Pod的yaml定義

下面是Pod的資源清單:

apiVersion: v1     #必選,版本號,例如v1
kind: Pod         #必選,資源型別,例如 Pod
metadata:         #必選,元資料
  name: string     #必選,Pod名稱
  namespace: string  #Pod所屬的名稱空間,預設為"default"
  labels:           #自定義標籤列表
    - name: string                 
spec:  #必選,Pod中容器的詳細定義
  containers:  #必選,Pod中容器列表
  - name: string   #必選,容器名稱
    image: string  #必選,容器的映象名稱
    imagePullPolicy: [ Always|Never|IfNotPresent ]  #獲取映象的策略 
    command: [string]   #容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
    args: [string]      #容器的啟動命令引數列表
    workingDir: string  #容器的工作目錄
    volumeMounts:       #掛載到容器內部的儲存卷配置
    - name: string      #引用pod定義的共享儲存卷的名稱,需用volumes[]部分定義的的卷名
      mountPath: string #儲存卷在容器內mount的絕對路徑,應少於512字元
      readOnly: boolean #是否為只讀模式
    ports: #需要暴露的埠庫號列表
    - name: string        #埠的名稱
      containerPort: int  #容器需要監聽的埠號
      hostPort: int       #容器所在主機需要監聽的埠號,預設與Container相同
      protocol: string    #埠協議,支援TCP和UDP,預設TCP
    env:   #容器執行前需設定的環境變數列表
    - name: string  #環境變數名稱
      value: string #環境變數的值
    resources: #資源限制和請求的設定
      limits:  #資源限制的設定
        cpu: string     #Cpu的限制,單位為core數,將用於docker run --cpu-shares引數
        memory: string  #記憶體限制,單位可以為Mib/Gib,將用於docker run --memory引數
      requests: #資源請求的設定
        cpu: string    #Cpu請求,容器啟動的初始可用數量
        memory: string #記憶體請求,容器啟動的初始可用數量
    lifecycle: #生命週期鉤子
        postStart: #容器啟動後立即執行此鉤子,如果執行失敗,會根據重啟策略進行重啟
        preStop: #容器終止前執行此鉤子,無論結果如何,容器都會終止
    livenessProbe:  #對Pod內各容器健康檢查的設定,當探測無響應幾次後將自動重啟該容器
      exec:         #對Pod容器內檢查方式設定為exec方式
        command: [string]  #exec方式需要制定的命令或指令碼
      httpGet:       #對Pod內個容器健康檢查方法設定為HttpGet,需要制定Path、port
        path: string
        port: number
        host: string
        scheme: string
        HttpHeaders:
        - name: string
          value: string
      tcpSocket:     #對Pod內個容器健康檢查方式設定為tcpSocket方式
         port: number
       initialDelaySeconds: 0       #容器啟動完成後首次探測的時間,單位為秒
       timeoutSeconds: 0          #對容器健康檢查探測等待響應的超時時間,單位秒,預設1秒
       periodSeconds: 0           #對容器監控檢查的定期探測時間設定,單位秒,預設10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged: false
  restartPolicy: [Always | Never | OnFailure]  #Pod的重啟策略
  nodeName: <string> #設定NodeName表示將該Pod排程到指定到名稱的node節點上
  nodeSelector: obeject #設定NodeSelector表示將該Pod排程到包含這個label的node上
  imagePullSecrets: #Pull映象時使用的secret名稱,以key:secretkey格式指定
  - name: string
  hostNetwork: false   #是否使用主機網路模式,預設為false,如果設定為true,表示使用宿主機網路
  volumes:   #在該pod上定義共享儲存卷列表
  - name: string    #共享儲存卷名稱 (volumes型別有很多種)
    emptyDir: {}       #型別為emtyDir的儲存卷,與Pod同生命週期的一個臨時目錄。為空值
    hostPath: string   #型別為hostPath的儲存卷,表示掛載Pod所在宿主機的目錄
      path: string                #Pod所在宿主機的目錄,將被用於同期中mount的目錄
    secret:          #型別為secret的儲存卷,掛載叢集與定義的secret物件到容器內部
      scretname: string  
      items:     
      - key: string
        path: string
    configMap:         #型別為configMap的儲存卷,掛載預定義的configMap物件到容器內部
      name: string
      items:
      - key: string
        path: string
#   yaml配置命令提示
#   在這裡,可通過一個命令來檢視每種資源的可配置項
#   kubectl explain 資源型別         檢視某種資源可以配置的一級屬性
#   kubectl explain 資源型別.屬性     檢視屬性的子屬性
kubectl explain pod

kubectl explain pod.metadata

在kubernetes中基本所有資源的一級屬性都是一樣的,主要包含5部分:

  • apiVersion 版本,由kubernetes內部定義,版本號必須可以用 kubectl api-versions 查詢到
  • kind 型別,由kubernetes內部定義,版本號必須可以用 kubectl api-resources 查詢到
  • metadata 元資料,主要是資源標識和說明,常用的有name、namespace、labels等
  • spec 描述,這是配置中最重要的一部分,裡面是對各種資源配置的詳細描述
  • status 狀態資訊,裡面的內容不需要定義,由kubernetes自動生成

在上面的屬性中,spec是接下來研究的重點,繼續看下它的常見子屬性:

  • containers <[]Object> 容器列表,用於定義容器的詳細資訊
  • nodeName 根據nodeName的值將pod排程到指定的Node節點上
  • nodeSelector <map[]> 根據NodeSelector中定義的資訊選擇將該Pod排程到包含這些label的Node 上
  • hostNetwork 是否使用主機網路模式,預設為false,如果設定為true,表示使用宿主機網路
  • volumes <[]Object> 儲存卷,用於定義Pod上面掛在的儲存資訊
  • restartPolicy 重啟策略,表示Pod在遇到故障的時候的處理策略

Pod配置

本小節主要來研究pod.spec.containers屬性,這也是pod配置中最為關鍵的一項配置。

[root@k8s-master01 ~]# kubectl explain pod.spec.containers
KIND:     Pod
VERSION:  v1
RESOURCE: containers <[]Object>   # 陣列,代表可以有多個容器
FIELDS:
   name  <string>     # 容器名稱
   image <string>     # 容器需要的映象地址
   imagePullPolicy  <string> # 映象拉取策略 
   command  <[]string> # 容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
   args     <[]string> # 容器的啟動命令需要的引數列表
   env      <[]Object> # 容器環境變數的配置
   ports    <[]Object>     # 容器需要暴露的埠號列表
   resources <Object>      # 資源限制和資源請求的設定

基本配置

建立pod-base.yaml檔案,內容如下:

注意提前建立namespace,名稱為dev

apiVersion: v1
kind: Pod
metadata:
  name: pod-base
  namespace: dev
  labels:
    user: heima
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30

上面定義了一個比較簡單Pod的配置,裡面有兩個容器:

  • nginx:用1.17.1版本的nginx映象建立,(nginx是一個輕量級web容器)
  • busybox:用1.30版本的busybox映象建立,(busybox是一個小巧的linux命令集合)
# 建立Pod
kubectl apply -f pod-base.yaml

# 檢視Pod狀況
# READY 1/2 : 表示當前Pod中有2個容器,其中1個準備就緒,1個未就緒
# RESTARTS  : 重啟次數,因為有1個容器故障了,Pod一直在重啟試圖恢復它
kubectl get pod -n dev

# 可以通過describe檢視內部的詳情
# 此時已經執行起來了一個基本的Pod,雖然它暫時有問題
kubectl describe pod pod-base -n dev

映象拉取

建立pod-imagepullpolicy.yaml檔案,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-imagepullpolicy
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.2
    imagePullPolicy: IfNotPresent # 用於設定映象拉取策略
  - name: busybox
    image: busybox:1.30

imagePullPolicy,用於設定映象拉取策略,kubernetes支援配置三種拉取策略:

  • Always:總是從遠端倉庫拉取映象(一直遠端下載)
  • IfNotPresent:本地有則使用本地映象,本地沒有則從遠端倉庫拉取映象(本地有就本地 本地沒遠端下載)
  • Never:只使用本地映象,從不去遠端倉庫拉取,本地沒有就報錯 (一直使用本地)

預設值說明:

如果映象tag為具體版本號, 預設策略是:IfNotPresent

如果映象tag為:latest(最終版本) ,預設策略是always

# 建立Pod
kubectl create -f pod-imagepullpolicy.yaml

# 檢視Pod詳情
# 此時明顯可以看到nginx映象有一步Pulling image "nginx:1.17.1"的過程
kubectl describe pod pod-imagepullpolicy -n dev

啟動命令

在前面的案例中,一直有一個問題沒有解決,就是的busybox容器一直沒有成功執行,那麼到底是什麼原因導致這個容器的故障呢?

原來busybox並不是一個程式,而是類似於一個工具類的集合,kubernetes叢集啟動管理後,它會自動關閉。解決方法就是讓其一直在執行,這就用到了command配置。

建立pod-command.yaml檔案,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-command
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;"]

command,用於在pod中的容器初始化完畢之後執行一個命令。

稍微解釋下上面命令的意思:

"/bin/sh","-c", 使用sh執行命令

touch /tmp/hello.txt; 建立一個/tmp/hello.txt 檔案

while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done; 每隔3秒向檔案中寫入當前時間

# 建立Pod
kubectl create  -f pod-command.yaml

# 檢視Pod狀態
# 此時發現兩個pod都正常運行了
kubectl get pods pod-command -n dev

# 進入pod中的busybox容器,檢視檔案內容
# 補充一個命令: kubectl exec  pod名稱 -n 名稱空間 -it -c 容器名稱 /bin/sh  在容器內部執行命令
# 使用這個命令就可以進入某個容器的內部,然後進行相關操作了
# 比如,可以檢視txt檔案的內容
kubectl exec pod-command -n dev -it -c busybox /bin/sh

tail -f /tmp/hello.txt
特別說明:
    通過上面發現command已經可以完成啟動命令和傳遞引數的功能,為什麼這裡還要提供一個args選項,用於傳遞引數呢?這其實跟docker有點關係,kubernetes中的command、args兩項其實是實現覆蓋Dockerfile中ENTRYPOINT的功能。
 1 如果command和args均沒有寫,那麼用Dockerfile的配置。
 2 如果command寫了,但args沒有寫,那麼Dockerfile預設的配置會被忽略,執行輸入的command
 3 如果command沒寫,但args寫了,那麼Dockerfile中配置的ENTRYPOINT的命令會被執行,使用當前args的引數
 4 如果command和args都寫了,那麼Dockerfile的配置被忽略,執行command並追加上args引數

環境變數

建立pod-env.yaml檔案,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-env
  namespace: dev
spec:
  containers:
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","while true;do /bin/echo $(date +%T);sleep 60; done;"]
    env: # 設定環境變數列表
    - name: "username"
      value: "admin"
    - name: "password"
      value: "123456"

env,環境變數,用於在pod中的容器設定環境變數。

# 建立Pod
kubectl create -f pod-env.yaml

# 進入容器,輸出環境變數
kubectl exec pod-env -n dev -c busybox -it /bin/sh
echo $username
echo $password

這種方式不是很推薦,推薦將這些配置單獨儲存在配置檔案中

埠設定

本小節來介紹容器的埠設定,也就是containers的ports選項。

首先看下ports支援的子選項:

kubectl explain pod.spec.containers.ports
KIND:     Pod
VERSION:  v1
RESOURCE: ports <[]Object>
FIELDS:
   name         <string>  # 埠名稱,如果指定,必須保證name在pod中是唯一的		
   containerPort<integer> # 容器要監聽的埠(0<x<65536)
   hostPort     <integer> # 容器要在主機上公開的埠,如果設定,主機上只能執行容器的一個副本(一般省略) 
   hostIP       <string>  # 要將外部埠繫結到的主機IP(一般省略)
   protocol     <string>  # 埠協議。必須是UDP、TCP或SCTP。預設為“TCP”。

接下來,編寫一個測試案例,建立pod-ports.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-ports
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: # 設定容器暴露的埠列表
    - name: nginx-port
      containerPort: 80
      protocol: TCP
# 建立Pod
kubectl create -f pod-ports.yaml

# 檢視pod
# 在下面可以明顯看到配置資訊
kubectl get pod pod-ports -n dev -o yaml

訪問容器中的程式需要使用的是Podip:containerPort

資源配額

容器中的程式要執行,肯定是要佔用一定資源的,比如cpu和記憶體等,如果不對某個容器的資源做限制,那麼它就可能吃掉大量資源,導致其它容器無法執行。針對這種情況,kubernetes提供了對記憶體和cpu的資源進行配額的機制,這種機制主要通過resources選項實現,他有兩個子選項:

  • limits:用於限制執行時容器的最大佔用資源,當容器佔用資源超過limits時會被終止,並進行重啟
  • requests :用於設定容器需要的最小資源,如果環境資源不夠,容器將無法啟動

可以通過上面兩個選項設定資源的上下限。

接下來,編寫一個測試案例,建立pod-resources.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-resources
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    resources: # 資源配額
      limits:  # 限制資源(上限)
        cpu: "2" # CPU限制,單位是core數
        memory: "10Gi" # 記憶體限制
      requests: # 請求資源(下限)
        cpu: "1"  # CPU限制,單位是core數
        memory: "10Mi"  # 記憶體限制

在這對cpu和memory的單位做一個說明:

  • cpu:core數,可以為整數或小數
  • memory: 記憶體大小,可以使用Gi、Mi、G、M等形式
# 執行Pod
kubectl create  -f pod-resources.yaml

# 檢視發現pod執行正常
kubectl get pod pod-resources -n dev  

# 接下來,停止Pod
kubectl delete  -f pod-resources.yaml

# 編輯pod,修改resources.requests.memory的值為10Gi
vi pod-resources.yaml

# 再次啟動pod
kubectl create  -f pod-resources.yaml

# 檢視Pod狀態,發現Pod啟動失敗
kubectl get pod pod-resources -n dev -o wide

# 檢視pod詳情會發現,如下提示
kubectl describe pod pod-resources -n dev
......
 Warning  FailedScheduling  16s   default-scheduler  0/3 nodes are available: 3 Insufficient memory.(記憶體不足)

Pod生命週期

我們一般將pod物件從建立至終的這段時間範圍稱為pod的生命週期,它主要包含下面的過程:

  • pod建立過程
  • 執行初始化容器(init container)過程
  • 執行主容器(main container)
    • 容器啟動後鉤子(post start)、容器終止前鉤子(pre stop)
    • 容器的存活性探測(liveness probe)、就緒性探測(readiness probe)
  • pod終止過程

在整個生命週期中,Pod會出現5種狀態相位),分別如下:

  • 掛起(Pending):apiserver已經建立了pod資源物件,但它尚未被排程完成或者仍處於下載映象的過程中
  • 執行中(Running):pod已經被排程至某節點,並且所有容器都已經被kubelet建立完成
  • 成功(Succeeded):pod中的所有容器都已經成功終止並且不會被重啟
  • 失敗(Failed):所有容器都已經終止,但至少有一個容器終止失敗,即容器返回了非0值的退出狀態
  • 未知(Unknown):apiserver無法正常獲取到pod物件的狀態資訊,通常由網路通訊失敗所導致

建立和終止

pod的建立過程

  1. 使用者通過kubectl或其他api客戶端提交需要建立的pod資訊給apiServer

  2. apiServer開始生成pod物件的資訊,並將資訊存入etcd,然後返回確認資訊至客戶端

  3. apiServer開始反映etcd中的pod物件的變化,其它元件使用watch機制來跟蹤檢查apiServer上的變動

  4. scheduler發現有新的pod物件要建立,開始為Pod分配主機並將結果資訊更新至apiServer

  5. node節點上的kubelet發現有pod排程過來,嘗試呼叫docker啟動容器,並將結果回送至apiServer

  6. apiServer將接收到的pod狀態資訊存入etcd中

pod的終止過程

  1. 使用者向apiServer傳送刪除pod物件的命令
  2. apiServcer中的pod物件資訊會隨著時間的推移而更新,在寬限期內(預設30s),pod被視為dead
  3. 將pod標記為terminating狀態
  4. kubelet在監控到pod物件轉為terminating狀態的同時啟動pod關閉過程
  5. 端點控制器監控到pod物件的關閉行為時將其從所有匹配到此端點的service資源的端點列表中移除
  6. 如果當前pod物件定義了preStop鉤子處理器,則在其標記為terminating後即會以同步的方式啟動執行
  7. pod物件中的容器程序收到停止訊號
  8. 寬限期結束後,若pod中還存在仍在執行的程序,那麼pod物件會收到立即終止的訊號
  9. kubelet請求apiServer將此pod資源的寬限期設定為0從而完成刪除操作,此時pod對於使用者已不可見

初始化容器

初始化容器是在pod的主容器啟動之前要執行的容器,主要是做一些主容器的前置工作,它具有兩大特徵:

  1. 初始化容器必須執行完成直至結束,若某初始化容器執行失敗,那麼kubernetes需要重啟它直到成功完成
  2. 初始化容器必須按照定義的順序執行,當且僅當前一個成功之後,後面的一個才能執行

初始化容器有很多的應用場景,下面列出的是最常見的幾個:

  • 提供主容器映象中不具備的工具程式或自定義程式碼
  • 初始化容器要先於應用容器序列啟動並執行完成,因此可用於延後應用容器的啟動直至其依賴的條件得到滿足

接下來做一個案例,模擬下面這個需求:

假設要以主容器來執行nginx,但是要求在執行nginx之前先要能夠連線上mysql和redis所在伺服器

為了簡化測試,事先規定好mysql(192.168.176.14)和redis(192.168.176.15)伺服器的地址

建立pod-initcontainer.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-initcontainer
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
  initContainers:
  - name: test-mysql
    image: busybox:1.30
    # ping通表示已經啟動
    command: ['sh', '-c', 'until ping 192.168.176.14 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
  - name: test-redis
    image: busybox:1.30
    command: ['sh', '-c', 'until ping 192.168.176.15 -c 1 ; do echo waiting for reids...; sleep 2; done;']

命令

# 建立pod
kubectl create -f pod-initcontainer.yaml

# 檢視pod狀態
# 發現pod卡在啟動第一個初始化容器過程中,後面的容器不會執行
kubectl get pods pod-initcontainer -n dev

# 動態檢視pod
kubectl get pods pod-initcontainer -n dev -w

# 接下來新開一個shell,為當前伺服器新增兩個ip,觀察pod的變化
ifconfig ens33:1 192.168.176.14 netmask 255.255.255.0 up
ifconfig ens33:2 192.168.176.15 netmask 255.255.255.0 up

鉤子函式

鉤子函式能夠感知自身生命週期中的事件,並在相應的時刻到來時執行使用者指定的程式程式碼。

kubernetes在主容器的啟動之後和停止之前提供了兩個鉤子函式:

  • post start:容器建立之後執行,如果失敗了會重啟容器
  • pre stop :容器終止之前執行,執行完成之後容器將成功終止,在其完成之前會阻塞刪除容器的操作

鉤子處理器支援使用下面三種方式定義動作:

  • Exec命令:在容器內執行一次命令

    ……
      lifecycle:
        postStart: 
          exec:
            command:
            - cat
            - /tmp/healthy
    ……
    
  • TCPSocket:在當前容器嘗試訪問指定的socket

    ……      
      lifecycle:
        postStart:
          tcpSocket:
            port: 8080
    ……
    
  • HTTPGet:在當前容器中向某url發起http請求

    ……
      lifecycle:
        postStart:
          httpGet:
            path: / #URI地址
            port: 80 #埠號
            host: 192.168.176.3 #主機地址
            scheme: HTTP #支援的協議,http或者https
    ……
    

接下來,以exec方式為例,演示下鉤子函式的使用,建立pod-hook-exec.yaml檔案,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
spec:
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart: 
        exec: # 在容器啟動的時候執行一個命令,修改掉nginx的預設首頁內容
          command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec: # 在容器停止之前停止nginx服務
          command: ["/usr/sbin/nginx","-s","quit"]

命令

# 建立pod
kubectl create -f pod-hook-exec.yaml

# 檢視pod
kubectl get pods  pod-hook-exec -n dev -o wide
  
# 訪問pod
curl 10.244.2.23

容器探測

容器探測用於檢測容器中的應用例項是否正常工作,是保障業務可用性的一種傳統機制。如果經過探測,例項的狀態不符合預期,那麼kubernetes就會把該問題例項" 摘除 ",不承擔業務流量。kubernetes提供了兩種探針來實現容器探測,分別是:

  • liveness probes:存活性探針,用於檢測應用例項當前是否處於正常執行狀態,如果不是,k8s會重啟容器
  • readiness probes:就緒性探針,用於檢測應用例項當前是否可以接收請求,如果不能,k8s不會轉發流量

livenessProbe 決定是否重啟容器,readinessProbe 決定是否將請求轉發給容器。

上面兩種探針目前均支援三種探測方式:

  • Exec命令:在容器內執行一次命令,如果命令執行的退出碼為0,則認為程式正常,否則不正常

    ……
      livenessProbe:
        exec:
          command:
          - cat
          - /tmp/healthy
    ……
    
  • TCPSocket:將會嘗試訪問一個使用者容器的埠,如果能夠建立這條連線,則認為程式正常,否則不正常

    ……      
      livenessProbe:
        tcpSocket:
          port: 8080
    ……
    
  • HTTPGet:呼叫容器內Web應用的URL,如果返回的狀態碼在200和399之間,則認為程式正常,否則不正常

    ……
      livenessProbe:
        httpGet:
          path: / #URI地址
          port: 80 #埠號
          host: 127.0.0.1 #主機地址
          scheme: HTTP #支援的協議,http或者https
    ……
    

下面以liveness probes為例,做幾個演示:

方式一:Exec

建立pod-liveness-exec.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"] # 執行一個檢視檔案的命令

建立pod,觀察效果

# 建立Pod
kubectl create -f pod-liveness-exec.yaml

# 檢視Pod詳情
kubectl describe pods pod-liveness-exec -n dev

# 觀察上面的資訊就會發現nginx容器啟動之後就進行了健康檢查
# 檢查失敗之後,容器被kill掉,然後嘗試進行重啟(這是重啟策略的作用)
# 稍等一會之後,再觀察pod資訊,就可以看到RESTARTS不再是0,而是一直增長
kubectl get pods pod-liveness-exec -n dev

# 當然接下來,可以修改成一個存在的檔案,比如/tmp/hello.txt,再試,結果就正常了......

方式二:TCPSocket

建立pod-liveness-tcpsocket.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-tcpsocket
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports: 
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      tcpSocket:
        port: 8080 # 嘗試訪問8080埠

建立pod,觀察效果

# 建立Pod
kubectl create -f pod-liveness-tcpsocket.yaml

# 檢視Pod詳情
kubectl describe pods pod-liveness-tcpsocket -n dev
 
# 觀察上面的資訊,發現嘗試訪問8080埠,但是失敗了
# 稍等一會之後,再觀察pod資訊,就可以看到RESTARTS不再是0,而是一直增長
kubectl get pods pod-liveness-tcpsocket  -n dev

# 當然接下來,可以修改成一個可以訪問的埠,比如80,再試,結果就正常了......

方式三:HTTPGet

建立pod-liveness-httpget.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:  # 其實就是訪問http://127.0.0.1:80/hello  
        scheme: HTTP #支援的協議,http或者https
        port: 80 #埠號
        path: /hello #URI地址

建立pod,觀察效果

# 建立Pod
kubectl create -f pod-liveness-httpget.yaml

# 檢視Pod詳情
kubectl describe pod pod-liveness-httpget -n dev
  
# 觀察上面資訊,嘗試訪問路徑,但是未找到,出現404錯誤
# 稍等一會之後,再觀察pod資訊,就可以看到RESTARTS不再是0,而是一直增長
kubectl get pod pod-liveness-httpget -n dev

# 當然接下來,可以修改成一個可以訪問的路徑path,比如/,再試,結果就正常了......

至此,已經使用liveness Probe演示了三種探測方式,但是檢視livenessProbe的子屬性,會發現除了這三種方式,還有一些其他的配置,在這裡一併解釋下:

kubectl explain pod.spec.containers.livenessProbe

FIELDS:
   exec <Object>  
   tcpSocket    <Object>
   httpGet      <Object>
   initialDelaySeconds  <integer>  # 容器啟動後等待多少秒執行第一次探測
   timeoutSeconds       <integer>  # 探測超時時間。預設1秒,最小1秒
   periodSeconds        <integer>  # 執行探測的頻率。預設是10秒,最小1秒
   failureThreshold     <integer>  # 連續探測失敗多少次才被認定為失敗。預設是3。最小值是1
   successThreshold     <integer>  # 連續探測成功多少次才被認定為成功。預設是1

下面稍微配置兩個,演示下效果即可:

more pod-liveness-httpget.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-httpget
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80 
        path: /
      initialDelaySeconds: 30 # 容器啟動後30s開始探測
      timeoutSeconds: 5 # 探測超時時間為5s

重啟策略

一旦容器探測出現了問題,kubernetes就會對容器所在的Pod進行重啟,其實這是由pod的重啟策略決定的,pod的重啟策略有 3 種,分別如下:

  • Always :容器失效時,自動重啟該容器,這也是預設值。
  • OnFailure : 容器終止執行且退出碼不為0時重啟
  • Never : 不論狀態為何,都不重啟該容器

重啟策略適用於pod物件中的所有容器,首次需要重啟的容器,將在其需要時立即進行重啟,隨後再次需要重啟的操作將由kubelet延遲一段時間後進行,且反覆的重啟操作的延遲時長以此為10s、20s、40s、80s、160s和300s,300s是最大延遲時長。

建立pod-restartpolicy.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: pod-restartpolicy
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /hello
  restartPolicy: Never # 設定重啟策略為Never

執行Pod測試

# 建立Pod
kubectl create -f pod-restartpolicy.yaml

# 檢視Pod詳情,發現nginx容器失敗
kubectl describe pods pod-restartpolicy -n dev

# 多等一會,再觀察pod的重啟次數,發現一直是0,並未重啟   
kubectl  get pods pod-restartpolicy -n dev

Pod排程

在預設情況下,一個Pod在哪個Node節點上執行,是由Scheduler元件採用相應的演算法計算出來的,這個過程是不受人工控制的。但是在實際使用中,這並不滿足的需求,因為很多情況下,我們想控制某些Pod到達某些節點上,那麼應該怎麼做呢?這就要求瞭解kubernetes對Pod的排程規則,kubernetes提供了四大類排程方式:

  • 自動排程:執行在哪個節點上完全由Scheduler經過一系列的演算法計算得出
  • 定向排程:NodeName、NodeSelector
  • 親和性排程:NodeAffinity、PodAffinity、PodAntiAffinity
  • 汙點(容忍)排程:Taints、Toleration

定向排程

定向排程,指的是利用在pod上宣告nodeName或者nodeSelector,以此將Pod排程到期望的node節點上。注意,這裡的排程是強制的,這就意味著即使要排程的目標Node不存在,也會向上面進行排程,只不過pod執行失敗而已。

NodeName

NodeName用於強制約束將Pod排程到指定的Name的Node節點上。這種方式,其實是直接跳過Scheduler的排程邏輯,直接將Pod排程到指定名稱的節點。

接下來,實驗一下:建立一個pod-nodename.yaml檔案

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 指定排程到node1節點上
#建立Pod
kubectl create -f pod-nodename.yaml

#檢視Pod排程到NODE屬性,確實是排程到了node1節點上
kubectl get pods pod-nodename -n dev -o wide

# 接下來,刪除pod,修改nodeName的值為node3(並沒有node3節點)
kubectl delete -f pod-nodename.yaml
vi pod-nodename.yaml
kubectl create -f pod-nodename.yaml

#再次檢視,發現已經向Node3節點排程,但是由於不存在node3節點,所以pod無法正常執行
kubectl get pods pod-nodename -n dev -o wide         

NodeSelector

NodeSelector用於將pod排程到添加了指定標籤的node節點上。它是通過kubernetes的label-selector機制實現的,也就是說,在pod建立之前,會由scheduler使用MatchNodeSelector排程策略進行label匹配,找出目標node,然後將pod排程到目標節點,該匹配規則是強制約束。

接下來,實驗一下:

1 首先分別為node節點新增標籤

kubectl label nodes node1 nodeenv=pro
kubectl label nodes node2 nodeenv=test

2 建立一個pod-nodeselector.yaml檔案,並使用它建立Pod

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeSelector: 
    nodeenv: pro # 指定排程到具有nodeenv=pro標籤的節點上
#建立Pod
kubectl create -f pod-nodeselector.yaml

#檢視Pod排程到NODE屬性,確實是排程到了node1節點上
kubectl get pods pod-nodeselector -n dev -o wide

# 接下來,刪除pod,修改nodeSelector的值為nodeenv: xxxx(不存在打有此標籤的節點)
kubectl delete -f pod-nodeselector.yaml
vi pod-nodeselector.yaml
kubectl create -f pod-nodeselector.yaml

#再次檢視,發現pod無法正常執行,Node的值為none
kubectl get pods -n dev -o wide

# 檢視詳情,發現node selector匹配失敗的提示
kubectl describe pods pod-nodeselector -n dev

親和性排程

上面介紹了兩種定向排程的方式,使用起來非常方便,但是也有一定的問題,那就是如果沒有滿足條件的Node,那麼Pod將不會被執行,即使在叢集中還有可用Node列表也不行,這就限制了它的使用場景。

基於上面的問題,kubernetes還提供了一種親和性排程(Affinity)。它在NodeSelector的基礎之上的進行了擴充套件,可以通過配置的形式,實現優先選擇滿足條件的Node進行排程,如果沒有,也可以排程到不滿足條件的節點上,使排程更加靈活。

Affinity主要分為三類:

  • nodeAffinity(node親和性): 以node為目標,解決pod可以排程到哪些node的問題
  • podAffinity(pod親和性) : 以pod為目標,解決pod可以和哪些已存在的pod部署在同一個拓撲域中的問題
  • podAntiAffinity(pod反親和性) : 以pod為目標,解決pod不能和哪些已存在pod部署在同一個拓撲域中的問題

關於親和性(反親和性)使用場景的說明:

親和性:如果兩個應用頻繁互動,那就有必要利用親和性讓兩個應用的儘可能的靠近,這樣可以減少因網路通訊而帶來的效能損耗。

反親和性:當應用的採用多副本部署時,有必要採用反親和性讓各個應用例項打散分佈在各個node上,這樣可以提高服務的高可用性。

NodeAffinity

首先來看一下NodeAffinity的可配置項:

pod.spec.affinity.nodeAffinity
  requiredDuringSchedulingIgnoredDuringExecution  Node節點必須滿足指定的所有規則才可以,相當於硬限制
    nodeSelectorTerms  節點選擇列表
      matchFields   按節點欄位列出的節點選擇器要求列表
      matchExpressions   按節點標籤列出的節點選擇器要求列表(推薦)
        key    鍵
        values 值
        operat or 關係符 支援Exists, DoesNotExist, In, NotIn, Gt, Lt
  preferredDuringSchedulingIgnoredDuringExecution 優先排程到滿足指定的規則的Node,相當於軟限制 (傾向)
    preference   一個節點選擇器項,與相應的權重相關聯
      matchFields   按節點欄位列出的節點選擇器要求列表
      matchExpressions   按節點標籤列出的節點選擇器要求列表(推薦)
        key    鍵
        values 值
        operator 關係符 支援In, NotIn, Exists, DoesNotExist, Gt, Lt
	weight 傾向權重,在範圍1-100。
關係符的使用說明:

- matchExpressions:
  - key: nodeenv              # 匹配存在標籤的key為nodeenv的節點
    operator: Exists
  - key: nodeenv              # 匹配標籤的key為nodeenv,且value是"xxx"或"yyy"的節點
    operator: In
    values: ["xxx","yyy"]
  - key: nodeenv              # 匹配標籤的key為nodeenv,且value大於"xxx"的節點
    operator: Gt
    values: "xxx"
requiredDuringSchedulingIgnoredDuringExecution

接下來首先演示一下requiredDuringSchedulingIgnoredDuringExecution ,

建立pod-nodeaffinity-required.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    nodeAffinity: #設定node親和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["xxx","yyy"]中的標籤
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
# 建立pod
kubectl create -f pod-nodeaffinity-required.yaml

# 檢視pod狀態 (執行失敗)
kubectl get pods pod-nodeaffinity-required -n dev -o wide

# 檢視Pod的詳情
# 發現排程失敗,提示node選擇失敗
kubectl describe pod pod-nodeaffinity-required -n dev

#接下來,停止pod
kubectl delete -f pod-nodeaffinity-required.yaml

# 修改檔案,將values: ["xxx","yyy"]------> ["pro","yyy"]
vi pod-nodeaffinity-required.yaml
# 再次啟動
kubectl create -f pod-nodeaffinity-required.yaml

# 此時檢視,發現排程成功,已經將pod排程到了node1上
kubectl get pods pod-nodeaffinity-required -n dev -o wide

排程失敗

修改之後排程成功

requiredDuringSchedulingIgnoredDuringExecution

接下來再演示一下requiredDuringSchedulingIgnoredDuringExecution ,

建立pod-nodeaffinity-preferred.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    nodeAffinity: #設定node親和性
      preferredDuringSchedulingIgnoredDuringExecution: # 軟限制
      - weight: 1
        preference:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的標籤(當前環境沒有)
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
# 建立pod
kubectl create -f pod-nodeaffinity-preferred.yaml

# 檢視pod狀態 (執行成功)
kubectl get pod pod-nodeaffinity-preferred -n dev
NodeAffinity規則設定的注意事項
NodeAffinity規則設定的注意事項:
    1 如果同時定義了nodeSelector和nodeAffinity,那麼必須兩個條件都得到滿足,Pod才能執行在指定的Node上
    2 如果nodeAffinity指定了多個nodeSelectorTerms,那麼只需要其中一個能夠匹配成功即可
    3 如果一個nodeSelectorTerms中有多個matchExpressions ,則一個節點必須滿足所有的才能匹配成功
    4 如果一個pod所在的Node在Pod執行期間其標籤發生了改變,不再符合該Pod的節點親和性需求,則系統將忽略此變化

PodAffinity

PodAffinity主要實現以執行的Pod為參照,實現讓新建立的Pod跟參照pod在一個區域的功能。

PodAffinity的可配置項

首先來看一下PodAffinity的可配置項:

pod.spec.affinity.podAffinity
  requiredDuringSchedulingIgnoredDuringExecution  硬限制
    namespaces       指定參照pod的namespace
    topologyKey      指定排程作用域
    labelSelector    標籤選擇器
      matchExpressions  按節點標籤列出的節點選擇器要求列表(推薦)
        key    鍵
        values 值
        operator 關係符 支援In, NotIn, Exists, DoesNotExist.
      matchLabels    指多個matchExpressions對映的內容
  preferredDuringSchedulingIgnoredDuringExecution 軟限制
    podAffinityTerm  選項
      namespaces      
      topologyKey
      labelSelector
        matchExpressions  
          key    鍵
          values 值
          operator
        matchLabels 
    weight 傾向權重,在範圍1-100
topologyKey指定排程時作用域
topologyKey用於指定排程時作用域,例如:
    如果指定為kubernetes.io/hostname,那就是以Node節點為區分範圍
	如果指定為beta.kubernetes.io/os,則以Node節點的作業系統型別來區分
requiredDuringSchedulingIgnoredDuringExecution

接下來,演示下requiredDuringSchedulingIgnoredDuringExecution,

1)首先建立一個參照Pod,pod-podaffinity-target.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-target
  namespace: dev
  labels:
    podenv: pro #設定標籤
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 將目標pod名確指定到node1上
# 啟動目標pod
kubectl create -f pod-podaffinity-target.yaml

# 檢視pod狀況
kubectl get pods  pod-podaffinity-target -n dev

2)建立pod-podaffinity-required.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    podAffinity: #設定pod親和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的標籤
          - key: podenv
            operator: In
            values: ["xxx","yyy"]
        topologyKey: kubernetes.io/hostname

上面配置表達的意思是:新Pod必須要與擁有標籤nodeenv=xxx或者nodeenv=yyy的pod在同一Node上,顯然現在沒有這樣pod,接下來,執行測試一下。

# 啟動pod
kubectl create -f pod-podaffinity-required.yaml

# 檢視pod狀態,發現未執行
kubectl get pods pod-podaffinity-required -n dev

# 檢視詳細資訊
kubectl describe pods pod-podaffinity-required  -n dev

# 接下來修改  values: ["xxx","yyy"]----->values:["pro","yyy"]
# 意思是:新Pod必須要與擁有標籤nodeenv=xxx或者nodeenv=yyy的pod在同一Node上
vi pod-podaffinity-required.yaml

# 然後重新建立pod,檢視效果
kubectl delete -f  pod-podaffinity-required.yaml
kubectl create -f pod-podaffinity-required.yaml

# 發現此時Pod執行正常
kubectl get pods pod-podaffinity-required -n dev

啟動失敗

檢視詳細原因

有一個汙點,其他兩個節點不符合條件

修改之後重新執行

關於PodAffinitypreferredDuringSchedulingIgnoredDuringExecution,這裡不再演示,也是同理。

PodAntiAffinity

PodAntiAffinity主要實現以執行的Pod為參照,讓新建立的Pod跟參照pod不在一個區域中的功能。

它的配置方式和選項跟PodAffinty是一樣的,這裡不再做詳細解釋,直接做一個測試案例。

1)繼續使用上個案例中目標pod

kubectl get pods -n dev -o wide --show-labels

2)建立pod-podantiaffinity-required.yaml,內容如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #親和性設定
    podAntiAffinity: #設定pod親和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配podenv的值在["pro"]中的標籤
          - key: podenv
            operator: In
            values: ["pro"]
        topologyKey: kubernetes.io/hostname

上面配置表達的意思是:新Pod必須要與擁有標籤nodeenv=pro的pod不在同一Node上,執行測試一下。

# 建立pod
kubectl create -f pod-podantiaffinity-required.yaml

# 檢視pod
# 發現排程到了node2上
kubectl get pods pod-podantiaffinity-required -n dev -o wide

汙點和容忍

汙點(Taints)

前面的排程方式都是站在Pod的角度上,通過在Pod上新增屬性,來確定Pod是否要排程到指定的Node上,其實我們也可以站在Node的角度上,通過在Node上新增汙點屬性,來決定是否允許Pod排程過來。

Node被設定上汙點之後就和Pod之間存在了一種相斥的關係,進而拒絕Pod排程進來,甚至可以將已經存在的Pod驅逐出去。

汙點的格式為:key=value:effect, key和value是汙點的標籤,effect描述汙點的作用,支援如下三個選項:

  • PreferNoSchedule:kubernetes將盡量避免把Pod排程到具有該汙點的Node上,除非沒有其他節點可排程
  • NoSchedule:kubernetes將不會把Pod排程到具有該汙點的Node上,但不會影響當前Node上已存在的Pod
  • NoExecute:kubernetes將不會把Pod排程到具有該汙點的Node上,同時也會將Node上已存在的Pod驅離

使用kubectl設定和去除汙點的命令示例如下:

# 設定汙點
kubectl taint nodes node1 key=value:effect

# 去除汙點
kubectl taint nodes node1 key:effect-

# 去除所有汙點
kubectl taint nodes node1 key-

接下來,演示下汙點的效果:

  1. 準備節點node1(為了演示效果更加明顯,暫時停止node2節點)

  2. 為node1節點設定一個汙點: tag=makalo:PreferNoSchedule;然後建立pod1( pod1 可以 )

    # 為node1設定汙點(PreferNoSchedule)
    kubectl taint nodes node1 tag=makalo:PreferNoSchedule
    
    # 建立pod1
    kubectl run taint1 --image=nginx:1.17.1 -n dev
    kubectl get pods -n dev -o wide
    
  3. 修改為node1節點設定一個汙點: tag=makalo:NoSchedule;然後建立pod2( pod1 正常 pod2 失敗 )

    # 為node1設定汙點(取消PreferNoSchedule,設定NoSchedule)
    kubectl taint nodes node1 tag:PreferNoSchedule-
    kubectl taint nodes node1 tag=makalo:NoSchedule
    
    # 建立pod2
    kubectl run taint2 --image=nginx:1.17.1 -n dev
    kubectl get pods -n dev -o wide
    
  4. 修改為node1節點設定一個汙點: tag=makalo:NoExecute;然後建立pod3 ( 3個pod都失敗 )

# 為node1設定汙點(取消NoSchedule,設定NoExecute)
kubectl taint nodes node1 tag:NoSchedule-
kubectl taint nodes node1 tag=makalo:NoExecute

# 建立pod3
kubectl run taint3 --image=nginx:1.17.1 -n dev
kubectl get pods -n dev -o wide     
master節點預設汙點設定
# 使用kubeadm搭建的叢集,預設就會給master節點新增一個汙點NoSchedule標記,所以pod就不會排程到master節點上.
# 檢視master汙點
kubectl describe node master

容忍(Toleration

上面介紹了汙點的作用,我們可以在node上新增汙點用於拒絕pod排程上來,但是如果就是想將一個pod排程到一個有汙點的node上去,這時候應該怎麼做呢?這就要使用到容忍

汙點就是拒絕,容忍就是忽略,Node通過汙點拒絕pod排程上去,Pod通過容忍忽略拒絕

下面先通過一個案例看下效果:

  1. 在上面,已經在node1節點上打上了NoExecute的汙點,此時pod是排程不上去的
  2. 這裡,可以通過給pod新增容忍,然後將其排程上去

建立pod-toleration.yaml,內容如下

apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 新增容忍
  - key: "tag"        # 要容忍的汙點的key
    operator: "Equal" # 操作符 等於
    value: "makalo"    # 容忍的汙點的value
    effect: "NoExecute"   # 新增容忍的規則,這裡必須和標記的汙點規則相同
# 新增容忍之前的pod,注意上面的yaml不加容忍配置
kubectl create -f pod-toleration.yaml
kubectl get pods pod-toleration -n dev -o wide      

# 新增容忍之後的pod,注意加上容忍配置
kubectl delete -f pod-toleration.yaml
vi pod-toleration.yaml
kubectl create -f pod-toleration.yaml
kubectl get pods pod-toleration -n dev -o wide         

新增容忍之前

新增容忍之後

下面看一下容忍的詳細配置:

kubectl explain pod.spec.tolerations
......
FIELDS:
   key       # 對應著要容忍的汙點的鍵,空意味著匹配所有的鍵
   value     # 對應著要容忍的汙點的值
   operator  # key-value的運算子,支援Equal和Exists(預設)
   effect    # 對應汙點的effect,空意味著匹配所有影響
   tolerationSeconds   # 容忍時間, 當effect為NoExecute時生效,表示pod在Node上的停留時間