編排
? Pod 這個看似復雜的 API 對象,實際上就是對容器的進一步抽象和封裝而已。
? 如果在這個集群中段直,攜帶 app=nginx 標簽的 Pod 的個數大于 2 的時候,就會有舊的 Pod 被刪除;反之市怎,就會有新的 Pod 被創(chuàng)建。
? kube-controller-manager :一系列控制器的集合辛慰。這些控制器之所以被統(tǒng)一放在 pkg/controller 目錄下焰轻,就是因為它們都遵循 Kubernetes 項目中的一個通用編排模式,即:控制循環(huán)(control loop)昆雀。
在具體實現中辱志,實際狀態(tài)往往來自于 Kubernetes 集群本身
而期望狀態(tài)蝠筑,一般來自于用戶提交的 YAML 文件。
- Deployment 控制器從 Etcd 中獲取到所有攜帶了“app: nginx”標簽的 Pod揩懒,然后統(tǒng)計它們的數量什乙,這就是實際狀態(tài);
- Deployment 對象的 Replicas 字段的值就是期望狀態(tài)已球;
- Deployment 控制器將兩個狀態(tài)做比較臣镣,然后根據比較結果,確定是創(chuàng)建 Pod智亮,還是刪除已有的 Pod
? 比如忆某,增加 Pod,刪除已有的 Pod阔蛉,或者更新 Pod 的某個字段弃舒。這也是 Kubernetes 項目“面向 API 對象編程”的一個直觀體現。
? 其中状原,這個控制器對象本身聋呢,負責定義被管理對象的期望狀態(tài)。比如颠区,Deployment 里的 replicas=2 這個字段削锰。而被控制對象的定義,則來自于一個“模板”毕莱。比如器贩,Deployment 里的 template 字段。
像 Deployment 定義的 template 字段朋截,在 Kubernetes 項目中有一個專有的名字磨澡,叫作 PodTemplate(Pod 模板)。
? 類似 Deployment 這樣的一個控制器质和,實際上都是由上半部分的控制器定義(包括期望狀態(tài))稳摄,加上下半部分的被控制對象的模板組成的。
Deployment
它實現了 Kubernetes 項目中一個非常重要的功能:Pod 的“水平擴展 / 收縮”(horizontal scaling out/in)
舉個例子饲宿,如果你更新了 Deployment 的 Pod 模板(比如厦酬,修改了容器的鏡像),那么 Deployment 就需要遵循一種叫作“滾動更新”(rolling update)的方式瘫想,來升級現有的容器仗阅。
ReplicaSet
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-set
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
從這個 YAML 文件中,我們可以看到国夜,一個 ReplicaSet 對象减噪,其實就是由副本數目的定義和一個 Pod 模板組成的。不難發(fā)現,它的定義其實是 Deployment 的一個子集筹裕。
更重要的是醋闭,Deployment 控制器實際操縱的,正是這樣的 ReplicaSet 對象朝卒,而不是 Pod 對象证逻。
? ReplicaSet 負責通過“控制器模式”,保證系統(tǒng)中 Pod 的個數永遠等于指定的個數抗斤。Deployment 同樣通過“控制器模式”囚企,來操作 ReplicaSet 的個數和屬性,進而實現“水平擴展 / 收縮”和“滾動更新”這兩個編排動作瑞眼。水平擴展 / 收縮”非常容易實現龙宏,Deployment Controller 只需要修改它所控制的 ReplicaSet 的 Pod 副本個數就可以了。
? 滾動更新:修改了Deployment的Pod模板后伤疙,自動觸發(fā)滾動更新银酗,比如直接使用kubectl edit指令編輯etcd里的api對象修改版本。就會逐個將老的RS中的pod副本減少掩浙,新的RS中的pod副本就會逐漸增加花吟。將一個集群中正在運行的多個 Pod 版本秸歧,交替地逐一升級的過程厨姚,就是“滾動更新”。其實就是灰度發(fā)布
kubectl edit 并不神秘键菱,它不過是把 API 對象的內容下載到了本地文件谬墙,讓你修改完成后再提交上去。
? 而為了進一步保證服務的連續(xù)性经备,Deployment Controller 還會確保拭抬,在任何時間窗口內,只有指定比例的 Pod 處于離線狀態(tài)侵蒙。同時造虎,它也會確保,在任何時間窗口內纷闺,只有指定比例的新 Pod 被創(chuàng)建出來算凿。這兩個比例的值都是可以配置的,默認都是 DESIRED 值的 25%犁功。就是cicd上的最大不可用實例數氓轰。
應用版本和 ReplicaSet 一一對應
Deployment 對應用進行版本控制的具體原理
回滾:kubectl rollout undo 命令,就能把整個 Deployment 回滾到上一個版本浸卦。
回滾到更早的版本:首先署鸡,需要使用 kubectl rollout history 命令,查看每次 Deployment 變更對應的版本。然后靴庆,我們就可以在 kubectl rollout undo 命令行最后时捌,加上要回滾到的指定版本的版本號,就可以回滾到指定版本了撒穷。Deployment Controller 還會按照“滾動更新”的方式匣椰,完成對 Deployment 的降級操作。
kubectl rollout pause:讓這個 Deployment 進入了一個“暫投死瘢”狀態(tài)禽笑,可以隨意修改而不會立即出發(fā)滾動更新。kubectl rollout resume :可以把這個 Deployment“恢復”回來
StatefulSet(有狀態(tài)應用的編排功能)
- 拓撲狀態(tài)蛤奥。這種情況意味著佳镜,應用的多個實例之間不是完全對等的關系。這些應用實例凡桥,必須按照某些順序啟動蟀伸,比如應用的主節(jié)點 A 要先于從節(jié)點 B 啟動。而如果你把 A 和 B 兩個 Pod 刪除掉缅刽,它們再次被創(chuàng)建出來時也必須嚴格按照這個順序才行啊掏。并且,新創(chuàng)建出來的 Pod衰猛,必須和原來 Pod 的網絡標識一樣迟蜜,這樣原先的訪問者才能使用同樣的方法,訪問到這個新 Pod啡省。
- 存儲狀態(tài)娜睛。這種情況意味著,應用的多個實例分別綁定了不同的存儲數據卦睹。對于這些應用實例來說畦戒,Pod A 第一次讀取到的數據,和隔了十分鐘之后再次讀取到的數據结序,應該是同一份障斋,哪怕在此期間 Pod A 被重新創(chuàng)建過。這種情況最典型的例子徐鹤,就是一個數據庫應用的多個存儲實例垃环。
? 所以,StatefulSet 的核心功能凳干,就是通過某種方式記錄這些狀態(tài)晴裹,然后在 Pod 被重新創(chuàng)建時,能夠為新 Pod 恢復這些狀態(tài)救赐。
Headless Service
? 一個 Deployment 有 3 個 Pod涧团,那么我就可以定義一個 Service只磷。然后,用戶只要能訪問到這個 Service泌绣,它就能訪問到某個具體的 Pod钮追。訪問這個service的方法:
第一種方式,是以 Service 的 VIP(Virtual IP阿迈,即:虛擬 IP)方式元媚。比如:當我訪問 10.0.23.1 這個 Service 的 IP 地址時,10.0.23.1 其實就是一個 VIP苗沧,它會把請求轉發(fā)到該 Service 所代理的某一個 Pod 上刊棕。這里的具體原理,我會在后續(xù)的 Service 章節(jié)中進行詳細介紹待逞。
第二種方式甥角,就是以 Service 的 DNS 方式。比如:這時候识樱,只要我訪問“my-svc.my-namespace.svc.cluster.local”這條 DNS 記錄嗤无,就可以訪問到名叫 my-svc 的 Service 所代理的某一個 Pod。
第二種方式的處理方法:
- Normal Service怜庸。這種情況下当犯,你訪問“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 這個 Service 的 VIP割疾,后面的流程就跟 VIP 方式一致了嚎卫。
- Headless Service。這種情況下杈曲,你訪問“my-svc.my-namespace.svc.cluster.local”解析到的驰凛,直接就是 my-svc 代理的某一個 Pod 的 IP 地址胸懈。可以看到担扑,這里的區(qū)別在于,Headless Service 不需要分配一個 VIP趣钱,而是可以直接以 DNS 記錄的方式解析出被代理 Pod 的 IP 地址涌献。
Headless Service 被創(chuàng)建后并不會被分配一個 VIP,而是會以 DNS 記錄的方式暴露出它所代理的 Pod首有。按照這樣的方式創(chuàng)建了一個 Headless Service 之后燕垃,它所代理的所有 Pod 的 IP 地址,都會被綁定一個這樣格式的 DNS 記錄:<pod-name>.<svc-name>.<namespace>.svc.cluster.local
StatefulSet 又是如何使用這個 DNS 記錄來維持 Pod 的拓撲狀態(tài)的呢井联?
- 這個 YAML 文件卜壕,和我們在前面文章中用到的 nginx-deployment 的唯一區(qū)別,就是多了一個 serviceName=nginx 字段烙常。就是告訴 StatefulSet 控制器轴捎,在執(zhí)行控制循環(huán)(Control Loop)的時候,請使用 nginx 這個 Headless Service 來保證 Pod 的“可解析身份”
- StatefulSet 給它所管理的所有 Pod 的名字,進行了編號侦副,編號規(guī)則是:-侦锯。而且這些編號都是從 0 開始累加,與 StatefulSet 的每個 Pod 實例一一對應秦驯,絕不重復尺碰。這兩個 Pod 的 hostname 與 Pod 名字是一致的,都被分配了對應的編號
- 當刪除這兩個pod后译隘,k8s會按照原先編號的順序亲桥,創(chuàng)建出兩個新的pod,且分配了原來相同的網絡身份固耘。通過這種嚴格的對應規(guī)則两曼,StatefulSet 就保證了 Pod 網絡標識的穩(wěn)定性。
? 比如玻驻,如果 web-0 是一個需要先啟動的主節(jié)點悼凑,web-1 是一個后啟動的從節(jié)點,那么只要這個 StatefulSet 不被刪除璧瞬,你訪問 web-0.nginx 時始終都會落在主節(jié)點上户辫,訪問 web-1.nginx 時,則始終都會落在從節(jié)點上嗤锉,這個關系絕對不會發(fā)生任何變化渔欢。
? Kubernetes 就成功地將 Pod 的拓撲狀態(tài)(比如:哪個節(jié)點先啟動,哪個節(jié)點后啟動)瘟忱,按照 Pod 的“名字 + 編號”的方式固定了下來奥额。此外,Kubernetes 還為每一個 Pod 提供了一個固定并且唯一的訪問入口访诱,即:這個 Pod 對應的 DNS 記錄垫挨。
總結:
? StatefulSet 這個控制器的主要作用之一,就是使用 Pod 模板創(chuàng)建 Pod 的時候触菜,對它們進行編號九榔,并且按照編號順序逐一完成創(chuàng)建工作。而當 StatefulSet 的“控制循環(huán)”發(fā)現 Pod 的“實際狀態(tài)”與“期望狀態(tài)”不一致涡相,需要新建或者刪除 Pod 進行“調諧”的時候哲泊,它會嚴格按照這些 Pod 編號的順序,逐一完成這些操作催蝗。
存儲狀態(tài)的管理機制——Persistent Volume Claim
? Kubernetes 項目引入了一組叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 對象切威,大大降低了用戶聲明和使用持久化 Volume 的門檻。
- 定義一個 PVC丙号,聲明想要的 Volume 的屬性先朦。
- 2.在應用的 Pod 中且预,聲明使用這個 PVC。
? 這時候烙无,只要我們創(chuàng)建這個 PVC 對象锋谐,Kubernetes 就會自動為它綁定一個符合條件的 Volume。這個PVC對象來自于由運維人員維護的 PV(Persistent Volume)對象截酷。所以涮拗,Kubernetes 中 PVC 和 PV 的設計,實際上類似于“接口”和“實現”的思想迂苛。開發(fā)者只要知道并會使用“接口”三热,即:PVC;而運維人員則負責給“接口”綁定具體的實現三幻,即:PV就漾。
? volumeClaimTemplates :從名字就可以看出來,它跟 Deployment 里 Pod 模板(PodTemplate)的作用類似念搬。也就是說抑堡,凡是被這個 StatefulSet 管理的 Pod,都會聲明一個對應的 PVC朗徊;而這個 PVC 的定義首妖,就來自于 volumeClaimTemplates 這個模板字段。更重要的是爷恳,這個 PVC 的名字有缆,會被分配一個與這個 Pod 完全一致的編號。PVC 其實就是一種特殊的 Volume温亲。只不過一個 PVC 具體是什么類型的 Volume棚壁,要在跟某個 PV 綁定之后才知道。
- 當你把一個 Pod栈虚,比如 web-0袖外,刪除之后,這個 Pod 對應的 PVC 和 PV节芥,并不會被刪除在刺,而這個 Volume 里已經寫入的數據逆害,也依然會保存在遠程存儲服務里(比如头镊,我們在這個例子里用到的 Ceph 服務器)。
- 此時魄幕,StatefulSet 控制器發(fā)現相艇,一個名叫 web-0 的 Pod 消失了。所以纯陨,控制器就會重新創(chuàng)建一個新的坛芽、名字還是叫作 web-0 的 Pod 來留储,“糾正”這個不一致的情況。
- 需要注意的是咙轩,在這個新的 Pod 對象的定義里获讳,它聲明使用的 PVC 的名字,還是叫作:www-web-0活喊。這個 PVC 的定義丐膝,還是來自于 PVC 模板(volumeClaimTemplates),這是 StatefulSet 創(chuàng)建 Pod 的標準流程钾菊。
- 所以帅矗,在這個新的 web-0 Pod 被創(chuàng)建出來之后,Kubernetes 為它查找名叫 www-web-0 的 PVC 時煞烫,就會直接找到舊 Pod 遺留下來的同名的 PVC浑此,進而找到跟這個 PVC 綁定在一起的 PV。
- 這樣滞详,新的 Pod 就可以掛載到舊 Pod 對應的那個 Volume凛俱,并且獲取到保存在 Volume 里的數據。
通過這種方式料饥,Kubernetes 的 StatefulSet 就實現了對應用存儲狀態(tài)的管理最冰。
- 首先,StatefulSet 的控制器直接管理的是 Pod稀火。這是因為暖哨,StatefulSet 里的不同 Pod 實例,不再像 ReplicaSet 中那樣都是完全一樣的凰狞,而是有了細微區(qū)別的篇裁。比如,每個 Pod 的 hostname赡若、名字等都是不同的达布、攜帶了編號的。而 StatefulSet 區(qū)分這些實例的方式逾冬,就是通過在 Pod 的名字里加上事先約定好的編號黍聂。
- 其次,Kubernetes 通過 Headless Service身腻,為這些有編號的 Pod产还,在 DNS 服務器中生成帶有同樣編號的 DNS 記錄。只要 StatefulSet 能夠保證這些 Pod 名字里的編號不變嘀趟,那么 Service 里類似于 web-0.nginx.default.svc.cluster.local 這樣的 DNS 記錄也就不會變脐区,而這條記錄解析出來的 Pod 的 IP 地址,則會隨著后端 Pod 的刪除和再創(chuàng)建而自動更新她按。這當然是 Service 機制本身的能力牛隅,不需要 StatefulSet 操心炕柔。
- 最后,StatefulSet 還為每一個 Pod 分配并創(chuàng)建一個同樣編號的 PVC媒佣。這樣匕累,Kubernetes 就可以通過 Persistent Volume 機制為這個 PVC 綁定上對應的 PV,從而保證了每一個 Pod 都擁有一個獨立的 Volume默伍。
- 在這種情況下哩罪,即使 Pod 被刪除,它所對應的 PVC 和 PV 依然會保留下來巡验。所以當這個 Pod 被重新創(chuàng)建出來之后际插,Kubernetes 會為它找到同樣編號的 PVC,掛載這個 PVC 對應的 Volume显设,從而獲取到以前保存在 Volume 里的數據框弛。
總結:StatefulSet 其實就是一種特殊的 Deployment,而其獨特之處在于捕捂,它的每個 Pod 都被編號了瑟枫。而且,這個編號會體現在 Pod 的名字和 hostname 等標識信息上指攒,這不僅代表了 Pod 的創(chuàng)建順序慷妙,也是 Pod 的重要網絡標識(即:在整個集群里唯一的、可被的訪問身份)允悦。
? 有了這個編號后膝擂,StatefulSet 就使用 Kubernetes 里的兩個標準功能:Headless Service 和 PV/PVC,實現了對 Pod 的拓撲狀態(tài)和存儲狀態(tài)的維護隙弛。