Kubernetes 核心概念


Kubernetes 核心概念

1. Master

Kubernetes 里的 Master指的是集群控制節點,在每個Kubernetes集群里都需要有一個Master來負責整個集群的管理和控制,基本上Kubernetes的所有控制命令都發給它,它負責具體的執行過程,我們后面執行的所有命令基本都是在Master上運行的。Master通常會占據一個獨立的服務器(高可用部署建議用3台服務器),主要原因是它太重要了,是整個集群的“首腦”,如果它宕機或者不可用,那么對集群內容器應用的管理都將失效。

在Master上運行着以下關鍵進程:

  • Kubernetes API Server(kube-apiserver):提供了HTTP Rest接口的關鍵服務進程,是Kubernetes里所有資源的增、刪、改、查等操作的唯一入口,也是集群控制的入口進程。
  • Kubernetes Controller Manager(kube-controller-manager):Kubernetes里所有資源對象的自動化控制中心,可以將其理解為資源對象的“大總管”。
  • Kubernetes Scheduler(kube-scheduler):負責資源調度(Pod調度)的進程,相當於公交公司的“調度室”。

另外,在Master上通常還需要部署etcd服務,因為Kubernetes里的所有資源對象的數據都被保存在etcd中。

2. Node

除了Master,Kubernetes集群中的其他機器被稱為Node,在較早的版本中也被稱為Minion。與Master一樣,Node可以是一台物理主機,也可以是一台虛擬機。Node是Kubernetes集群中的工作負載節點,每個Node都會被Master分配一些工作負載(Docker容器),當某個Node宕機時,其上的工作負載會被Master自動轉移到其他節點上。

在每個Node上都運行着以下關鍵進程:

  • kubelet:負責管控容器,Kubelet會從Kubernetes API Server接收Pod的創建請求,啟動和停止容器,監控容器運行狀態並匯報給Kubernetes API Server。同時與Master密切協作,實現集群管理的基本功能。
  • kube-proxy:實現Kubernetes Service的通信與負載均衡機制的重要組件。負責為Pod創建代理服務,Kubernetes Proxy會從Kubernetes API Server獲取所有的Service信息,並根據Service的信息創建代理服務,實現Service到Pod的請求路由和轉發,從而實現Kubernetes層級的虛擬轉發網絡。
  • Docker Engine(docker):Docker引擎,負責本機的容器創建和管理工作。

Node可以在運行期間動態增加到Kubernetes集群中,前提是在這個節點上已經正確安裝、配置和啟動了上述關鍵進程,在默認情況下Kubelet會向Master注冊自己,這也是Kubernetes推薦的Node管理方式。一旦Node被納入集群管理范圍,Kubelet進程就會定時向Master匯報自身的情報,例如操作系統、Docker版本、機器的CPU和內存情況,以及當前有哪些Pod在運行等,這樣Master就可以獲知每個Node的資源使用情況,並實現高效均衡的資源調度策略。而某個Node在超過指定時間不上報信息時,會被Master判定為“失聯”,Node的狀態被標記為不可用(Not Ready),隨后Master會觸發“工作負載大轉移”的自動流程。

查看在集群中有多少個Node:

# kubectl get nodes

查看某個Node的詳細信息:

# kubectl describe node k8s-node01

上述命令展示了Node的如下關鍵信息:

  • Node的基本信息:名稱、標簽、創建時間等。
  • Node當前的運行狀態:Node啟動后會做一系列的自檢工作,比如磁盤空間是否不足(DiskPressure)、內存是否不足(MemoryPressure)、網絡是否正常(NetworkUnavailable)、PID資源是否充足(PIDPressure)。在一切正常時設置Node為Ready狀態(Ready=True),該狀態表示Node處於健康狀態,Master將可以在其上調度新的任務了(如啟動Pod)。
  • Node的主機地址與主機名。
  • Node上的資源數量:描述Node可用的系統資源,包括CPU、內存數量、最大可調度Pod數量等。
  • Node可分配的資源量:描述Node當前可用於分配的資源量。
  • 主機系統信息:包括主機ID、系統UUID、Linux kernel版本號、操作系統類型與版本、Docker版本號、kubelet與kube-proxy的版本號等。
  • 當前運行的Pod列表概要信息。
  • 已分配的資源使用概要信息,例如資源申請的最低、最大允許使用量占系統總量的百分比。
  • Node相關的Event信息。

3. Pod

Pod是Kubernetes最重要的基本概念,如下圖所示是Pod的組成示意圖,我們看到每個Pod都有一個特殊的被稱為“根容器”的Pause容器。Pause容器對應的鏡像屬於Kubernetes平台的一部分,除了Pause容器,每個Pod還包含一個或多個緊密相關的用戶業務容器。

為什么Kubernetes會設計出一個全新的Pod的概念並且Pod有這樣特殊的組成結構?

  1. 在一組容器作為一個單元的情況下,我們難以簡單地對“整體”進行判斷及有效地行動。比如,一個容器死亡了,此時算是整體死亡么?是N/M的死亡率么?引入業務無關並且不易死亡的Pause容器作為Pod的根容器,以它的狀態代表整個容器組的狀態,就簡單、巧妙地解決了這個難題。
  2. Pod里的多個業務容器共享Pause容器的IP,共享Pause容器掛接的Volume,這樣既簡化了密切關聯的業務容器之間的通信問題,也很好地解決了它們之間的文件共享問題。

Kubernetes為每個Pod都分配了唯一的IP地址,稱之為Pod IP,一個Pod里的多個容器共享Pod IP地址。Kubernetes要求底層網絡支持集群內任意兩個Pod之間的TCP/IP直接通信,這通常采用虛擬二層網絡技術來實現,例如Flannel、Open vSwitch等,因此我們需要牢記一點:在Kubernetes里,一個Pod里的容器與另外主機上的Pod容器能夠直接通信。

Pod其實有兩種類型:普通的Pod及靜態Pod(Static Pod)。后者比較特殊,它並沒被存放在Kubernetes的etcd存儲里,而是被存放在某個具體的Node上的一個具體文件中,並且只在此Node上啟動、運行。而普通的Pod一旦被創建,就會被放入etcd中存儲,隨后會被Kubernetes Master調度到某個具體的Node上並進行綁定(Binding),隨后該Pod被對應的Node上的kubelet進程實例化成一組相關的Docker容器並啟動。在默認情況下,當Pod里的某個容器停止時,Kubernetes會自動檢測到這個問題並且重新啟動這個Pod(重啟Pod里的所有容器),如果Pod所在的Node宕機,就會將這個Node上的所有Pod重新調度到其他節點上。

Pod、容器與Node的關系圖

Kubernetes里的所有資源對象都可以采用YAML或者JSON格式的文件來定義或描述,下面是一個Pod的資源定義文件示例:

apiVersion: v1
kind: Pod
metadata:
  name: myweb
  labels:
    name: myweb
spec:
  containers:
  - name: myweb
    image: kubeguide/tomcat-app:v1
    ports:
    - containerPort: 8080
  env:
    - name: MYSQL_SERVICE_HOST
      value: 'mysql'
    - name: MYSQL_SERVICE_PORT
      value: '3306'

Kind為Pod表明這是一個Pod的定義,metadata里的name屬性為Pod的名稱,在metadata里還能定義資源對象的標簽,這里聲明myweb擁有一個"name=myweb"的標簽。在Pod里所包含的容器組的定義則在spec一節中聲明,這里定義了一個名為myweb、對應鏡像為"kubeguide/tomcat-app:v1"的容器,該容器注入了名為"MYSQL_SERVICE_HOST='mysql'"和"MYSQL_SERVICE_PORT='3306'"的環境變量(env關鍵字),並且在8080端口(containerPort)啟動容器進程。Pod的IP加上這里的容器端口(containerPort),組成了一個新的概念——Endpoint,它代表此Pod里的一個服務進程的對外通信地址。一個Pod也存在具有多個Endpoint的情況,比如當我們把Tomcat定義為一個Pod時,可以對外暴露管理端口與服務端口這兩個Endpoint。

Kubernetes的Event概念:
Event是一個事件的記錄,記錄了事件的最早產生時間、最后重現時間、重復次數、發起者、類型,以及導致此事件的原因等眾多信息。Event通常會被關聯到某個具體的資源對象上,是排查故障的重要參考信息,之前我們看到Node的描述信息包括了Event,而Pod同樣有Event記錄,當我們發現某個Pod遲遲無法創建時,可以用kubectl describe pod xxxx來查看它的描述信息,以定位問題的成因。

舉例:Event記錄信息表明Pod里的一個容器被探針檢測為失敗

每個Pod都可以對其能使用的服務器上的計算資源設置限額,當前可以設置限額的計算資源有CPU與Memory兩種,其中CPU的資源單位為CPU(Core)的數量,是一個絕對值而非相對值。

對於絕大多數容器來說,一個CPU的資源配額相當大,所以在Kubernetes里通常以千分之一的CPU配額為最小單位,用m來表示。通常一個容器的CPU配額被定義為100~300m,即占用0.1~0.3個CPU。由於CPU配額是一個絕對值,所以無論在擁有一個Core的機器上,還是在擁有48個Core的機器上,100m這個配額所代表的CPU的使用量都是一樣的。與CPU配額類似,Memory配額也是一個絕對值,它的單位是內存字節數。

在Kubernetes里,一個計算資源進行配額限定時需要設定以下兩個參數。

  • Requests:該資源的最小申請量,系統必須滿足要求。
  • Limits:該資源最大允許使用的量,不能被突破,當容器試圖使用超過這個量的資源時,可能會被Kubernetes“殺掉”並重啟。

通常,我們會把Requests設置為一個較小的數值,符合容器平時的工作負載情況下的資源需求,而把Limit設置為峰值負載情況下資源占用的最大量。下面這段定義表明MySQL容器申請最少0.25個CPU及64MiB內存,在運行過程中MySQL容器所能使用的資源配額為0.5個CPU及128MiB內存:

spec:
  containers:
  - name: db
    image: mysql
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Pod及Pod周邊對象的示意圖:

4. Replication Controller(RC)

RC是Kubernetes系統中的核心概念之一,簡單來說,它其實定義了一個期望的場景,即聲明某種Pod的副本數量在任意時刻都符合某個預期值,所以RC的定義包括如下幾個部分。

  • Pod期待的副本數量。
  • 用於篩選目標Pod的Label Selector。
  • 當Pod的副本數量小於預期數量時,用於創建新Pod的Pod模板(template)。

下圖是一個完整的例子:確保擁有標簽tier=frontend的Pod在整個Kubernetes集群中始終只有一個副本。

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    tier: frontedn
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: tomcat-demo
        image: tomcat
        imagePullPolicy: IfNotPresent
        env:
        - name: GET_HOSTS_FROM
          value: dns
        ports:
        - containerPort: 80

在我們定義了一個RC並將其提交到Kubernetes集群中后,Master上的Controller Manager組件就得到通知,定期巡檢系統中當前存活的目標Pod,並確保目標Pod實例的數量剛好等於此RC的期望值,如果有過多的Pod副本在運行,系統就會停掉一些Pod,否則系統會再自動創建一些Pod。通過RC,Kubernetes實現了用戶應用集群的高可用性,並且大大減少了系統管理員在傳統IT環境中需要完成的許多手工運維工作(如主機監控腳本、應用監控腳本、故障恢復腳本等)。

以有3個Node的集群為例,說明Kubernetes如何通過RC來實現Pod副本數量自動控制的機制。假如在我們的RC里定義redis-slave這個Pod需要保持兩個副本,系統將可能在其中的兩個Node上創建Pod。下圖描述了在兩個Node上創建redis-slave Pod的情形。

假設Node 2上的Pod 2意外終止,則根據RC定義的replicas數量2,Kubernetes將會自動創建並啟動一個新的Pod,以保證在整個集群中始終有兩個redis-slave Pod運行。
如圖所示,系統可能選擇Node 3或者Node 1來創建一個新的Pod。

此外,在運行時,我們可以通過修改RC的副本數量,來實現Pod的動態縮放(Scaling),通過執行kubectl scale命令來一鍵完成:

# kubectl scale rc redis-slave --replicas=3
scaled

Scaling的執行結果如圖所示。

刪除RC並不會影響通過該RC已創建好的Pod。為了刪除所有Pod,可以設置replicas的值為0,然后更新該RC。另外,kubectl提供了stop和delete命令來一次性刪除RC和RC控制的全部Pod。

應用升級時,通常會使用一個新的容器鏡像版本替代舊版本。我們希望系統平滑升級,比如在當前系統中有10個對應的舊版本的Pod,則最佳的系統升級方式是舊版本的Pod每停止一個,就同時創建一個新版本的Pod,在整個升級過程中此消彼長,而運行中的Pod數量始終是10個,幾分鍾以后,當所有的Pod都已經是新版本時,系統升級完成。通過RC機制,Kubernetes很容易就實現了這種高級實用的特性,被稱為“滾動升級”(Rolling Update)。

在Kubernetes 1.2后的版本中,Replication Controller升級為另一個新概念--Replica Set,官方解釋為“下一代的RC”。Replica Set與RC當前的唯一區別是,Replica Sets支持基於集合的Label selector(Set-based selector),而RC只支持基於等式的Label Selector(equality-based selector),這使得Replica Set的功能更強。

kubectl命令行工具適用於RC的絕大部分命令同樣適用於ReplicaSet。此外,我們當前很少單獨使用Replica Set,它主要被Deployment這個更高層的資源對象所使用,從而形成一整套Pod創建、刪除、更新的編排機制。我們在使用Deployment時,無須關心它是如何創建和維護Replica Set的,這一切都是自動發生的。

Replica Set與Deployment這兩個重要的資源對象逐步替代了之前RC的作用,是Kubernetes 1.3里Pod自動擴容(伸縮)這個告警功能實現的基礎,也將繼續在Kubernetes未來的版本中發揮重要的作用。

最后總結一下RC(Replica Set)的一些特性與作用:

  • 在大多數情況下,我們通過定義一個RC實現Pod的創建及副本數量的自動控制。
  • 在RC里包括完整的Pod定義模板。
  • RC通過Label Selector機制實現對Pod副本的自動控制。
  • 通過改變RC里的Pod副本數量,可以實現Pod的擴容或縮容。
  • 通過改變RC里Pod模板中的鏡像版本,可以實現Pod的滾動升級。

5. Service

5.1 概述

Service服務也是Kubernetes里的核心資源對象之一,Kubernetes里的每個Service其實就是我們經常提起的微服務架構中的一個微服務,之前講解Pod、RC等資源對象其實都是為講解Kubernetes Service做鋪墊的。
下圖顯示了Pod、RC與Service的邏輯關系。

從上圖中可以看到,Kubernetes的Service定義了一個服務的訪問入口地址,前端的應用(Pod)通過這個入口地址訪問其背后的一組由Pod副本組成的集群實例,Service與其后端Pod副本集群之間則是通過Label Selector來實現無縫對接的。RC的作用實際上是保證Service的服務能力和服務質量始終符合預期標准。

通過分析、識別並建模系統中的所有服務為微服務——Kubernetes Service,我們的系統最終由多個提供不同業務能力而又彼此獨立的微服務單元組成的,服務之間通過TCP/IP進行通信,從而形成了強大而又靈活的彈性網格,擁有強大的分布式能力、彈性擴展能力、容錯能力,程序架構也變得簡單和直觀許多,如圖所示。

既然每個Pod都會被分配一個單獨的IP地址,而且每個Pod都提供了一個獨立的Endpoint(Pod IP+ContainerPort)以被客戶端訪問,現在多個Pod副本組成了一個集群來提供服務,那么客戶端如何來訪問它們呢?一般的做法是部署一個負載均衡器(軟件或硬件),為這組Pod開啟一個對外的服務端口如8000端口,並且將這些Pod的Endpoint列表加入8000端口的轉發列表,客戶端就可以通過負載均衡器的對外IP地址+服務端口來訪問此服務。客戶端的請求最后會被轉發到哪個Pod,由負載均衡器的算法所決定。

Kubernetes也遵循上述常規做法,運行在每個Node上的kube-proxy進程其實就是一個智能的軟件負載均衡器,負責把對Service的請求轉發到后端的某個Pod實例上,並在內部實現服務的負載均衡與會話保持機制。但Kubernetes發明了一種很巧妙又影響深遠的設計:Service沒有共用一個負載均衡器的IP地址,每個Service都被分配了一個全局唯一的虛擬IP地址,這個虛擬IP被稱為Cluster IP。這樣一來,每個服務就變成了具備唯一IP地址的通信節點,服務調用就變成了最基礎的TCP網絡通信問題。

我們知道,Pod的Endpoint地址會隨着Pod的銷毀和重新創建而發生改變,因為新Pod的IP地址與之前舊Pod的不同。而Service一旦被創建,Kubernetes就會自動為它分配一個可用的Cluster IP,而且在Service的整個生命周期內,它的Cluster IP不會發生改變。於是,服務發現這個棘手的問題在Kubernetes的架構里也得以輕松解決:只要用Service的Name與Service的Cluster IP地址做一個DNS域名映射即可完美解決問題。現在想想,這真是一個很棒的設計。

需要說明的是,Kubernetes集群中的Service既不是一個實體的組件,也不是一個應用服務,而是一個Iptables的DNAT規則。每當創建Service后,會創建下面一條iptables規則:

-A KUBE-SERVICES -d 10.111.15.33/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3

凡是地址是10.111.15.33、目的端口是80的IP包,都會跳轉到KUBE-SVC-NWV5X2332I4OT4T3的Iptables鏈處理。

為了加深對Service的理解,我們創建一個Server的示例。創建一個名為tomcat-service.yaml的定義文件,內容如下:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
  - port: 8080
  selector:
    tier: frontend

上述內容定義了一個名為tomcat-service的Service,它的服務端口為8080,凡是擁有“tier=frontend”這個Label的所有Pod實例都屬於它,運行下面的命令進行創建:

# kubectl apply -f tomcat-service.yaml
service "tomcat-service" created

我們之前在tomcat-deployment.yaml里定義的Tomcat的Pod剛好擁有這個標簽,所以剛才創建的tomcat-service已經對應一個Pod實例,運行下面的命令可以查看tomcat-service的Endpoint列表,其中172.17.1.3是Pod的IP地址,端口8080是Container暴露的端口:

# kubectl get endpoints
NAME             ENDPOINTS             AGE
kubernetes       192.168.18.131:6443   15d
tomcat-service   172.17.1.3:8080       1m

上結果似乎並沒有看到Service 的ClusterIP,運行下面的命令即可看到tomct-service被分配的Cluster IP及更多的信息:

# kubectl get svc tomcat-service -o yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: 2016-07-21T17:05:52Z
  name: tomcat-service
  namespace: default
  resourceVersion: "23964"
  selfLink: /api/v1/namespaces/default/services/tomcat-service
  uid: 61987d3c-4f65-11e6-a9d8-000c29ed42c1
spec:
  clusterIP: 169.169.65.227
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    tier: frontend
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

在spec.ports的定義中,targetPort屬性用來確定提供該服務的容器所暴露(EXPOSE)的端口號,即具體業務進程在容器內的targetPort上提供TCP/IP接入;port屬性則定義了Service的虛端口。前面定義Tomcat服務時沒有指定targetPort,則默認targetPort與port相同。

很多服務都存在多個端口的問題,通常一個端口提供業務服務,另外一個端口提供管理服務,比如Mycat、Codis等常見中間件。Kubernetes Service支持多個Endpoint,在存在多個Endpoint的情況下,要求每個Endpoint都定義一個名稱來區分。下面是Tomcat多端口的Service定義樣例:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
  - port: 8080
  name: service-port
  - port: 8005
  name: shutdown-port
  selector:
    tier: frontend

多端口為什么需要給每個端口都命名呢?這就涉及Kubernetes的服務發現機制了。

5.2 Kubernetes的服務發現機制

任何分布式系統都會涉及“服務發現”這個基礎問題,大部分分布式系統都通過提供特定的API接口來實現服務發現功能,但這樣做會導致平台的侵入性比較強,也增加了開發、測試的難度。Kubernetes則采用了直觀朴素的思路去解決這個棘手的問題。

首先,每個Kubernetes中的Service都有唯一的Cluster IP及唯一的名稱,而名稱是由開發者自己定義的,部署時也沒必要改變,所以完全可以被固定在配置中。接下來的問題就是如何通過Service的名稱找到對應的Cluster IP。

最早時Kubernetes采用了Linux環境變量解決這個問題,即每個Service都生成一些對應的Linux環境變量(ENV),並在每個Pod的容器啟動時自動注入這些環境變量。以下是tomcat-service產生的環境變量條目:

TOMCAT_SERVICE_SERVICE_HOST=169.169.41.218
TOMCAT_SERVICE_SERVICE_PORT_SERVICE_PORT=8080
TOMCAT_SERVICE_SERVICE_PORT_SHUTDOWN_PORT=8005
TOMCAT_SERVICE_SERVICE_PORT=8080
TOMCAT_SERVICE_PORT_8005_TCP_PORT=8005
TOMCAT_SERVICE_PORT=tcp://169.169.41.218:8080
TOMCAT_SERVICE_PORT_8080_TCP_ADDR=169.169.41.218
TOMCAT_SERVICE_PORT_8080_TCP=tcp://169.169.41.218:8080
TOMCAT_SERVICE_PORT_8080_TCP_PROTO=tcp
TOMCAT_SERVICE_PORT_8080_TCP_PORT=8080
TOMCAT_SERVICE_PORT_8005_TCP=tcp://169.169.41.218:8005
TOMCAT_SERVICE_PORT_8005_TCP_ADDR=169.169.41.218
TOMCAT_SERVICE_PORT_8005_TCP_PROTO=tcp

在上述環境變量中,比較重要的是前3個環境變量。可以看到,每個Service的IP地址及端口都有標准的命名規范,遵循這個命名規范,就可以通過代碼訪問系統環境變量來得到所需的信息,實現服務調用。

考慮到通過環境變量獲取Service地址的方式仍然不太方便、不夠直觀,后來Kubernetes通過Add-On增值包引入了DNS系統,把服務名作為DNS域名,這樣程序就可以直接使用服務名來建立通信連接了。目前,Kubernetes上的大部分應用都已經采用了DNS這種新興的服務發現機制,后面會講解如何部署DNS系統。

5.3 外部系統訪問Service

為了更深入地理解和掌握Kubernetes,我們需要弄明白Kubernetes里的3種IP,這3種IP分別如下。

  • Node IP:Node的IP地址。
  • Pod IP:Pod的IP地址。
  • Cluster IP:Service的IP地址。

首先,Node IP是Kubernetes集群中每個節點的物理網卡的IP地址,是一個真實存在的物理網絡,所有屬於這個網絡的服務器都能通過這個網絡直接通信,不管其中是否有部分節點不屬於這個Kubernetes集群。這也表明在Kubernetes集群之外的節點訪問Kubernetes集群之內的某個節點或者TCP/IP服務時,都必須通過Node IP通信。

其次,Pod IP是每個Pod的IP地址,它是Docker Engine根據docker0網橋的IP地址段進行分配的,通常是一個虛擬的二層網絡,前面說過,Kubernetes要求位於不同Node上的Pod都能夠彼此直接通信,所以Kubernetes里一個Pod里的容器訪問另外一個Pod里的容器時,就是通過Pod IP所在的虛擬二層網絡進行通信的,而真實的TCP/IP流量是通過Node IP所在的物理網卡流出的。

最后,Service的Cluster IP,它也是一種虛擬的IP,但更像一個“偽造”的IP網絡,原因有以下幾點:

  • Cluster IP僅僅作用於Kubernetes Service這個對象,並由Kubernetes管理和分配IP地址(來源於Cluster IP地址池)。
  • Cluster IP無法被Ping,因為沒有一個“實體網絡對象”來響應。
  • Cluster IP只能結合Service Port組成一個具體的通信端口,單獨的Cluster IP不具備TCP/IP通信的基礎,並且它們屬於Kubernetes集群這樣一個封閉的空間,集群外的節點如果要訪問這個通信端口,則需要做一些額外的工作。
  • 在Kubernetes集群內,Node IP網、Pod IP網與Cluster IP網之間的通信,采用的是Kubernetes自己設計的一種編程方式的特殊路由規則,與我們熟知的IP路由有很大的不同。

根據上面的分析和總結,我們基本明白了:Service的Cluster IP屬於Kubernetes集群內部的地址,無法在集群外部直接使用這個地址。實際上在我們開發的業務系統中肯定有一部分服務是要提供給Kubernetes集群外部的應用或者用戶來使用的,典型的例子就是Web端的服務模塊,比如上面的tomcat-service,那么用戶怎么訪問它?答案是:采用NodePort是解決上述問題的最直接、有效的常見做法。

以tomcat-service為例,在Service的定義里做如下擴展即可(見代碼中的紅框部分)

其中,nodePort:31002這個屬性表明手動指定tomcat-service的NodePort為31002,否則Kubernetes會自動分配一個可用的端口。接下來在瀏覽器里訪問http:// :31002/,就可以看到Tomcat的歡迎界面了,如圖所示

NodePort的實現方式是在Kubernetes集群里的每個Node上都為需要外部訪問的Service開啟一個對應的TCP監聽端口,外部系統只要用任意一個Node的IP地址+具體的NodePort端口號即可訪問此服務,在任意Node上運行netstat命令,就可以看到有NodePort端口被監聽:

# netstat -tlp | grep 31002
tcp6  0   0 [::]:31002      [::]:*    LISTEN    1125/kube-proxy

但NodePort還沒有完全解決外部訪問Service的所有問題,比如負載均衡問題。假如在我們的集群中有10個Node,則此時最好有一個負載均衡器,外部的請求只需訪問此負載均衡器的IP地址,由負載均衡器負責轉發流量到后面某個Node的NodePort上,如圖所示:

上圖中的Load balancer 組件獨立於Kubernetes集群之外,通常是一個硬件的負載均衡器,或者是以軟件方式實現的,例如HAProxy或者Nginx。對於每個Service,我們通常需要配置一個對應的Load balancer 實例來轉發流量到后端的Node上,這的確增加了工作量及出錯的概率。於是Kubernetes提供了自動化的解決方案,如果我們的集群運行在谷歌的公有雲GCE上,那么只要把Service的 type=NodePort 改為 type=LoadBalancer,Kubernetes就會自動創建一個對應的Load balancer 實例並返回它的IP地址供外部客戶端使用。其他公有雲提供商只要實現了支持此特性的驅動,則也可以達到上述目的。此外,裸機上的類似機制(Bare Metal Service Load Balancers)也在被開發。

6. Label

Label(標簽)是Kubernetes系統中另外一個核心概念。一個Label是一個key=value的鍵值對,其中key與value由用戶自己指定。Label可以被附加到各種資源對象上,例如Node、Pod、Service、RC等,一個資源對象可以定義任意數量的Label,同一個Label也可以被添加到任意數量的資源對象上。Label通常在資源對象定義時確定,也可以在對象創建后動態添加或者刪除。

我們可以通過給指定的資源對象捆綁一個或多個不同的Label來實現多維度的資源分組管理功能,以便靈活、方便地進行資源分配、調度、配置、部署等管理工作。例如,部署不同版本的應用到不同的環境中;監控和分析應用(日志記錄、監控、告警)等。

一些常用的Label示例如下:

  • 版本標簽:"release" : "stable"、"release" : "canary"。
  • 環境標簽:"environment":"dev"、"environment":"qa"、"environment":"production"。
  • 架構標簽:"tier" : "frontend"、"tier" : "backend"、"tier" : "middleware"。
  • 分區標簽:"partition" : "customerA"、"partition" : "customerB"。
  • 質量管控標簽:"track" : "daily"、"track" : "weekly"。

Label相當於我們熟悉的“標簽”。給某個資源對象定義一個Label,就相當於給它打了一個標簽,隨后可以通過Label Selector(標簽選擇器)查詢和篩選擁有某些Label的資源對象,Kubernetes通過這種方式實現了類似SQL的簡單又通用的對象查詢機制。

Label Selector可以被類比為SQL語句中的where查詢條件,例如,name=redis-slave 這個Label Selector 作用於Pod時,可以被類比為 select * from pod where pod's name =‘redis-slave' 這樣的語句。當前有兩種 Label Selector 表達式:基於等式的(Equality-based)和基於集合的(Set-based):

  • 等式類表達式匹配標簽(Equality-based):
    • name = redis-slave:匹配所有具有標簽name=redis-slave的資源對象。
    • env != production:匹配所有不具有標簽env=production的資源對象,比如env=test就是滿足此條件的標簽之一。
  • 集合操作類表達式匹配標簽(Set-based):
    • name in(redis-master, redis-slave):匹配所有具有標簽name=redis-master或者name= redis-slave的資源對象。
    • name not in(php-frontend):匹配所有不具有標簽name=php-frontend的資源對象。

可以通過多個Label Selector表達式的組合實現復雜的條件選擇,多個表達式之間用“,”進行分隔即可,幾個條件之間是“AND”的關系,即同時滿足多個條件,比如下面的例子:

name=redis-slave,env!=production
name not in (php-frontend),env!=production

以myweb Pod為例,Label被定義在其metadata中:

apiVersion: v1
kind: Pod
metadata:
  name: myweb
  labels:
    name: myweb

管理對象RC和Service則通過Selector字段設置需要關聯Pod的Label:

apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb
spec:
  replicas: 1
  selector:
    app: myweb
  template:
  .......

apiVersion: v1
kind: Service
metadata:
  name: myweb
spec:
  selector:
    app: myweb
  ports;
  - port: 8080

其他管理對象如Deployment、ReplicaSet、DaemonSet和Job則可以在Selector中使用基於集合的篩選條件定義:

......
spec:
  selector:
    matchLabels:
      app: myweb
    matchExpressions;
      - {key: tier, operator: In, values: [frontend]}
      - {key: environment, operator: NotIn, values: [dev]}
  • matchLabels用於定義一組Label,與直接寫在Selector中的作用相同;
  • matchExpressions用於定義一組基於集合的篩選條件,可用的條件運算符包括In 、 NotIn 、 Exists和DoesNotExist。
  • 如果同時設置了matchLabels和matchExpressions,則兩組條件為AND關系,即需要同時滿足所有條件才能完成Selector的篩選。

Label Selector在Kubernetes中的重要使用場景如下:

  • kube-controller進程通過在資源對象RC上定義的Label Selector來篩選要監控的Pod副本數量,使Pod副本數量始終符合預期設定的全自動控制流程。
  • kube-proxy進程通過Service的Label Selector來選擇對應的Pod,自動建立每個Service到對應Pod的請求轉發路由表,從而實現Service的智能負載均衡機制。
  • 通過對某些Node定義特定的Label,並且在Pod定義文件中使用NodeSelector這種標簽調度策略,kube-scheduler進程可以實現Pod定向調度的特性。

舉例:假設為Pod定義了3個Label:release、env和role,不同的Pod定義了不同的Label值,如果設置“role=frontend”的Label Selector,則會選取到Node 1和Node 2上的Pod,如圖所示。

如果設置“release=beta”的Label Selector,則會選取到Node 2和Node 3上的Pod,如圖1.8所示。

總之,使用Label可以給對象創建多組標簽,Label和Label Selector共同構成了Kubernetes系統中核心的應用模型,使得被管理對象能夠被精細地分組管理,同時實現了整個集群的高可用性。

7. Deployment

Deployment是Kubernetes在1.2版本中引入的新概念,用於更好地解決Pod的編排問題。為此,Deployment在內部使用了Replica Set來實現目的,無論從Deployment的作用與目的、YAML定義,還是從它的具體命令行操作來看,我們都可以把它看作RC的一次升級,兩者的相似度超過90%。

Deployment相對於RC的一個最大升級是我們可以隨時知道當前Pod“部署”的進度。實際上由於一個Pod的創建、調度、綁定節點及在目標Node上啟動對應的容器這一完整過程需要一定的時間,所以我們期待系統啟動N個Pod副本的目標狀態,實際上是一個連續變化的“部署過程”導致的最終狀態。

Deployment的典型使用場景有以下幾個。

  • 創建一個Deployment對象來生成對應的Replica Set並完成Pod副本的創建。
  • 檢查Deployment的狀態來看部署動作是否完成(Pod副本數量是否達到預期的值)。
  • 更新Deployment以創建新的Pod(比如鏡像升級)。
  • 如果當前Deployment不穩定,則回滾到一個早先的Deployment版本。
  • 暫停Deployment以便於一次性修改多個PodTemplateSpec的配置項,之后再恢復Deployment,進行新的發布。
  • 擴展Deployment以應對高負載。
  • 查看Deployment的狀態,以此作為發布是否成功的指標。
  • 清理不再需要的舊版本ReplicaSets。

運行一些簡單的例子來直觀地感受Deployment的概念。創建一個名為tomcat-deployment.yaml的Deployment描述文件,內容如下:

apiVersion: v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata;
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: tomcat-demo
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports;
        - containerPort: 8080

運行一下命令創建Deployment:

# kubectl apply -f tomcat-deployment.yaml
deployment "tomcat-deploy" created

運行以下命令查看Deployment的信息:

# kubectl get deployments
NAME            DESIRED   CURRENT  UP-TO-DATE   AVAILABLE   AGE
tomcat-depoly   1         1        1            1           4m

上述命令輸出涉及的數量解釋如下:

  • DESIRED:Pod副本數量的期望值,即在Deployment里定義的Replica。
  • CURRENT:當前Replica的值,實際上是Deployment創建的Replica Set里的Replica值,這個值不斷增加,直到達到DESIRED為止,表明整個部署過程完成。
  • UP-TO-DATE:最新版本的Pod的副本數量,用於指示在滾動升級的過程中,有多少個Pod副本已經成功升級。
  • AVAILABLE:當前集群中可用的Pod副本數量,即集群中當前存活的Pod數量。

運行以下命令查看對應的Replica Set,我們看到它的命名與Deployment的名稱有關系:

# kubectl get rc
NAME                       DESIRED   CURRENT  AGE
tomcat-depoly-1640611518   1         1        1m

運行以下命令查看創建的Pod,我們發現Pod的命名以Deployment對應的Replica Set的名稱為前綴,這種命名很清晰地表明了一個Replica Set創建了哪些Pod,對於Pod滾動升級這種復雜的過程來說,很容易排查錯誤:

# kubectl get pods
NAME                              REDAY   STATUS    RESTARTS  AGE
tomcat-depoly-1640611518-zhrsc    1/1     Running   0         3m

8. Horizontal Pod Autoscaler

通過手工執行kubectl scale命令,我們可以實現Pod擴容或縮容。如果僅僅到此為止,顯然不符合谷歌對Kubernetes的定位目標——自動化、智能化。在谷歌看來,分布式系統要能夠根據當前負載的變化自動觸發水平擴容或縮容,因為這一過程可能是頻繁發生的、不可預料的,所以手動控制的方式是不現實的。

因此,在Kubernetes的1.0版本實現后,就有人在默默研究Pod智能擴容的特性了,並在Kubernetes 1.1中首次發布重量級新特性——Horizontal Pod Autoscaling(Pod橫向自動擴容,HPA)。在Kubernetes 1.2中HPA被升級為穩定版本(apiVersion: autoscaling/v1),但仍然保留了舊版本(apiVersion: extensions/v1beta1)。Kubernetes從1.6版本開始,增強了根據應用自定義的指標進行自動擴容和縮容的功能,API版本為autoscaling/v2alpha1,並不斷演進。

HPA與之前的RC、Deployment一樣,也屬於一種Kubernetes資源對象。通過追蹤分析指定RC控制的所有目標Pod的負載變化情況,來確定是否需要有針對性地調整目標Pod的副本數量,這是HPA的實現原理。當前,HPA有以下兩種方式作為Pod負載的度量指標。

  • CPUUtilizationPercentage。
  • 應用程序自定義的度量指標,比如服務在每秒內的相應請求數(TPS或QPS)。

CPUUtilizationPercentage是一個算術平均值,即目標Pod所有副本自身的CPU利用率的平均值。一個Pod自身的CPU利用率是該Pod當前CPU的使用量除以它的Pod Request的值,比如定義一個Pod的Pod Request為0.4,而當前Pod的CPU使用量為0.2,則它的CPU使用率為50%,這樣就可以算出一個RC控制的所有Pod副本的CPU利用率的算術平均值了。如果某一時刻CPUUtilizationPercentage的值超過80%,則意味着當前Pod副本數量很可能不足以支撐接下來更多的請求,需要進行動態擴容,而在請求高峰時段過去后,Pod的CPU利用率又會降下來,此時對應的Pod副本數應該自動減少到一個合理的水平。如果目標Pod沒有定義Pod Request的值,則無法使用CPUUtilizationPercentage實現Pod橫向自動擴容。除了使用CPUUtilizationPercentage,Kubernetes從1.2版本開始也在嘗試支持應用程序自定義的度量指標。

在CPUUtilizationPercentage計算過程中使用到的Pod的CPU使用量通常是1min內的平均值,通常通過查詢Heapster監控子系統來得到這個值,所以需要安裝部署Heapster,這樣便增加了系統的復雜度和實施HPA特性的復雜度。因此,從1.7版本開始,Kubernetes自身孵化了一個基礎性能數據采集監控框架——Kubernetes Monitoring Architecture,從而更好地支持HPA和其他需要用到基礎性能數據的功能模塊。在Kubernetes Monitoring Architecture中,Kubernetes定義了一套標准化的API接口Resource Metrics API,以方便客戶端應用程序(如HPA)從Metrics Server中獲取目標資源對象的性能數據,例如容器的CPU和內存使用數據。到了Kubernetes 1.8版本,Resource Metrics API被升級為metrics.k8s.io/v1beta1,已經接近生產環境中的可用目標了。

下面是HPA定義的一個具體例子:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
  namespace: default
spec:
  maxReplicas: 10
  minReplicas: 1
  scaleTargetRef:
    kind: Deployment
    name: php-apache
  targetCPUUtilizationPercentage: 90

根據上面的定義,我們可以知道這個HPA控制的目標對象為一個名為php-apache的Deployment里的Pod副本,當這些Pod副本的CPUUtilizationPercentage的值超過90%時會觸發自動動態擴容行為,在擴容或縮容時必須滿足的一個約束條件是Pod的副本數為1~10。

除了可以通過直接定義YAML文件並且調用kubectrl create的命令來創建一個HPA資源對象的方式,還可以通過下面的簡單命令行直接創建等價的HPA對象:

# kubectl autoscale deployment php-apache  --cpu-percent=90  --min=1 --max=10

后續將會給出一個完整的HPA例子來說明其用法和功能。

9. StatefulSet

在Kubernetes系統中,Pod的管理對象RC、Deployment、DaemonSet和Job都面向無狀態的服務。但現實中有很多服務是有狀態的,特別是一些復雜的中間件集群,例如MySQL集群、MongoDB集群、Kafka集群、ZooKeeper集群等,這些應用集群有4個共同點。

  1. 每個節點都有固定的身份ID,通過這個ID,集群中的成員可以相互發現並通信。
  2. 集群的規模是比較固定的,集群規模不能隨意變動。
  3. 集群中的每個節點都是有狀態的,通常會持久化數據到永久存儲中。
  4. 如果磁盤損壞,則集群里的某個節點無法正常運行,集群功能受損。

如果通過RC或Deployment控制Pod副本數量來實現上述有狀態的集群,就會發現第1點是無法滿足的,因為Pod的名稱是隨機產生的,Pod的IP地址也是在運行時才確定且可能有變動的,我們事先無法為每個Pod都確定唯一不變的ID。另外,為了能夠在其他節點上恢復某個失敗的節點,這種集群中的Pod需要掛接某種共享存儲,為了解決這個問題,Kubernetes從1.4版本開始引入了PetSet這個新的資源對象,並且在1.5版本時更名為StatefulSet,StatefulSet從本質上來說,可以看作Deployment/RC的一個特殊變種,它有如下特性:

  • StatefulSet里的每個Pod都有穩定、唯一的網絡標識,可以用來發現集群內的其他成員。假設StatefulSet的名稱為kafka,那么第1個Pod叫kafka-0,第2個叫kafka-1,以此類推。
  • StatefulSet控制的Pod副本的啟停順序是受控的,操作第n個Pod時,前n-1個Pod已經是運行且准備好的狀態。
  • StatefulSet里的Pod采用穩定的持久化存儲卷,通過PV或PVC來實現,刪除Pod時默認不會刪除與StatefulSet相關的存儲卷(為了保證數據的安全)。

StatefulSet除了要與PV卷捆綁使用以存儲Pod的狀態數據,還要與Headless Service配合使用,即在每個StatefulSet定義中都要聲明它屬於哪個Headless Service。Headless Service與普通Service的關鍵區別在於,它沒有Cluster IP,如果解析Headless Service的DNS域名,則返回的是該Service對應的全部Pod的Endpoint列表。StatefulSet在Headless Service的基礎上又為StatefulSet控制的每個Pod實例都創建了一個DNS域名,這個域名的格式為:

$(podname).$(headless service name)

比如一個3節點的Kafka的StatefulSet集群對應的Headless Service的名稱為kafka,StatefulSet的名稱為kafka,則StatefulSet里的3個Pod的DNS名稱分別為kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,這些DNS名稱可以直接在集群的配置文件中固定下來。

10. Volume

Volume(存儲卷)是Pod中能夠被多個容器訪問的共享目錄。Kubernetes的Volume概念、用途和目的與Docker的Volume比較類似,但兩者不能等價。首先,Kubernetes中的Volume被定義在Pod上,然后被一個Pod里的多個容器掛載到具體的文件目錄下;其次,Kubernetes中的Volume與Pod的生命周期相同,但與容器的生命周期不相關,當容器終止或者重啟時,Volume中的數據也不會丟失。最后,Kubernetes支持多種類型的Volume,例如GlusterFS、Ceph等先進的分布式文件系統。

Volume的使用也比較簡單,在大多數情況下,我們先在Pod上聲明一個Volume,然后在容器里引用該Volume並掛載(Mount)到容器里的某個目錄上。舉例來說,我們要給之前的Tomcat Pod增加一個名為“datavol”的Volume,並且掛載到容器的 /mydata-data 目錄上,則只要對Pod的定義文件做如下修正即可(注意代碼中的紅框部分):

除了可以讓一個Pod里的多個容器共享文件、讓容器的數據寫到宿主機的磁盤上或者寫文件到網絡存儲中,Kubernetes的Volume還擴展出了一種非常有實用價值的功能,即容器配置文件集中化定義與管理,這是通過ConfigMap這種新的資源對象來實現的,后面會詳細說明。

Kubernetes提供了非常豐富的Volume類型:

  1. emptyDir
    一個emptyDir Volume是在Pod分配到Node時創建的。從它的名稱就可以看出,它的初始內容為空,並且無須指定宿主機上對應的目錄文件,因為這是Kubernetes自動分配的一個目錄,當Pod從Node上移除時,emptyDir中的數據也會被永久刪除。emptyDir的一些用途如下:

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

    目前,用戶無法控制emptyDir使用的介質種類。如果kubelet的配置是使用硬盤,那么所有emptyDir都將被創建在該硬盤上。Pod在將來可以設置emptyDir是位於硬盤、固態硬盤上還是基於內存的tmpfs上,上面的例子便采用了emptyDir類的Volume。

  2. hostPath
    hostPath為在Pod上掛載宿主機上的文件或目錄,它通常可以用於以下幾方面:

    • 容器應用程序生成的日志文件需要永久保存時,可以使用宿主機的高速文件系統進行存儲。
    • 需要訪問宿主機上Docker引擎內部數據結構的容器應用時,可以通過定義hostPath為宿主機/var/lib/docker目錄,使容器內部應用可以直接訪問Docker的文件系統。

    在使用這種類型的Volume時,需要注意以下幾點:

    • 在不同的Node上具有相同配置的Pod,可能會因為宿主機上的目錄和文件不同而導致對Volume上目錄和文件的訪問結果不一致。
    • 如果使用了資源配額管理,則Kubernetes無法將hostPath在宿主機上使用的資源納入管理。

    下面的例子,使用宿主機的/data目錄定義了一個hostPath類型的Volume:

    volumes:
    - name: persistent-storage
      hostPath:
        path: /data
    
  3. NFS
    使用NFS網絡文件系統提供的共享目錄存儲數據時,我們需要在系統中部署一個NFS Server。定義NFS類型的Volume的示例如下:

volumes:
  - name: nfs
    server: nfs-server.default    # 改為你的NFS服務器地址
    path: /
  1. 其他類型的Volume
  • gcePersistentDisk:使用谷歌公有雲提供的永久磁盤(Persistent Disk,PD)存放Volume的數據。PD上的內容會被永久保存,當Pod被刪除時,PD只是被卸載(Unmount),但不會被刪除。你需要先創建一個PD,才能使用gcePersistentDisk。
  • awsElasticBlockStore:使用亞馬遜公有雲提供的EBS Volume存儲數據,需要先創建一個EBS Volume才能使用awsElasticBlockStore。
  • iscsi:使用iSCSI存儲設備上的目錄掛載到Pod中。
  • flocker:使用Flocker管理存儲卷。
  • glusterfs:使用開源GlusterFS網絡文件系統的目錄掛載到Pod中。
  • rbd:使用Ceph塊設備共享存儲(Rados Block Device)掛載到Pod中。
  • gitRepo:通過掛載一個空目錄,並從Git庫clone一個git repository以供Pod使用。
  • secret:一個Secret Volume用於為Pod提供加密的信息,你可以將定義在Kubernetes中的Secret直接掛載為文件讓Pod訪問。Secret Volume是通過TMFS(內存文件系統)實現的,這種類型的Volume總是不會被持久化的。

11. Persistent Volume

之前提到的Volume是被定義在Pod上的,屬於計算資源的一部分,而實際上,網絡存儲是相對獨立於計算資源而存在的一種實體資源。比如在使用虛擬機的情況下,我們通常會先定義一個網絡存儲,然后從中划出一個“網盤”並掛接到虛擬機上。Persistent Volume(PV)和與之相關聯的Persistent Volume Claim(PVC)也起到了類似的作用。

PV可以被理解成Kubernetes集群中的某個網絡存儲對應的一塊存儲,它與Volume類似,但有以下區別:

  • PV只能是網絡存儲,不屬於任何Node,但可以在每個Node上訪問。
  • PV並不是被定義在Pod上的,而是獨立於Pod之外定義的。
  • PV目前支持的類型包括:gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibre Channel)、Flocker、NFS、iSCSI、RBD(Rados Block Device)、CephFS、Cinder、GlusterFS、VsphereVolume、Quobyte Volumes、VMware Photon、Portworx Volumes、ScaleIO Volumes和HostPath(僅供單機測試)。

下面是一個NFS類型的PV的YAML定義文件,聲明了需要5Gi的存儲空間:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /somepath
    server: 172.17.0.2

比較重要的是PV的accessModes屬性,目前有以下類型。

  • ReadWriteOnce:讀寫權限,並且只能被單個Node掛載。
  • ReadOnlyMany:只讀權限,允許被多個Node掛載。
  • ReadWriteMany:讀寫權限,允許被多個Node掛載。

如果某個Pod想申請某種類型的PV,則首先需要定義一個PersistentVolumeClaim對象:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi

然后,在Pod的Volume定義中引用上述PVC即可:

......
volumes:
- name: mypd
  persistentVolumeClaim:
    claimName: myclaim

PV是有狀態的對象,它的狀態有以下幾種:

  • Available:空閑狀態。
  • Bound:已經綁定到某個PVC上。
  • Released:對應的PVC已經被刪除,但資源還沒有被集群收回。
  • Failed:PV自動回收失敗

12. ConfigMap

為了能夠准確和深刻理解Kubernetes ConfigMap的功能和價值,我們需要從Docker說起。我們知道,Docker通過將程序、依賴庫、數據及配置文件“打包固化”到一個不變的鏡像文件中的做法,解決了應用的部署的難題,但這同時帶來了棘手的問題,即配置文件中的參數在運行期如何修改的問題。我們不可能在啟動Docker容器后再修改容器里的配置文件,然后用新的配置文件重啟容器里的用戶主進程。為了解決這個問題,Docker提供了兩種方式:

  • 在運行時通過容器的環境變量來傳遞參數;
  • 通過Docker Volume將容器外的配置文件映射到容器內。

這兩種方式都有其優勢和缺點,在大多數情況下,后一種方式更合適我們的系統,因為大多數應用通常從一個或多個配置文件中讀取參數。但這種方式也有明顯的缺陷:我們必須在目標主機上先創建好對應的配置文件,然后才能映射到容器里。

上述缺陷在分布式情況下變得更為嚴重,因為無論采用哪種方式,寫入(修改)多台服務器上的某個指定文件,並確保這些文件保持一致,都是一個很難完成的目標。此外,在大多數情況下,我們都希望能集中管理系統的配置參數,而不是管理一堆配置文件。針對上述問題,Kubernetes給出了一個很巧妙的設計實現,如下所述:

  1. 把所有的配置項都當作key-value字符串,當然value可以來自某個文本文件,比如配置項password=123456、user=root、host=192.168.8.4用於表示連接FTP服務器的配置參數。這些配置項可以作為Map表中的一個項,整個Map的數據可以被持久化存儲在Kubernetes的Etcd數據庫中,然后提供API以方便Kubernetes相關組件或客戶應用CRUD操作這些數據,上述專門用來保存配置參數的Map就是Kubernetes ConfigMap資源對象。
  2. Kubernetes提供了一種內建機制,將存儲在etcd中的ConfigMap通過Volume映射的方式變成目標Pod內的配置文件,不管目標Pod被調度到哪台服務器上,都會完成自動映射。進一步地,如果ConfigMap中的key-value數據被修改,則映射到Pod中的“配置文件”也會隨之自動更新。於是,Kubernetes ConfigMap就成了分布式系統中最為簡單(使用方法簡單,但背后實現比較復雜)且對應用無侵入的配置中心。

ConfigMap配置集中化的一種簡單方案如下圖所示:

13. Secret

Secret 資源的功能類似於ConfigMap,但其專用於存放敏感數據,如密碼,數字證書,私鑰,令牌和SSH key 等。

Secret 是以鍵值的方式進行數據存儲的,在POD資源中通過環境變量或存儲卷進行數據訪問。不同的是,Secret對象僅僅會被分發至調用了此對象的POD資源所在的節點,且只能由節點將其存儲於內存中,其數據存儲及打印格式都是使用base64編碼的字符串,因此用戶在創建Secret對象時也要使用此種編碼格式的數據,不過,在容器中以環境變量或存儲卷的方式訪問時,其會被自動解碼為明文格式。

在Master上,Secret對象以非加密的格式存儲於Etcd中,管理員必須確保Etcd集群鍵API Server的安全通信,Etcd服務的授權訪問,還包括用戶訪問API Server時的授權,因為擁有創建Pod資源的用戶都可以使用Secret資源並能夠通過Pod中的容器訪問其數據。


免責聲明!

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



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