《kubernetes權威指南第五版》讀書筆記


修改kubeadm的默認配置

  kubeadm的初始化控制平面(init)命令和加入節點(join)命令均可以通過指定的配置文件修改默認參數的值。kubeadm將配置文件以ConfigMap形式保存到集群中,便於后續的查詢和升級工作。kubeadm config子命令提供了對這組功能的支持。

  • kubeadm config print init-defaults:輸出kubeadm init命令默認參數的內容。
  • kubeadm config print join-defaults:輸出kubeadm join命令默認參數的內容。
  • kubeadm config migrate:在新舊版本之間進行配置轉換。
  • kubeadm config images list:列出所需的鏡像列表。
  • kubeadm config images pull:拉取鏡像到本地。
kubeadm config print init-defaults >init-default.yaml

  對生成的文件進行編輯,可以按需生成合適的配置。例如,若需要自定義鏡像的倉庫地址、需要安裝的Kubernetes版本號及Pod的IP地址范圍

kubeadm config images list:列出所需的鏡像列表

  如果無法訪問k8s.gcr.io,則可以使用國內鏡像托管站點進行下載,例如https://1nj0zren.mirror.aliyuncs.com,這可以通過修改Docker服務的配置文件(默認為/etc/docker/daemon.json)進行設置,例如:
  然后,使用kubeadm config images pull命令或者docker pull命令下載上述鏡像,

kubeadm config images pull --config=init-config.yaml

  在鏡像下載完成之后,就可以進行安裝了。

  在開始之前需要注意:kubeadm的安裝過程不涉及網絡插件(CNI)的初始化,因此kubeadm初步安裝完成的集群不具備網絡功能,任何Pod(包括自帶的CoreDNS)都無法正常工作。而網絡插件的安裝往往對kubeadm init命令的參數有一定要求

創建和使用命令行插件


  為了擴展kubectl的功能,Kubernetes從1.8版本開始引入插件機制,在1.14版本時達到穩定版。
  用戶自定義插件的可執行文件名需要以“kubectl-”開頭,復制到$PATH中的某個目錄(如/usr/local/bin)下,然后就可以通過kubectl <plugin-name>運行自定義插件了。
  例如,通過Shell腳本實現一個名為hello的插件,其功能為在屏幕上輸出字符串“hello world”。創建名為“kubectl-hello”的Shell腳本文件
  通過插件機制,可以將某些復雜的kubectl命令簡化為運行插件的方式。例如想創建一個命令來查看當前上下文環境(context)中的用戶名,則可以通過kubectl config view命令進行查看

使用kubectl plugin list命令可以查看當前系統中已安裝的插件列表

Pod深入了解

pod生命周期和重啟策略

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.90.14)和redis(192.168.90.15)服務器的地址

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
    command: ['sh', '-c', 'until ping 192.168.90.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.90.15 -c 1 ; do echo waiting for reids...; sleep 2; done;']
# 創建pod
[root@k8s-master01 ~]# kubectl create -f pod-initcontainer.yaml
pod/pod-initcontainer created

# 查看pod狀態
# 發現pod卡在啟動第一個初始化容器過程中,后面的容器不會運行
root@k8s-master01 ~]# kubectl describe pod  pod-initcontainer -n dev
........
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  49s   default-scheduler  Successfully assigned dev/pod-initcontainer to node1
  Normal  Pulled     48s   kubelet, node1     Container image "busybox:1.30" already present on machine
  Normal  Created    48s   kubelet, node1     Created container test-mysql
  Normal  Started    48s   kubelet, node1     Started container test-mysql

# 動態查看pod
[root@k8s-master01 ~]# kubectl get pods pod-initcontainer -n dev -w
NAME                             READY   STATUS     RESTARTS   AGE
pod-initcontainer                0/1     Init:0/2   0          15s
pod-initcontainer                0/1     Init:1/2   0          52s
pod-initcontainer                0/1     Init:1/2   0          53s
pod-initcontainer                0/1     PodInitializing   0          89s
pod-initcontainer                1/1     Running           0          90s

# 接下來新開一個shell,為當前服務器新增兩個ip,觀察pod的變化
[root@k8s-master01 ~]# ifconfig ens33:1 192.168.90.14 netmask 255.255.255.0 up
[root@k8s-master01 ~]# ifconfig ens33:2 192.168.90.15 netmask 255.255.255.0 up
創建pod,觀察狀態變化
  鈎子函數

  鈎子函數能夠感知自身生命周期中的事件,並在相應的時刻到來時運行用戶指定的程序代碼。

  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.5.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
[root@k8s-master01 ~]# kubectl create -f pod-hook-exec.yaml
pod/pod-hook-exec created

# 查看pod
[root@k8s-master01 ~]# kubectl get pods  pod-hook-exec -n dev -o wide
NAME           READY   STATUS     RESTARTS   AGE    IP            NODE    
pod-hook-exec  1/1     Running    0          29s    10.244.2.48   node2   

# 訪問pod
[root@k8s-master01 ~]# curl 10.244.2.48
postStart...
演示

 

Pod的重啟策略(RestartPolicy)應用於Pod內的所有容器

 Pod的重啟策略包括Always、OnFailure和Never,默認值為Always。

  • Always:當容器失效時,由kubelet自動重啟該容器。
  • OnFailure:當容器終止運行且退出碼不為0時,由kubelet自動重啟該容器。
  • Never:不論容器運行狀態如何,kubelet都不會重啟該容器。

  kubelet重啟失效容器的時間間隔以sync-frequency乘以2n來計算,例如1、2、4、8倍等,最長延時5min,並且在成功重啟后的10min后重置該時間

  Pod的重啟策略與控制方式息息相關,當前可用於管理Pod的控制器包括ReplicationController、Job、DaemonSet,還可以通過kubelet管理(靜態Pod)。每種控制器對Pod的重啟策略要求如下

  • RC和DaemonSet:必須設置為Always,需要保證該容器持續運行
  • Job:OnFailure或Never,確保容器執行完成后不再重啟
  • kubelet:在Pod失效時自動重啟它,不論將RestartPolicy設置為什么值,也不會對Pod進行健康檢查

Pod常見的狀態轉換場景

  

  RC的繼任者其實並不是Deployment,而是ReplicaSet,因為ReplicaSet進一步增強了RC標簽選擇器的靈活性。之前RC的標簽選擇器只能選擇一個標簽,而ReplicaSet擁有集合式的標簽選擇器,可以選擇多個Pod標簽
  其實,Kubernetes的滾動升級就是巧妙運用ReplicaSet的這個特性來實現的,同時,Deployment也是通過ReplicaSet來實現Pod副本自動控制功能的。我們不應該直接使用底層的ReplicaSet來控制Pod副本,而應該通過管理ReplicaSet的Deployment對象來控制副本

Pod健康檢查和服務可用性檢查

探針的種類

  Kubernetes對Pod的健康狀態可以通過三類探針來檢查:LivenessProbe、ReadinessProbe及StartupProbe,其中最主要的探針為LivenessProbe與ReadinessProbe,kubelet會定期執行這兩類探針來診斷容器的健康狀況。

(1)LivenessProbe探針:用於判斷容器是否存活(Running狀態),如果LivenessProbe探針探測到容器不健康,則kubelet將“殺掉”該容器,並根據容器的重啟策略做相應的處理。如果一個容器不包含LivenessProbe探針,那么kubelet認為該容器的LivenessProbe探針返回的值永遠是Success。
(2)ReadinessProbe探針:用於判斷容器服務是否可用(Ready狀態),達到Ready狀態的Pod才可以接收請求。對於被Service管理的Pod,Service與Pod Endpoint的關聯關系也將基於Pod是否Ready進行設置。如果在運行過程中Ready狀態變為False,則系統自動將其從Service的后端Endpoint列表中隔離出去,后續再把恢復到Ready狀態的Pod加回后端Endpoint列表。這樣就能保證客戶端在訪問Service時不會被轉發到服務不可用的Pod實例上。需要注意的是,ReadinessProbe也是定期觸發執行的,存在於Pod的整個生命周期中。
(3)StartupProbe探針:某些應用會遇到啟動比較慢的情況,例如應用程序啟動時需要與遠程服務器建立網絡連接,或者遇到網絡訪問較慢等情況時,會造成容器啟動緩慢,此時ReadinessProbe就不適用了,因為這屬於“有且僅有一次”的超長延時,可以通過StartupProbe探針解決該問題

探針均可配置以下三種實現方式

(1)ExecAction:在容器內部運行一個命令,如果該命令的返回碼為0,則表明容器健康

  通過運行cat/tmp/health命令來判斷一個容器運行是否正常。在該Pod運行后,將在創建/tmp/health文件10s后刪除該文件,而LivenessProbe健康檢查的初始探測時間(initialDelaySeconds)為15s,探測結果是Fail,將導致kubelet“殺掉”該容器並重啟它

……
  livenessProbe:
    exec:
      command:
      - cat
      - /tmp/healthy
……

(2)TCPSocketAction:通過容器的IP地址和端口號執行TCP檢查,如果能夠建立TCP連接,則表明容器健康。

……      
  livenessProbe:
    tcpSocket:
      port: 8080
……

 

(3)HTTPGetAction:通過容器的IP地址、端口號及路徑調用HTTP Get方法,如果響應的狀態碼大於等於200且小於400,則認為容器健康

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

 

對於每種探測方式,都需要設置initialDelaySeconds和timeoutSeconds兩個參數,它們的含義分別如下。

  • initialDelaySeconds:啟動容器后進行首次健康檢查的等待時間,單位為s。
  • timeoutSeconds:健康檢查發送請求后等待響應的超時時間,單位為s。當超時發生時,kubelet會認為容器已經無法提供服務,將會重啟該容器。

  如下代碼片段是StartupProbe探針的一個參考配置,可以看到,這個Pod可以有長達30×10=300s的超長啟動時間:

  

pod調度策略

Deployment或RC:全自動調度

  Deployment或RC的主要功能之一就是自動部署一個容器應用的多份副本,以及持續監控副本的數量,在集群內始終維持用戶指定的副本數量
  Pod由系統全自動完成調度。它們各自最終運行在哪個節點上,完全由Master的Scheduler經過一系列算法計算得出,用戶無法干預調度過程和結果
  除了使用系統自動調度算法完成一組Pod的部署,Kubernetes也提供了多種豐富的調度策略,用戶只需在Pod的定義中使用NodeSelector、NodeAffinity、PodAffinity、Pod驅逐等更加細粒度的調度策略設置,就能完成對Pod的精准調度

NodeSelector:定向調度

  除了用戶可以自行給Node添加標簽,Kubernetes也會給Node預定義一些標簽,包括:

  • kubernetes.io/hostname;
  • beta.kubernetes.io/os(從1.14版本開始更新為穩定版,到1.18版本刪除);
  • beta.kubernetes.io/arch(從1.14版本開始更新為穩定版,到1.18版本刪除);
  • kubernetes.io/os(從1.14版本開始啟用);
  • kubernetes.io/arch(從1.14版本開始啟用)。

  NodeSelector通過標簽的方式,簡單實現了限制Pod所在節點的方法。親和性調度機制則極大擴展了Pod的調度能力,主要的增強功能如下。

  • 更具表達力(不僅僅是“符合全部”的簡單情況)。
  • 可以使用軟限制、優先采用等限制方式,代替之前的硬限制,這樣調度器在無法滿足優先需求的情況下,會退而求其次,繼續運行該Pod。
  • 可以依據節點上正在運行的其他Pod的標簽來進行限制,而非節點本身的標簽。這樣就可以定義一種規則來描述Pod之間的親和或互斥關系。

  親和性調度功能包括節點親和性(NodeAffinity)和Pod親和性(PodAffinity)兩個維度的設置。節點親和性與NodeSelector類似,增強了上述前兩點優勢;Pod的親和與互斥限制則通過Pod標簽而不是節點標簽來實現,也就是上面第4點內容所陳述的方式,同時具有前兩點提到的優點

NodeAffinity:Node親和性調度

  NodeAffinity意為Node親和性的調度策略,是用於替換NodeSelector的全新調度策略。目前有兩種節點親和性表達。

  • RequiredDuringSchedulingIgnoredDuringExecution:必須滿足指定的規則才可以調度Pod到Node上(功能與nodeSelector很像,但是使用的是不同的語法),相當於硬限制。
  • PreferredDuringSchedulingIgnoredDuringExecution:強調優先滿足指定規則,調度器會嘗試調度Pod到Node上,但並不強求,相當於軟限制。多個優先級規則還可以設置權重(weight)值,以定義執行的先后順序。

  IgnoredDuringExecution的意思是:如果一個Pod所在的節點在Pod運行期間標簽發生了變更,不再符合該Pod的節點親和性需求,則系統將忽略Node上Label的變化,該Pod能繼續在該節點上運行。
  下面的例子設置了NodeAffinity調度的如下規則。

  • required DuringS chedulingIgnoredDuringExecution:要求只運行在amd64的節點上(beta.kubernetes.io/arch In amd64)。
  • preferredDuringSchedulingIgnoredDuringExecution:要求盡量運行在磁盤類型為ssd(disk-type In ssd)的節點上。

  NodeAffinity規則設置的注意事項如下。

  • 如果同時定義了nodeSelector和nodeAffinity,那么必須兩個條件都得到滿足,Pod才能最終運行在指定的Node上。
  • 如果nodeAffinity指定了多個nodeSelectorTerms,那么其中一個能匹配成功即可。
  • 如果在nodeSelectorTerms中有多個matchExpressions,則一個節點必須滿足所有matchExpressions才能運行該Pod。

PodAffinity:Pod親和與互斥調度策略

  與節點親和性類似,Pod親和性的操作符也包括In、NotIn、Exists、DoesNotExist、Gt、Lt。
  原則上,topologyKey可以使用任意合法的標簽Key賦值,但是出於性能和安全方面的考慮,對topologyKey有如下限制。

  • 在Pod親和性和RequiredDuringScheduling的Pod互斥性的定義中,不允許使用空的topologyKey。
  • 如果Admission controller包含了LimitPodHardAntiAffinityTopology,那么針對Required DuringScheduling的Pod互斥性定義就被限制為kubernetes.io/hostname,要使用自定義的topologyKey,就要改寫或禁用該控制器。
  • 在PreferredDuringScheduling類型的Pod互斥性定義中,空的topologyKey會被解釋為kubernetes.io/hostname、failure-domain.beta.kubernetes.io/zone及failure-domain.beta.kubernetes.io/region的組合。

  如果不是上述情況,就可以采用任意合法的topologyKey了。

  PodAffinity規則設置的注意事項如下

  • 除了設置Label Selector和topologyKey,用戶還可以指定Namespace列表進行限制,同樣,使用Label Selector對Namespace進行選擇。Namespace的定義和Label Selector及topologyKey同級。省略Namespace的設置,表示使用定義了affinity/anti-affinity的Pod所在的命名空間。如果Namespace被設置為空值(""),則表示所有命名空間。
  • 在所有關聯requiredDuringSchedulingIgnoredDuringExecution的matchExpressions全都滿足之后,系統才能將Pod調度到某個Node上

Pod Priority Preemption:Pod優先級調度

  在Kubernetes 1.8版本之前,當集群的可用資源不足時,在用戶提交新的Pod創建請求后,該Pod會一直處於Pending狀態,即使這個Pod是一個很重要(很有身份)的Pod,也只能被動等待其他Pod被刪除並釋放資源,才能有機會被調度成功。

  Kubernetes 1.8版本引入了基於Pod優先級搶占(Pod Priority Preemption)的調度策略,此時Kubernetes會嘗試釋放目標節點上低優先級的Pod,以騰出空間(資源)安置高優先級的Pod,這種調度方式被稱為“搶占式調度”。在Kubernetes 1.11版本中,該特性升級為Beta版本,默認開啟,在后續的Kubernetes 1.14版本中正式Release。如何聲明一個負載相對其他負載更重要?我們可以通過以下幾個維度來定義:Priority:優先級;QoS:服務質量等級;系統定義的其他度量指標。

  優先級搶占調度策略的核心行為分別是驅逐(Eviction)與搶占(Preemption),這兩種行為的使用場景不同,效果相同。Eviction是kubelet進程的行為,即當一個Node資源不足(under resource pressure)時,該節點上的kubelet進程會執行驅逐動作,此時kubelet會綜合考慮Pod的優先級、資源申請量與實際使用量等信息來計算哪些Pod需要被驅逐;當同樣優先級的Pod需要被驅逐時,實際使用的資源量超過申請量最大倍數的高耗能Pod會被首先驅逐。對於QoS等級為“Best Effort”的Pod來說,由於沒有定義資源申請(CPU/Memory Request),所以它們實際使用的資源可能非常大。Preemption則是Scheduler執行的行為,當一個新的Pod因為資源無法滿足而不能被調度時,Scheduler可能(有權決定)選擇驅逐部分低優先級的Pod實例來滿足此Pod的調度目標,這就是Preemption機制

  優先級為100000,數字越大,優先級越高,超過一億的數字被系統保留,用於指派給系統組件

DaemonSet:在每個Node上都調度一個Pod

  這種用法適合有這種需求的應用。

  • 在每個Node上都運行一個GlusterFS存儲或者Ceph存儲的Daemon進程。
  • 在每個Node上都運行一個日志采集程序,例如Fluentd或者Logstach。
  • 在每個Node上都運行一個性能監控程序,采集該Node的運行性能數據,例如Prometheus Node Exporter、collectd、New Relic agent或者Ganglia gmond等。

  DaemonSet調度不同於普通的Pod調度,所以沒有用默認的Kubernetes Scheduler進行調度,而是通過專有的**DaemonSet Controller**進行調度。但是隨着Kubernetes版本的改進和調度特性不斷豐富,產生了一些難以解決的矛盾,最主要的兩個矛盾如下。

  • 普通的Pod是在Pending狀態觸發調度並被實例化的,DaemonSet Controller並不是在這個狀態調度Pod的,這種不一致容易誤導和迷惑用戶。
  • Pod優先級調度是被Kubernetes Scheduler執行的,而DaemonSet Controller並沒有考慮到Pod優先級調度的問題,也產生了不一致的結果。
  • 從Kubernetes 1.18開始,DaemonSet的調度默認切換到Kubernetes Scheduler進行,**從而一勞永逸地解決了以上問題及未來可能的新問題。因為默認切換到了Kubernetes Scheduler統一調度Pod,因此DaemonSet也能正確處理Taints和Tolerations的問題。

Taints和Tolerations(污點和容忍)

  Pod的Toleration聲明中的key和effect需要與Taint的設置保持一致,並且滿足以下條件之一。

  • operator的值是Exists(無須指定value)。
  • operator的值是Equal並且value相等。
  • 如果不指定operator,則默認值為Equal。

  另外,有如下兩個特例

  • 空的key配合Exists操作符能夠匹配所有鍵和值。
  • 空的effect匹配所有effect

  幾種特殊情況

  • 如果在剩余的Taint中存在effect=NoSchedule,則調度器不會把該Pod調度到這一節點上。
  • 如果在剩余的Taint中沒有NoSchedule效果,但是有PreferNoSchedule效果,則調度器會嘗試不把這個Pod指派給這個節點。

Job:批處理調度

  

  考慮到批處理的並行問題,Kubernetes將Job分以下三種類型

(1)Non-parallel Jobs:通常一個Job只啟動一個Pod,除非Pod異常,才會重啟該Pod,一旦此Pod正常結束,Job將結束。
(2)Parallel Jobs with a fixed completion count:並行Job會啟動多個Pod,此時需要設定Job的.spec.completions參數為一個正數,當正常結束的Pod數量達至此參數設定的值后,Job結束。此外,Job的.spec.parallelism參數用來控制並行度,即同時啟動幾個Job來處理Work item。
(3)Parallel Jobs with a work queue:任務隊列方式的並行Job需要一個獨立的Queue,Work item都在一個Queue中存放,不能設置Job的.spec.completions參數,此時Job有以下特性。

  • 每個Pod都能獨立判斷和決定是否還有任務項需要處理。
  • 如果某個Pod正常結束,則Job不會再啟動新的Pod。
  • 如果一個Pod成功結束,則此時應該不存在其他Pod還在工作的情況,它們應該都處於即將結束、退出的狀態。
  • 如果所有Pod都結束了,且至少有一個Pod成功結束,則整個Job成功結束

Cronjob:定時任務

  首先,確保Kubernetes的版本為1.8及以上
  其次,需要掌握Cron Job的定時表達式,它基本上照搬了Linux Cron的表達式

pod的容災調度

  為了滿足這種容災場景下的特殊調度需求,在Kubernetes 1.16版本中首次引入Even Pod Spreading特性,用於通過topologyKey屬性識別Zone,並通過設置新的參數topologySpreadConstraints來將Pod均勻調度到不同的Zone
  關鍵的參數是maxSkew。maxSkew用於指定Pod在各個Zone上調度時能容忍的最大不均衡數:值越大,表示能接受的不均衡調度越大;值越小,表示各個Zone的Pod數量分布越均勻。
  為了理解maxSkew,我們需要先理解skew參數的計算公式:skew[topo]=count[topo]-min(count[topo]),即每個拓撲區域的skew值都為該區域包括的目標Pod數量與整個拓撲區域最少Pod數量的差,而maxSkew就是最大的skew值

假如在上面的例子中有3個拓撲區域,分別為Zone A、Zone B及Zone C,有3個目標Pod需要調度到這些拓撲區域,那么前兩個毫無疑問會被調度到Zone A和Zone B
可以手動計算每個Zone的skew,首先計算出min(count[topo])是0,對應Zone C,於是Zone A的skew=1-0=1,Zone B的skew=1-0=0,Zone C的skew=0-0=0,於是第3個Pod應該被放在Zone C,此時min(count[topo])的值就變成了1,而實際的maxSkew的值為0,符合預期設置。如果我們把maxSkew設置為2,則在這種情況下,第3個Pod被放在Zone A或Zone B都是符合要求的

StatefulSet

  在創建StatefulSet之前,需要確保在Kubernetes集群中管理員已經創建好共享存儲,並能夠與StorageClass對接,以實現動態存儲供應的模式
  為了完成MongoDB集群的搭建,需要部署以下三個資源對象。

  • 一個StorageClass:用於StatefulSet自動為各個應用Pod申請PVC。
  • 一個Headless Service(clusterIP: None):用於設置MongoDB實例的域名。
  • 一個StatefulSet

  mongo-sidecar作為MongoDB集群的管理者,將使用此Headless Service來維護各個MongoDB實例之間的集群關系,以及集群規模變化時的自動更新

StatefulSet的常見應用場景

  • 集群進行擴容,僅需要通過對StatefulSet進行scale操作(或修改yaml文件)
  • 自動故障修復
    • 實例或其所在主機發生故障,則StatefulSet將會自動重建該實例,並保證其身份(ID)和使用的數據(PVC)不變

Service

Service概念和原理

  Service是Kubernetes實現微服務架構的核心概念,通過創建Service,可以為一組具有相同功能的容器應用提供一個統一的入口地址,並且將請求負載分發到后端的各個容器應用上

  Service用於為一組提供服務的Pod抽象一個穩定的網絡訪問地址,是Kubernetes實現微服務的核心概念。通過Service的定義設置的訪問地址是DNS域名格式的服務名稱,對於客戶端應用來說,網絡訪問方式並沒有改變(DNS域名的作用等價於主機名、互聯網域名或IP地址)。Service還提供了負載均衡器功能,將客戶端請求負載分發到后端提供具體服務的各個Pod上

  Service主要用於提供網絡服務,通過Service的定義,能夠為客戶端應用提供穩定的訪問地址(域名或IP地址)和負載均衡功能,以及屏蔽后端Endpoint的變化,是Kubernetes實現微服務的核心資源

  Service不僅具有標准網絡協議的IP地址,還以DNS域名的形式存在。Service的域名表示方法為<servicename>.<namespace>.svc.<clusterdomain>,servicename為服務的名稱,namespace為其所在namespace的名稱,clusterdomain為Kubernetes集群設置的域名后綴。服務名稱的命名規則遵循RFC 1123規范

Service負載均衡機制

  kube-proxy的代理模式、會話保持機制和基於拓撲感知的服務路由機制(EndpointSlices)

kube-proxy的代理模式

  kube-proxy提供了以下代理模式(通過啟動參數--proxy-mode設置)

  • userspace模式:用戶空間模式,由kube-proxy完成代理的實現,效率最低,不再推薦使用。
  • iptables模式:kube-proxy通過設置Linux Kernel的iptables規則,實現從Service到后端Endpoint列表的負載分發規則,效率很高。但是,如果某個后端Endpoint在轉發時不可用,此次客戶端請求就會得到失敗的響應,相對於userspace模式來說更不可靠。此時應該通過為Pod設置readinessprobe(服務可用性健康檢查)來保證只有達到ready狀態的Endpoint才會被設置為Service的后端Endpoint。
  • ipvs模式:在Kubernetes 1.11版本中達到Stable階段,kube-proxy通過設置Linux Kernel的netlink接口設置IPVS規則,轉發效率和支持的吞吐率都是最高的。ipvs模式要求Linux Kernel啟用IPVS模塊,如果操作系統未啟用IPVS內核模塊,kube-proxy則會自動切換至iptables模式。同時,ipvs模式支持更多的負載均衡策略,如下所述。
    • rr:round-robin,輪詢。
    • lc:least connection,最小連接數。
    • dh:destination hashing,目的地址哈希。
    • sh:source hashing,源地址哈希。
    • sed:shortest expected delay,最短期望延時。
    • nq:never queue,永不排隊。
  • kernelspace模式:Windows Server上的代理模式
會話保持機制

  Service支持通過設置sessionAffinity實現基於客戶端IP的會話保持機制,即首次將某個客戶端來源IP發起的請求轉發到后端的某個Pod上,之后從相同的客戶端IP發起的請求都將被轉發到相同的后端Pod上,配置參數為service.spec.sessionAffinity

  同時,用戶可以設置會話保持的最長時間,在此時間之后重置客戶端來源IP的保持規則,配置參數為service.spec.sessionAffinityConfig.clientIP.timeoutSeconds

基於拓撲感知的服務路由機制制(EndpointSlices)

將外部服務定義為Service

  普通的Service通過Label Selector對后端Endpoint列表進行了一次抽象,如果后端的Endpoint不是由Pod副本集提供的,則Service還可以抽象定義任意其他服務,將一個Kubernetes集群外部的已知服務定義為Kubernetes內的一個Service,供集群內的其他應用訪問,常見的應用場景包括:

  • 已部署的一個集群外服務,例如數據庫服務、緩存服務等;
  • 其他Kubernetes集群的某個服務;
  • 遷移過程中對某個服務進行Kubernetes內的服務名訪問機制的驗證。

  對於這種應用場景,用戶在創建Service資源對象時不設置Label Selector(后端Pod也不存在),同時再定義一個與Service關聯的Endpoint資源對象,在Endpoint中設置外部服務的IP地址和端口號

  

將Service暴露到集群外部

目前Service的類型如下。

  • ClusterIP:Kubernetes默認會自動設置Service的虛擬IP地址,僅可被集群內部的客戶端應用訪問。當然,用戶也可手工指定一個ClusterIP地址,不過需要確保該IP在Kubernetes集群設置的ClusterIP地址范圍內(通過kube-apiserver服務的啟動參數--service-cluster-ip-range設置),並且沒有被其他Service使用。
  • NodePort:將Service的端口號映射到每個Node的一個端口號上,這樣集群中的任意Node都可以作為Service的訪問入口地址,即NodeIP:NodePort。
  • LoadBalancer:將Service映射到一個已存在的負載均衡器的IP地址上,通常在公有雲環境中使用。
  • ExternalName:將Service映射為一個外部域名地址,通過externalName字段進行設置

Service支持的網絡協議

  • TCP:Service的默認網絡協議,可用於所有類型的Service。
  • UDP:可用於大多數類型的Service,LoadBalancer類型取決於雲服務商對UDP的支持。
  • HTTP:取決於雲服務商是否支持HTTP和實現機制。
  • PROXY:取決於雲服務商是否支持HTTP和實現機制。
  • SCTP:從Kubernetes 1.12版本引入,到1.19版本時達到Beta階段,默認啟用,如需關閉該特性,則需要設置kube-apiserver的啟動參數--feature-gates=SCTPSupport=false進行關閉  

  Kubernetes從1.17版本開始,可以為Service和Endpoint資源對象設置一個新的字段“AppProtocol”,用於標識后端服務在某個端口號上提供的應用層協議類型,例如HTTP、HTTPS、SSL、DNS等,該特性在Kubernetes 1.19版本時達到Beta階段,計划於Kubernetes 1.20 版本時達到GA階段。要使用AppProtocol,需要設置kube-apiserver的啟動參數--feature-gates=ServiceAppProtocol=true進行開啟,然后在Service或Endpoint的定義中設置AppProtocol字段指定應用層協議的類型

Kubernetes的服務發現機制

環境變量方式

  在一個Pod運行起來的時候,系統會自動為其容器運行環境注入所有集群中有效Service的信息。Service的相關信息包括服務IP、服務端口號、各端口號相關的協議等,通過{SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT格式進行設置。其中,SVCNAME的命名規則為:將Service的name字符串轉換為全大寫字母,將中橫線“-”替換為下畫線“_”

DNS方式

  Service在Kubernetes系統中遵循DNS命名規范,Service的DNS域名表示方法為<servicename>.<namespace>.svc.<clusterdomain>,其中servicename為服務的名稱,namespace為其所在namespace的名稱,clusterdomain為Kubernetes集群設置的域名后綴(例如cluster.local),服務名稱的命名規則遵循RFC 1123規范的要求

  對於客戶端應用來說,DNS域名格式的Service名稱提供的是穩定、不變的訪問地址,可以大大簡化客戶端應用的配置,是Kubernetes集群中推薦的使用方式。
  目前由CoreDNS作為Kubernetes集群的默認DNS服務器提供域名解析服務

  Service定義中的端口號如果設置了名稱(name),則該端口號也會擁有一個DNS域名,在DNS服務器中以SRV記錄的格式保存:_<portname>._<protocol>.<servicename>.<namespace>.svc.<clusterdomain>,其值為端口號的數值

Headless Service

  Headless Service的概念是這種服務沒有入口訪問地址(無ClusterIP地址),kube-proxy不會為其創建負載轉發規則,而服務名(DNS域名)的解析機制取決於該Headless Service是否設置了Label Selector

Headless Service沒有設置Label Selector

如果Headless Service沒有設置Label Selector,則Kubernetes將不會自動創建對應的Endpoint列表。DNS系統會根據下列條件嘗試對該服務名設置DNS記錄:

  • 如果Service的類型為ExternalName,則對服務名的訪問將直接被DNS系統轉換為Service設置的外部名稱(externalName);
  • 如果系統中存在與Service同名的Endpoint定義,則服務名將被解析為Endpoint定義中的列表,適用於非ExternalName類型的Service。

端點分片與服務拓撲

  Service的后端是一組Endpoint列表,為客戶端應用提供了極大的便利。但是隨着集群規模的擴大及Service數量的增加,特別是Service后端Endpoint數量的增加,kube-proxy需要維護的負載分發規則(例如iptables規則或ipvs規則)的數量也會急劇增加,導致后續對Service后端Endpoint的添加、刪除等更新操作的成本急劇上升。

  舉例來說,假設在Kubernetes集群中有10000個Endpoint運行在大約5000個Node上,則對單個Pod的更新將需要總計約5GB的數據傳輸,這不僅對集群內的網絡帶寬浪費巨大,而且對Master的沖擊非常大,會影響Kubernetes集群的整體性能,在Deployment不斷進行滾動升級操作的情況下尤為突出。

  Kubernetes從1.16版本開始引入端點分片(Endpoint Slices)機制,包括一個新的EndpointSlice資源對象和一個新的EndpointSlice控制器。從Kubernetes 1.17版本開始,EndpointSlice機制默認是啟用的(在1.16版本中需要通過設置kube-apiserver和kube-proxy服務的啟動參數--feature-gates="EndpointSlice=true"進行啟用)

  另外,kube-proxy默認仍然使用Endpoint對象,為了提高性能,可以設置kube-proxy啟動參數--feature-gates="EndpointSliceProxying=true"讓kube-proxy使用EndpointSlice,這樣可以減少kube-proxy與master之間的網絡通信並提高性能。Kubernetes從1.19版本開始默認開啟該特性

  EndpointSlice通過對Endpoint進行分片管理來實現降低Master和各Node之間的網絡傳輸數據量及提高整體性能的目標。對於Deployment的滾動升級,可以實現僅更新部分Node上的Endpoint信息,Master與Node之間的數據傳輸量可以減少100倍左右,能夠大大提高管理效率

  

 

  默認情況下,在由EndpointSlice控制器創建的EndpointSlice中最多包含100個Endpoint,如需修改,則可以通過kube-controller-manager服務的啟動參數--max-endpoints-per-slice設置,但上限不能超過1000

  EndpointSlice的關鍵信息如下。
(1)關聯的服務名稱:將EndpointSlice與Service的關聯信息設置為一個標簽kubernetes.io/service-name=webapp,該標簽標明了服務名稱。
(2)地址類型AddressType:包括以下3種取值類型

  • IPv4:IPv4格式的IP地址
  • IPv6:IPv6格式的IP地址。
  • FQDN:全限定域名。

(3)在Endpoints列表中列出的每個Endpoint的信息。

  • Addresses:Endpoint的IP地址。
  • Conditions:Endpoint狀態信息,作為EndpointSlice的查詢條件。
  • Hostname:在Endpoint中設置的主機名hostname。
  • TargetRef:Endpoint對應的Pod名稱。
  • Topology:拓撲信息,為基於拓撲感知的服務路由提供數據。

  目前EndpointSlice控制器自動設置的拓撲信息如下。

  • kubernetes.io/hostname:Endpoint所在Node的名稱。
  • topology.kubernetes.io/zone:Endpoint所在的Zone信息,使用Node標簽topology.kubernetes.io/zone的值,例如上例中的Node擁有“topology.kubernetes.io/zone:north”標簽。
  • topology.kubernetes.io/region:Endpoint所在的Region信息,使用Node標簽topology.kubernetes.io/region的值。

(4)EndpointSlice的管理控制器:通過endpointslice.kubernetes.io/managed-by標簽進行設置,用於存在多個管理控制器的應用場景中

DNS服務搭建和配置指南

kubernetes中的dns發展史

  在Kubernetes 1.2版本時,DNS服務是由SkyDNS提供的,它由4個容器組成:kube2sky、skydns、etcd和healthz

  從Kubernetes 1.4版本開始,SkyDNS組件便被KubeDNS替換,主要考慮的是SkyDNS組件之間通信較多,整體性能不高。KubeDNS由3個容器組成:kubedns、dnsmasq和sidecar,去掉了SkyDNS中的etcd存儲,將DNS記錄直接保存在內存中,以提高查詢性能

  從Kubernetes 1.11版本開始,Kubernetes集群的DNS服務便由CoreDNS提供。CoreDNS是CNCF基金會孵化的一個項目,是用Go語言實現的高性能、插件式、易擴展的DNS服務端,目前已畢業。

  CoreDNS解決了KubeDNS的一些問題,例如dnsmasq的安全漏洞、externalName不能使用stubDomains進行設置,等等。CoreDNS支持自定義DNS記錄及配置upstream DNS Server,可以統一管理Kubernetes基於服務的內部DNS和數據中心的物理DNS。它沒有使用多個容器的架構,只用一個容器便實現了KubeDNS內3個容器的全部功能

  coreDNS的架構

    

 

 

修改每個Node上kubelet的啟動參數,在其中加上以下兩個參數。

◎ --cluster-dns=169.169.0.100:為DNS服務的ClusterIP地址。
◎ --cluster-domain=cluster.local:為在DNS服務中設置的域名。
然后重啟kubelet服務。

部署CoreDNS服務

  部署CoreDNS服務時需要創建3個資源對象:1個ConfigMap、1個Deployment和1個Service。在啟用了RBAC的集群中,還可以設置ServiceAccount、ClusterRole、ClusterRoleBinding對CoreDNS容器進行權限設置

  Deployment“coredns”主要設置CoreDNS容器應用的內容,其中,replicas副本的數量通常應該根據集群的規模和服務數量確定,如果單個CoreDNS進程不足以支撐整個集群的DNS查詢,則可以通過水平擴展提高查詢能力。由於DNS服務是Kubernetes集群的關鍵核心服務,所以建議為其Deployment設置自動擴縮容控制器,自動管理其副本數量。

  另外,對資源限制部分(CPU限制和內存限制)的設置也應根據實際環境進行調整

  Service“kube-dns”是DNS服務的配置,這個服務需要設置固定的ClusterIP地址,也需要將所有Node上的kubelet啟動參數--cluster-dns都設置為這個ClusterIP地址

CoreDNS的配置說明

  CoreDNS的主要功能是通過插件系統實現的。CoreDNS實現了一種鏈式插件結構,將DNS的邏輯抽象成了一個個插件,能夠靈活組合使用

常用的插件如下。

  • loadbalance:提供基於DNS的負載均衡功能。
  • loop:檢測在DNS解析過程中出現的簡單循環問題。
  • cache:提供前端緩存功能。
  • health:對Endpoint進行健康檢查。
  • kubernetes:從Kubernetes中讀取zone數據。
  • etcd:從etcd中讀取zone數據,可用於自定義域名記錄。
  • file:從RFC1035格式文件中讀取zone數據。
  • hosts:使用/etc/hosts文件或者其他文件讀取zone數據,可用於自定義域名記錄。
  • auto:從磁盤中自動加載區域文件。
  • reload:定時自動重新加載Corefile配置文件的內容。
  • forward:轉發域名查詢到上游DNS服務器上。
  • prometheus:為Prometheus系統提供采集性能指標數據的URL。
  • pprof:在URL路徑/debug/pprof下提供運行時的性能數據。
  • log:對DNS查詢進行日志記錄。
  • errors:對錯誤信息進行日志記錄。

  域名“cluster.local”設置了一系列插件,包括errors、health、ready、kubernetes、prometheus、forward、cache、loop、reload和loadbalance,在進行域名解析時,這些插件將以從上到下的順序依次執行

  

       

數據存儲

基本存儲

  容器的生命周期可能很短,會被頻繁地創建和銷毀。那么容器在銷毀時,保存在容器中的數據也會被清除。這種結果對用戶來說,在某些情況下是不樂意看到的。為了持久化保存容器的數據,kubernetes引入了Volume的概念

  Volume是Pod中能夠被多個容器訪問的共享目錄,它被定義在Pod上,然后被一個Pod里的多個容器掛載到具體的文件目錄下,kubernetes通過Volume實現同一個Pod中不同容器之間的數據共享以及數據的持久化存儲。Volume的生命容器不與Pod中單個容器的生命周期相關,當容器終止或者重啟時,Volume中的數據也不會丟失。

  kubernetes的Volume支持多種類型,比較常見的有下面幾個:

  • 簡單存儲:EmptyDir、HostPath、NFS
  • 高級存儲:PV、PVC
  • 配置存儲:ConfigMap、Secret

EmptyDir

  EmptyDir是在Pod被分配到Node時創建的,它的初始內容為空,並且無須指定宿主機上對應的目錄文件,因為kubernetes會自動分配一個目錄,當Pod銷毀時, EmptyDir中的數據也會被永久刪除。 EmptyDir用途如下:

  • 臨時空間,例如用於某些應用程序運行時所需的臨時目錄,且無須永久保留
  • 一個容器需要從另一個容器中獲取數據的目錄(多容器共享目錄)

  在一個Pod中准備兩個容器nginx和busybox,然后聲明一個Volume分別掛在到兩個容器的目錄中,然后nginx容器負責向Volume中寫日志,busybox中通過命令將日志內容讀到控制台。

apiVersion: v1
kind: Pod
metadata:
  name: volume-emptydir
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:  # 將logs-volume掛在到nginx容器中,對應的目錄為 /var/log/nginx
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,動態讀取指定文件中內容
    volumeMounts:  # 將logs-volume 掛在到busybox容器中,對應的目錄為 /logs
    - name: logs-volume
      mountPath: /logs
  volumes: # 聲明volume, name為logs-volume,類型為emptyDir
  - name: logs-volume
    emptyDir: {}
View Code

 

HostPath

  EmptyDir中數據不會被持久化,它會隨着Pod的結束而銷毀,如果想簡單的將數據持久化到主機中,可以選擇HostPath。

  HostPath就是將Node主機中一個實際目錄掛在到Pod中,以供容器使用,這樣的設計就可以保證Pod銷毀了,但是數據依據可以存在於Node主機上。

apiVersion: v1
kind: Pod
metadata:
  name: volume-hostpath
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","tail -f /logs/access.log"]
    volumeMounts:
    - name: logs-volume
      mountPath: /logs
  volumes:
  - name: logs-volume
    hostPath: 
      path: /root/logs
      type: DirectoryOrCreate  # 目錄存在就使用,不存在就先創建后使用
View Code
關於type的值的一點說明:
    DirectoryOrCreate 目錄存在就使用,不存在就先創建后使用
    Directory   目錄必須存在
    FileOrCreate  文件存在就使用,不存在就先創建后使用
    File 文件必須存在 
    Socket  unix套接字必須存在
    CharDevice  字符設備必須存在
    BlockDevice 塊設備必須存

NFS

  HostPath可以解決數據持久化的問題,但是一旦Node節點故障了,Pod如果轉移到了別的節點,又會出現問題了,此時需要准備單獨的網絡存儲系統,比較常用的用NFS、CIFS。

  NFS是一個網絡文件存儲系統,可以搭建一台NFS服務器,然后將Pod中的存儲直接連接到NFS系統上,這樣的話,無論Pod在節點上怎么轉移,只要Node跟NFS的對接沒問題,數據就可以成功訪問。

# 在nfs上安裝nfs服務
[root@nfs ~]# yum install nfs-utils -y

# 准備一個共享目錄
[root@nfs ~]# mkdir /root/data/nfs -pv

# 將共享目錄以讀寫權限暴露給192.168.5.0/24網段中的所有主機
[root@nfs ~]# vim /etc/exports
[root@nfs ~]# more /etc/exports
/root/data/nfs     192.168.5.0/24(rw,no_root_squash)

# 啟動nfs服務
[root@nfs ~]# systemctl restart nfs
2)接下來,要在的每個node節點上都安裝下nfs,這樣的目的是為了node節點可以驅動nfs設備

# 在node上安裝nfs服務,注意不需要啟動
[root@k8s-master01 ~]# yum install nfs-utils -y
3)接下來,就可以編寫pod的配置文件了,創建volume-nfs.yaml

apiVersion: v1
kind: Pod
metadata:
  name: volume-nfs
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    ports:
    - containerPort: 80
    volumeMounts:
    - name: logs-volume
      mountPath: /var/log/nginx
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","tail -f /logs/access.log"] 
    volumeMounts:
    - name: logs-volume
      mountPath: /logs
  volumes:
  - name: logs-volume
    nfs:
      server: 192.168.5.6  #nfs服務器地址
      path: /root/data/nfs #共享文件路徑
4)最后,運行下pod,觀察結果

# 創建pod
[root@k8s-master01 ~]# kubectl create -f volume-nfs.yaml
pod/volume-nfs created

# 查看pod
[root@k8s-master01 ~]# kubectl get pods volume-nfs -n dev
NAME                  READY   STATUS    RESTARTS   AGE
volume-nfs        2/2     Running   0          2m9s

# 查看nfs服務器上的共享目錄,發現已經有文件了
[root@k8s-master01 ~]# ls /root/data/
access.log  error.log
View Code

高級存儲

  由於kubernetes支持的存儲系統有很多,要求客戶全都掌握,顯然不現實。為了能夠屏蔽底層存儲實現的細節,方便用戶使用, kubernetes引入PV和PVC兩種資源對象。

  • PV(Persistent Volume)是持久化卷的意思,是對底層的共享存儲的一種抽象。一般情況下PV由kubernetes管理員進行創建和配置,它與底層具體的共享存儲技術有關,並通過插件完成與共享存儲的對接。

  • PVC(Persistent Volume Claim)是持久卷聲明的意思,是用戶對於存儲需求的一種聲明。換句話說,PVC其實就是用戶向kubernetes系統發出的一種資源需求申請。

  

  使用了PV和PVC之后,工作可以得到進一步的細分:

  • 存儲:存儲工程師維護
  • PV: kubernetes管理員維護
  • PVC:kubernetes用戶維護

PV

  PV是存儲資源的抽象,下面是資源清單文件:

apiVersion: v1  
kind: PersistentVolume
metadata:
  name: pv2
spec:
  nfs: # 存儲類型,與底層真正存儲對應
  capacity:  # 存儲能力,目前只支持存儲空間的設置
    storage: 2Gi
  accessModes:  # 訪問模式
  storageClassName: # 存儲類別
  persistentVolumeReclaimPolicy: # 回收策略

  PV 的關鍵配置參數說明:

  • 存儲類型

    底層實際存儲的類型,kubernetes支持多種存儲類型,每種存儲類型的配置都有所差異

  • 存儲能力(capacity)

  目前只支持存儲空間的設置( storage=1Gi ),不過未來可能會加入IOPS、吞吐量等指標的配置

  • 訪問模式(accessModes)

    用於描述用戶應用對存儲資源的訪問權限,訪問權限包括下面幾種方式:

    • ReadWriteOnce(RWO):讀寫權限,但是只能被單個節點掛載
    • ReadOnlyMany(ROX): 只讀權限,可以被多個節點掛載
    • ReadWriteMany(RWX):讀寫權限,可以被多個節點掛載

    需要注意的是,底層不同的存儲類型可能支持的訪問模式不同

  • 回收策略(persistentVolumeReclaimPolicy)

    當PV不再被使用了之后,對其的處理方式。目前支持三種策略:

    • Retain (保留) 保留數據,需要管理員手工清理數據
    • Recycle(回收) 清除 PV 中的數據,效果相當於執行 rm -rf /thevolume/*
    • Delete (刪除) 與 PV 相連的后端存儲完成 volume 的刪除操作,當然這常見於雲服務商的存儲服務

    需要注意的是,底層不同的存儲類型可能支持的回收策略不同

  • 存儲類別

    PV可以通過storageClassName參數指定一個存儲類別

    • 具有特定類別的PV只能與請求了該類別的PVC進行綁定
    • 未設定類別的PV則只能與不請求任何類別的PVC進行綁定
  • 狀態(status)

    一個 PV 的生命周期中,可能會處於4中不同的階段:

    • Available(可用): 表示可用狀態,還未被任何 PVC 綁定
    • Bound(已綁定): 表示 PV 已經被 PVC 綁定
    • Released(已釋放): 表示 PVC 被刪除,但是資源還未被集群重新聲明
    • Failed(失敗): 表示該 PV 的自動回收失敗
 
使用NFS作為存儲,來演示PV的使用,創建3個PV,對應NFS中的3個暴露的路徑。

准備NFS環境
# 創建目錄
[root@nfs ~]# mkdir /root/data/{pv1,pv2,pv3} -pv

# 暴露服務
[root@nfs ~]# more /etc/exports
/root/data/pv1     192.168.5.0/24(rw,no_root_squash)
/root/data/pv2     192.168.5.0/24(rw,no_root_squash)
/root/data/pv3     192.168.5.0/24(rw,no_root_squash)

# 重啟服務
[root@nfs ~]#  systemctl restart nfs
創建pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name:  pv1
spec:
  capacity: 
    storage: 1Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /root/data/pv1
    server: 192.168.5.6

---

apiVersion: v1
kind: PersistentVolume
metadata:
  name:  pv2
spec:
  capacity: 
    storage: 2Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /root/data/pv2
    server: 192.168.5.6
    
---

apiVersion: v1
kind: PersistentVolume
metadata:
  name:  pv3
spec:
  capacity: 
    storage: 3Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /root/data/pv3
    server: 192.168.5.6
# 創建 pv
[root@k8s-master01 ~]# kubectl create -f pv.yaml
persistentvolume/pv1 created
persistentvolume/pv2 created
persistentvolume/pv3 created

# 查看pv
[root@k8s-master01 ~]# kubectl get pv -o wide
NAME   CAPACITY   ACCESS MODES  RECLAIM POLICY  STATUS      AGE   VOLUMEMODE
pv1    1Gi        RWX            Retain        Available    10s   Filesystem
pv2    2Gi        RWX            Retain        Available    10s   Filesystem
pv3    3Gi        RWX            Retain        Available    9s    Filesystem
View Code

PVC

  PVC是資源的申請,用來聲明對存儲空間、訪問模式、存儲類別需求信息。下面是資源清單文件:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc
  namespace: dev
spec:
  accessModes: # 訪問模式
  selector: # 采用標簽對PV選擇
  storageClassName: # 存儲類別
  resources: # 請求空間
    requests:
      storage: 5Gi

  

  PVC 的關鍵配置參數說明:

  • 訪問模式(accessModes)

  用於描述用戶應用對存儲資源的訪問權限

  • 選擇條件(selector)

    通過Label Selector的設置,可使PVC對於系統中己存在的PV進行篩選

  • 存儲類別(storageClassName)

    PVC在定義時可以設定需要的后端存儲的類別,只有設置了該class的pv才能被系統選出

  • 資源請求(Resources )

    描述對存儲資源的請求

創建pvc.yaml,申請pv
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
  namespace: dev
spec:
  accessModes: 
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc2
  namespace: dev
spec:
  accessModes: 
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc3
  namespace: dev
spec:
  accessModes: 
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
# 創建pvc
[root@k8s-master01 ~]# kubectl create -f pvc.yaml
persistentvolumeclaim/pvc1 created
persistentvolumeclaim/pvc2 created
persistentvolumeclaim/pvc3 created

# 查看pvc
[root@k8s-master01 ~]# kubectl get pvc  -n dev -o wide
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE   VOLUMEMODE
pvc1   Bound    pv1      1Gi        RWX                           15s   Filesystem
pvc2   Bound    pv2      2Gi        RWX                           15s   Filesystem
pvc3   Bound    pv3      3Gi        RWX                           15s   Filesystem

# 查看pv
[root@k8s-master01 ~]# kubectl get pv -o wide
NAME  CAPACITY ACCESS MODES  RECLAIM POLICY  STATUS    CLAIM       AGE     VOLUMEMODE
pv1    1Gi        RWx        Retain          Bound    dev/pvc1    3h37m    Filesystem
pv2    2Gi        RWX        Retain          Bound    dev/pvc2    3h37m    Filesystem
pv3    3Gi        RWX        Retain          Bound    dev/pvc3    3h37m    Filesystem   
創建pods.yaml, 使用pv
apiVersion: v1
kind: Pod
metadata:
  name: pod1
  namespace: dev
spec:
  containers:
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","while true;do echo pod1 >> /root/out.txt; sleep 10; done;"]
    volumeMounts:
    - name: volume
      mountPath: /root/
  volumes:
    - name: volume
      persistentVolumeClaim:
        claimName: pvc1
        readOnly: false
---
apiVersion: v1
kind: Pod
metadata:
  name: pod2
  namespace: dev
spec:
  containers:
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","while true;do echo pod2 >> /root/out.txt; sleep 10; done;"]
    volumeMounts:
    - name: volume
      mountPath: /root/
  volumes:
    - name: volume
      persistentVolumeClaim:
        claimName: pvc2
        readOnly: false
# 創建pod
[root@k8s-master01 ~]# kubectl create -f pods.yaml
pod/pod1 created
pod/pod2 created

# 查看pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP            NODE   
pod1   1/1     Running   0          14s   10.244.1.69   node1   
pod2   1/1     Running   0          14s   10.244.1.70   node1  

# 查看pvc
[root@k8s-master01 ~]# kubectl get pvc -n dev -o wide
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES      AGE   VOLUMEMODE
pvc1   Bound    pv1      1Gi        RWX               94m   Filesystem
pvc2   Bound    pv2      2Gi        RWX               94m   Filesystem
pvc3   Bound    pv3      3Gi        RWX               94m   Filesystem

# 查看pv
[root@k8s-master01 ~]# kubectl get pv -n dev -o wide
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM       AGE     VOLUMEMODE
pv1    1Gi        RWX            Retain           Bound    dev/pvc1    5h11m   Filesystem
pv2    2Gi        RWX            Retain           Bound    dev/pvc2    5h11m   Filesystem
pv3    3Gi        RWX            Retain           Bound    dev/pvc3    5h11m   Filesystem

# 查看nfs中的文件存儲
[root@nfs ~]# more /root/data/pv1/out.txt
node1
node1
[root@nfs ~]# more /root/data/pv2/out.txt
node2
node2
View Code

生命周期

  PVC和PV是一一對應的,PV和PVC之間的相互作用遵循以下生命周期:

  • 資源供應:管理員手動創建底層存儲和PV

  • 資源綁定:用戶創建PVC,kubernetes負責根據PVC的聲明去尋找PV,並綁定

    在用戶定義好PVC之后,系統將根據PVC對存儲資源的請求在已存在的PV中選擇一個滿足條件的

    • 一旦找到,就將該PV與用戶定義的PVC進行綁定,用戶的應用就可以使用這個PVC了
    • 如果找不到,PVC則會無限期處於Pending狀態,直到等到系統管理員創建了一個符合其要求的PV

    PV一旦綁定到某個PVC上,就會被這個PVC獨占,不能再與其他PVC進行綁定了

  • 資源使用:用戶可在pod中像volume一樣使用pvc

    Pod使用Volume的定義,將PVC掛載到容器內的某個路徑進行使用。

  • 資源釋放:用戶刪除pvc來釋放pv

    當存儲資源使用完畢后,用戶可以刪除PVC,與該PVC綁定的PV將會被標記為“已釋放”,但還不能立刻與其他PVC進行綁定。通過之前PVC寫入的數據可能還被留在存儲設備上,只有在清除之后該PV才能再次使用。

  • 資源回收:kubernetes根據pv設置的回收策略進行資源的回收

    對於PV,管理員可以設定回收策略,用於設置與之綁定的PVC釋放資源之后如何處理遺留數據的問題。只有PV的存儲空間完成回收,才能供新的PVC綁定和使用

  

 

 

 

配置存儲

ConfigMap

  ConfigMap是一種比較特殊的存儲卷,它的主要作用是用來存儲配置信息的。

    創建configmap.yaml,內容如下:

apiVersion: v1
kind: ConfigMap
metadata:
  name: configmap
  namespace: dev
data:
  info: |
    username:admin
    password:123456
# 創建configmap
[root@k8s-master01 ~]# kubectl create -f configmap.yaml
configmap/configmap created

# 查看configmap詳情
[root@k8s-master01 ~]# kubectl describe cm configmap -n dev
Name:         configmap
Namespace:    dev
Labels:       <none>
Annotations:  <none>

Data
====
info:
----
username:admin
password:123456

Events:  <none>
接下來創建一個pod-configmap.yaml,將上面創建的configmap掛載進去

apiVersion: v1
kind: Pod
metadata:
  name: pod-configmap
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    volumeMounts: # 將configmap掛載到目錄
    - name: config
      mountPath: /configmap/config
  volumes: # 引用configmap
  - name: config
    configMap:
      name: configmap
# 創建pod
[root@k8s-master01 ~]# kubectl create -f pod-configmap.yaml
pod/pod-configmap created

# 查看pod
[root@k8s-master01 ~]# kubectl get pod pod-configmap -n dev
NAME            READY   STATUS    RESTARTS   AGE
pod-configmap   1/1     Running   0          6s

#進入容器
[root@k8s-master01 ~]# kubectl exec -it pod-configmap -n dev /bin/sh
# cd /configmap/config/
# ls
info
# more info
username:admin
password:123456

# 可以看到映射已經成功,每個configmap都映射成了一個目錄
# key--->文件     value---->文件中的內容
# 此時如果更新configmap的內容, 容器中的值也會動態更新
View Code
Secret

  在kubernetes中,還存在一種和ConfigMap非常類似的對象,稱為Secret對象。它主要用於存儲敏感信息,例如密碼、秘鑰、證書等等。

首先使用base64對數據進行編碼
[root@k8s-master01 ~]# echo -n 'admin' | base64 #准備username
YWRtaW4=
[root@k8s-master01 ~]# echo -n '123456' | base64 #准備password
MTIzNDU2
接下來編寫secret.yaml,並創建Secret
apiVersion: v1
kind: Secret
metadata:
  name: secret
  namespace: dev
type: Opaque
data:
  username: YWRtaW4=
  password: MTIzNDU2
# 創建secret
[root@k8s-master01 ~]# kubectl create -f secret.yaml
secret/secret created

# 查看secret詳情
[root@k8s-master01 ~]# kubectl describe secret secret -n dev
Name:         secret
Namespace:    dev
Labels:       <none>
Annotations:  <none>
Type:  Opaque
Data
====
password:  6 bytes
username:  5 bytes
創建pod-secret.yaml,將上面創建的secret掛載進去:
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    volumeMounts: # 將secret掛載到目錄
    - name: config
      mountPath: /secret/config
  volumes:
  - name: config
    secret:
      secretName: secret
# 創建pod
[root@k8s-master01 ~]# kubectl create -f pod-secret.yaml
pod/pod-secret created

# 查看pod
[root@k8s-master01 ~]# kubectl get pod pod-secret -n dev
NAME            READY   STATUS    RESTARTS   AGE
pod-secret      1/1     Running   0          2m28s

# 進入容器,查看secret信息,發現已經自動解碼了
[root@k8s-master01 ~]# kubectl exec -it pod-secret /bin/sh -n dev
/ # ls /secret/config/
password  username
/ # more /secret/config/username
admin
/ # more /secret/config/password
123456
至此,已經實現了利用secret實現了信息的編碼。
View Code

 

  1. 首先使用base64對數據進行編碼
[root@k8s-master01 ~]# echo -n 'admin' | base64 #准備username
YWRtaW4=
[root@k8s-master01 ~]# echo -n '123456' | base64 #准備password
MTIzNDU2
  1. 接下來編寫secret.yaml,並創建Secret
apiVersion: v1
kind: Secret
metadata:
  name: secret
  namespace: dev
type: Opaque
data:
  username: YWRtaW4=
  password: MTIzNDU2
# 創建secret
[root@k8s-master01 ~]# kubectl create -f secret.yaml
secret/secret created

# 查看secret詳情
[root@k8s-master01 ~]# kubectl describe secret secret -n dev
Name:         secret
Namespace:    dev
Labels:       <none>
Annotations:  <none>
Type:  Opaque
Data
====
password:  6 bytes
username:  5 bytes
  1. 創建pod-secret.yaml,將上面創建的secret掛載進去:
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
    volumeMounts: # 將secret掛載到目錄
    - name: config
      mountPath: /secret/config
  volumes:
  - name: config
    secret:
      secretName: secret
# 創建pod
[root@k8s-master01 ~]# kubectl create -f pod-secret.yaml
pod/pod-secret created

# 查看pod
[root@k8s-master01 ~]# kubectl get pod pod-secret -n dev
NAME            READY   STATUS    RESTARTS   AGE
pod-secret      1/1     Running   0          2m28s

# 進入容器,查看secret信息,發現已經自動解碼了
[root@k8s-master01 ~]# kubectl exec -it pod-secret /bin/sh -n dev
/ # ls /secret/config/
password  username
/ # more /secret/config/username
admin
/ # more /secret/config/password
123456

至此,已經實現了利用secret實現了信息的編碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM