1.4 Kubernetes基本概念和術語
?Kubernetes中的大部分概念如Node、Pod、Replication Controller、Service等都可以看作一種“資源對象”,幾乎所有的資源對象都可以通過Kubernetes提供的kubectl工具(或者API編程調用)執行增、刪、改、查等操作并將其保存在etcd中持久化存儲。從這個角度來看,Kubernetes其實是一個高度自動化的資源控制系統,它通過跟蹤對比etcd庫里保存的“資源期望狀態”與當前環境中的“實際資源狀態”的差異來實現自動控制和自動糾錯的高級功能。
?在介紹資源對象之前,我們先了解一下Kubernetes集群的兩種管理角色:Master和Node。
1.4.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中的。
Node
?除了Master,Kubernetes集群中的其他機器被稱為Node節點,在較早的版本中也被稱為Minion。與Master一樣,Node節點可以是一臺物理主機,也可以是一臺虛擬機。Node節點才是Kubernetes集群中的工作負載節點,每個Node都會被Master分配一些工作負載(Docker容器),當某個Node宕機時,其上的工作負載會被Master自動轉移到其他節點上去。
?每個Node節點上都運行著以下一組關鍵進程。
- kubelet:負責Pod對應的容器的創建、啟停等任務,同時與Master節點密切協作,實現集群管理的基本功能。
- kube-proxy:實現Kubernetes Service的通信與負載均衡機制的重要組件。
- 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
NAME STATUS AGE
127.0.0.1 Ready 1d
?然后,通過kubectl describe node <node_name>來查看某個Node的詳細信息:
# kubectl describe node 127.0.0.1
Name: 127.0.0.1
Role:
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/hostname=127.0.0.1
Taints: <none>
CreationTimestamp: Mon, 02 Jul 2018 10:11:27 +0800
Phase:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
OutOfDisk False Tue, 03 Jul 2018 15:10:49 +0800 Mon, 02 Jul 2018 10:11:27 +0800 KubeletHasSufficientDisk kubelet has sufficient disk space available
MemoryPressure False Tue, 03 Jul 2018 15:10:49 +0800 Mon, 02 Jul 2018 10:11:27 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Tue, 03 Jul 2018 15:10:49 +0800 Mon, 02 Jul 2018 10:11:27 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
Ready True Tue, 03 Jul 2018 15:10:49 +0800 Mon, 02 Jul 2018 10:11:38 +0800 KubeletReady kubelet is posting ready status
Addresses: 127.0.0.1,127.0.0.1,127.0.0.1
Capacity:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 1883844Ki
pods: 110
Allocatable:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 1883844Ki
pods: 110
System Info:
Machine ID: f9d400c5e1e8c3a8209e990d887d4ac1
System UUID: 13C940BE-9125-4594-9C8B-82E19C997FF3
Boot ID: 09a9b2bf-14cf-4e32-a724-8b279d44a387
Kernel Version: 3.10.0-514.26.2.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://1.13.1
Kubelet Version: v1.5.2
Kube-Proxy Version: v1.5.2
ExternalID: 127.0.0.1
Non-terminated Pods: (3 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
default mysql-pgb63 0 (0%) 0 (0%) 0 (0%) 0 (0%)
default myweb-c994c 0 (0%) 0 (0%) 0 (0%) 0 (0%)
default myweb-jgcqn 0 (0%) 0 (0%) 0 (0%) 0 (0%)
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.
CPU Requests CPU Limits Memory Requests Memory Limits
------------ ---------- --------------- -------------
0 (0%) 0 (0%) 0 (0%) 0 (0%)
No events.
?上述命令展示了Node的如下關鍵信息。
- Node基本信息:名稱、標簽、創建時間等。
- Node當前的運行狀態,Node啟動以后會做一系列的自檢工作,比如磁盤是否滿了,如果滿了就標注OutOfDisk=True,否則繼續檢查內存是否不足(如果內存不足,就標注MemoryPressure=True),最后一切正常,就設置為Ready狀態(Ready=True),該狀態表示Node處于健康狀態,Master將可以在其上調度新的任務了(如啟動Pod)。
- Node的主機地址與主機名。
- Node上的資源總量:描述Node可用的系統資源,包括CPU、內存數量、最大可調度Pod數量等,注意到目前Kubernetes已經實驗性地支持GPU資源分配了(alpha.kubernetes.io/nvidia-gpu=0)。
- Node可分配資源量:描述Node當前可用于分配等資源量。
- 主機系統信息:包括主機等唯一標識UUID、Linux kernel版本號、操作系統類型與版本、Kubernetes版本號、kubelet與kube-proxy的版本號等。
- 當前正在運行等Pod列表概要信息。
- 已分配的資源使用概要信息,例如資源申請的最低、最大允許使用量占系統總量等百分比。
- Node相關的Event信息。
1.4.3 Pod
?Pod是Kubernetes的最重要也最基本的概念,如圖1.4所示是Pod的組成示意圖,我們看到每個Pod都有一個特殊的被成為“根容器”的Pause容器。Pause容器對應的鏡像屬于Kubernetes平臺的一部分,除了Pause容器,每個Pod還包含一個或多個緊密相關的用戶業務容器。?為什么Kubernetes會設計出一個全新的Pod概念并且Pod有這樣特殊的組成結構?
?原因之一:在一組容器作為一個單元的情況下,我們難以對“整體”簡單地進行判斷及有效地進行行動。比如,一個容器死亡了,此時算是整體死亡么?引入業務無關并且不易死亡的Pause容器作為Pod的根容器,以它的狀態代表整體容器組的狀態,就簡單、巧妙地解決了這個難題。
?原因之二: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的關系圖如圖1.5所示。?Kubernetes里的所有資源對象都可以采用yaml或者JSON格式的文件來定義或描述,下面是我們在之前Hello World例子里用到的myweb這個Pod的資源定義文件:
apiVersion: v1
kind: Pod
metadata:
name: myweb
spec:
replicas: 2
selector:
app: myweb
template:
metadata:
labels:
app: myweb
spec:
containers:
- name: myweb
image: kubeguide/tomcat-app:v1
ports:
- containerPort: 8080
env:
- name: MYSQL_SERVICE_HOST
value: 'mysql'
- name: MYSQL_SERVICE_PORTT
value: '3306'
?Kind為Pod表明這是一個Pod的定義,metadata里的name屬性為Pod的名字,metadata里還能定義資源對象的標簽(Label),這里表明myweb擁有一個name=myweb的標簽(Label)。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。
?我們所熟悉的Docker Volume在Kubernetes里也有對應的概念--Pod Volume,后者有一些擴展,比如可以用分布式文件系統GlusterFS實現后端存儲功能;Pod Volume是定義在Pod之上,然后被各個容器掛載到自己的文件系統忠的。
?這里順便提一下Kubernetes的Event概念,Event是一個事件的記錄,記錄了事件的最早產生時間、最后重現時間、重復次數、發起者、類型,以及導致此事件的原因等眾多信息。Event通常會關聯到某個具體的資源對象上,是排查故障的重要參考信息,之前我們看到Node的描述信息包括了Event,而Pod同樣有Event記錄,用來發現某個Pod遲遲無法創建時,可以用kubectl describe pod xxxx來查看它的描述信息,用來定位問題的原因,比如下面這個Event記錄信息表明Pod里的一個容器被探針檢測失敗一次:
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- ---- ------ -------
10h 12m 32 {kubelet k8s-node-1} spec.container{kube2sky} Warning Unhealthy Liveness probe failed: Get http://172.17.1.2:8080/healthz:net/http: request canceled {Client.Timeout exceeded while awaiting headers}
?每個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里,一個計算資源進行配額限定需要設定以下兩個參數。
- Request:該資源的最小申請量,系統必須滿足要求。
- Limits:該資源最大允許使用量,不能被突破,當容器試圖使用超過這個量的資源時,可能會被Kubernetes Kill并重啟。
?通常我們會把Request設置為一個比較小的數值,符合容器平時的工作負載情況下的資源需求,而把Limit設置為負載均衡情況下資源占用的最大量。比如下面這些定義,表明MySQL容器申請最少0.25個CPU及64MiB內存,在運行過程中MySQL容器所能使用的資源配額為0.5個CPU及128MiB內存:
spec:
containers:
- name: db
image: mysql
resources:
request:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
?本節最后筆者給出Pod及Pod周邊對象的示意圖作為總結,如圖1.6所示,后面部分涉及這張圖里的對象和概念,以進一步加強理解。
1.4.4 Laebl(標簽)
?Label是Kubernetes系統中另外一個核心概念。一個Label是一個key=value的鍵值對,其中key與vaue由用戶自己指定。Label可以附加到各種資源對象上,例如Node、Pod、Service、RC等,一個資源對象可以定義任意數量的Label,同一個Label也可以被添加到任意數量的資源對象上去,Label通常在資源對象定義時確定,也可以在對象創建后動態添加或者刪除。
?我們可以通過指定的資源對象捆綁一個或多個不同的Label來實現多維度的資源分組管理功能,以便于靈活、方便地進行資源分配、調度、配置、部署等管理工作。例如:部署不同版本的應用到不同的環境中;或者監控和分析應用(日志記錄、監控、告警)等。一些常用等label示例如下。
- 版本標簽:"release" : "stable" , "release" : "canary"...
- 環境標簽:"environment" : "dev" , "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),前者采用“等式類”的表達式匹配標簽,下面是一些具體的例子。
- name=redis-slave:匹配所有具有標簽name=redis-slave的資源對象。
- env != production:匹配所有不具有標簽env=production的資源對象,比如env=test就滿足此條件的標簽之一。
- name in (redis-master):匹配所有具有標簽name=redis-master或者name=redis-slave的資源對象。
- name not in (php-frontend):匹配所有不具有標簽name=php-frontend的資源對象。
?可以通過多個Label Selector表達式的組合實現復雜的條件,多個表達式之間用“,”進行分隔即可,幾個條件之間是“AND”的關系,即同時滿足多個條件,比如下面的例子:
name=redis-slave,env!=production
name notin (php-fronted),env!=production
?以myweb Pod為例,Label定義在其metadata中:
apiVersion: v1
kind: Pod
metadata:
name: myweb
labels:
app: myweb
?管理對象RC和Service在spec中定義Selector與Pod進行關聯:
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
?新出現的管理對象如Deploment、ReplicaSet、DaemonSet和Job則可以在Selector中使用基于集合的篩選條件定義,例如:
selector:
matchLabels:
app: myweb
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
- {key: environment, operator: NorIn, values: [dev]}
?matchLabels用于定義一組Label,與直接寫在Selector中作用相同:matchExpression用于定義一組基于集合的篩選條件,可用的條件運算符包括:In、NotIn、Exists和DoesNotExist。
?如果同時設置了matchLabels和matchExpression,則兩組條件為“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“定向調度”的特性。
?在前面的留言板例子中,我們只使用了一個name=XXX的Label Selector。讓我們看一個更復雜的例子。假設為Pod定義了Label: release、env和role,不同的Pod定義了不同的Label值,如圖1.7所示,如果我們設置了“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系統中最核心的應用模型,使得被管理對象能夠被精細地分組管理,同時實現了整個集群的高可用性。
1.4.5 Replication Controller
?上一節的例子中已經對Replication Controller(簡稱RC)的定義和作用做了一些說明,本節對RC的概念進行深入描述。
?RC是Kubernetes系統中的核心概念之一,簡單來說,它其實是定義了一個期望的場景,即聲明某種Pod的副本數量在任意時刻都符合某個預期值,所以RC的定義包括如下幾個部分。
- Pod期待的副本數(replicas)。
- 用于篩選目標Pod的Label Selector。
- 當Pod的副本數量小于預期數量時,用于創建新Pod的Pod模版(template)。
?下面是一個完整的RC定義的例子,即確保擁有tier=frontend標簽的這個Pod(運行Tomcat容器)在整個Kubernetes集群中始終只有一個副本:
apiVersion: v1
kind: ReplicationController
metadata:
name: frontend
spec:
replicas: 1
selector:
tier: frontend
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
ports:
imagePullPolicy: IfNotPresent
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
?當我們定義了一個RC并提交到Kubernetes集群中以后,Master節點上的Controller Manager組件就得到通知,定期巡檢系統中當前存活的目標Pod,并確保目標Pod實例的數量剛好等于此RC的期望值,如果有過多的Pod副本在運行,系統就會停掉一些Pod,否則系統就會再自動創建一些Pod??梢哉f,通過RC,Kubernetes實現了用戶應用集群的高可用性,并且大大減少了系統管理員在傳統IT環境中需要完成的許多手工運維工作(如主機監控腳本、應用監控腳本、故障恢復腳本等)。
?下面我們以3個Node節點的集群為例,說明Kubernetes如何通過RC來實現Pod副本數量自動控制的機制。假如我們的RC里定義redis-slave這個Pod需要保持3個副本,系統將可能在其中的兩個Node上創建Pod。圖1.9描述了在兩個Node上創建redis-slave Pod的情形。
?假設Node2上的Pod2意外終止,根據RC定義的replicas數量2,Kubernetes將會自動創建并啟動一個新的Pod,以保住整個集群中始終有兩個redis-slave Pod在運行。
?如圖1.10所示,系統可能選擇Node3或者Node1來創建一個新的Pod。
?此外,在運行時,我們可以通過修改RC的副本數量,來實現Pod的動態縮放(Scaling)功能,還可以通過執行kubectl scale命令來一鍵完成:
$kubectl scale rc redis-slave --replicas=3
scaled
?Scaling的執行結果如圖1.11所示。
?需要注意的是,刪除RC并不會影響通過該RC已創建好的Pod。為了刪除所有Pod,可以設置replicas的值為0,然后更新該RC。另外,kubectl提供了stop和delete命令來一次性刪除RC和RC控制的全部Pod。
?當我們的應用升級時,通常會通過Build一個新的Docker鏡像,并用新的鏡像版本來替代舊的版本的方式達到目的,在系統升級的過程中,我們希望是平滑的方式,比如當前系統中10個對應的舊版本的Pod,最佳的方式是舊版本的Pod每次停止一個,同時創建一個新版本的Pod,在整個升級過程中,此消彼長,而運行中的Pod數量始終是10個,幾分鐘以后,當所有的Pod都已經是最新版本時,升級過程完成。通過RC的機制,Kubernetes很容易就實現了這種高級實用的特性,被稱為“滾動升級”(Rolling Update),具體的操作方法詳見第四章。
?由于Replication Controller與Kubernetes代碼中的模塊Replication Controller同名,同時這個詞也無法準確表達它的本意,所以在Kubernetes v1.2時,它就升級成了另外一個新的概念--Replica Set,官方解釋為“下一代的RC”,它與RC當前存在的唯一區別是:Replica Sets支持基于集合的Label selector(Set-based selector),而RC只支持基于等式的Label Selector(equality-based selector),這使得Replica Set的功能更強,下面是等價于之前RC例子的Replica Set的定義(省去了Pod模版部分的內容):
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: frontend
spec:
selector:
matchLabels:
tier: frontend
matchExpression:
- {key: tier, operator: In, values: [frontend]}
template:
.......
?kubectl命令工具適用于RC的絕大部分命令都同樣適用于Replica Set。此外,當前我們很少單獨使用Replica Set,它主要被Deployment這個更高層的資源對象所使用,從而形成一整套Pod創建、刪除、更新的編排機制。當我們使用Deployment時,無須關心它是如何創建和維護Replica Set的,這一切都是自動發生的。
?Replica Set與Deployment這兩個重要資源對象逐步替換了之前的RC的作用,是Kubernetes v1.3里Pod自動擴容(伸縮)這個告警功能實現的基礎,也將繼續在Kubernetes未來的版本中發揮重要的作用。
?最后我們總結一下關于RC(Replica Set)的一些特性與作用。
- 在大多數情況下,我們通過定義一個RC實現Pod的創建過程及副本數量的自動控制。
- RC里包括完整的Pod定義模版。
- RC通過Label Selector機制實現對Pod副本的自動控制。
- 通過改變RC里的Pod副本數量,可以實現Pod的擴容或縮容功能。
- 通過改變RC里的Pod模版中的鏡像版本,可以實現Pod的滾動升級功能。
1.4.6 Deployment
?Deployment是Kubernetes v1.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的定義與Replica Set的定義很類似,除了API聲明與Kind類型等有所區別:
apiVersion: extensions/v1beta1 apiVersion: v1
kind: Deployment kind: ReplicaSet
metadata: metadata:
name: nginx-deployment name: nginx-repset
?下面我們通過運行一些例子來一起直觀地感受這個新概念。首先創建一個名為tomcat-deployment.yaml的Deployment描述文件,內容如下:
apiVersion: extensions/v1beta1
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
?運行下述命令創建Deploymeny:
# kubectl create -f tomcat-deployment.yaml
deployment "tomcat-deploy" created
?運行下述命令查看Deployment的信息:
# kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
tomcat-deploy 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 rs
NAME DESIRED CURRENT AGE
tomcat-deploy-1640611518 1 1 1m
?運行下述命令查看創建的Pod,我們發現Pod的命名以Deployment對應的Replica Set的名字為前綴,這種命名很清晰地表明了一個Replica Set創建了哪些Pod,對于滾動升級這種復雜的過程來說,很容易排查錯誤:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
tomcat-deploy-1640611518-zhrsc 1/1 Running 0 3m
?運行kubectl describe deployments,可以清楚地看到Deployment控制的Pod的水平擴展過程,詳見第2章的說明,這里不再贅述。
?Pod的管理對象,除了RC和Deployment,還包括ReplicaSet、DaemonSet、StatefulSet、Job等,分別用于不同的應用場景中,將在第2章中進行詳細介紹。
1.4.7 Horizotal(水平的) Pod Autoscaler(自動伸縮器)
?在前兩節提到過,通過手工執行kubectl scale命令,我們可以實現Pod擴容或縮容,如果僅僅到此為止,顯然不符合谷歌對Kubernetes的定位目標----自動化、智能化。在谷歌看來,分布式系統要能夠根據當前負載的變化情況自動觸發水平擴展或縮容的行為,因為這一過程可能是頻繁發生的、不可預料的,所以手動控制的方式是不現實的。
?因此,Kubernetes的v1.0版本實現后,這幫大牛們就已經在默默研究Pod智能擴容的特性了,并在Kubernetes v1.1的版本中首先發布這一重量級新特性----Horizotal Pod Autoscaler(Pod橫向自動擴容,簡稱HPA)。隨后的v1.2版本中HPA被升級為穩定版本(apiVersion:autoscaling/v1),但同時仍然保留舊版本(apiVersion:extensions/v1beta1)。從v1.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的副本數很可能不足以支撐接下來更多的請求,需要進行動態擴容,而當請求高峰時刻過去后,Podd的CPU利用率又會降下來,此時對應的Pod副本數應該自動減少到一個合理的水平。
?CPUUtilizationPercentage計算過程中使用到的Pod的CPU使用量通常1min內的平均值,目前通過查詢Heapster擴展組件來得到這個值,所以需要安裝部署Heapster,這樣一來便增加了系統的復雜度和實施HPA特性的復雜度,因此,未來的計劃是Kubernetes自身實現一個基礎性能數據采集模塊,從而更好地支持HPA和其他需要用到基礎性能數據的功能模塊。此外,我們也看到,如果目標Pod沒有定義Pod Request的值,則無法使用CPUUtilizationPercentage來實現Pod橫向自動擴容的能力。除了使用CPUUtilizationPercentage,Kubernetes從v1.2版本開始嘗試支持應用程序自定義的度量指標,目前仍然為實驗特性,不建議在生產環境中使用。
?下面是HPA定義的一個具體例子:
apiVersion: autoscaling/v1
kind: HorizotalPodAutoscaler
metadata:
name: php-apache
namesapce: default
spec:
maxReplicas: 10
minReplicas: 1
scaleTargeRef:
kind: Deployment
name: php-apache
targetCPUUtilizationPercentage: 90
?根據上面的定義,我們可以知道這個HPA控制的目標對象為一個名為php-apache的Deployment里d的Pod副本,當這些Pod副本的CPUUtilizationPercentage的值超過90%時會觸發自動擴容行為,擴容或縮容時必須滿足的一個約束條件是Pod的副本數要介于1與10之間。
?除了可以通過直接定義yaml文件并且調用kubectl create的命令來創建一個HPA資源對象的方式,我們還能通過下面的簡單命令行直接創建等價的HPA對象:
# kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10
?第二章將會給出一個完整的HPA例子來說明其用法和功能。
1.4.8 StatefulSet
?在Kubernetes系統中,Pod的管理對象RC、Deployment、DaemonSet和Job都是面向無狀態的服務。但現實中有很多服務是有狀態的,特別是一些復雜的中間件集群,例如MySQL集群、MongoDB集群、Kafka集群、Zookeeper集群等,這些應用集群有以下一些共同點。
- 每個節點都有固定的身份ID,通過這個ID,集群中的成員可以相互發現并且通信。
- 集群的規模是比較固定的,集群規模不能隨意變動。
- 集群里的每個節點都是有狀態的,通常會持久化數據到永久存儲中。
- 如果磁盤損壞,則集群里的某個節點無法正常運行,集群功能受損。
?如果用RC/Deployment控制Pod副本數的方式來實現上述有狀態的集群,則我們會發現第一點是無法滿足的,因為Pod的名字是隨機產生的,Pod的IP地址也是在運行期才確定且可能有變動的,我們事先無法為每個Pod確定唯一不變的ID,為了能夠在其他節點上恢復某個失敗的節點,這種集群中的Pod需要掛接某種共享存儲,為了解決這個問題,Kubernetes從v1.4版本開始引入了PetSet這個新的資源對象,并且在v1.5版本時更名為StatefulSet,StatefulSet從本質上來說,可以看作Deployment/RC的一個特殊變種,它有如下一些特性。
- StatefulSet里的每個Pod都有穩定、唯一的網絡標識,可以用來發現集群內的其他成員。假設StatefulSet的名字叫kafka,那么第一個Pod叫kafak-0,第二個Pod叫kafak-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名稱可以直接在集群的配置文件中固定下來。
1.4.9 Service(服務)
1.概述
?Service也是Kubernetes里的最核心的資源對象之一,Kubernetes里的每個Service其實就是我們經常提起的微服務架構中的一個“微服務”,之前我們所說的Pod、RC等資源對象其實都是為這節所說的“服務”------Kubernetes Service作“嫁衣”的。圖1.12顯示了Pod、RC與Service的邏輯關系。
?從圖1.12中我們看到,Kubernetes的Service定義了一個服務的訪問入口地址,前端的應用(Pod)通過這個入口地址訪問其背后的一組由Pod副本組成的集群實例,Service與其后端Pod副本集群之間則是通過Label Selector來實現“無縫對接”的。而RC的作用實際上是保證Service的服務能力和服務質量始終處于預期的標準。
?通過分析、識別并建模系統中的所有服務為微服務-----Kubernetes Service,最終我們的系統由多個提供不同業務能力而又彼此獨立的微服務單元所組成,服務之間通過TCP/IP進行通信,從而形成了我們強大而又靈活的彈性網格,擁有了強大的分布式能力、彈性擴展能力、容錯能力,于此同時,我們的程序架構也變得簡單和直觀許多,如圖1.13所示。
?既然每個Pod都會被分配一個單獨的IP地址,而且每個Pod都提供了一個獨立的Endpoint(Pod IP+ContainerPort)以被客戶端訪問,現在多個Pod副本組成了一個集群來提供服務,那么客戶端如何來訪問它們呢?一般的做法是部署一個負載均衡器(軟件或硬件),為這組Pod開啟一個對外的服務端口如8000端口,并且將這些Pod的Endpoint列表加入8000端口的轉發列表中,客戶端就可以通過負載均衡器的對外IP地址+服務端口來訪問服務,而客戶端的請求最后會被zhaun fa轉發到哪個Pod,則由負載均衡器的算法所決定。
?Kubernetes也遵循了上述常規做法,運行在每個Node上的kube-proxy進程其實就是一個智能的軟件負載均衡器,它負責把對Service的請求轉發到后端的某個Pod實例上,并在內部shi xian實現服務的負載均衡與會話機制。但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域名映射即可完美解決問題。現在想想,這真是一個很棒的設計。
?說了這么久,下面我們動手創建一個Service,來加深對它的理解。首先我們創建一個名為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 create -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的Cluster IP呢?怎么沒有看到?”我們運行下面的命令即可看到tomcat-service被分配的Cluster IP及更多的信息:
# kubectl get svc tomcat-service -o yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2018-10-17T10:04:21Z
name: tomcat-service
namespace: default
resourceVersion: "10169415"
selfLink: /api/v1/namespaces/default/services/tomcat-service
uid: 04caf53f-d1f4-11e8-83a3-5254008f2a0b
spec:
clusterIP: 10.254.169.39
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相同。
?接下來,我們來看看Service的多端口問題。
?很多服務都存在多個端口的問題,通常一個端口提供業務服務,另外一個端口提供管理服務,比如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的服務發現機制了,我們接下來進行講解。
2.Kubernetes的服務發現機制
?任何分布式系統都會涉及“服務發現”這個基礎問題,大部分分布式系統通過提供特定的API接口來實現服務發現的功能,但這樣做會導致平臺的入侵性比較強,也增加了開發測試的困難。Kubernetes則采用了直觀樸素的思路去解決這個棘手的問題。
?首先,每個Kubernetes中的Service都有一個唯一的Cluster IP及唯一的名字,而名字是由開發者自己定義的,部署時也沒有改變,所以完全可以固定在配置中。接下來的問題就是如何通過Service的名字找到對應的Cluster IP?
?最早時Kubernetes采用了Linux環境變量的方式解決這個問題,即每個Service生成一些對應的Linux環境變量(ENV),并在每個Pod的容器在啟動時,自動注入這些環境變量,以下是tomcat-service產生的環境變量條目:
TOMCAT_SERVICE_SERVICE_HOST=10.254.93.4
TOMCAT_SERVICE_SERVICE_PORT_SERVICE_PORT=8080
TOMCAT_SERVICE_SERVICE_PORT_SHUTDOWN_PORT=8005
TOMCAT_SERVICE_SERVICE_PORT=8080
TOMCAT_SERVICE_PORT=tcp://10.254.93.4:8080
TOMCAT_SERVICE_PORT_8080_TCP_ADDR=10.254.93.4
TOMCAT_SERVICE_PORT_8080_TCP=tcp://10.254.93.4:8080
TOMCAT_SERVICE_PORT_8080_TCP_PROTO=tcp
TOMCAT_SERVICE_PORT_8080_TCP_PORT=8080
TOMCAT_SERVICE_PORT_8005_TCP=tcp://10.254.93.4:8005
TOMCAT_SERVICE_PORT_8005_TCP_ADDR=10.254.93.4
TOMCAT_SERVICE_PORT_8005_TCP_PROTO=tcp
TOMCAT_SERVICE_PORT_8005_TCP_PORT=8005
?上述環境變量中,比較重要的是前3條環境變量,我們可以看到,每個Service的IP地址及端口都是有標準的命名規范,就可以通過代碼訪問系統環境變量的方式得到所需的信息,實現服務調用。
?考慮到環境變量的方式獲取Service的IP與端口的方式仍然不太方便,不夠直觀,后來Kubernetes通過Add-On增值包的方式引入了DNS系統,把服務名作為dns域名,這樣一來,程序就可以直接使用服務名來建立通信連接了。目前Kubernetes上的大部分應用都已經采用了DNS這些新型的服務發現機制,后面的章節中我們會講述如何部署這套DNS系統。
3.外部系統訪問Service的問題
?為了更好深入地理解和掌握Kubernetes,我們需要弄明白Kubernetes里的“三種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里一個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網與Clsuter IP之間的通信,采用的是Kubernetes自己設計的一種編程方式的特殊的路由規則,與我們所熟知的IP路由有很大的不同。
?根據上面的分析和總結,我們基本明白了:Service的Cluster IP屬于Kubernetes集群內部的地址,無法在集群外部直接使用這個地址。那么矛盾來了:實際上我們開發的業務系統中肯定多少由一部分服務是要提供給Kubernetes集群外部的應用或者用戶來使用的,典型的例子就是Web端的服務模塊,比如上面的tomcat-service,那么用戶怎么訪問它?
?采用NodePort是解決上述問題的最直接、最常用的做法。具體做法如下,以tomcat-service為例,我們在Service的定義里做如下擴展即可(黑體字部分):
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
type: NodePort
ports:
- port: 8080
nodePort: 31002
selector:
tier: frontend
?其中,nodePort:31002這個屬性表明我們手動指定tomcat-service的NodePort為31002,否則Kubernetes會自動分配一個可用的端口。接下來,我們在瀏覽器里訪問http://<nodePort IP>:31002,就可以看到Tomcat的歡迎界面了,如圖1.14所示。
?NodePort的實現方式是在Kubernetes集群里的每個Node上為需要外部訪問的Service開啟一個對應的TCP監聽端口,外部系統只要用任意一個Node的IP地址+具體的NodePort端口號即可訪問此服務,在任意Node上運行netstat命令,我們就可以看到有NodePort端口被監聽:
# netstat -tlp|grep 31002
tcp6 0 0 [::]:31002 [::]:* LISTEN 19043/kube-proxy
?但NodePort還沒有完全解決外部訪問Service的所有問題,比如負載均衡問題,假如我們的集群中有10個Node,則此時最好有一個負載均衡器,外部的請求只需要訪問此負載均衡器的IP地址,由負載均衡負責轉發流量到后面某個Node的NodePort上。如圖1.15所示。
?圖1.15中的Load balancer組件獨立于Kubernetes集群之外,通常是一個硬件的負載均衡器,或者是以軟件方式實現的,例如HAProxy或者Nginx。對于每個Service,我們通常需要配置一個對應的Load balancer實例來轉發流量到后端的Node上,這的確增加了工作量及出錯的概率。于是Kubernetes提供了自動化的解決方案,如果我們的集群運行在谷歌的GCE公有云上,那么只要我們把Service的type=NodePort改為type=LoadBalancer,此時Kubernetes會自動創建一個對應的Load balancer實例并返回它的IP地址供外部客戶端使用。此時Kubernetes會自動創建一個對應的Load balancer實例并返回它的IP地址供外部客戶端使用。其他公有云提供商只要實現了支持此特性的驅動,則也可以達到上述目的。此外,裸機上的類似機制(Bare Metal Service Load Balancers)也正在被開發。
1.4.10 Volume(存儲卷)
?Volume是Pod中能夠被多個容器訪問的共享目錄。Kubernetes的Volume概念、用途和目的與Docker的Volume比較類似,但兩者不能等價。首先,Kubernetes中的Volume定義在Pod上,然后被一個Pod里的多個容器掛載到具體的文件目錄下;其次,Kubernetes中的Volume中的數據也不會丟失。最后,Kubernetes支持多種類型的Volume,例如Gluster、Ceph等先進的分布式文件系統。
?Volume的使用也比較簡單,在大多數情況下,我們先在Pod上聲明一個Volume,然后在容器里引用該Volume并Mount到容器里的某個目錄上。舉例來說,我們要給之前的Tomcat Pod增加一個名字為datavol的Volume,并且Mount到容器的/mydata-data目錄上,則只要對Pod的定義文件做如下修正即可(注意黑體字部分):
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
volumes:
- name: datavol
emptyDir: {}
containers:
- name: tomcat-demo
image: tomcat
volumeMounts:
- mountPath: /mydata-data
name: datavol
imagePullPolicy: IfNotPersent
?除了可以讓一個Pod里的多個容器共享文件、讓容器的數據寫到宿主機的磁盤上或者寫文件到網絡存儲中,Kubernetes的Volume還擴展出了一種非常有實用價值的功能,即容器配置文件集中化定義與管理,這是通過ConfigMap這個新的資源對象來實現的,后面我們會詳細說明。
?Kubernetes提供了非常豐富的Volume類型,下面逐一進行說明。
1.emptyDir
?一個emptyDir Volume是在Pod分配到Node時創建的。從它的名稱就可以看出,它的初始內容為空,并且無須指定宿主機上對應的目錄文件,因為這是Kubernetes自動分配的一個目錄,當Pod從Node上移除時,emptyDir中的數據也會被永久刪除。empty的一些用途如下。
- 臨時空間,例如用于某些應用程序運行時所需的臨時目錄,且無須永久保留。
- 長時間任務的中間過程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.gcePersistentDisk
?使用這種類型的Volume表示使用谷歌公有云提供的永久磁盤(Persistent Disk,PD)存放Volume的數據,它與emotyDir不同,PD上的內容會被永久保存,當Pod被刪除時,PD只是被卸載(Unmount),但不會被刪除。需要注意的是,你需要先創建一個永久磁盤(PD),才能使用gcePersistentDisk。
?使用gcePersistentDisk有以下一些限制條件。
- Node(運行kubeket的節點)需要是GCE虛擬機。
- 這些虛擬機需要與PD存在于相同的GCE項目和Zone中。
?通過gcloud命令即可創建一個PD:
gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk
?定義gcePersisentDisk類型的Volume的示例如下:
volumes:
- name: test-volume
# This GCE PD must already exist.
gcePersistentDisk:
pdName: my-data-disk
fsType: ext4
4.awsElasticBlockStore
?與GCE類似,該類型的Volume使用亞馬遜公有云提供的EBS Volume存儲數據,需要先創建一個EBS Volume才能使用awsElasticBlockStore。
?使用awsElasticBlockStore的一些限制條件如下:
- Node(運行kubelet的節點)需要是AWS EC2實例。
- 這些AWS EC2實例需要與EBS volume存在于相同的region和availability-zone中。
- EBS只支持單個EC2實例mount一個volume。
?通過aws ec2 create-volume命令可以創建一個EBS volume:
aws ec2 create-volume --availability-zone eu-west-1a --size 10 --volume-type gp2
?定義awsElasticBlockStore類型的Volume的示例如下:
volumes:
- name: test-volume
# This AWS EBS volume must already exist.
awsElasticBlockStore:
volumeID: aws://<availability-zone>/<volume-id>
fsType: ext4
5.NFS
?使用NFS網絡文件系統提供的共享目錄存儲數據時,我們需要在系統中部署一個NFS Server。定義NFS類型的Volume的示例如下:
volumes:
- name: nfs
nfs:
# 改為你的NFS服務器地址
server: nfs-server.localhost
path: "/"
6.其他類型的Volume
- 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直接掛載的volume總是不會持久化的。
1.4.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: pv0003
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(PVC)對象:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
?然后在Pod的Volume定義中引用上述PVC即可:
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
?最后,我們說說PV的狀態,PV是有狀態的對象,它有以下幾種狀態。
- Available:空閑狀態。
- Bound:已經綁定到某個PVC上。
- Released:對應的PVC已經刪除,但資源還沒有被集群收回。
- Failed:PV自動回收失敗。
?共享存儲的原理解析和實踐指南詳見3.8節。
1.4.12
?Namespace(命名空間)是Kubernetes系統中的另一個非常重要的概念,Namespace在很多情況下用于實現多租戶的資源隔離。Nameaspace通過將集群內部的資源對象“分配”到不同的Namespce中,形成邏輯上分組的不同項目、小組或用戶組,便于不同的分組在共享使用整個集群的資源的同時還能被分別管理。
?Kubernetes集群在啟動后,會創建一個名為“default”的Namespace,通過kubectl可以查看到:
$ kubectl get namespaces
NAME STATUS AGE
default Active 21h
docker Active 21h
kube-public Active 21h
kube-system Active 21h
?接下來,如果不特別指明Namespace,則用戶創建的Pod、RC、Service都被系統創建到這個默認的名為default的Namespace中。
?Namespace的定義很簡單。如下所示的yaml定義了名為development的Namespace。
apiVersion: v1
kind: Namespace
metadata:
name: development
?一旦創建了Namespace,我們在創建資源對象時就可以指定這個資源對象屬于哪個Namespace。比如在下面的例子中,我們定義了一個名為busybox的Pod,放人development這個Namespace里:
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: development
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
name: busybox
?此時,使用kubectl get命令查看將無法顯示:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
?這是因為如果不加參數,則kubectl get 命令將僅顯示屬于“default”命名空間的資源對象。
?可以在kubectl命令中加入--namespace參數來查看某個命名空間中的對象:
# kubectl get pods --namespace=development
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 2m
?當我們給每個租戶創建一個Namespace來實現多租戶的資源隔離時,還能結合Kubernetes的資源配額管理,限定不同租戶能占用的資源,例如CPU使用量、內存使用量等。關于資源配額管理等問題,在后面的章節中會詳細介紹。
1.4.13 Annotation(注解)
?Annotation與Label類似,也使用key/value鍵值對的形式進行定義。不同的是Label具有嚴格的命名規則,它定義的是Kubernetes對象的元數據(Metadata),并且用于Label Selector。而Annotation則是用戶任意定義的“附加”信息,以便于外部工具進行查找,很多時候,Kubernetes的模塊自身會通過Annotation的方式標記資源對象的特殊信息。
?通常來說,用Annotation來記錄的信息如下。
- build信息、release信息、Docker鏡像信息等,例如時間戳、release id號、PR號、鏡像hash值、docker registry地址等。
- 日志庫、監控庫、分析庫等資源庫的地址信息。
- 程序調試工具信息,例如工具、版本號等。
- 團隊等聯系信息,例如電話號碼、負責人名稱、網址等。
1.4.14 小結
?上述組件是Kubernetes系統的核心組件,它們共同構成了Kubernetes系統的框架和計算模型。通過對它們進行靈活組合,用戶就可以快速、方便地對容器集群進行配置、創建和管理。除了本章所介紹的核心組件,在Kubernetes系統中還有許多輔助配置的資源對象,例如LimitRange、Resurce。另外,一些系統內部使用的對象Binding、Event等請參考Kubernetes的API文檔。
?容器化代表著未來,隨著越來越多的公司熟悉docker、kubernetes、Istio等開源技術,普通公司也能利用相關技術實現容器化、彈性伸縮、容災容備等等,從而讓普通公司也有與大廠比肩的技術能力。接下來的博文內容,將都和docker、kubernetes、Istio等技術相關。
如果需要給我修改意見的發送郵箱:erghjmncq6643981@163.com
資料參考:《Kubernetes-權威指南》
轉發博客,請注明,謝謝。